美文网首页
组合与排列

组合与排列

作者: 抬头挺胸才算活着 | 来源:发表于2020-08-17 10:39 被阅读0次

参考资料:
1. 全排列题目
2. 全排列官方解法
3. 全排列题目2
3. 全排列题目2官方解法
4. 可重复组合题目
5. 不可重复组合题目
6. 子集题目

化抽象为具象:画出参考资料2中的树形图



排列和组合的区别:排列只要剩下的没有用到的都要用,组合只用当前位置后面的,两者用的都是DFS。
子集除了跟组合一样用DFS之外还可以用BFS。

全排列——回溯法

  • 全排列思路一
    用一个visited数组记录访问过的元素
public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> result = new LinkedList<>();
    permute(nums, result, new LinkedList<>(), new boolean[nums.length]);
    return result;
}

private void permute(int[] nums, List<List<Integer>> result, LinkedList<Integer> temp, boolean[] visited) {
    if(temp.size()==nums.length)
        result.add(new LinkedList<>(temp));

    for (int i = 0; i < nums.length; i++) {
        if(!visited[i]) {
            visited[i] = true;
            temp.add(nums[i]);
            permute(nums, result, temp, visited);
            temp.removeLast();
            visited[i] = false;
        }
    }
}
  • 全排列思路二
    start之后是还没有使用过的元素,参见参考资料2
public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> result = new LinkedList<>();
    permute(nums, 0, result);
    return result;
}

private void permute(int[] nums, int start, List<List<Integer>> result) {
    if(start == nums.length) {
        result.add(Arrays.stream(nums).boxed().collect(Collectors.toList()));
    }

    for (int i = start; i < nums.length; i++) {
        swap(nums, i, start);
        permute(nums, start + 1, result);
        swap(nums, i, start);
    }
}

void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

全排列2

只能使用上面的思路1进行剪枝,因为思路2交换元素之后,元素没有保持从小到大的状态。

    // https://leetcode-cn.com/problems/permutations-ii/
    // 在之前的permute下进行剪枝 参考:https://leetcode-cn.com/problems/permutations-ii/solution/
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result = new LinkedList<>();
        Arrays.sort(nums);
        permuteUnique(nums, result, new LinkedList<>(), new boolean[nums.length]);
        return result;
    }

    private void permuteUnique(int[] nums, List<List<Integer>> result, LinkedList<Integer> temp, boolean[] visited) {
        if(temp.size()==nums.length)
            result.add(new LinkedList<>(temp));

        for (int i = 0; i < nums.length; i++) {
            if(!visited[i]) {
                // !visited[i-1] 的理解是关键,
                // visited[i-1]如果为true,是上一层遍历过的
                // 这一层的visited[i-1]为false才对,然后还要满足nums[i-1] == nums[i],那么就是重复的,去掉
                if(i > 0 && nums[i-1] == nums[i] && !visited[i-1])
                    continue;

                visited[i] = true;
                temp.add(nums[i]);
                permuteUnique(nums, result, temp, visited);
                temp.removeLast();
                visited[i] = false;
            }
        }
    }

组合

public List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> result = new LinkedList<>();
    if(k == 0)
        return result;

    combine(1, n, k, result, new LinkedList<>());
    return result;
}

private void combine(int start, int n, int k, List<List<Integer>> result, LinkedList<Integer> current) {
    for (int i = start; i <= n; i++) {
        current.add(i);
        if(current.size() == k){
            result.add(new LinkedList<>(current));
        }else if(current.size() < k){
            combine(i+1, n, k, result, current);
        }
        current.removeLast();
    }
}

可重复组合

下面的写法会产生重复,因为在入口if(target==0)检查了一次,然后原封不动在进入combinationSum(candidates, start + 1, target);又检查了一次。

private void combinationSum(int[] candidates, int start, int target) {
    if(target<0 || start == candidates.length) {
        return;
    }

    if(target==0) {
        ret.add(new LinkedList<>(tempList));
    }

    tempList.add(candidates[start]);
    combinationSum(candidates, start, target - candidates[start]);
    tempList.removeLast();

    combinationSum(candidates, start + 1, target);
}

正确写法

private LinkedList<Integer> tempList = new LinkedList<>();
private List<List<Integer>> ret = new LinkedList<>();

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    combinationSum(candidates, 0, target);
    return ret;
}

private void combinationSum(int[] candidates, int start, int target) {
    if(start == candidates.length) {
        return;
    }

    int left = target - candidates[start];
    tempList.add(candidates[start]);
    if(left == 0) {
        ret.add(new LinkedList<>(tempList));
    }else if(left > 0) {
        combinationSum(candidates, start, left);
    }
    tempList.removeLast();

    combinationSum(candidates, start + 1, target);
}

另外一种写法

    private void combinationSum(int[] candidates, int start, int target) {
        for (int i = start; i < candidates.length; i++) {
            tempList.add(candidates[i]);
            int left = target - candidates[i];
            if(left == 0){
                ret.add(new LinkedList<>(tempList));
            }else if(left > 0){
                combinationSum(candidates, i, left);
            }
            tempList.remove(tempList.size()-1);
        }
    }

不可重复组合且结果去重

剪枝——同全排列2的解法,不能将candidates去重,因为组合可以使用多个相同的数字。

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    if(candidates.length==0)
        return new LinkedList<>();

    Arrays.sort(candidates);

    List<List<Integer>> result = new LinkedList<>();
    LinkedList<Integer> current = new LinkedList<>();
    combinationSum2(candidates, target, 0, result, current);
    return result;
}

private void combinationSum2(int[] candidates, int target, int start, List<List<Integer>> result, LinkedList<Integer> current) {
    for (int i = start; i < candidates.length; i++) {
        if(i > start && candidates[i-1] == candidates[i])
            continue;

        current.addLast(candidates[i]);
        int left = target - candidates[i];
        if(left==0){
            result.add(new LinkedList<>(current));
        }else if(left > 0) {
            combinationSum2(candidates, left, i+1, result, current);
        }
        current.removeLast();
    }
}

子集

也就是组合

  • 思路1:按照前面组合的DFS来进行
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new LinkedList<>();
        result.add(new LinkedList<>());
        subsets(nums, 0, new LinkedList<>(), result);
        return result;
    }

    private void subsets(int[] nums, int start, LinkedList<Integer> current, List<List<Integer>> result) {
        for (int i = start; i < nums.length; i++) {
            current.add(nums[i]);
            result.add(new LinkedList<>(current));
            subsets(nums, i+1, current, result);
            current.removeLast();
        }
    }
  • 思路2:按照BFS(层序遍历)的思路来,以某个元素作为结尾
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new LinkedList<>();
        result.add(new LinkedList<>());
        for (int i = 0; i < nums.length; i++) {
            int numOfPrev = result.size();
            int newElement = nums[i];

            for (int j = 0; j < numOfPrev; j++) {
                List<Integer> r = result.get(j);
                r = new LinkedList<>(r);
                r.add(newElement);
                result.add(r);
            }
        }

        return result;
    }

子集2

子字符串分割

131. 分割回文串

public List<List<String>> partition(String s) {
    List<List<String>> result = new LinkedList<>();
    partition(s, result, new LinkedList<>());
    return result;
}

private void partition(String s, List<List<String>> result, LinkedList<String> current) {
    if(s.length()==0)
        return ;

    for (int i = 0; i < s.length(); i++) {
        String before = s.substring(0, i + 1);
        if(!isHuiwen(before))
            continue;

        String after = s.substring(i + 1);
        current.add(before);
        if(after.length()==0){
            result.add(new LinkedList<>(current));
        }
        else{
            partition(after, result, current);
        }
        current.removeLast();
    }
}

private boolean isHuiwen(String s) {
    int i = 0, j = s.length() - 1;
    while(i < j) {
        if(s.charAt(i)!=s.charAt(j))
            return false;

        i += 1;
        j -= 1;
    }

    return true;
}

相关文章

  • 排列与组合

    排列(permutation) 上面表示的都是n中选择k个。P代表的是permutation公式: 组合(comb...

  • 组合与排列

    参考资料:1. 全排列题目2. 全排列官方解法3. 全排列题目23. 全排列题目2官方解法4. 可重复组合题目5....

  • 教学“排列与组合”

    教学“排列与组合” 今天开始教学“排列与组合”,人教版教材分了两册来编排这部分内容,单元...

  • 教学排列与组合2

    今天,继续教学排列与组合。主要就是让孩子明白有的事情与顺序有关(排列),有的事情与顺序无关(组合)。 先出示用5,...

  • 时间长了就生疏的排列组合

    排列数:组合数:全排列:排列是先组合再排列: 最基本的排列组合公式,不能忘在脑后,应该熟稔于心。 排列和组合的区...

  • Leetcode日记:46&47.排列组合与回溯(backtra

    Leetcode日记:46&47.排列组合与回溯(backtrack) 46排列组合1 题目 Given a co...

  • 教学排列与组合3

    上文(教学排列与组合2)中说到,这类知识关键在于让孩子明白什么时候与顺序有关(排列),什么时候与顺序无关(组合)。...

  • 再说排列与组合

    春节过后,和许多人一样,我背上行囊,走进拥挤的人群,走进拥挤的车厢,踏上为了生活而奔徙的旅途。 一...

  • 排列组合公式及排列组合算法

    排列组合公式 排列组合公式/排列组合计算公式 公式P是指排列,从N个元素取M个进行排列。 公式C是指组合,从N个元...

  • 排列组合-js

    排列组合 数学相关知识:5分钟彻底了解排列组合 参考:程序员必备算法——排列组合 排列有序,组合无序 3选2 :排...

网友评论

      本文标题:组合与排列

      本文链接:https://www.haomeiwen.com/subject/fpjadktx.html