一、39. 组合总和
本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制
题目链接/文章讲解:代码随想录
视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili
(1)定义函数的参数和返回值:定义二维数组result存放结果集,定义一维数组path收集单一结果,返回值为空。参数有数组、target、sum(统计组合的和)、startindex(用于找到下一个组合的开始位置,也就是找到下一个for循环的开始位置)
(2)确定终止条件:如果sum大于target,那么直接return,终止;如果sum等于target,那么说明满足题目条件,将这个path存入结果集,并返回。
(3)单层搜索逻辑:for循环,i从startindex开始,一直循环到等于数组长度,i就是收集每一个元素的过程,所以将每一个元素添加进入path中;然后更新sum,将这个元素加入sum;接下来进行递归,传入的start index依然是从i开始,因为组合里的元素可以重复;回溯过程,sum减出刚才的元素,然后将path清空。
class Solution:
def __init__(self):
self.result = []
self.path = []
def backTracking(self, candidates, target, sum, startindex):
# 终止条件:
if sum > target:
return
if sum == target:
self.result.append(self.path[:])
return
# 单层递归条件
for i in range(startindex, len(candidates)):
self.path.append(candidates[i])
sum += candidates[i]
self.backTracking(candidates, target, sum, i)
sum -= candidates[i]
self.path.pop()
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.backTracking(candidates, target, 0, 0)
return self.result
二、40.组合总和II
本题开始涉及到一个问题了:去重。注意题目中给我们 集合是有重复元素的,那么求出来的 组合有可能重复,但题目要求不能有重复组合。
题目链接/文章讲解:代码随想录
视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili
1. 本题和组合总和Ⅰ、组合总和Ⅲ的区别
给出的数组中有重复的元素,所以在组合过程中会出现相同组合,此时需要对相同组合进行去重;并且因为给出的数组有重复的元素,在组合中会出现相同元素的组合,但是其实来自数组的不同元素而已,需要注意。
2. 去重的逻辑:树层去重 + 树枝去重
由于给出的数组中会含有重复的元素,所以在组合过程中,重复元素里,第一个元素得到的组合,会和与自己重复的元素得到的组合重复,所以需要进行去重。
因此,将数组进行排序,将重复元素放在一起,便于组合过程中去重。
3. 代码实现
(1)定义result用于存放结果集,path用于收集单条结果。
(2)定义递归函数的参数和返回值:返回值为空;参数nums是题目给定的数组,target为目标和,sum用于记录单条路径的和,startindex用于防止组合里使用相同元素,used数组用于标记哪些元素使用过(使用过为1,未使用为0)。
(3)确定终止条件:sum大于target,则返回;sum等于target,那么满足条件,将path放入result。
(4)单层递归逻辑:for循环从startindex开始遍历,一直到nums数组长度-1结束。
去重:如果当前元素等于前一个元素(注意,数组需要排序),并且used[i-1]等于0,只有此时才需要进行树层的去重,如果used[i-1]等于1,那么属于树枝的状态,不需要去重,否则会排除两个相同元素出现在一个组合的情况;遇到上述情况直接continue,不进行处理。
接下来,path收集元素,sum加上这个数,并且将used赋成true;
递归,传入参数,其中startindex传为i+1。
回溯:sum减去这个数,path弹出元素,used赋成false。
class Solution:
def __init__(self):
self.result = []
self.path = []
def backTracking(self, candidates, target, sum, startindex, used):
# 确定终止条件:
if sum > target:
return
if sum == target:
self.result.append(self.path[:])
return
# 单层递归逻辑:
for i in range(startindex, len(candidates)):
# 去重:
if i > startindex and candidates[i] == candidates[i-1] and used[i-1] == 0:
continue
# 递归:
self.path.append(candidates[i])
sum += candidates[i]
used[i] = 1
self.backTracking(candidates, target, sum, i+1, used)
# 回溯:
sum -= candidates[i]
used[i] = 0
self.path.pop()
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
used = [0] * len(candidates)
candidates.sort()
self.backTracking(candidates, target, 0, 0, used)
return self.result
三、131.分割回文串
本题较难 ,大家先看视频来理解 分割问题,明天还会有一道分割问题,先打打基础。
回文 串是向前和向后读都相同的字符串。
(1)定义一维数组path收集单个组合,二维数组result存储结果集。
(2)定义递归函数参数和返回值:参数有字符串s,int型startindex控制下一次切割的位置。
(3)确定终止条件:切割到字符串最后就终止,startindex就是切割线。如果startindex大于等于s的长度,那么终止递归,将path放入result。
(4)单层递归逻辑:for循环,从startindex到字符串长度结束。子串的范围是[startindex, i],因为startindex是固定值不动,而i一直在++,所以它是切割的子串,因此需要判断它是否是回文的。
判断回文子串:定义一个新函数,传入子串、起始位置、终止位置;如果是回文,就将区间加入result,如果不是回文,就直接跳过。
递归:起始位置从i+1开始。
回溯:path.pop()
class Solution:
def __init__(self):
self.result = []
self.path = []
def isPalindrome(self, s, left, right):
i = left
j = right
while i <= j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
def backTracking(self, s, startindex):
# 定义终止条件:
if startindex == len(s):
self.result.append(self.path[:])
return
# 单层递归条件:
for i in range(startindex, len(s)):
if self.isPalindrome(s, startindex, i):
self.path.append(s[startindex:i+1])
self.backTracking(s, i+1)
self.path.pop()
def partition(self, s: str) -> List[List[str]]:
self.backTracking(s, 0)
return self.result