leetcode-master/problems/周总结/20201107回溯周末总结.md

170 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 本周小结!(回溯算法系列二)
> 例行每周小结
## 周一
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中讲解的组合总和问题,和以前的组合问题还都不一样。
本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
不少录友都是看到可以重复选择就义无反顾的把startIndex去掉了。
**本题还需要startIndex来控制for循环的起始位置对于组合问题什么时候需要startIndex呢**
我举过例子如果是一个集合来求组合的话就需要startIndex例如[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。
如果是多个集合取组合各个集合之间相互不影响那么就不用startIndex例如[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我在讲解排列的时候会重点介绍**
最后还给出了本题的剪枝优化,如下:
```
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
```
这个优化如果是初学者的话并不容易想到。
**在求和问题中,排序之后加剪枝是常见的套路!**
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)第一个树形结构没有画出startIndex的作用**这里这里纠正一下,准确的树形结构如图所示:**
![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223170730367.png)
## 周二
在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。
**所以难就难在去重问题上了**
这个去重问题,相信做过的录友都知道有多么的晦涩难懂。网上的题解一般就说“去掉重复”,但说不清怎么个去重,代码一甩就完事了。
为了讲解这个去重问题,**我自创了两个词汇,“树枝去重”和“树层去重”**。
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因**。
![40.组合总和II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123202817973.png)
我在图中将used的变化用橘黄色标注上可以看出在candidates[i] == candidates[i - 1]相同的情况下:
* used[i - 1] == true说明同一树枝candidates[i - 1]使用过
* used[i - 1] == false说明同一树层candidates[i - 1]使用过
**这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!**
对于去重,其实排列问题也是一样的道理,后面我会讲到。
## 周三
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。
我列出如下几个难点:
* 切割问题其实类似组合问题
* 如何模拟那些切割线
* 切割问题中递归如何终止
* 在递归循环中如何截取子串
* 如何判断回文
如果想到了**用求解组合问题的思路来解决 切割问题本题就成功一大半了**,接下来就可以对着模板照葫芦画瓢。
**但后序如何模拟切割线,如何终止,如何截取子串,其实都不好想,最后判断回文算是最简单的了**
除了这些难点,**本题还有细节例如切割过的地方不能重复切割所以递归函数需要传入i + 1**。
所以本题应该是一个道hard题目了。
**本题的树形结构中,和代码的逻辑有一个小出入,已经判断不是回文的子串就不会进入递归了,纠正如下:**
![131.分割回文串](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203228309.png)
## 周四
如果没有做过[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)的话,[回溯算法复原IP地址](https://programmercarl.com/0093.复原IP地址.html)这道题目应该是比较难的。
复原IP照[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。
树形图如下:
![93.复原IP地址](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203735933-20230310133532452.png)
在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少!
本题还可以有一个剪枝合法ip长度为12如果s的长度超过了12就不是有效IP地址直接返回
代码如下:
```
if (s.size() > 12) return result; // 剪枝
```
我之前给出的C++代码没有加这个限制,也没有超时,因为在第四段超过长度之后,就会截止了,所以就算给出特别长的字符串,搜索的范围也是有限的(递归只会到第三层),及时就会返回了。
## 周五
在[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。
如图:
![78.子集](https://code-thinking-1253855093.file.myqcloud.com/pics/202011232041348.png)
认清这个本质之后,今天的题目就是一道模板题了。
其实可以不需要加终止条件因为startIndex >= nums.size()本层for循环本来也结束了本来我们就要遍历整棵树。
有的同学可能担心不写终止条件会不会无限递归?
并不会因为每次递归的下一层就是从i+1开始的。
如果要写终止条件,注意:`result.push_back(path);`要放在终止条件的上面,如下:
```
result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
if (startIndex >= nums.size()) { // 终止条件可以不加
return;
}
```
## 周六
早起的哈希表系列没有总结,所以[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)如约而至。
可能之前大家做过很多哈希表的题目,但是没有串成线,总结篇来帮你串成线,捋顺哈希表的整个脉络。
大家对什么时候各种set与map比较疑惑想深入了解红黑树哈希之类的。
**如果真的只是想清楚什么时候使用各种set与map不用看那么多把[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)看了就够了**
## 总结
本周我们依次介绍了组合问题,分割问题以及子集问题,子集问题还没有讲完,下周还会继续。
**我讲解每一种问题,都会和其他问题作对比,做分析,所以只要跟着细心琢磨相信对回溯又有新的认识**
最近这两天题目有点难度,刚刚开始学回溯算法的话,按照现在这个每天一题的速度来,确实有点快,学起来吃力非常正常,这些题目都是我当初学了好几个月才整明白的。
**所以大家能跟上的话,已经很优秀了!**
还有一些录友会很关心leetcode上的耗时统计。
这个是很不准确的,相同的代码多提交几次,大家就知道怎么回事了。
leetcode上的计时应该是以4ms为单位有的多提交几次多个4ms就多击败50%所以比较夸张如果程序运行是几百ms的级别可以看看leetcode上的耗时因为它的误差10几ms对最终影响不大。
**所以我的题解基本不会写击败百分之多少多少,没啥意义,时间复杂度分析清楚了就可以了**,至于回溯算法不用分析时间复杂度了,都是一样的爆搜,就看谁剪枝厉害了。
一些录友表示最近回溯算法看的实在是有点懵回溯算法确实是晦涩难懂可能视频的话更直观一些我最近应该会在B站同名「代码随想录」出回溯算法的视频大家也可以看视频在回顾一波。
**就酱,又是充实的一周,做好本周总结,迎接下一周,冲!**
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>