代码随想录第二十六天 | 回溯算法P3 |● 39. ● 40.● 131.

news2024/11/25 14:50:31

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

思路

与组合类似的做法,回溯三步曲:

        回溯函数参数及返回值:candidate、target 以及 start(记录当前遍历起始位置)

        终止条件:当path和为target 则停止

        单层遍历的操作:依然是for循环横向遍历 回溯纵向遍历, for循环里 i 从start 开始

未剪枝代码如下

class Solution {
    public List<List<Integer>> resList = new ArrayList<List<Integer>>();
    public LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracking(candidates, target, 0);
        return resList;
    }
    public void backtracking(int [] candidates, int target, int start){
        if(getSum(path) == target){
            resList.add(new ArrayList<>(path));
            return;
        }
        if(getSum(path) > target){
            return;
        }

        for(int i = start; i < candidates.length; i++){
            path.addLast(candidates[i]);
            backtracking(candidates, target, i);
            path.removeLast();
        }
    }
    public int getSum(List<Integer> path){
        int sum = 0;
        for(int i  : path){
            sum += i;
        }
        return sum;
    }
}

剪枝

        在每层遍历的过程中, 如果当前加入的数 会使得 path和大于 target 那么就不需要继续遍历 ,但仍然存在一个问题,那就是 这需要candidates是升序的,因为只有升序才能保证后续数均会使得sum大于 target。

代码如下

class Solution {
    public List<List<Integer>> resList = new ArrayList<List<Integer>>();
    public LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return resList;
    }
    public void backtracking(int [] candidates, int target, int start){
        if(getSum(path) == target){
            resList.add(new ArrayList<>(path));
            return;
        }
        if(getSum(path) > target){
            return;
        }

        for(int i = start; i < candidates.length && getSum(path) + candidates[i] <= target; i++){
            path.addLast(candidates[i]);
            backtracking(candidates, target, i);
            path.removeLast();
        }
    }
    public int getSum(List<Integer> path){
        int sum = 0;
        for(int i  : path){
            sum += i;
        }
        return sum;
    }
}

什么时候需要startIndex

本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?

我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。

如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合(opens new window)

注意以上只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我在讲解排列的时候会重点介绍

40.组合总和II

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

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

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

思路

这个与上面的区别在于

        1、数不能重复使用,那么只需要更改递归调用回溯函数中的参数 start即可

        2、集合(数组candidates)有重复元素,但还不能有重复的组合

对于2 求出结果后用set/map去重,会超时

所以需要在回溯的过程中进行去重。

        这里考虑什么时候会出现重复组合,

组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?

回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

而数层去重,需要对数组进行排序。

要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

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

可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。

而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:

剪枝 + 去重

利用used数组去重:

class Solution {
    public List<List<Integer>> resList = new ArrayList<List<Integer>>();
    public LinkedList<Integer> path = new LinkedList<>();
    public boolean [] used;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        used = new boolean[candidates.length];
        Arrays.fill(used, false);
        backtracking(candidates, target, 0, used);
        return resList;
    }
    public void backtracking(int [] candidates, int target, int start, boolean[] used){
        if(getSum(path) == target){
            resList.add(new ArrayList<>(path));
            return;
        }
        if(getSum(path) > target){
            return;
        }
        //<= target 剪枝
        for(int i = start; i < candidates.length && getSum(path) + candidates[i] <= target; i++){
            // 利用used数组去重 
            if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]){
                continue;
            }
            path.addLast(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, i + 1, used);
            used[i] = false;
            path.removeLast();
        }
    }
    public int getSum(List<Integer> path){
        int sum = 0;
        for(int i  : path){
            sum += i;
        }
        return sum;
    }
}

直接利用start去重:

这里考虑 当出现同一树层重复时,除了满足candidate[i] == candidate[i-1]外,还要知道,此时 i 大于当前的start。这是因为 对每层,start固定,随层深入start递增,每层 i 从 start开始,从左往右递增。而对同一树枝重复,当出现candidate[i] == candidate[i-1]时,此时的 i 一定等于start(candidate升序) 所以 可利用这一点实现同一树层重复去重。

代码如下

class Solution {
    public List<List<Integer>> resList = new ArrayList<List<Integer>>();
    public LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return resList;
    }
    public void backtracking(int [] candidates, int target, int start){
        if(getSum(path) == target){
            resList.add(new ArrayList<>(path));
            return;
        }
        if(getSum(path) > target){
            return;
        }
        //<= target 剪枝
        for(int i = start; i < candidates.length && getSum(path) + candidates[i] <= target; i++){
            // 利用start去重
            if(i > start && candidates[i] == candidates[i-1]){
                //此时为 同一树层重复
                continue;
            }
            path.addLast(candidates[i]);
            backtracking(candidates, target, i + 1);
            path.removeLast();
        }
    }
    public int getSum(List<Integer> path){
        int sum = 0;
        for(int i  : path){
            sum += i;
        }
        return sum;
    }
}

总结

本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于39.组合总和 (opens new window)难度提升了不少。

注意对数层重复 与 树枝重复 的理解

131.分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

思路

本体存在如下几个难点:

  • 切割问题可以抽象为组合问题
  • 如何模拟那些切割线
  • 切割问题中递归如何终止
  • 在递归循环中如何截取子串
  • 如何判断回文

注:[ startIndex, i ]  即为切割的子串。

代码

根据组合问题的代码模板,三步曲

class Solution {
    public List<List<String>> resList = new ArrayList<>();
    public LinkedList<String> path = new LinkedList<String>();
    public List<List<String>> partition(String s) {
        if(s == null || s.length() == 0) return resList;
        backtracking(s, 0);
        return resList;
    }
    public void backtracking(String s, int startIndex){
        if(startIndex >= s.length()){
            resList.add(new ArrayList<String>(path));
            return;
        }
        for(int i = startIndex; i < s.length(); i++){
            if(isPrime(s, startIndex, i)){
                //注意substring 不包含end
                path.addLast(s.substring(startIndex, i + 1));
                backtracking(s, i+1);
                path.removeLast();
            }
        }
    }
    public boolean isPrime(String s, int startIndex, int i){
        while(startIndex <= i){
            if(s.charAt(startIndex) != s.charAt(i)){
                return false;
            }
            startIndex++;
            i--;
        }
        return true;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1561523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows下部署llama.cpp

下载cmake 下载地址 解压&#xff0c;设置Path环境变量D:\CMake\bin 打开cmd输入cmake -version 安装mingw powershell下执行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser iex "& {$(irm get.scoop.sh)} -RunAsAdmin" scoop bucket add extras s…

蓝桥杯算法题——暴力枚举法

先估算这个数小于3的50次方 cnt0 for i in range(50):for j in range(50):for k in range(50):a3**ib5**jc7**kif a*b*c<59084709587505:cnt1 print(cnt-1)#当ijk都为0时&#xff0c;a*b*c1不是幸运数字所以要减去

C++笔记:命名空间

引入&#xff1a; 平常&#xff0c;我们在进行C编写时&#xff0c;一般我们都会默认在开始去写这样的代码&#xff1a; #include<iostream>//包含头文件using namespace std;//展开命名空间 这里就出现了与C语言不同的地方&#xff1a;这里的命名空间就是C对于C语言进…

深度学习| DiceLoss解决图像数据不平衡问题

图像数据不平衡问题 图像数据不平衡&#xff1a;在进行图像分割时&#xff0c;二分类问题中&#xff0c;背景过大&#xff0c;前景过小&#xff1b;多分类问题中&#xff0c;某一类别的物体体积过小。在很多图像数据的时候都会遇到这个情况&#xff0c;尤其是在医学图像处理的…

如何快速掌握数字化运维方法,构建数字化运维体系?

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 主要内容读者…

操作符:左移(<<)右移(>>)

在介绍移位操作符前&#xff0c;我们先介绍一下原码反码和补码 这里要讲的左移和右移牵扯到原码补码和反码&#xff0c; 原码即这个整数转化为2进制时的一串&#xff0c; 正整数的原码、反码、补码相同&#xff0c; 10&#xff08;int类型&#xff09;的原码&#xff1a;00…

day4 linux上部署第一个nest项目(java转ts全栈/3R教室)

背景&#xff1a;上一篇吧nest-vben-admin项目&#xff0c;再开发环境上跑通了&#xff0c;并且build出来了dist文件&#xff0c;接下来再部署到linux试试吧 dist文件夹是干嘛的&#xff1f; 一个pnpn install 直接生成了两个dist文件夹&#xff0c;前端admin项目一个&#xf…

黑马鸿蒙笔记 3

目录 11.ArkUI组件-Column和Row 12.ArkUI组件-循环控制 13.ArkUI组件-List 14.ArkUI组件-自定义组件 15.ArkUI组件-状态管理State装饰器 16.ArkUI组件-状态管理-任务统计案例 17.ArkUI组件-状态管理-PropLinkProvideConsume 11.ArkUI组件-Column和Row Colum和Row的交叉…

力扣-python-故障键盘

题解&#xff1a; from collections import dequeclass Solution:def finalString(self, s: str) -> str:# 创建一个双端队列用于存储字符q deque()# 定义一个标志位&#xff0c;用于标记当前字符应该添加到队列的哪一端head False# 遍历输入的字符串s的每一个字符for ch…

k8s安装traefik作为ingress

一、先来介绍下Ingress Ingress 这个东西是 1.2 后才出现的&#xff0c;通过 Ingress 用户可以实现使用 nginx 等开源的反向代理负载均衡器实现对外暴露服务&#xff0c;以下详细说一下 Ingress&#xff0c;毕竟 traefik 用的就是 Ingress 使用 Ingress 时一般会有三个组件: …

从0到1利用express搭建后端服务

目录 1 架构的选择2 环境搭建3 安装express4 创建启动文件5 express的核心功能6 加入日志记录功能7 日志记录的好处本节代码总结 不知不觉学习低代码已经进入第四个年头了&#xff0c;既然低代码很好&#xff0c;为什么突然又自己架构起后端了呢&#xff1f;我有一句话叫低代码…

大模型 web ui 界面 text-generation-webui

目录 前言 web ui ValueError: When localhost is not accessible 前言 使用 text-generation-webui 生成大模型界面&#xff0c;这个是专门用于文本对话生成的 web ui 界面 GitHub - oobabooga/text-generation-webui: A Gradio web UI for Large Language Models. Suppo…

【语言信号增强算法研究-1】维纳滤波(Wiener Filter)

1 语音增强方法分类 2 维纳滤波的局限性 对于非线性和非高斯噪声的处理效果不佳&#xff1b; 对于信号和噪声的统计特性要求比较高&#xff0c;需要准确地了解信号和噪声的分布规律&#xff08;说明自适应很差&#xff09;&#xff1b; 在处理复杂信号时&#xff0c;需要进行多…

【Functional Affordances】如何确认可抓取的区域?(前传)

文章目录 1. 【Meta AI】Emerging Properties in Self-Supervised Vision Transformers2. 【Meta AI】DINOv2: Learning Robust Visual Features without Supervision3. 【NeurIPS 2023】Diffusion Hyperfeatures: Searching Through Time and Space for Semantic Corresponden…

ElasticSearch理论指导

引子 本文致力于ElasticSearch理论体系构建&#xff0c;从基本概念和术语讲起&#xff0c;具体阐述了倒排索引和TransLog&#xff0c;接着讲了ElasticSearch的增删改查的流程和原理&#xff0c;最后讲了讲集群的选举和脑裂问题。 前言 大碗宽面-Kafka一本道万事通&#xff0…

蓝桥杯真题:成绩统计

这题思路简单&#xff0c;但是输出结果的位置容易出错&#xff0c;题目要求四舍五入&#xff0c;所以要用Math.round&#xff08;&#xff09;的方法

瑞吉外卖实战学习--10、完成新增菜品分类

完成新增菜品分类 前言1、前期准备定义实体类和实体对象 2、创建修改的接口 前言 1、前期准备 定义实体类和实体对象 package com.example.ruiji_demo.entity;import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; …

kubernetes-Pod基于污点、容忍度、亲和性的多种调度策略(二)

Pod调度策略 一.污点-Taint二.容忍度-Tolerations三.Pod常见状态和重启策略1.Pod常见状态2.Pod的重启策略2.1测试Always重启策略2.2测试Never重启策略2.3测试OnFailure重启策略&#xff08;生产环境中常用&#xff09; 一.污点-Taint 在 Kubernetes 中&#xff0c;污点&#x…

稻盛和夫|普通人如何才能取得非凡成就?

哈喽,你好啊,我是雷工! 稻盛和夫老先生曾经回答过这么一个问题: 资质平庸的普通人如何才能取得非凡的成就? 稻盛和夫认为:人生成就=能力努力态度。 也就是:做一个努力工作却不甘于只做眼前的事,而想要做更有挑战的事,这种人才能逃离平庸,取得非凡成就。 01 不甘平凡…

django+uniapp校园失物招领系统e5asg 微信小程序python

本失物招领小程序&#xff0c;使用的是比较成熟的python语言和比较完善的MySQL数据库&#xff0c;将网络失物招领小程序信息管理系统可以更安全、技术性更强的满足网站所有信息的管理。 失物招领小程序主要实现了管理员服务端模块、学生微信端模块二大部分。通过本失物招领小程…