【经典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(candidates[i]==candidates[i-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/ #这里理解used[i - 1]非常重要 # // used[i - 1] == true,说明同一树枝上nums[i - 1]使用过,在同一个递归栈内,试用过的都是Ture # // used[i - 1] == false,说明同一树层nums[i - 1]使用过,因为回溯会使上一个元素重新变为False # // 如果同一树层nums[i - 1]使用过则直接跳过 if i > 0 and nums****[**** i****]**** == nums****[**** i****-**** 1****]**** and used****[**** i****-**** 1****]**** == False: continue if used[i] == 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] (为了方便举例,已经排序),抽象为一棵树,然后在同一树层上对nums[i-1]使用过的话,进行去重如图:

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

拓展

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

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {

continue;

}

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

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {

continue;

}

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

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

这么说是不是有点抽象?

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

树层上去重 (used[i - 1] == false),的树形结构如下:

树枝上去重( used[i - 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:阿旭算法与机器学习,共同学习交流~

相关推荐
深科文库37 分钟前
构建 MCP 服务器:第 4 部分 — 创建工具
python·chatgpt·prompt·aigc·agi·ai-native
witton42 分钟前
美化显示LLDB调试的数据结构
数据结构·python·lldb·美化·debugger·mupdf·pretty printer
SteveDraw1 小时前
C++动态链接库封装,供C#/C++ 等编程语言使用——C++动态链接库概述(总)
开发语言·c++·c#·封装·动态链接库
十五年专注C++开发2 小时前
设计模式之单例模式(二): 心得体会
开发语言·c++·单例模式·设计模式
nenchoumi31192 小时前
AirSim/Cosys-AirSim 游戏开发(一)XBox 手柄 Windows + python 连接与读取
windows·python·xbox
GoodStudyAndDayDayUp2 小时前
初入 python Django 框架总结
数据库·python·django
星辰大海的精灵2 小时前
基于Dify+MCP实现通过微信发送天气信息给好友
人工智能·后端·python
精灵vector2 小时前
Agent短期记忆的几种持久化存储方式
人工智能·python
flyair_China2 小时前
【云架构】
开发语言·php
北京_宏哥2 小时前
🔥Python零基础从入门到精通详细教程4-数据类型的转换- 上篇
前端·python·面试