回溯

1 模板

解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:

  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件。
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

时间复杂度:O(N×N!)

2 例题

2.1 N皇后

public int queen(int n){
    if (n < 1){
        return 0;
    }
    int[] record = new int[n];
    return process(0,record,n);
}

public int process(int i, int[] record, int n) {
    if (i == n){
        return 1;
    }
    int res= 0;
    for (int j = 0; j < n; j++) {
        if (isValiad(i,j,record)){
            record[i] = j;
            res += process(i +1, record, n);
        }
    }
    return res;
}

public boolean isValiad(int i , int j , int[] record){
    for (int k = 0; k < i; k++) {
        if (record[k] == j || Math.abs(record[k] - j) == Math.abs(i - k)){
            return false;
        }
    }
    return true;
}

record的记录方式简化了撤销选择这一步。

2.2 全排列

2.2.1 LeetCode 46. 全排列

题目描述:
给定一个没有重复数字的序列,返回其所有可能的全排列。

用例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

参考代码:
方案一:

public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> resList = new LinkedList<>();
    LinkedList<Integer> trace = new LinkedList();
    int[] visited = new int[nums.length];
    backtrack(0, nums, trace, visited, resList);
    return resList;
}

public void backtrack(int i, int[] nums, LinkedList<Integer> trace, int[] visited, List<List<Integer>> resList) {
    if (i == nums.length) {
        resList.add(new LinkedList<>(trace));
    }

    for (int j = 0; j < nums.length; j++) {
        if (visited[j] == 0){
            trace.addLast(nums[j]);
            visited[j] = 1;
            backtrack(i + 1, nums, trace, visited, resList);
            trace.removeLast();
            visited[j] = 0;
        }
    }
}

方案二:
直接交换。

public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> resList = new LinkedList<>();
    List<Integer> output = new ArrayList<>();

    for (int i = 0; i < nums.length; i++) {
        output.add(nums[i]);
    }
    backtrack(0, nums.length, output, resList);
    return resList;
}

public static void backtrack(int i, int n, List<Integer> output, List<List<Integer>> resList) {
    if (i == n) {
        resList.add(new ArrayList(output));
        return;
    }

    for (int j = i; j < n; j++) {
        Collections.swap(output, i, j);
        backtrack(i + 1, n, output, resList);
        Collections.swap(output, i, j);
    }
}

2.2.2 LeetCode 47. 全排列 II

题目描述:
给定一个可包含重复数字的序列,返回所有不重复的全排列。

用例:

输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

实现思路:
先排序,然后搜索过程中剪枝。

参考代码:

public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> resList = new LinkedList<>();
    LinkedList<Integer> trace = new LinkedList();
    Arrays.sort(nums);
    int[] visited = new int[nums.length];
    backtrack(0, nums, trace, visited, resList);
    return resList;
}

public void backtrack(int i, int[] nums, LinkedList<Integer> trace, int[] visited, List<List<Integer>> resList) {
    if (i == nums.length) {
        resList.add(new LinkedList<>(trace));
        return;
    }

    int pre = 0;
    boolean flag = false;
    for (int j = 0; j < nums.length; j++) {
        if (flag && pre == nums[j]){
            continue;
        }
        if (visited[j] == 0){
            trace.addLast(nums[j]);
            visited[j] = 1;
            backtrack(i + 1,nums,trace,visited,resList);
            trace.removeLast();
            visited[j] = 0;
            flag = true;
            pre = nums[j];
        }
    }
}

2.2.3 全排列 III permuteSkip

题目描述:
给定一个数n,代表小于等于n的所有自然数。给出这些自然数的全排列,限制条件是相邻两个数之间的差值大于1

用例:

输入: 4
输出:
[
[2,4,1,3],
[3,1,4,2]
]

参考代码:

public static List<List<Integer>> permuteSkip(int n) {
    int[] nums = new int[n];
    for (int i = 0; i < n; i++) {
        nums[i] = i + 1;
    }
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> trace = new LinkedList<>();
    int[] visited = new int[n];
    process(0, n, nums, visited, trace, res);
    return res;
}

public static void process(int i, int n, int[] nums, int[] visited, LinkedList<Integer> trace, LinkedList<List<Integer>> res) {
    if (i == n) {
        res.add(new LinkedList<>(trace));
        return;
    }

    for (int j = 0; j < n; j++) {
        if (visited[j] == 0 && (i == 0 || Math.abs(trace.getLast() - nums[j]) > 1)) {
            trace.addLast(nums[j]);
            visited[j] = 1;
            process(i + 1, n, nums, visited, trace, res);
            trace.removeLast();
            visited[j] = 0;
        }
    }
}

2.3 子集问题

2.3.1 LeetCode 78. 子集

题目描述:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

用例:

输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

参考代码:

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    process(0,nums,res,new ArrayList<>());
    return res;
}

public void process(int start,int[] nums,List<List<Integer>> res,List<Integer> list){
    res.add(new ArrayList<Integer>(list));
     
    for(int j = start;j < nums.length;j++){
        list.add(nums[j]);
        process(j + 1,nums,res,list);
        list.remove(list.size() - 1);
    }
}

2.3.2 LeetCode 90. 子集 II

题目描述:
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

用例:

输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

参考代码:

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    Arrays.sort(nums);
    process(0,nums,res,new ArrayList<>());
    return res;
}

public void process(int start,int[] nums,List<List<Integer>> res,List<Integer> list){
    res.add(new ArrayList<Integer>(list));

    for(int j = start;j < nums.length;j++){
        if (j > start && nums[j] == nums[j - 1]){
            continue;
        }
        list.add(nums[j]);
        process(j + 1,nums,res,list);
        list.remove(list.size() - 1);
    }
}

2.4 组合问题

2.4.1 LeetCode 77. 组合

题目描述:
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

用例:

输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

参考代码:

public List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> res = new ArrayList();
    process(0,n,k,new ArrayList(),res);
    return res;
}

public void process(int start,int n,int k,List<Integer> trace,List<List<Integer>> res){
    if(trace.size() == k){
        res.add(new ArrayList(trace));
        return;
    }

    for(int i = start;i < n;i++){
        trace.add(i + 1);
        process(i + 1,n,k,trace,res);
        trace.remove(trace.size() - 1);
    }
}

复用模板。

public List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> res = new ArrayList();
    int[] nums = new int[n];
    for(int i = 0;i < n;i++){
        nums[i] = i + 1;
    }
    process(0,n,k,nums,new ArrayList(),res);
    return res;
}

public void process(int start,int n,int k,int[] nums,List<Integer> trace,List<List<Integer>> res){
    if(trace.size() == k){
        res.add(new ArrayList(trace));
        return;
    }

    for(int i = start;i < n;i++){
        trace.add(nums[i]);
        process(i + 1,n,k,nums,trace,res);
        trace.remove(trace.size() - 1);
    }
}

2.4.2 LeetCode 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]
]

参考代码:

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    List<List<Integer>> res = new ArrayList();
    process(0,candidates.length,candidates,target,new ArrayList(),res);
    return res;
}

public void process(int start,int n,int[] nums,int target,List<Integer> trace,List<List<Integer>> res){
    if(target == 0){
        res.add(new ArrayList(trace));
        return;
    }

    for(int i = start;i < n;i++){
        if(target - nums[i] >= 0){
            trace.add(nums[i]);
            process(i,n,nums,target - nums[i],trace,res);
            trace.remove(trace.size() - 1);
        }
    }
}

2.4.3 LeetCode 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]
]

参考代码:

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    List<List<Integer>> res = new ArrayList();
    Arrays.sort(candidates);
    process(0,candidates.length,candidates,target,new ArrayList(),res);
    return res;
}

public void process(int start,int n,int[] nums,int target,List<Integer> trace,List<List<Integer>> res){
    if(target == 0){
        res.add(new ArrayList(trace));
        return;
    }

    for(int i = start;i < n;i++){
        if(i > start && nums[i - 1] == nums[i]){
            continue;
        }
        if(target - nums[i] >= 0){
            trace.add(nums[i]);
            process(i + 1,n,nums,target - nums[i],trace,res);
            trace.remove(trace.size() - 1);
        }
    }
}

2.4.4 LeetCode 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]]

参考代码:

public List<List<Integer>> combinationSum3(int k, int n) {
    List<List<Integer>> res = new ArrayList();
    process(1,k,n,new ArrayList(),res);
    return res;
}

public void process(int start,int k,int target,List<Integer> trace,List<List<Integer>> res){
    if(trace.size() == k || target == 0){
        if(trace.size() == k && target == 0){
            res.add(new ArrayList(trace));
        }
        return;
    }

    for(int i = start;i <= 9;i++){
        if(target - i >= 0){
            trace.add(i);
            process(i + 1,k,target -i,trace,res);
            trace.remove(trace.size() - 1);
        }
    }
}

2.4.5 LeetCode 377. 组合总和 Ⅳ

题目描述:
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

用例:

nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

实现思路:
回溯+记忆化(时间复杂度较大)

参考代码:
回溯+记忆化

public int combinationSum4(int[] nums, int target) {
    Arrays.sort(nums);
    HashMap<Integer,Integer> cache = new HashMap();
    return process(target,nums,cache);
}

public int process(int target,int[] nums,HashMap<Integer,Integer> cache){
    if(target == 0){
        return 1;
    }

    int res = 0;
    if(cache.containsKey(target)){
        return cache.get(target);
    }else{
        for(int i = 0;i < nums.length;i++){
            if(target - nums[i] >= 0){
                res += process(target - nums[i],nums,cache);
            }
        }
        cache.put(target,res);
    }
    return res;
}

2.5 LeetCode 37. 解数独

2.6 LeetCode 22. 括号生成

2.7 吃汉堡

题目描述:
小红吃n天汉堡,要求每天吃的汉堡数目不一样,而且要尽可能多吃鸡肉汉堡,且尽可能少吃牛肉汉堡。
每天鸡肉汉堡供应数目a[i],牛肉汉堡供应数目b[i]。求至少要吃多少牛肉汉堡?

用例:
输入:
n = 5;
a = [1, 2, 2, 2, 4]
b = [4, 3, 5, 2, 1]

输出:
8

参考代码:

public int minBeef(int n, int[] a, int[] b) {
    return process(0, n, 0, new HashSet<>(), a, b);
}

public int process(int day, int n, int sum, HashSet<Integer> cache, int[] a, int[] b) {
    if (day == n) {
        return sum;
    }

    int minSum = -1;
    if (!cache.contains(a[day])) {
        cache.add(a[day]);
        int res = process(day + 1, n, sum, cache, a, b);
        minSum = res;
        cache.remove(a[day]);
    }

    if (!cache.contains(b[day])) {
        cache.add(b[day]);
        int res = process(day + 1, n, sum + b[day], cache, a, b);
        minSum = res == -1 ? minSum : minSum == -1 ? res : Math.min(minSum, res);
        cache.remove(b[day]);
    }
    return minSum;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值