代码随想录训练营第二十三天 | 39. 组合总和 40.组合总和II 131.分割回文串

39. 组合总和

此处的去重略有不同:元素可以重复取,只不过最后的集合结果仍旧不可重复。
因此,需要先对数组进行排序。

没必要剪枝了,只能判断下一层递归的。因此区别不大。

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        result = new ArrayList<>();
        path = new LinkedList<>();
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return result;
    }
    private List<List<Integer>> result;
    private LinkedList<Integer> path;

    private void backTracking(int[] candidates, int targetSum, int startIndex){ // 如果至少一个数字的被选数量不同,则两种组合是不同的。需要去重——只能选择比自己大的。
        // 终止条件
        if(targetSum<0){
            return;
        }else if(targetSum==0){
            result.add(new ArrayList<Integer>(path));
            return;
        }

        // 回溯
        for(int i=startIndex; i<candidates.length; i++){
            path.add(candidates[i]);
            backTracking(candidates, targetSum-candidates[i], i);
            path.removeLast();
        }

    }
}

40.组合总和II

本来还想改改上一题的代码,发现不太行。

这题又是新花样:取元素的组合里有重复的,但是不能重复取。

因为是同层之间的重复,所以在for循环内解决。

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        result = new ArrayList<>();
        path = new LinkedList<>();
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return result;
    }
    private List<List<Integer>> result;
    private LinkedList<Integer> path;

    private void backTracking(int[] candidates, int targetSum, int startIndex){
        // 终止条件
        if(targetSum<0){
            return;
        }else if(targetSum==0){
            result.add(new ArrayList<Integer>(path));
            return;
        }

        // 回溯
        int pre = 0;
        for(int i=startIndex; i<candidates.length; i++){
            if(pre==candidates[i]) continue;
            path.add(candidates[i]);
            backTracking(candidates, targetSum-candidates[i], i+1);
            path.removeLast();
            pre = candidates[i];
        }
    }
}

代码随想录解法:使用boolean[] used数组,用来记录同一树枝上的元素是否使用
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

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

因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] for循环而来的。

不得不吐槽一下, 缩进为2太难看了。

class Solution {
  LinkedList<Integer> path = new LinkedList<>();
  List<List<Integer>> ans = new ArrayList<>();
  boolean[] used;
  int sum = 0;

  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);
    return ans;
  }

  private void backTracking(int[] candidates, int target, int startIndex) {
    if (sum == target) {
      ans.add(new ArrayList(path));
    }
    for (int i = startIndex; i < candidates.length; i++) {
      if (sum + candidates[i] > target) {
        break;
      }
      // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
      if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
        continue;
      }
      used[i] = true;
      sum += candidates[i];
      path.add(candidates[i]);
      // 每个节点仅能选择一次,所以从下一位开始
      backTracking(candidates, target, i + 1);
      used[i] = false;
      sum -= candidates[i];
      path.removeLast();
    }
  }
}

组合总和总结

共同点:解集不能包含重复的组合 —— 为了解决该问题,有的用了startIndex,有的用了排序,有的用了used数组。

区别:

  • 216.组合总和III
    candidates不能重复,也不能重复取。
    使用startIndex。

  • 39.组合总和
    candidates不能重复,但可以重复取。
    使用startIndex,并且排序。

  • 40.组合总和II
    candidates可以重复,但不能重复取。
    使用startIndex,并且排序,并且使用used数组。

131.分割回文串

要求每个子串都是回文串

如何切割?
切割问题类似于组合问题,需要用到startIndex:一旦切割了,只需要继续切割后半部分即可。
直到切割完毕:最后没有后半部分了。

组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。

注意:

  1. substring中的字母都是小写……
class Solution {
    public List<List<String>> partition(String s) {
        result = new ArrayList<>();
        path = new LinkedList<>();
        backTracking(s, 0);
        return result;
    }
    private List<List<String>> result;
    private List<String> path;
    private void backTracking(String s, int startIndex){ // 分割,左闭右开
        // 判断是否回文
        if(path.size()>0){
            String temp = path.getLast();
            for(int i=0, j=temp.length()-1; i<j; i++, j--){
                if(temp.charAt(i)!=temp.charAt(j)) return;
            }
        }
        if(startIndex==s.length()){
            result.add(new LinkedList<String>(path));
            return;
        }

        // 回溯
        for(int i=startIndex + 1; i<=s.length(); i++){
            path.add(s.substring(startIndex, i));
            backTracking(s, i);
            path.removeLast();
        }
    }
}

代码随想录解法:

  1. 使用动态规划判断回文串。
  2. 将回文判断放在了回溯时:我个人是不喜欢这种写法,容易把回溯弄得复杂了。

动规判断回文串

给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是s[0] == s[n-1]s[1:n-1]是回文字串。
可以高效地事先一次性计算出, 针对一个字符串s, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤。
dp 用二维数组表示start与end。

class Solution {
    List<List<String>> result;
    LinkedList<String> path;
    boolean[][] dp;

    public List<List<String>> partition(String s) {
        result = new ArrayList<>();
        char[] str = s.toCharArray();
        path = new LinkedList<>();
        dp = new boolean[str.length + 1][str.length + 1];
        isPalindrome(str);
        backtracking(s, 0);
        return result;
    }

    public void backtracking(String str, int startIndex) {
        if (startIndex >= str.length()) {
            //如果起始位置大于s的大小,说明找到了一组分割方案
            result.add(new ArrayList<>(path));
        } else {
            for (int i = startIndex; i < str.length(); ++i) {
                if (dp[startIndex][i]) {
                    //是回文子串,进入下一步递归
                    //先将当前子串保存入path
                    path.addLast(str.substring(startIndex, i + 1));
                    //起始位置后移,保证不重复
                    backtracking(str, i + 1);
                    path.pollLast();
                } else {
                    //不是回文子串,跳过
                    continue;
                }
            }
        }
    }

    //通过动态规划判断是否是回文串,参考动态规划篇 52 回文子串
    public void isPalindrome(char[] str) {
        for (int i = 0; i <= str.length; ++i) {
            dp[i][i] = true;
        }
        for (int i = 1; i < str.length; ++i) {
            for (int j = i; j >= 0; --j) {
                if (str[j] == str[i]) {
                    if (i - j <= 1) {
                        dp[j][i] = true;
                    } else if (dp[j + 1][i - 1]) {
                        dp[j][i] = true;
                    }
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值