【经典LeetCode算法题目专栏分类】【第2期】组合与排列问题系列

《博主简介》

小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。
更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~
👍感谢小伙伴们点赞、关注!

组合总和1

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def combinationSum****(**** self****,**** candidates****:**** List******** ****int**** ****,**** target****:**** int ) -> List******** List****\[**** ****int**** ****]:**** def DFS****(**** candidates****,**** target****,**** start****,**** track****):**** if sum ( track****)**** == target****:**** res****.**** append****(**** track****.**** copy****())**** return if sum ( track****)**** > target****:**** return for i in range ( start , len ( candidates****)):**** track****.**** append****(**** candidates******** i****)**** DFS****(**** candidates****,**** target****,**** i , track****)**** track****.**** pop****()**** res =\[\] DFS****(**** candidates****,**** target****,**** 0****,\[\])**** return res |

元素可以重复使用,进入下一层时从i开始。元素不能重复使用时,进入下一层时从i +1 卡开始。

组合总和 2

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def combinationSum2****(**** self****,**** candidates****:**** List******** ****int**** ****,**** target****:**** int ) -> List******** List****\[**** ****int**** ****]:**** def DFS****(**** candidates****,**** target****,**** start****,**** track****):**** if sum ( track****)**** > target****:**** return if sum ( track****)**** == target****:**** res****.**** append****(**** track****.**** copy****())**** for i in range ( start****,**** len ( candidates****)):**** # 忽略掉同一层中重复的选项,上述for循环为本层的可选择列表 if i - 1 >= start and candidates******** i****-**** 1******** == candidates******** i****:**** continue track****.**** append****(**** candidates******** i****)**** DFS****(**** candidates****,**** target****,**** i + 1 , track****)**** track****.**** pop****()**** res = \[\] candidates****.**** sort****()**** DFS****(**** candidates****,**** target****,**** 0****,\[\])**** return res |

注: 当题目中说明所给序列可能包含重复的组合或者子集时,解题套路是要先对原数组进行排序,并且在回溯的写法中,要加上对重复元素跳过的判断 ,即:

if(i>startIndex)

{

if(candidatesi==candidatesi-1)

continue;

}

其它的写题做法和回溯是一样的,可以加上剪枝判断,回溯中也要对sum一并进行回溯。

#for 循环枚举出选项时,加入下面判断,忽略掉同一层重复的选项,避免产生重复的组合。比如1,2,2,2,5中,选了第一个 2,变成 1,2,第一个 2 的下一选项也是 2,跳过它,因为选它,就还是 1,2

关键总结:

  1. 如果元素可以重复使用,进入下一层时从i开始。元素不能重复使用时,进入下一层时从i +1 开始。
  2. 当题目中说明所给序列可能包含重复的组合或者子集时,解题套路是要先对原数组进行排序,并且在回溯的写法中,要加上对重复元素跳过的判断

全排列1

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def permute****(**** self****,**** nums****:**** List******** ****int**** ****)**** -> List******** List****\[**** ****int**** ****]:**** def backtrack****(**** nums****,**** track****):**** if len ( nums****)**** == len ( track****):**** res****.**** append****(**** track****.**** copy****())**** return for i in nums****:**** # 排除不合法的选择,即不能选择已经在track中的元素 if i in track****:**** continue track****.**** append****(**** i****)**** backtrack****(**** nums****,**** track****)**** track****.**** pop****()**** res = \[\] backtrack****(**** nums****,\[\])**** return res |

全排列问题1与组合问题1的主要差别在于:

1.是否需要使用start参数来进行下一层开始选择元素的标识。

2.全排列问题,需要if i in track****:**** continue ,剔除 上次选择的元素。

全排列2

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def permuteUnique****(**** self****,**** nums****:**** List******** ****int**** ****)**** -> List******** List****\[**** ****int**** ****]:**** def DFS****(**** nums****,**** track****,**** used****):**** if len ( track****)**** == len ( nums****):**** res****.**** append****(**** track****.**** copy****())**** return for i in range ( len ( nums****)):**** #https://leetcode-cn.com/problems/permutations-ii/solution/47-quan-pai-lie-iiche-di-li-jie-pai-lie-zhong-de-q/ #这里理解usedi - 1非常重要 # // usedi - 1 == true,说明同一树枝上numsi - 1使用过,在同一个递归栈内,试用过的都是Ture # // usedi - 1 == false,说明同一树层numsi - 1使用过,因为回溯会使上一个元素重新变为False # // 如果同一树层numsi - 1使用过则直接跳过 if i > 0 and nums******** i******** == nums******** i****-**** 1******** and used******** i****-**** 1******** == False: continue if usedi == False: used******** i******** = True track****.**** append****(**** nums******** i****)**** DFS****(**** nums****,**** track****,**** used****)**** track****.**** pop****()**** used******** i******** = False res = \[\] nums****.**** sort****()**** used = **** ****False**** ****for**** i ****in**** ****range**** ****(**** ****len**** ****(**** nums****)) DFS****(**** nums****,\[\],**** used****)**** return res |

注:上述组合总和2与全排列2,去重的条件有一点点差别,主要原因是排列的话需要考虑前后位置差异,而组合的话不需要考虑位置差异。

组合的去重条件:

忽略掉同一层中重复的选项,上述for循环为本层的可选择列表

if i - 1 >= start and candidates******** i****-**** 1******** == candidates******** i****:****

排列的去重条件:

if i > 0 and nums******** i******** == nums******** i****-**** 1******** and used******** i****-**** 1******** == False:

彻底理解排列中的去重问题】详解

思路

这道题目和 46.全排列的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。

这里就涉及到去重了。

要注意全排列是要取树的叶子节点的,如果是子集问题,就取树上的所有节点。

这个去重为什么很难理解呢,所谓 去重,其实就是使用过的元素不能重复选取 这么一说好像很简单!

但是什么又是"使用过",我们把排列问题抽象为树形结构之后, "使用过"在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。

没有理解这两个层面上的"使用过" 是造成大家没有彻底理解去重的根本原因。

那么排列问题,既可以在 同一树层上的"使用过"来去重,也可以在同一树枝上的"使用过"来去重!

理解这一本质,很多疑点就迎刃而解了。

还要强调的是去重一定要对元素经行排序,这样我们才方便通过相邻的节点来判断是否重复使用了

首先把示例中的 1,1,2 (为了方便举例,已经排序),抽象为一棵树,然后在同一树层上对numsi-1使用过的话,进行去重如图:

图中我们对同一树层,前一位(也就是 numsi-1)如果使用过,那么就进行去重。

拓展

大家发现,去重最为关键的代码为:

if (i > 0 && numsi == numsi - 1 && usedi - 1 == false) {

continue;

}

可是如果把 usedi - 1 == true 也是正确的,去重代码如下:

if (i > 0 && numsi == numsi - 1 && usedi - 1 == true) {

continue;

}

这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用 usedi - 1 == false,如果要对树枝前一位去重用用usedi - 1 == true。

对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!

这么说是不是有点抽象?

来来来,我就用输入 : 1,1,1 来举一个例子。

树层上去重 (usedi - 1 == false),的树形结构如下:

树枝上去重( usedi - 1 == true)的树型结构如下:

大家应该很清晰的看到,树层上去重非常彻底,效率很高,树枝上去重虽然最后可能得到答案,但是多做了很多无用搜索。

子集( 组合问题

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def subsets****(**** self****,**** nums****:**** List******** ****int**** ****)**** -> List******** List****\[**** ****int**** ****]:**** def backtrack****(**** nums****,**** track****,**** start****):**** res****.**** append****(**** track****.**** copy****())**** if start >= len ( nums****):**** return for i in range ( start****,**** len ( nums****)):**** track****.**** append****(**** nums******** i****)**** backtrack****(**** nums****,**** track****,**** i + 1****)**** track****.**** pop****()**** res = \[\] backtrack****(**** nums****,**** \[\], 0****)**** return res |

子集2(组合问题)

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def subsetsWithDup****(**** self****,**** nums****:**** List******** ****int**** ****)**** -> List******** List****\[**** ****int**** ****]:**** def backtrack****(**** nums****,**** start****,**** track****):**** res****.**** append****(**** track****.**** copy****())**** for i in range ( start****,**** len ( nums****)):**** if i****-**** 1****>=**** start and nums******** i****==**** nums******** i****-**** 1****:**** # 同层去重 continue track****.**** append****(**** nums******** i****)**** backtrack****(**** nums****,**** i****+**** 1****,**** track****)**** track****.**** pop****()**** res****=**** \[\] nums****.**** sort****()**** backtrack****(**** nums****,**** 0****,\[\])**** return res |

字符串排列 排列问题且涉及去重

原始字符串可能存在重复字符'aab'---'aba','baa'

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| class Solution : def permutation****(**** self****,**** s****:**** str ) -> List******** ****str**** ****:**** def backtrack****(**** s****,**** cur_s****):**** if len ( cur_s****)**** == len ( s****):**** res****.**** append****(**** cur_s****)**** return for i in range ( len ( s****)):**** if i > 0 and s******** i******** == s******** i****-**** 1******** and visited******** i****-**** 1******** == False : # 同层去重 continue if visited******** i******** == False : visited******** i******** = True backtrack****(**** s****,**** cur_s + s******** i****)**** visited******** i******** = False res = \[\] visited = **** ****False**** ****for**** _ ****in**** ****range**** ****(**** ****len**** ****(**** s****)) s = ''. join****(**** sorted ( list ( s****)))**** backtrack****(**** s****,**** '') return res 写法二 class Solution : def permutation****(**** self****,**** s****:**** str ) -> List******** ****str**** ****:**** def backtrack****(**** s****,**** path****):**** if not s****:**** res****.**** append****(**** path****)**** seen = set () for i in range ( len ( s****)):**** if s******** i******** in seen****:**** continue # 同层去重 seen****.**** add****(**** s******** i****)**** #传递的字符串去除了当前选择的字符 s :**** **i** ****+ s **** **i** ****+**** **1** ****: backtrack****(**** s :**** **i** ****+ s **** **i** ****+**** **1** ****: , path + s******** i****)**** res = \[\] backtrack****(**** s****,**** "") return res |

关于本篇文章大家有任何建议或意见,欢迎在评论区留言交流!

觉得不错的小伙伴,感谢点赞、关注加收藏哦!
欢迎关注下方GZH:阿旭算法与机器学习,共同学习交流~

相关推荐
方也_arkling1 天前
【Java-Day08】static / final / 枚举
java·开发语言
风吹夏回1 天前
Python 全局异常处理:从“满屏 try-except”到优雅兜底
开发语言·python
Chengbei111 天前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
llz_1121 天前
web-第一次课后作业
java·开发语言·idea
小熊Coding1 天前
Python爬取当当网二手图书项目实战!
开发语言·爬虫·python·beautifulsoup·requests·二手图书
秋91 天前
Java项目运行5天左右自动宕机:系统性定位与解决方案
java·开发语言·python
小江的记录本1 天前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
xiaoshuaishuai81 天前
C# 内存管理与资源泄漏
开发语言·c#
lsx2024061 天前
SVN 检出操作
开发语言
田里的水稻1 天前
OE_ubuntu26.04与宿主机之间复制粘贴内容
人工智能·python·机器人