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

39. 组合总和

题目链接:https://leetcode.com/problems/combination-sum

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.
The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.

思路:

  1. 无重复元素,但单个元素可重复选取,所以startindex要包含当前元素,但是之前的不行,因为组合会重复
  2. 什么时候需要startindex?
    如果是一个集合来求组合的话(组合是无序的),就需要startIndex,例如:77.组合,216.组合总和III。
    如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合

回溯

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return result;
    }
    private void backtracking(int[] candidates, int target, int sum, int startIndex){
        if (sum > target) return;
        if (sum == target){
            result.add(new ArrayList<>(path));
            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(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。

回溯+剪枝(排序后,一旦大于target,停止遍历)

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, 0);
        return result;
    }
    private void backtracking(int[] candidates, int target, int sum, int startIndex){
        if (sum == target){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < candidates.length; i++){
            // 如果 sum + candidates[i] > target 就终止遍历
            if (sum + candidates[i] > target) break;
            path.add(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i);
            path.remove(path.size()-1);
        }
    }
}

或者

for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){

时间复杂度: O(n * 2^n),注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
空间复杂度: O(target)

40.组合总和II

题目链接:https://leetcode.com/problems/combination-sum-ii

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target.
Each number in candidates may only be used once in the combination.
Note: The solution set must not contain duplicate combinations.

Input: candidates = [10,1,2,7,6,1,5], target = 8
Output:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

思路:
和39的区别,1. 本题的每个数字在每个组合中只能使用一次,不能重复选取 2. 本题candidates中的数字有重复,39数字无重复 3. 但两题都不能有重复的组合
即 candidates中有重复的元素,但不能重复选取同一个(i+1),也不能有重复的组合
元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

去重

使用过的元素不能重复选取,但“used”也分为两个维度,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。
数层去重(要做的,因为排序后,一定包含后面的相同数字搜索的内容)
树枝去重(可以重复,因为只是数值相同,但是元素不同)

在这里插入图片描述
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1],而不是同一树枝使用。
在这里插入图片描述

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        boolean[] used = new boolean[candidates.length];
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
    private void backtracking(int[] candidates, int target, int sum, int startIndex, boolean[] used){
        if (sum == target) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < candidates.length; i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if (i >0 && candidates[i] == candidates[i-1] && used[i-1] == false) {
                continue;
            }
            // 剪枝
            if (sum+candidates[i] > target) break;
            path.add(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, sum+candidates[i], i+1, used);
            path.remove(path.size()-1);
            used[i] = false;
        }
    }
}

时间复杂度: O(n * 2^n)
空间复杂度: O(n)

131.分割回文串

题目链接:https://leetcode.com/problems/palindrome-partitioning/

Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.
Input: s = “aab”
Output: [[“a”,“a”,“b”],[“aa”,“b”]]

思路:

  1. 类似组合回溯
  2. startIndex就是切割线,index是上一层已经确定了的分割线,i是这一层试图寻找的新分割线。
  3. for循环定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串
    在这里插入图片描述
class Solution {
    List<String> path = new ArrayList<>();
    List<List<String>> result = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backtracking(s, 0);
        return result;
    }
    private void backtracking(String s, int startIndex) {
    // 如果起始位置已经大于等于s的大小,说明已经找到了一组分割方案了
    // 不是s.length()-1,因为最后一个也可以单独作为palindrome
        if (startIndex >= s.length()) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i < s.length(); i++){
            if (isParlindrome(s, startIndex, i)) {
            // 获取[startIndex,i]在s中的子串
                String substr = s.substring(startIndex, i+1);
                path.add(substr);
            } else {
                continue;
            }
            backtracking(s, i+1); //注意切割过的位置,不能重复切割,i+1
            path.remove(path.size()-1);
        }
    }
    private boolean isParlindrome(String s, int start, int end){
        for (int i = start, j = end; i<j; i++, j--){
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }
}

时间复杂度: O(n * 2^n)
空间复杂度: O(n^2)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值