39. 组合总和
需要startIndex的情况:
一个集合求组合需要startIndex来控制for循环起始位置
多个集合取组合不需要startInex控制,各个集合独立,互不影响
重复选取元素的方法:backtrack 直接回溯当前位置,相当于可以选择当前元素或者之后的元素( // 不用i+1了,表示可以重复读取当前的数)
剪枝:一般在循环中进行剪枝
如果已经比目标更大,就不需要接着循环。
(结束条件中也有类似剪枝e.g sum>target return;但是循环仍要进行,还会backtrack到下一层效率不如直接在循环中剪枝高)
40.组合总和II
去重的重复选取有两类:
1. 同一树枝上的重复选取
2. 同一树层上的重复选取
本题去重是去去除同一树层的重复选取,如[1,2(1),2 (2),2(3),4,5]中和为11的子集有[2,4,5] [2(第二个2),4,5]重复属于第二类重复
第一类重复时子集[2(1),2 (2),2(3)]相当于是用了三个不同的2,是树枝上的重复
树层去重,需要对数组排序
图源:代码随想录
- used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。
而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上
也可以使用startIndex进行去重:
// 要对同一树层使用过的元素进行跳过
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
131.分割回文串
两个问题:
1.切割问题:用回溯遍历不同的切割方式:
图源:代码随想录
如果切割出来的字串不是回文串,直接略过,只有所有子串都是回文串时,切割线才会到字符串最后,此时结束,保存结果到结果集,返回
startIndex就可以作为切割线:是下一个子串开始的位置,所以当startIndex==s.length()时,就时结束的条件达成的时候
2. 如何判断回文串:双指针/dp+memo
优化:对于回文串判断的优化:每次查询回文,会有重复计算,可以利用一下规律减少计算:
给定一个字符串s
, 长度为n
, 它成为回文字串的充分必要条件是s[0] == s[n-1]
且s[1:n-1]
是回文字串。
其实就是动态规划的方法:我们可以高效地事先一次性计算出, 针对一个字符串s
, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤.
本题难点
- 切割问题可以抽象为组合问题
- 如何模拟那些切割线 (把字符串存入path)
- 切割问题中递归如何终止 (startIndex作为切割线达到字符串尾部)
- 在递归循环中如何截取子串(截取i到startIndex的字符串)
- 如何判断回文(双指针/dp)