leetcode 刷题_day29_回溯03_组合去重的两种维度&分割问题

39. 组合总和

思路

数字重复

  • 不同于之前不允许重复数字的组合题,本题允许重复的数字,那对于组合而言,只需要允许当前节点的数值在下一层级也允许被选到就行

输入源的有序性对剪枝优化的差异

  • 如果有序,则剪枝时根据有序性,可以推断右边及右下的所有节点可选数值都大于当前节点值,因此直接裁掉
  • 如果无序,则无法如上推断,只能裁掉当前节点及其子树,裁剪空间有限

注意:

  • 排序耗费的时间和剪枝优化的时间要对比

image.png

解题方法

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code

class Solution {

    private List<List<Integer>> result = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    private int sum = 0;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // 先将数组排序,这样剪枝优化时可以直接裁掉树的右下角
        // 但实际运行起来,似乎这个排序的时间大于裁剪优化的时间
        // Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return result;
    }

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

         // 单层操作 -- 横向遍历
         // 注意:这里无法通过 path 的个数来剪枝
         for (int i = startIndex; i < candidates.length; i++) {
            if (sum + candidates[i] > target) {
                // 如果是有序
                // return;

                // 如果是无序,只能裁掉当前节点的子树
                continue;
            }

             path.add(candidates[i]);
             sum += candidates[i];
            
            // 纵向遍历
             backTracking(candidates, target, i); // startIndex 不加一,表示下一层可以与当前的一致

             path.removeLast();
             sum -= candidates[i];
         }

         return;
    }
}

40.组合总和II

思路

注意:不能使用 map、set 去重,会超时

所谓去重,其实就是使用过的元素不能重复选取
组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的

  1. 一个维度是同一树枝上使用过(纵向)
  2. 一个维度是同一树层上使用过(横向)

本题中,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”(横向)同一树枝上的都是一个组合里的元素(纵向),不用去重
image.png

注意:树层去重的话,需要对数组排序,然后用类似双指针就能做对比

解题方法

描述你的解题方法

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code


  class Solution {
    // 不能使用map、set 去重,会超时

    // 所谓去重,其实就是使用过的元素不能重复选取
    // 都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是**同一树枝上使用过(纵向)**,**一个维度是同一树层上使用过(横向)**
    // 本题中,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
    // 所以我们要去重的是**同一树层上的“使用过”(横向)**,**同一树枝上的都是一个组合里的元素(纵向),不用去重**。
    // 注意:树层去重的话,需要对数组排序,然后用类似双指针就能做对比
    
    private List<List<Integer>> result = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    private int sum = 0;
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return result;
    }

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

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

            // 树层去重
            if (i > startIndex && candidates[i] == candidates[i - 1]) {
                continue;
            }

            path.add(candidates[i]);
            sum += candidates[i];

            backTracking(candidates, target, i + 1);

            path.removeLast();
            sum -= candidates[i];
        }

        return;
    }


}

131.分割回文串

思路

通过回溯来解决分割问题,将分割问题转换成逻辑树,在每一层中,通过 startIndex控制截取的位置,每个节点往下是纵向的递归遍历,注意,传入的是 i + 1,也即,切割过的地方不能再切割

回文判断

  1. 反转后判断字符串是否相等
  2. 双指针对向遍历,判断是否相等

image.png

解题方法

描述你的解题方法

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code


  class Solution {

    private List<List<String>> result = new ArrayList<>();
    private LinkedList<String> path = new LinkedList<>();

    public List<List<String>> partition(String s) {
        backTracking(s, 0);
        return result;
    }

    private void backTracking(String s, int startIndex) {
        // 终止条件
        // 能走到叶子节点的,说明 path 内的都是符合回文定义的字符串,如果不符合,会在之前节点中被终止
        if (s.length() <= startIndex) {
            result.add(new ArrayList<>(path));
            return;
        }

        // 横向遍历
        // 通过 startIndex 控制切割字符串 s 的位置
        for (int i = startIndex; i < s.length(); i++) {
            // 当前节点处理
            // 判断切割出来的字符串 [startIndex, i] 是不是回文,如果是,则加入到 path 中,如果不是,则当前节点及其子树都丢弃
            if (!isPalindrome(s, startIndex, i)) {
                continue;
            }
            path.add(s.substring(startIndex, i + 1));

            // 纵向遍历
            backTracking(s, i + 1);

            path.removeLast();
        }

        return;
    }

    private boolean isPalindrome(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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值