leetcode40.组合总和II(去重思路精讲,经典题也可以有困难的思考!)

首先感谢您,打开本文章,一道网上很多题解的一道经典回溯题,能讲出什么花来呢?

看了这篇文章,希望能使您眼前一新。

大概的思路对于一些读者可能很简单,我也简单提一嘴解题思路,因为可能有新读者来看本篇文章。

基本的思路就是和普通求组合总和差不多,多的部分是去重,我们的思路是这样的用一个数组辅助加数据,当满足一定条件时也就是该辅助数组数据和等于target,把辅助数组得到的组合作为答案放入答案数组中,递归函数的思路是:用传进来的start,也就是下一次递归的遍历开始位置开始循环,这样做是为了避免一个数组里取重复数据,然后用一个数组来记录每一个位置是否取过,这个是为了做树层去重。

先讲一下什么树层去重什么是树枝去重?

树层去重就是根据题目的意思,一个答案取过组合答案了,那么后面的组合答案不能使用前面已经得到答案的数字了,也就是说要对后面的组合进行去重,不要让它的内容出现与前面答案相等的数字,树枝去重就是同一个答案里,相等的数字不能出现,本题的意思是要进行树层去重而不是树枝去重,请读者注意。

一定要对数据进行排序,这是最重要的,根据测试用例答案也能知道,虽然第一个测试用例给定的数组两个1是不挨着的,但是答案要求它们是挨着的,不能返回不同的答案顺序,
这就暗示你要排序,其次要进行去重的工作也得排序。


用一个bool数组记录每一个位置,判断是否使用过,但是我们这个数组的用途是判断树层间的去重,递归可以画成一棵树,对树层方面的去重可以避免其他的组合答案,重用前面的组合答案里的数字,这样就能
得到题目要的结果,而树枝之间的去重,和我们本题的思路不同,它更为简单,它是为了避免取到的组合答案里,出现相等的数字。
在i大于0,也就是i不在起始位置之后,我们每次进行判断,如果i与i-1的位置所对应的数据相等,那么查看我们的布尔类型的树层去重数组,如果此时的上一个i的位置也就是i-1位置在该数组中取的是false,那么
认定为树层去重生效,则跳过本次接下来的代码,使i+1。如果i-1位置不在数组中标记为false,并且此时i和i-1位置数字还相等,则说明此时是树枝上的重复,这符合答案要求。
我们每次进来循环,让arr【i】=true,这就使得下一次的递归,不会因为树枝重复而跳出,让回溯之后的arr【i】=false,这是避免树层重复而跳出递归。
因为回溯到的位置,一定是已经递归过的位置,所以把相应位置置为false,是没问题的,它一定就是树层上的重复。
而且回溯并不会马上全部回溯,它会先找到该树层中没有其余的解之后,再向上层回溯,也就是说初步来看,我们深层树层最先回溯,这样的影响也是最小的,
它保证后几个数填数不会重复,你是不是在担心,会不会出现前面重复后面不重复的情况得到target?
别担心,实际不会出现这种情况,因为前面重复后面不重复的数据根本凑不成target,所以它不会停在深层的树层去重,而是继续向上层回溯
向上层回溯之后,回到之前的略靠近最上层的位置,这通常是第二层(第一层是根,相当于不填数字,无法回溯),然后去找其他的解,而此时一开始找数的那些路径已经全部被标记为false
无法查找,也就实现了不会在其他解中找到与前面已找到的数字相同的情况。

实现代码如下

class Solution {
public:
vector<vector<int>>res;
vector<int>path;void back(vector<int>&candidates,int target,int sum,int start,vector<bool>&arr){if(sum>target)return ;if(sum==target){res.push_back(path);return ;}for(int i=start;i<candidates.size();++i){if(i>0&&candidates[i]==candidates[i-1]&&arr[i-1]==true)continue;path.push_back(candidates[i]);sum+=candidates[i];arr[i]=false;back(candidates,target,sum,i+1,arr);path.pop_back();sum-=candidates[i];arr[i]=true;}
}vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());vector<bool>arr(candidates.size(),true);back(candidates,target,0,0,arr);return res;}
};

再来说说为什么初始化arr数组为false,实际上你要是完全相反的写代码判断逻辑,也是可以对的,也就是说一开始全部初始化为true,然后判断树层去重就是上一个位置的去重数组里是否标记为rue,再然后
树枝去重的避免是把它置为false,然后回溯置为true,这样完全相反写也是行得通的,这里主要想说明的不是初始化为true还是false的问题,而是要说明那个中间的判断位置也就是为回溯的部分,有一个修改
arr以避免树枝去重,这个步骤是十分重要的,为什么要特意说这一点呢?
我有一个想法是如果把arr初始化为true,然后判断上一个位置是否为false,回溯时候更改这个标志位为false,来避免树层循环,那是不是就可以节省树枝循环这一步的arr【i】=true了?

实际上是错误的答案,在测试用例【2,2,2】中target=2时候,得到的组合结果有两个且都为【2】。这说明确实树层去重起了作用,但是并没有去重干净。
第一个答案取得是理所应当的,顺其自然的,然后由于sum此时已经==target造成回溯,回溯导致第一个位置2被置false,这样的结果会导致第二个数据2被直接跳过,然后i++,此时再进行判断,然而第二个2
的跳过并不完全是好事,虽然第二个2的跳过完成了树层去重,但是也导致了它没有被置为false,这时候第三个2虽然与第二个2相等,但是无法被去重,这也就导致了为什么会有两个2的结果。
这些推理得出一个结论:不能省略树枝的标志位

那是不是加上就可以了呢?
把arr初始化为true,然后判断上一个位置是否为false,回溯时候更改这个标志位为false,不回溯也就是正常情况还是加上一个arr【i】=true
你会发现加了实际上和没加没有任何区别,还是会报错,确实是因为要附上标志位的问题,但是不是这种初始化为true,但是你树枝去重还是arr【i】=true,这实际上没有发生任何改变。
那是不是arr【i】=false就可以了呢?也不对,回溯置为false,递归也置为false是什么意思?
意思是对树层和树枝进行双重的去重
问题实际上发生在if判断上!if判断树层去重为false进行树层去重还是为true进行树层去重,完全取决于初始化,且该if判断必须与初始化保持一致,才能正确的进行树层去重

也就是说实际逻辑是这样的,初始化为true那么if就是判断true,初始化为false,那if判断false,树枝去重的改变也就是递归之前是把该标志位标成相反的,回溯标成与初始化相同的,只有这一种方法能使答案正确
但是初始化为true还是false,是没关系的,有关系的是if和中间的各个状态如何改变的事情。
结论:树层去重很大一部分取决于if判断,if判断写错了,那你之间代码无论是回溯还是递归为true或者false,都不行。而且递归的那个标志转换不能省略!


本文章的精髓在于清晰的介绍了为什么这样写去重逻辑,以及去重数组是否一定要写成false,可以写成其他的吗?以及数组的初始化与里层实现递归逻辑和回溯逻辑的关系,相信已经讲的非常清楚了

本期内容就到这里
如果对您有用的话别忘了一键三连哦,如果是互粉回访我也会做的!

大家有什么想看的题解,或者想看的算法专栏、数据结构专栏,可以去看看往期的文章,有想看的新题目或者专栏也可以评论区写出来,讨论一番,本账号将持续更新。
期待您的关注

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

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

相关文章

C# 22H2之后的windows版本使用SetDynamicTimeZoneInformation设置时区失败处理

使用SetDynamicTimeZoneInformation设置时区返回false&#xff0c;设置失败。 使用PowerShell设置Set-TimeZone成功。 /// <summary> /// 设置本地时区 /// 参数取值"China Standard Time"&#xff0c;即可设置为中国时区 /// </summary> /// <param …

Redis从入门到精通(二)- 入门篇

文章目录 0. 前言1. 入门篇[【入门篇】1.1 redis 基础数据类型详解和示例](https://icepip.blog.csdn.net/article/details/134438573)[【入门篇】1.2 Redis 客户端之 Jedis 详解和示例](https://icepip.blog.csdn.net/article/details/134440061)[【入门篇】1.3 redis客户端之…

(六)什么是Vite——热更新时vite、webpack做了什么

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

LR学习笔记——基本面板

文章目录 面板介绍色彩调整区域明暗调整区域纹理及质感色彩饱和 面板介绍 面板如上图所示 基本可分为几个板块&#xff1a;色彩、明暗、纹理及质感、色彩饱和 色彩调整区域 色温&#xff1a;由蓝色和黄色控制色调&#xff1a;由绿色和洋红控制 互补色&#xff1a;蓝色对黄色&…

模电 01

一.半导体基本知识 1.优点&#xff1a;体积小、重量轻、使用寿命长、输入功率小、功率转换效率高。 2.性能介于导体与绝缘体 3.常用半导体材料&#xff1a;硅&#xff08;SI&#xff09; 镉&#xff08;Ge&#xff09;,化合物半导体&#xff1a;砷化镓&#xff08;GaAs&…

RabbitMQ 基础操作

概念 从计算机术语层面来说&#xff0c;RabbitMQ 模型更像是一种交换机模型。 Queue 队列 Queue&#xff1a;队列&#xff0c;是RabbitMQ 的内部对象&#xff0c;用于存储消息。 RabbitMQ 中消息只能存储在队列中&#xff0c;这一点和Kafka相反。Kafka将消息存储在topic&am…

C语言进制转换(1112:进制转换(函数专题))

题目描述 输入一个十进制整数n&#xff0c;输出对应的二进制整数。常用的转换方法为“除2取余&#xff0c;倒序排列”。将一个十进制数除以2&#xff0c;得到余数和商&#xff0c;将得到的商再除以2&#xff0c;依次类推&#xff0c;直到商等于0为止&#xff0c;倒取除得的余数…

链路聚合-静态和动态区别

链路聚合之动静态聚合方式 链路聚合组是由一组相同速率、以全双工方式工作的网口组成。 1、动态聚合&#xff1a; 动态聚合对接的双方通过交互LACP(链路聚合控制协议)协议报文&#xff0c;来协商聚合对接。 优点&#xff1a;对接双方相互交互端口状态信息&#xff0c;使端口…

基于Vue+SpringBoot的考研专业课程管理系统

项目编号&#xff1a; S 035 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S035&#xff0c;文末获取源码。} 项目编号&#xff1a;S035&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高…

写论文的步骤有没有?正确的顺序是什么?

一、引言 在进行学术写作之前&#xff0c;我们需要明确写论文的基本步骤流程。学术写作是一项复杂而重要的任务&#xff0c;对于提升自身学术能力和研究水平具有重要意义。本文旨在详细介绍如何正确进行学术写作&#xff0c;以帮助读者更好地完成论文撰写工作。通过按照正确的…

AIGC: 关于ChatGPT的提问方式和Prompt工程

向 ChatGPT 提问的秘诀 我们打开ChatGPT的界面&#xff0c;准备输入我们问题的时候&#xff0c;我们可能会想&#xff0c;应如何和ChatGPT进行交流 我们可能会疑问是否用英文会比中文提问要好呢&#xff1f;我应该提哪些问题&#xff1f;我又如何去进行提问&#xff1f;当我们…

断点检测学习

突然看到了一种反调试的手段&#xff0c;检测api函数的首字节是否为0xcc&#xff0c;即int 3类型的断点&#xff0c;来反调试&#xff0c;尝试一下 #include<stdio.h> #include<stdlib.h> void fun(int a) {a;a--;a 5;a - 5;return; } int main() {void (*ptr)(i…

项目讲解:让你在IT行业面试中以开发、实施、产品更近一步

1、会议系统项目 项目介绍 提示&#xff1a;可以简单介绍IT技术发展的背景 面试准备 开发 实施 产品 2、医疗项目 项目介绍 提示&#xff1a;可以谈谈你认为IT行业就业方向有哪些&#xff0c;并说出你认为最好的就业领域是什么&#xff1f; 面试准备 开发 实施 产品 3、数字化交…

【STM32】W25Q64 SPI(串行外设接口)

一、SPI通信 0.IIC与SPI的优缺点 https://blog.csdn.net/weixin_44575952/article/details/124182011 1.SPI介绍 同步&#xff08;有时钟线&#xff09;&#xff0c;高速&#xff0c;全双工&#xff08;数据发送和数据接收各占一条线&#xff09; 1&#xff09;SCK:时钟线--&…

Spring Boot要如何学习?【云驻共创】

Spring Boot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。我这里会分享一些学习Spring Boot的方法和干货&#xff0c;包括…

计算机毕业设计选题推荐-内蒙古旅游微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Spring Cloud Alibaba Sentinel 简单使用

Sentinel Sentinel 主要功能Sentinel 作用常见的流量控制算法计数器算法漏桶算法 令牌桶算法Sentinel 流量控制Sentinel 熔断Sentinel 基本使用添加依赖定义资源定义限流规则定义熔断规则如何判断熔断还是限流自定义 Sentinel 异常局部自定义异常全局自定义异常系统自定义异常…

基于Apache部署虚拟主机网站

文章目录 Apache释义Apache配置关闭防火墙和selinux 更改默认页内容更改默认页存放位置个人用户主页功能基于口令登录网站虚拟主机功能基于ip地址相同ip不同域名相同ip不同端口 学习本章完成目标 1.httpd服务程序的基本部署。 2.个人用户主页功能和口令加密认证方式的实现。 3.…

树与二叉树堆:树

目录 树&#xff1a; 树的概念&#xff1a; 树的相关概念&#xff1a; 1、结点的度&#xff1a; 2、叶节点&#xff1a;度为0的节点 3、非终端节点或分支节点&#xff1a; 4、父节点和子节点&#xff1a; 5、兄弟节点&#xff1a; 6、树的度&#xff1a; 7、树的层次或…

linux centos7 安装nginx

1、添加CentOS 7 Nginx yum资源库,打开终端,使用以下命令: sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm2、安装nginx sudo yum install -y nginx3、启动nginx sudo systemctl start nginx.service开机自动启…