Leetcode 刷题 回溯算法

39. 组合总和

经典的回溯题  组合问题

startIndex 的使用 

因为本地可以使用重复的元素     单层for循环依然是从startIndex开始,搜索candidates集合。

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

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

    private void backtracking(int[] candidates, int target, int sum, int startIndex){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        if(sum > target){
            return;
        }

        for(int i = startIndex; i < candidates.length; i++){
            path.add(candidates[i]);
            sum += candidates[i];
            backtracking(candidates, target, sum, i);
            path.remove(path.size() - 1);
            sum -= candidates[i];
        }
    }
}

 可以做剪枝优化 当sum > target 的时候 不需要进入递归

 

// 剪枝优化
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(candidates); // 先进行排序
        backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
        return res;
    }

    public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
        // 找到了数字和为 target 的组合
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = idx; i < candidates.length; i++) {
            // 如果 sum + candidates[i] > target 就终止遍历
            if (sum + candidates[i] > target) break;
            path.add(candidates[i]);
            backtracking(res, path, candidates, target, sum + candidates[i], i);
            path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
        }
    }
}

在求和问题中,排序之后加剪枝是常见的套路!

 

 40. 组合总和 II

没做出来 

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合

去重的逻辑 

        

组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过一个维度是同一树层上使用过

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

强调一下,树层去重的话,需要对数组排序!

此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

这个集合去重的重任就是used来完成的

class Solution {
    
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        used = new boolean[candidates.length];
        Arrays.fill(used, false);
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, 0);
        return res;
    }

    private void backtracking(int[] candidates, int target, int sum, int startIndex){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }

        for(int i = startIndex; i < candidates.length; i++){  
            // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过  
            if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]){
                continue;
            }
            if(sum + candidates[i] > target){
                break;
            }
            used[i] = true;
            sum += candidates[i];
            path.add(candidates[i]);
            backtracking(candidates, target, sum, i + 1);
            used[i] = false;
            sum -= candidates[i];
            path.remove(path.size() - 1);
        }

    }
}
  • used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的

 

注意:

去重逻辑可以不适用used数组

这里直接用startIndex来去重也是可以的, 就不用used数组了。

// 要对同一树层使用过的元素进行跳过
if (i > startIndex && candidates[i] == candidates[i - 1]) {
    continue;
}

这道题目 主要是要分清楚去重逻辑是应用在 同一树枝 还是 同一树层

131. 分割回文串

切割问题其实是一种组合问题!

分析出是组合问题就好办了

Java 切割子串的方法

s.substring(int start, int end);
class Solution {

    List<List<String>> res = new ArrayList<>();
    List<String> path = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backtracking(s, 0);
        return res;
    }

    private void backtracking(String s, int startIndex){
        if(startIndex >= s.length()){
            res.add(new ArrayList<>(path));
            return;
        }

        for(int i = startIndex; i < s.length(); i++){
            if(isValid(s.substring(startIndex, i+1))){
                path.add(s.substring(startIndex, i+1));
                backtracking(s, i+1);
                path.remove(path.size() - 1);
            }
        }
    }

    private boolean isValid(String s){
        int left = 0;
        int right = s.length() - 1;

        while(left <= right){
            if(s.charAt(left) != s.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

判断是否为回文子串可以优化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值