代码随想录算法训练营 || 回溯算法 39 40 131

Day23

39. 组合总和


力扣题目链接

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

思路

  • 正常进行回溯

  • 设置res和path两个全局变量,并使用sum记录数字

  • 终止条件:因为这个题目不知道递归的层数,因此不能像之前那样size为k结束递归,递归结束就是要么sum > target,这时候直接返回,然后在循环中把这个元素减去继续递归;如果sum == target,就加入res

  • 循环中,还需要设置startIndex,如果不设置的话,每次递归都从第一个元素开始,会出现2 2 3 3 2 2这样取重复的情况,因此我们通过startIndex保证第一个位置拿到3之后,第二个位置不会拿到比3小的数字

  • 优化逻辑

  • 对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    int sum = 0;

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

    private void backtracking(int[] candidates, int target, int startIndex) {
        if (sum > target) return;
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < candidates.length; i++) {
            path.addFirst(candidates[i]);
            sum += candidates[i];
            backtracking(candidates, target, i);
            path.removeFirst();
            sum -= candidates[i];
        }
    }
}

class Solution1 {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    int sum = 0;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);//先把数组进行排序,得到递增数组
        backtracking(target,candidates,0);
        return res;
    }
    private void backtracking(int target, int[] candidates, int startIndex){
        if (sum > target) return;
        if (sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < candidates.length; i++){
            if (sum + candidates[i] > target) break;//如果加上这个元素之后一定大了,后面的就不要看了,就算回溯加完也更大
            int temp = candidates[i];
            path.addFirst(temp);
            sum += temp;
            backtracking(target,candidates,i);
            path.removeFirst();
            sum -= temp;
        }
    }
}

40.组合总和II


力扣题目链接

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

思路

  1. 本题candidates 中的每个数字在每个组合中只能使用一次。

  1. 本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates

  • 每个数字用一次好处理,递归的时候让startIndex + 1即可

  • 关键是元素有重复的,比如1 1 6 7中拿8,我需要1 71 1 6两个答案,不能有两个1 7

  • 我们可以先把数组排序,得到定序的数组

  • 先类似上一题进行剪枝处理,如果遍历到某个元素,把它加到sum里面之后发现已经大于target了,那就不用再循环了,这层递归可以结束了,因为后面的元素一定比这个元素大,一定不满足要求了

  • 后面就有难度了

  • 在第一层循环中,先拿到1,然后我进入递归,我想要在递归中能拿到第二个1,保证1 1 6这个答案,所以我不能简单的判断如果这个元素和上一个元素一样就continue,应该保证这个元素不能是每层递归的第一个元素,比如1 1 1 1中拿3也是一样,第一层递归拿到1,第二层递归还要再拿到1,因此只有当这层递归循环了至少一次,才能判断,因此需要是 i > startIndex

  • 加入我第二层递归结束了,返回了第一层递归,继续循环,这时候1 1 61 7都拿到了,第一层递归走到了第二个1的位置,这时候应该continue,因为这个元素和上一个元素一样;同理1 1 1 1中拿3,第二层递归结束之后已经拿到了答案,返回第一层递归,走到第二个1,发现和上一个元素一样,那就continue

  • 其实就是,同一数层上两个相同元素不能同时选取,但同一树枝上两个相同元素可以同时选取

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    int sum = 0;

    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 startIndex) {
        if (sum == target) {
            res.add(new ArrayList<>(path));
        }
        for (int i = startIndex; i < candidates.length; i++) {
            if (candidates[i] + sum > target) break;//类比之前的剪枝操作
            if (i > startIndex && candidates[i] == candidates[i - 1]) continue;
            //如果同层有相同的就返回;注意同枝可以有相同的
            path.addFirst(candidates[i]);
            sum += candidates[i];
            backtracking(candidates,target,i + 1);
            path.removeFirst();
            sum -= candidates[i];
        }
    }
}

131.分割回文串


力扣题目链接

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

思路

  • 最简单的是判断回文串,使用双指针即可,传入字符串和头尾指针

  • 思考:需要把字符串进行切割,切割几段不知道,所以还是回溯问题

  • 我们设置一个startIndex表示切割是否结束,如果startIndex == s.length()了,那切割就结束了,然后加入res,注意回文串的判断再前面判断过,所以终止条件这里我们直接加入到res即可

  • 开始递归循环,每次先判断一下是不是回文串,从startIndex到i这段进行判断,如果是,就可以加入path中,如果不是,这次循环结束,让i++再循环

  • 举个例子:

  • a a b的切割,首先把a传进去,第一层递归,startIndex = 0, i = 0; 判断a这个子字符串是回文串,加入path,然后进入第二层递归,startIndex = 1; i = 1; 判断a是回文串,加入path,进入第三层递归,startIndex = 2; i = 2; 判断b是回文串,加入path,继续递归。这时startIndex = 3,因此加入res,返回第三层递归;

  • 第三层递归把a移除,然后继续循环,startIndex = 2,但是i = 3,已经不满足要求,结束循环,返回第二层递归

  • 第二层递归把a移除,path里面还有一个a,这时startIndex = 1,i++变为2;继续循环,看aa是不是回文串,是因此加入path,然后进入第三层递归,发现b也是,进入第四层递归,然后加入res,结束第四层和第三层递归,移出a,然后回到第二层递归发现aab不是回文串,continue,然后循环结束

代码

class Solution {
    List<List<String>> res = new ArrayList<>();
    Deque<String> path = new LinkedList<>();

    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 (isPalindrome(s,startIndex,i)) {
                path.addLast(s.substring(startIndex, i + 1));//subString是左闭右开的,想拿到i需要是i+1
            } else {
                continue;
            }
            backtracking(s, i + 1);
            path.removeLast();
        }
    }

    private boolean isPalindrome(String s, int head, int end) {//判断字符串是否是回文串
        while (head <= end) {
            if (s.charAt(head) != s.charAt(end)) return false;
            head++;
            end--;
        }
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值