代码随想录算法训练营第27天

今日任务

● 39. 组合总和 ● 40.组合总和II ● 131.分割回文串

链接:https://docs.qq.com/doc/DUElpbnNUR3hIbXlY (opens in a new tab)

39. 组合总和

自己想法+题解想法

注:本题题目说明集合没有重复元素,但是可以重复使用

题解:可以先排序,再利用有序性进行剪枝

class Solution {
    public LinkedList<Integer> path = new LinkedList<>();
    public int now_sum = 0;
    public List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates); // 先进行排序
        backtrack(candidates, target, 0, candidates.length - 1);
        return ans;
    }
    public void backtrack(int[] candidates, int target, int start, int end) {
        if(now_sum == target) ans.add(new ArrayList(path));
        else if(now_sum > target) return;
        else {
            for(int i = start; i <= end; i++) {
                path.add(candidates[i]);
                now_sum += candidates[i];
                if(now_sum > target) { // 剪枝
                    path.removeLast();
                    now_sum -= candidates[i];
                    break;
                }
                backtrack(candidates, target, i, end); // i不变,因为可以重复使用
                path.removeLast();
                now_sum -= candidates[i];
            }
        }
    }
}

40.组合总和II

自己想法

注:本题集合中的元素有重复,并且每个元素只能使用一次,并且最后的结果集合中不能有重复的组合

题解:那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢? 回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。 所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。

自己:因此,去重应该放在for末尾,而不是backtrack前面

class Solution {
    public LinkedList<Integer> path = new LinkedList<>();
    public int now_sum = 0;
    public List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates); // 先进行排序
        backtrack(candidates, target, 0, candidates.length - 1);
        return ans;
    }
    public void backtrack(int[] candidates, int target, int start, int end) {
        if(now_sum == target) ans.add(new ArrayList(path));
        else if(now_sum > target) return;
        else {
            for(int i = start; i <= end; i++) {
                path.add(candidates[i]);
                now_sum += candidates[i];
                if(now_sum > target) { // 剪枝
                    path.removeLast();
                    now_sum -= candidates[i];
                    break;
                }
                backtrack(candidates, target, i + 1, end);
                path.removeLast();
                now_sum -= candidates[i];                
                while((i + 1) <= end && candidates[i] == candidates[i + 1]) { // 去重
                    i++;
                    // System.out.println("skip: " + i);
                }
            }
        }
    }
}

131.分割回文串

自己想法+题解想法

  • 切割问题可以抽象为组合问题

  • 如何模拟那些切割线

  • 切割问题中递归如何终止

  • 可选优化:使用动态规划,高效地事先一次性计算出任意字串是不是回文串

class Solution {
    public List<List<String>> ans = new ArrayList<>();
    public List<String> tmp = new ArrayList<>();
    boolean[][] dp;
    public List<List<String>> partition(String s) {
        palindromeCheck2(s);
        backtrack(s, 0, s.length());
        return ans;
    }
    // [start, end)
    public void backtrack(String s, int start, int end) {
        // 不能再切的时候得到一个答案
        if(start == end) ans.add(new ArrayList<String>(tmp));
        else {
            for(int i = start; i < end; i++) {
                // for的i决定字串在哪里切第一刀
                String front = s.substring(start, i + 1);
                if(dp[start][i]) {
                // if(palindromeCheck(front)) {
                    tmp.add(front);
                    // 递归回溯决定切完第一刀后的字串继续怎么切
                    backtrack(s, i + 1, end);
                    tmp.remove(tmp.size() - 1);
                }
            }
        }
    }
    // 直接判断传入的字串是否回文
    public boolean palindromeCheck(String s) {
        for(int i = 0; i < s.length() / 2; i++) {
            if(s.charAt(i) != s.charAt(s.length() - i - 1)) return false;
        }
        return true;
    }
    // 动态规划判断“任意字串is回文串?”
    public void palindromeCheck2(String s) {
        int n = s.length();
        dp = new boolean[n][n];
        for(int i = 0; i < n; i++) dp[i][i] = true;
        for(int i = n - 1; i >= 0; i--) {
            for(int j = i + 1; j < n; j++) {
                if(s.charAt(i) == s.charAt(j)) {
                    if(j - i == 1) dp[i][j] = true;
                    else dp[i][j] = dp[i + 1][j - 1];
                }
            }
        }
    }
}