回溯算法总结——团灭组合、排列、切割、N皇后等问题

1. 概述

回溯大体分为:组合、排列、子集、切割、搜索几种类型

类型题目链接思路
组合问题77.组合
39.组合总和
40. 组合总和 II
216. 组合总和 III
排列问题46. 全排列
47. 全排列 II
子集问题78. 子集
90. 子集 II
491. 递增子序列
切割问题131. 分割回文串
93. 复原 IP 地址
搜索问题51. N 皇后
37. 解数独

2. 组合问题

2.1 组合

LC链接:77.组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4],]

  • 思路:
  1. 组合问题,使用 start (indexStart) 来逐层缩小搜索范围。比如这层选择了 i ,那么递归到下层时,应该从 i + 1 开始进行迭代。
  2. 递归何时结束,当 path 中的元素数量等于 k 时,即可结束。
    在这里插入图片描述
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;

class Solution {
    
    List<List<Integer>> res;
    
    Deque<Integer> path;
    
    public List<List<Integer>> combine(int n, int k) {
        res = new ArrayList();
        // 固定path的大小,防止运行中反复扩容带来的性能损失
        path = new ArrayDeque(k);
        backtracking(n, k, 1);
        return res;
    }
    
    private void backtracking(int n, int k, int start) {
        // 当 path 中的元素数量等于k时,返回
        if (path.size() == k) {
            res.add(new ArrayList(path));
            return;
        }
        // 下层遍历从 i+1 开始
        for (int number = start; number <= n; number++) {
            path.addLast(number);
            backtracking(n, k, number + 1);
            path.removeLast();
        }
    }
}
  • 剪枝优化:当剩余的数全部添加到 path 中,也不足以使得 path.size() == k 时,即可结束搜索(当前层及后续层级)。比如n = 5, k = 4,表示需要从 [1, 2, 3, 4, 5] 中选取4个元素。当程序运行到某个时刻,假设此时path.size()为1,那么还需要3个元素添加到 path 中才能满足 k = 4 这个条件,因此就不能从 4 及 4 以后开始进行遍历,因为即使把所有这些元素加入到path中,也无法满足 k = 4 这个条件。
  1. path.size() 表示当前路径元素的个数
  2. k - path.size() 表示还需要的元素个数
  3. 所以上述代码中 number 最大只能到 n - (k - path.size()) + 1
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;

class Solution {
    
    List<List<Integer>> res;
    
    Deque<Integer> path;
    
    public List<List<Integer>> combine(int n, int k) {
        res = new ArrayList();
        // 固定path的大小,防止运行中反复扩容带来的性能损失
        path = new ArrayDeque(k);
        backtracking(n, k, 1);
        return res;
    }
    
    private void backtracking(int n, int k, int start) {
        // 当 path 中的元素数量等于k时,返回
        if (path.size() == k) {
            res.add(new ArrayList(path));
            return;
        }
        // 下层遍历从 i+1 开始
        // 剪枝优化
        for (int number = start; number <= n - (k - path.size()) + 1; number++) {
            path.addLast(number);
            backtracking(n, k, number + 1);
            path.removeLast();
        }
    }
}

优化前后运行时间对比,能看到剪枝后速度提升了19倍!
在这里插入图片描述


2.2 组合总和

LC链接:39.组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1: 输入:candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]
示例 2: 输入:candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]

  • 思路:
  1. 求组合需要利用 indexStart 缩小搜索范围,但是本题中每个元素可以使用次数的不限,所以下一次遍历的indexStart等于本次遍历的indexStart,而不需要加1
  2. 递归何时结束,由于目前还需要的 target 是由原 target 一路减去 path 中的数字而传递下来的,所以当 target 小于等于 0 的时候,退出。特别地,当 target 等于0 的时候说明找到了和为原 target 的路径,需要将路径添加到最终的 res 中。

在这里插入图片描述

import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;

class Solution {
    
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracking(candidates, target, 0);
        return res;
    }
    
    private void backtracking(int[] candidates, int remain, int indexStart) {
        // target小于0 退出
        if (remain < 0) return;
        
        // target等于于0,添加路径、退出
        if (remain == 0) {
            res.add(new ArrayList(path));
            return;
        }
        
        for (int i = indexStart; i < candidates.length; i++) {
            path.addLast(candidates[i]);
            // 每个数字可以使用无限次,所以下次还从i开始遍历即可
            backtracking(candidates, remain - candidates[i], i);
            path.removeLast();
        }
         
    }
}
  • 剪枝:先对原数组进行排序,然后在遍历过程中如果发现如果 remain < candidates[i],则当candidates[i]或者 candidates[i] 之后的数字再加入 path 中,会导致 path 中的数字和超过target ,所以一旦出现 remain < candidates[i],即可终止。代码如下:
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Arrays;

class Solution {
    
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if (candidates == null || candidates.length == 0 || target <= 0) return res;
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return res;
    }
    
    private void backtracking(int[] candidates, int remain, int indexStart) {
        // target小于0 退出
        if (remain < 0) return;
        
        // target等于于0,添加路径、退出
        if (remain == 0) {
            res.add(new ArrayList(path));
            return;
        }
        // 剪枝 remain >= candidates[i],如果 remain < candidates[i] 说明后续的数再加入path会导致path数字和超过原target
        for (int i = indexStart; i < candidates.length && remain >= candidates[i]; i++) {
            path.addLast(candidates[i]);
            // 每个数字可以使用无限次,所以下次还从i开始遍历即可
            backtracking(candidates, remain - candidates[i], i);
            path.removeLast();
        }
         
    }
}

剪枝后,LC上运行时间从14ms降到3ms


2.3 组合总和 II

LC链接:40. 组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。
示例 1: 输入: candidates = [10,1,2,7,6,1,5], target = 8, 所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
示例 2: 输入: candidates = [2,5,2,1,2], target = 5, 所求解集为: [ [1,2,2], [5] ]

  • 思路:
  1. 使用 indexStart 来逐层缩小搜索范围:该题是求组合,每个索引对应的元素只能使用一次,虽然数组中的元素可能重复(如例子 [10,1,2,7,6,1,5] 中,索引1和索引5对应的元素都是1),但是最终结果集中的每个组合都是对应索引只使用了一次,比如[1, 1, 6] 里的两个1分别对应[10,1,2,7,6,1,5] 数组索引1和索引5的位置。
  2. 树层去重:需要对原数组先排序!如果不排序的话,示例2中会出现 [2, 2, 1][2, 1, 2] 的组合,但其实这两个算作一个组合。当然,也可以当把这两个组合都求出来后,在最后添加进结果集的时候再去重,但是这样做了很多无用功,势必会超时。而排序后,上述两个组合都变成 [1, 2, 2],这也能够在搜索过程中进行去重。

在这里插入图片描述

树层去重1:使用used 数组:

  • used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过
class Solution {
    
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        boolean[] used = new boolean[candidates.length];
        backtracking(candidates, target, 0, used);
        return res;
    }
    
    private void backtracking(int[] candidates, int target, int indexStart, boolean[] used) {
        if (target == 0) {
            res.add(new ArrayList(path));
            return;
        }
        
        for (int i = indexStart; i < candidates.length && target >= candidates[i]; i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) continue;
            used[i] = true;
            path.addLast(candidates[i]);
            backtracking(candidates, target - candidates[i], i + 1, used);
            path.removeLast();
            used[i] = false;
            
        }
    }
}

树层去重2: 三数之和的思想,其实这种思想有类似点 15. 三数之和 ,不管是求和的目的,还是去重的逻辑。

  1. 先排序
  2. if (i > indexStart && candidates[i] == candidates[i - 1]) continue;
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Arrays;

class Solution {
    List<List<Integer>> res = new ArrayList();

    Deque<Integer> path = new LinkedList();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return res;
    }

    private void backtracking(int[] candidates, int target, int indexStart) {
        // target 每添加一个元素到 path,target 都会减去悉新加入元素的值,当 target 为0的时候,说明 path 里元素和等于 target
        if (target == 0) {
            res.add(new ArrayList(path));
            return;
        }

        // 每次从 indexStart 开始遍历
        // 且由于数组排序过,当剩余的 target < candidates[i]时,说明再将 candidates[i] 加入path,path里的元素和将会大于 target,
        // 所以对于candidates[i] candidates[i+1] ... 不用再遍历
        for (int i = indexStart; i < candidates.length && target >= candidates[i]; i++) {
            // 树层去重,注意 i > indexStart
            if (i > indexStart && candidates[i] == candidates[i - 1]) continue;
            path.addLast(candidates[i]);
            backtracking(candidates, target - candidates[i], i + 1);
            path.removeLast();
        }
    }
}

树层去重3:桶的思想,由于 1 <= candidates[i] <= 50 ,我们为每一层创建一个桶(数组),如果每一层上某个数字已经使用过,就跳过

class Solution {
    
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return res;
    }
    
    private void backtracking(int[] candidates, int target, int indexStart) {
        if (target == 0) {
            res.add(new ArrayList(path));
            return;
        }
        // 1 <= candidates[i] <= 50 记录每层数字是否使用过
        boolean[] used = new boolean[51];
        
        for (int i = indexStart; i < candidates.length && target >= candidates[i]; i++) {
            if (used[candidates[i]] == true) continue;
            used[candidates[i]] = true;
            path.addLast(candidates[i]);
            backtracking(candidates, target - candidates[i], i + 1);
            path.removeLast();            
        }
    }
}
  • 要点:
  1. 求组合需要利用 indexStart 缩小搜索范围,也是为了避免结果集中每个组合中的元素重复;
  2. 树层去重,有3种方法,推荐使用第一种,可以和树枝去重共用同一个数组。

2.4 组合总和 III

LC链接:216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

  • 思路:
  1. 组合问题考虑 start (indexStart)。由于题目中提到 “每种组合中不存在重复的数字”,所以需要利用 indexStart 来缩小搜索范围确保元素的之前使用过的元素不会再选取到。至于是使用 start 还是 indexStart 取决于我们在 for 循环中是使用索引进行遍历还是使用元素进行遍历。显然,本题直接使用元素进行遍历。
  2. 递归合适结束:当 path 中的元素个数等于k时,需要退出。并且当 remain 刚好为0时,说明 path 中所有数相加等于 n,所以此时需要将 path 添加到 res 中。
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;

class Solution {
    
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path;
    
    public List<List<Integer>> combinationSum3(int k, int n) {
        // 固定path的大小,防止运行中反复扩容带来的性能损失
        path = new ArrayDeque(k);
        backtrackin(k, n, 1);
        return res;
        
    }
    
    private void backtrackin(int k, int remain, int start) {   
        // 元素达到 k 个,需要返回。特别地,如果此时 remain 刚好为0,需要将路径 path 添加到 res 中
        if (path.size() == k) {
            if (remain == 0) {
                res.add(new ArrayList(path));
            }
            return;
        }
        
        // remain >= num 进行剪枝
        for (int num = start; num <= 9 && remain >= num; num++) {
            path.addLast(num);
            backtrackin(k, remain - num, num + 1);
            path.removeLast();
        }
    }
}

2.5 组合问题小结

  1. 组合问题一般都需要使用 indexStart (start) 来在递归中缩小搜索范围,避免组合中出现重复元素;
  2. 当写出基本版本的代码后,可以考虑是否能通过剪枝的方式提高效率;
  3. 树层去重的时候需要对原数组进行排序。

3. 排列问题

3.1 全排列

LC链接:46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例: 输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

  • 思路:
  1. 每层从0开始便利,不用 indexStart :排列由于需要把数组中的数字(1、2、3)全部用到,所以不能像组合子集问题一样利用 indexStart 来逐步缩小问题范围,而是需要在每次递归时做完整的遍历(即从索引0开始遍历),把没有用到的元素添加到 path 中。
  2. 递归何时结束:当 path 中元素数量等于原数组中元素数量时,说明原数组元素远不用完,返回。
  3. 树枝去重:我们规定使用 usedPath 数组来记录一条路径(树枝)上对应索引元素的使用情况。
    在这里插入图片描述
import java.util.List;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;

class Solution {
    List<List<Integer>> res = new ArrayList();

    Deque<Integer> path = new LinkedList();

    public List<List<Integer>> permute(int[] nums) {
        boolean[] usedPath = new boolean[nums.length];
        backtracking(nums, usedPath);
        return res;
    }

    // 排列,数组里的元素都要用到且只能用一次,不用像组合一样使用indexStart缩小取值范围
    // 而是使用 usedPath 数组来标记每一条路径(树枝)上元素的使用情况,如果已经在该路径上使用过就跳过
    private void backtracking(int[] nums, boolean[] usedPath) {
        // 退出条件:当路径元素跟数组元素相等时
        if (path.size() == nums.length) {
            res.add(new ArrayList(path));
            return;
        }

        // 排列每次都从0开始遍历,利用usedPath来确保每一条路径上元素的唯一性
        for (int i = 0; i < nums.length; i++) {
            if (usedPath[i] == true) continue;
            usedPath[i] = true;  // 记录该下标的元素在当前路径上已经使用过
            path.addLast(nums[i]);  // 将元素添加到当前路径上
            backtracking(nums, usedPath);  // 带着当前路径元素的使用记录,继续去(从下标0开始)遍历
            path.removeLast();  // 回溯路径
            usedPath[i] = false;  // 回溯路径元素使用记录
        }
    }
}
  • 要点:
  1. 树枝去重:排列问题需要每层都从0开始遍历不需要 indexStart,而是使用 usedPath 完成路径上的去重, usedPath 记录的是数组元素在每条路径上的使用情况——元素下标

3.2 全排列 II

LC链接:47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1: 输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2: 输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10


  • 思路:利用 used 数组完成数层和树枝去重
class Solution {
    
    List<List<Integer>> res;
    Deque<Integer> path;
    public List<List<Integer>> permuteUnique(int[] nums) {
        res = new ArrayList();
        path = new ArrayDeque(nums.length);
        
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        backtracking(nums, used);
        return res;
    }
    
    // 思路:在全排列的基础上增加树层去重
    private void backtracking(int[] nums, boolean[] used) {
        if (path.size() == nums.length) {
            res.add(new ArrayList(path));
            return;
        }
                
        for (int i = 0; i < nums.length; i++) {
            // 树层去重
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            // 树枝去重
            if (used[i] == true) continue;

            used[i] = true;
            path.addLast(nums[i]);
            backtracking(nums, used);
            path.removeLast();
            used[i] = false;
            
        }
    }
}

  • 思路2:利用桶的思想完成数层去重,笔者目前还没有想通为什么这种方法不用对原数组排序???
class Solution {
    
    List<List<Integer>> res;
    Deque<Integer> path;
    public List<List<Integer>> permuteUnique(int[] nums) {
        res = new ArrayList();
        path = new ArrayDeque(nums.length);
        
        boolean[] usedPath = new boolean[nums.length];
        // Arrays.sort(nums);
        backtracking(nums, usedPath);
        return res;
    }
    
    // 思路:在全排列的基础上增加树层去重
    private void backtracking(int[] nums, boolean[] usedPath) {
        if (path.size() == nums.length) {
            res.add(new ArrayList(path));
            return;
        }
        
        boolean[] usedLayer = new boolean[21];
        
        for (int i = 0; i < nums.length; i++) {
            // 树层去重
            if (usedLayer[nums[i] + 10] == true) continue;
            // 树枝去重
            if (usedPath[i] == true) continue;

            usedLayer[nums[i] + 10] = true;
            usedPath[i] = true;
            path.addLast(nums[i]);
            backtracking(nums, usedPath);
            path.removeLast();
            usedPath[i] = false;
            
        }
    }
}

4. 子集问题

4.1 子集

LC链接:78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]

  • 思路:子集跟组合问题一样,也要求集合无序,所以需要 indexStart 来确保取过的元素不会重复添加。但是组合问题是到叶子结点才进行收集,而子集问题是收集所有节点。

在这里插入图片描述

class Solution {
    List<List<Integer>> res;
    
    Deque<Integer> path;
    
    public List<List<Integer>> subsets(int[] nums) {
        res = new ArrayList();
        path = new ArrayDeque(nums.length);
        backtracking(nums, 0);
        return res;
    }
    
    private void backtracking(int[] nums, int indexStart) {
        // 子集问题不需要主动退出,当遍历完数组自然结束即可
        
        res.add(new ArrayList(path));
                
        for (int i = indexStart; i < nums.length; i++) {
            path.addLast(nums[i]);
            backtracking(nums, i + 1);
            path.removeLast();
        }
    }
}

4.2 子集 II

90. 子集 II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: [1,2,2] 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

  • 思路(推荐):used 数组去重。原数组有重复,而要求的集合中不允许有重复,画树形图分析后可知需要树层去重。使用 used 数组结合 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue; 条件完成树层去重
class Solution {
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        backtracking(nums, 0, used);
        return res;
    }
    
    private void backtracking(int[] nums, int indexStart, boolean[] used) {
        res.add(new ArrayList(path));
        
        for (int i = indexStart; i < nums.length; i++) {
            // 树层去重
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            used[i] = true;
            path.addLast(nums[i]);
            backtracking(nums, i + 1, used);
            path.removeLast();
            used[i] = false;
            
        }
    }
}

  • 思路(推荐):桶的思想去重。为每一层创建一个 usedLayer 数组用来在遍历某一层(for 循环)时,记录当前层的数字使用情况,由于题目限定 -10 <= nums[i] <= 10,所以usedLayer大小为 21,相当于把 [-10,10] 平移到 [0, 20],然后以下标来进行统计,下标 0 对应 nums 中的-10,下标 1 对应 nums 中的-9 … 下标 20 对应 nums 中的10。
class Solution {
    List<List<Integer>> res = new ArrayList();
    
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtracking(nums, 0);
        return res;
    }
    
    private void backtracking(int[] nums, int indexStart) {
        res.add(new ArrayList(path));
        
        boolean[] usedLayer = new boolean[21];
        
        for (int i = indexStart; i < nums.length; i++) {
            // 树层去重,桶的思想
            if (usedLayer[nums[i] + 10] == true) continue;
            usedLayer[nums[i] + 10] = true;
            path.addLast(nums[i]);
            backtracking(nums, i + 1);
            path.removeLast();
            
        }
    }
}

4.3 递增子序列

LC链接:491. 递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
输入: [4, 6, 7, 7] 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

这道题坑的点还挺多的:
1、树层去重(不能排序)
2、大于才能添加到path中

class Solution {
    
    List<List<Integer>> res = new ArrayList();
    Deque<Integer> path = new LinkedList();
    
    public List<List<Integer>> findSubsequences(int[] nums) {
        // 不能排序,求递增子序列,如果排序就改变了数组原有的顺序
        backtracking(nums, 0);
        return res;
    }
    
    private void backtracking(int[] nums, int indexStrat) {
        if (path.size() >= 2) {
            res.add(new ArrayList(path));
        }
        
        boolean[] usedLayer = new boolean[201];
        
        for (int i = indexStrat; i < nums.length; i++) {
            // 树层去重
            if (usedLayer[nums[i] + 100] == true) continue;
            // 确保递增
            if (path.isEmpty() || nums[i] >= path.getLast()) {
                usedLayer[nums[i] + 100] = true;
                path.addLast(nums[i]);
                backtracking(nums, i + 1);
                path.removeLast();
            }
        }
    }
}

4.4 划分为k个相等的子集

LC链接:698. 划分为k个相等的子集

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
提示:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000

5 棋盘问题

5.1 N皇后

5.2 解数独


思考

  • 三种树层去重的区别:
  1. used 数组既可以用来树层去重,也可以用来树枝去重,为什么 46. 全排列 树枝去重时使用 if (i > 0 && nums[i] == nums[i - 1] && usedPath[i - 1] == true) continue; 不对? 因为46题中的元素没有重复的?

  2. 说好的树层去重需要排序,但是为什么 47. 全排列 II 利用桶的思想去重时就不用排序?因为[2, 1, 2] 和 [2, 2, 1] 是不同的排列,但是属于同一个组合(子集)。

  3. 2.3、3.2、4.2三个题结合看,总结提炼树层去重的共性!

  4. 树层去重里的层指的是具有直接共同父节点的层,而不是层次遍历中指的一整层!

  • 凡是原数组有重复,或者原数组的数字可以重复使用的情况,都需要树层去重,且树层去重需要先对原数组排序

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值