Leetcode题目整理【二、回溯-中等题-1】

一网打尽回溯子数组!

面试字节和快手都被问了回溯数组的问题,这里整理一下。

1. 组合总和

在这里插入图片描述

方法一:回溯+深度优先搜索
分“使用当前数”和“不使用当前数,直接跳过”
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        List<Integer> combine = new ArrayList<Integer>();
        dfs(candidates, target, ans, combine, 0);
        return ans;
    }

    public void dfs(int[] candidates, int target, List<List<Integer>> ans, List<Integer> combine, int idx) {
        if (idx == candidates.length) {
            return;
        }
        if (target == 0) {
            ans.add(new ArrayList<Integer>(combine));
            return;
        }
        // 不用当前数,直接跳过
        dfs(candidates, target, ans, combine, idx + 1);
        // 选择当前数
        if (target - candidates[idx] >= 0) {
            combine.add(candidates[idx]);
            dfs(candidates, target - candidates[idx], ans, combine, idx);
            combine.remove(combine.size() - 1);
        }
    }
}
方法二:回溯
因为可以无限次使用,所以递归的时候,不用i+1,用i,保证“无限次”的条件
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res=new ArrayList<>();
        search(candidates,target,0,new ArrayList<Integer>(),res,0);
        return res;
    }
    private void search(int[] nums,int target,int sum,List<Integer> tmp,List<List<Integer>> res,int p){
        if(sum>target) return;
        else if(sum==target){
            res.add(new ArrayList<Integer>(tmp));
            return;
        }
        for(int i=p;i<nums.length;i++){
            tmp.add(nums[i]);
            sum+=nums[i];
            search(nums,target,sum,tmp,res,i);
            tmp.remove(tmp.size()-1);
            sum-=nums[i];
        }
    }
}

2. 组合总和Ⅱ

在这里插入图片描述
本题和上一题的区别在于,本体中数组的每一个元素,只能只用一次,而且元素的值可能重复。也就是说:
1.如果将上题中递归的“i”换成“i+1”,可能会有重复的结果,解决办法是set或者list.indexOf判重,但是会带来空间和时间复杂度;
2.如果将“i”不换成“i+1”,而是先排序,再绕过重复的元素,则可能会少某种结果。比如target=7,list=[1,1,5],因为绕过了第二个1,所以少了结果。

最好的办法就是排序、用freq存储每个元素出现的次数(这时数组freq是去重的,所以直接i+1即可),然后分使用或者不使用此元素,使用的时候,可以使用不同的次数。

class Solution {
    List<int[]> freq = new ArrayList<int[]>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> sequence = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        for (int num : candidates) {
            int size = freq.size();
            if (freq.isEmpty() || num != freq.get(size - 1)[0]) {
                freq.add(new int[]{num, 1});
            } else {
                ++freq.get(size - 1)[1];
            }
        }
        dfs(0, target);
        return ans;
    }
    public void dfs(int pos, int rest) {
        if (rest == 0) {
            ans.add(new ArrayList<Integer>(sequence));
            return;
        }
        if (pos == freq.size() || rest < freq.get(pos)[0]) {
            return;
        }
        dfs(pos + 1, rest);
        int most = Math.min(rest / freq.get(pos)[0], freq.get(pos)[1]);
        for (int i = 1; i <= most; ++i) {
            sequence.add(freq.get(pos)[0]);
            dfs(pos + 1, rest - i * freq.get(pos)[0]);
        }
        for (int i = 1; i <= most; ++i) {
            sequence.remove(sequence.size() - 1);
        }
    }
}

3. 组合总和Ⅲ

在这里插入图片描述
最正常的回溯了,谢谢leetcode

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> tmp = new ArrayList<Integer>();
        dfs(0,k,0,n,1,res,tmp);
        return res;
    }
    private void dfs(int t,int k,int sum,int n,int index, List<List<Integer>> res,List<Integer> tmp){
        if(t==k){
            if(sum==n){
                res.add(new ArrayList<>(tmp));
                return;
            }
        }
        if(sum>n) return;
        for(int i=index;i<=9;i++){
            tmp.add(i);
            sum+=i;
            dfs(t+1,k,sum,n,i+1,res,tmp);
            sum-=i;
            tmp.remove(tmp.size()-1);
        }
        return;
    }
}

4. 全排列

在这里插入图片描述

每次选择一个数加入到list,加入之后把这个数的index和回溯递归的次数index交换,保证不会选择到重复的数字

class Solution {
    public void backtrack(int n, ArrayList<Integer> output,List<List<Integer>> res, int first) {
        // 所有数都填完了
        if (first == n)
            res.add(new ArrayList<Integer>(output));
        for (int i = first; i < n; i++) {
            // 动态维护数组
            Collections.swap(output, first, i);
            // 继续递归填下一个数
            backtrack(n, output, res, first + 1);
            // 撤销操作
            Collections.swap(output, first, i);
        }
    }
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new LinkedList();
        ArrayList<Integer> output = new ArrayList<Integer>();
        for (int num : nums)
            output.add(num);
        int n = nums.length;
        backtrack(n, output, res, 0);
        return res;
    }
}

5. 全排列 II

在这里插入图片描述
这个题和之前的组合总和Ⅱ有相似的地方,元素有重复的,还要求结果不重复。除了最后判重之外(这种方式复杂度较高),组合总和Ⅱ是按照使用该元素的个数进行回溯的,但是全排列里面必须全部使用,所以也需要排序,外加 在一次回溯中,如果相邻元素相等,则跳过该元素来实现去重

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

public class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }
        // 排序(升序或者降序都可以),排序是剪枝的前提
        Arrays.sort(nums);
        boolean[] used = new boolean[len];
        // 使用 Deque 是 Java 官方 Stack 类的建议
        Deque<Integer> path = new ArrayDeque<>(len);
        dfs(nums, len, 0, used, path, res);
        return res;
    }
    private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
        if (depth == len) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < len; ++i) {
            if (used[i]) {
                continue;
            }
            // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
            // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
            // 如果used[i - 1]为true。说明仍在下一次回溯中
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            path.addLast(nums[i]);
            used[i] = true;

            dfs(nums, len, depth + 1, used, path, res);
            // 回溯部分的代码,和 dfs 之前的代码是对称的
            used[i] = false;
            path.removeLast();
        }
    }
}

6. 组合

在这里插入图片描述

方法一:回溯
class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res=new ArrayList<>();
        search(n,k,0,1,res,new ArrayList<>());
        return res;
    }
    /*
    * tmp:保存当前组合 res:最后的结果集合
    * c:用来记录tmp中元素的个数,index:当前的数字大小,保证不会有[2,1]这种情况
    */
    public void search(int n,int k,int c,int index,List<List<Integer>> res, List<Integer> tmp){
        if(c==k){
            res.add(new ArrayList<>(tmp));
            return;
        }
        for(int i=index;i<=n;i++){
            tmp.add(i);
            search(n,k,c+1,i+1,res,tmp);
            tmp.remove(c);
        }
    }
}
方法二:递归
class Solution {
    List<Integer> temp = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int cur, int n, int k) {
        // 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
        if (temp.size() + (n - cur + 1) < k) {
            return;
        }
        // 记录合法的答案
        if (temp.size() == k) {
            ans.add(new ArrayList<Integer>(temp));
            return;
        }
        // 考虑选择当前位置
        temp.add(cur);
        dfs(cur + 1, n, k);
        temp.remove(temp.size() - 1);
        // 考虑不选择当前位置
        dfs(cur + 1, n, k);
    }
}

7. 子集

在这里插入图片描述
在递归的一开始就add

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res=new ArrayList<>();
        List<Integer> tmp=new ArrayList<>();
        dfs(0,nums,res,tmp);
        return res;
    }
    private void dfs(int k,int[] nums,List<List<Integer>> res, List<Integer> tmp){
        if(k>nums.length) return;
        res.add(new ArrayList<>(tmp));        
        for(int i=k;i<nums.length;i++){
            tmp.add(nums[i]);
            dfs(i+1,nums,res,tmp);
            tmp.remove(tmp.size()-1);
        }
    }
}

8. 子集Ⅱ

在这里插入图片描述
在递归的一开始就add,此外,每轮递归的时候跳过重复的元素

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums); //排序
        getAns(nums, 0, new ArrayList<>(), ans);
        return ans;
    }
    private void getAns(int[] nums, int start, ArrayList<Integer> temp, List<List<Integer>> ans) {
        ans.add(new ArrayList<>(temp));
        for (int i = start; i < nums.length; i++) {
            //和上个数字相等就跳过
            if (i > start && nums[i] == nums[i - 1]) {
                continue;
            }
            temp.add(nums[i]);
            getAns(nums, i + 1, temp, ans);
            temp.remove(temp.size() - 1);
        }
    }
}

9. 累加数

在这里插入图片描述

class Solution {
    public boolean isAdditiveNumber(String num) {
        return dfs(num, num.length(), 0, 0, 0, 0);
    }
    /**
     * @param num    原始字符串
     * @param len    已用字符串的长度
     * @param idx    当前处理下标
     * @param sum    前面的两个数字之和
     * @param pre    前一个数字
     * @param k      当前是处理的第几个数字
     */
    private boolean dfs(String num, int len, int idx, long sum, long pre, int k) {
        if (idx == len) {
            return k > 2;
        }
        for (int i = idx; i < len; i++) {
            long cur = fetchCurValue(num, idx, i);
            // 剪枝:无效数字
            if (cur < 0) {
                continue;
            }
            // 剪枝:当前数字不等于前面两数之和
            if (k >= 2 && cur != sum) {
                continue;
            }
            if (dfs(num, len, i + 1, pre + cur, cur, k + 1)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 获取 l ~ r 组成的有效数字
     */
    private long fetchCurValue(String num, int l, int r) {
        if (l < r && num.charAt(l) == '0') {
            return -1;
        }
        long res = 0;
        while (l <= r) {
            res = res * 10 + num.charAt(l++) - '0';
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值