diff --git a/README.md b/README.md index 2f204e10..69c285bc 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ * [本周小结!(回溯算法系列一)](https://mp.weixin.qq.com/s/m2GnTJdkYhAamustbb6lmw) * [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw) * [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) + * [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q) (持续更新中....) @@ -336,6 +337,7 @@ |[0113.路径总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0113.路径总和II.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** **栈**| |[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**| |[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**| +|[0127.单词接龙](https://github.com/youngyangyang04/leetcode/blob/master/problems/ 0127.单词接龙.md) |广度优先搜索 |中等|**广度优先搜索**| |[0129.求根到叶子节点数字之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0129.求根到叶子节点数字之和.md) |二叉树 |中等|**递归/回溯** 递归里隐藏着回溯,和113.路径总和II类似| |[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**| |[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**| @@ -377,6 +379,7 @@ |[0434.字符串中的单词数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0434.字符串中的单词数.md) |字符串 |简单|**模拟**| |[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**| |[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| +|[0455.分发饼干](https://github.com/youngyangyang04/leetcode/blob/master/problems/0455.分发饼干.md) |贪心 |简单| **贪心**| |[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| |[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**| |[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**| diff --git a/pics/127.单词接龙.png b/pics/127.单词接龙.png new file mode 100644 index 00000000..581bb558 Binary files /dev/null and b/pics/127.单词接龙.png differ diff --git a/pics/455.分发饼干.png b/pics/455.分发饼干.png new file mode 100644 index 00000000..ae66b57f Binary files /dev/null and b/pics/455.分发饼干.png differ diff --git a/problems/0057.插入区间.md b/problems/0057.插入区间.md index 8d6ecc42..79dba1c4 100644 --- a/problems/0057.插入区间.md +++ b/problems/0057.插入区间.md @@ -1,4 +1,8 @@ +# 链接 +https://leetcode-cn.com/problems/insert-interval/ + +# 思路 这道题目合并的情况有很多种,想想都让人头疼。 我把这道题目化为三步: diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 3da5002c..ab6621d8 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -1,7 +1,10 @@ ## 题目地址 -https://leetcode-cn.com/problems/restore-ip-addresses/ -# 93. 复原IP地址 +> 一些录友表示跟不上现在的节奏,想从头开始打卡学习起来,可以在公众号左下方,「算法汇总」可以找到历史文章,都是按系列排好顺序的,挨个看就可以了,看文章下的留言你就会发现,有很多录友都在从头打卡,你并不孤单! + +# 93.复原IP地址 + +题目地址:https://leetcode-cn.com/problems/restore-ip-addresses/ 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。 @@ -9,9 +12,7 @@ https://leetcode-cn.com/problems/restore-ip-addresses/ 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。 -  - -示例 1: +示例 1: 输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"] @@ -36,17 +37,44 @@ https://leetcode-cn.com/problems/restore-ip-addresses/ s 仅由数字组成 -## 思路 +# 思路 -这道题目相信大家刚看时看到的时候,应该会一脸茫然。 +做这道题目之前,最好先把[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)这个做了。 -那么只要意识到这是切割问题,那么切割问题就可以使用回溯搜索法把所有可能性搜出来,和[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) 类似。 +这道题目相信大家刚看的时候,应该会一脸茫然。 -那么切割问题可以抽象为树型结构,如图: +其实只要意识到这是切割问题,**切割问题就可以使用回溯搜索法把所有可能性搜出来**,和刚做过的[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)就十分类似了。 + +切割问题可以抽象为树型结构,如图: -终止条件: 和[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) 不同,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。 + +## 回溯三部曲 + +* 递归参数 + +在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我们就提到切割问题类似组合问题。 + +startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。 + +本题我们还需要一个变量pointNum,记录添加逗点的数量。 + +所以代码如下: + +``` + vector result;// 记录结果 + // startIndex: 搜索的起始位置,pointNum:添加逗点的数量 + void backtracking(string& s, int startIndex, int pointNum) { +``` + +* 递归终止条件 + +终止条件和[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。 + +pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。 + +然后验证一下第四段是否合法,如果合法就加入到结果集里 代码如下: @@ -60,24 +88,40 @@ if (pointNum == 3) { // 逗点数量为3时,分隔结束 } ``` -那么再来看循环遍历的过程如何截取子串。 +* 单层搜索的逻辑 -在`for (int i = startIndex; i < s.size(); i++)`循环中 [startIndex, i]这个区间就是截取的子串,需要判断这个子串是否合法,如果合法就在字符串后面加上符号`.`表示已经分割。 +在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中已经讲过在循环遍历中如何截取子串。 + +在`for (int i = startIndex; i < s.size(); i++)`循环中 [startIndex, i]这个区间就是截取的子串,需要判断这个子串是否合法。 + +如果合法就在字符串后面加上符号`.`表示已经分割。 + +如果不合法就结束本层循环,如图中剪掉的分支: + + 然后就是递归和回溯的过程: -递归调用时,下一层递归的startIndex要从i+2开始(因为刚刚在字符串中加入了分隔符`.`),同时记录分割符的数量pointNum 要 +1。 +递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符`.`),同时记录分割符的数量pointNum 要 +1。 -回溯的时候,就将刚刚加入的分隔符`.` 删掉就可以了,**pointNum其实也要减一,但是 pointNum+1 的逻辑放在递归函数的参数里了,这里相当于隐藏了回溯pointNum的过程**。 +回溯的时候,就将刚刚加入的分隔符`.` 删掉就可以了,pointNum也要-1。 -递归和回溯代码如下: +代码如下: ``` -// 插入逗点之后下一个子串的起始位置为i+2 -backtracking(s, i + 2, pointNum + 1); -s.erase(s.begin() + i + 1); // 回溯时删掉逗点 +for (int i = startIndex; i < s.size(); i++) { + if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法 + s.insert(s.begin() + i + 1 , '.'); // 在i的后面插入一个逗点 + pointNum++; + backtracking(s, i + 2, pointNum); // 插入逗点之后下一个子串的起始位置为i+2 + pointNum--; // 回溯 + s.erase(s.begin() + i + 1); // 回溯删掉逗点 + } else break; // 不合法,直接结束本层循环 +} ``` +## 判断子串是否合法 + 最后就是在写一个判断段位是否是有效段位了。 主要考虑到如下三点: @@ -111,9 +155,27 @@ bool isValid(const string& s, int start, int end) { } ``` -关键代码已经讲完,整体代码如下: +## C++代码 -## C++代码 + +根据[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)给出的回溯算法模板: + +``` +void backtracking(参数) { + if (终止条件) { + 存放结果; + return; + } + + for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { + 处理节点; + backtracking(路径,选择列表); // 递归 + 回溯,撤销处理结果 + } +} +``` + +可以写出如下回溯算法C++代码: ``` class Solution { @@ -128,16 +190,14 @@ private: } return; } - // 从起始位置开始构造字段字符串串 for (int i = startIndex; i < s.size(); i++) { - // 判断 [startIndex,i] 这个区间的子串是否合法 - if (isValid(s, startIndex, i)) { - // 合法,在i的后面插入一个逗点 - s.insert(s.begin() + i + 1 , '.'); - // 插入逗点之后下一个子串的起始位置为i+2 - backtracking(s, i + 2, pointNum + 1); - s.erase(s.begin() + i + 1); // 回溯时删掉逗点 - } else break; + if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法 + s.insert(s.begin() + i + 1 , '.'); // 在i的后面插入一个逗点 + pointNum++; + backtracking(s, i + 2, pointNum); // 插入逗点之后下一个子串的起始位置为i+2 + pointNum--; // 回溯 + s.erase(s.begin() + i + 1); // 回溯删掉逗点 + } else break; // 不合法,直接结束本层循环 } } // 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法 @@ -167,6 +227,27 @@ public: return result; } }; + ``` -> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 +# 总结 + +在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中我列举的分割字符串的难点,本题都覆盖了。 + +而且本题还需要操作字符串添加逗号作为分隔符,并验证区间的合法性。 + +可以说是[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)的加强版。 + +在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少! + +**就酱,「代码随想录」值得推荐给你的朋友们!** + + +一些录友表示跟不上现在的节奏,想从头开始打卡学习起来,可以在在公众号左下方,「算法汇总」可以找到历史文章,都是按系列排好顺序的,挨个看就可以了,别忘了打卡。 + +**很多录友都在从头开始打卡学习,看看前面文章的留言区就知道了,你并不孤单!** + + + +> 我是[程序员Carl](https://github.com/youngyangyang04),更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),期待你的关注! + diff --git a/problems/0127.单词接龙.md b/problems/0127.单词接龙.md new file mode 100644 index 00000000..575fc4c0 --- /dev/null +++ b/problems/0127.单词接龙.md @@ -0,0 +1,73 @@ + +## 题目链接 + +https://leetcode-cn.com/problems/word-ladder/ + +## 思路 + +以示例1为例,从这个图中可以看出 hit 到 cog的路线,不止一条,有三条,两条是最短的长度为5,一条长度为6。 + + + +本题只需要求出最短长度就可以了,不用找出路径。 + +所以这道题要解决两个问题: + +* 图中的线是如何连在一起的 +* 起点和终点的最短路径长度 + + +首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。 + +然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。 + +本题如果用深搜,会非常麻烦。 + +另外需要有一个注意点: + +* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环! +* 本题给出集合是数组型的,可以转成set结构,查找更快一些 + +C++代码如下:(详细注释) + +``` +class Solution { +public: + int ladderLength(string beginWord, string endWord, vector& wordList) { + // 将vector转成unordered_set,提高查询速度 + unordered_set wordSet(wordList.begin(), wordList.end()); + // 如果endWord没有在wordSet出现,直接返回0 + if (wordSet.find(endWord) == wordSet.end()) return 0; + // 记录word是否访问过 + unordered_map visitMap; // + // 初始化队列 + queue que; + que.push(beginWord); + // 初始化visitMap + visitMap.insert(pair(beginWord, 1)); + + while(!que.empty()) { + string word = que.front(); + que.pop(); + int path = visitMap[word]; // 这个word的路径长度 + for (int i = 0; i < word.size(); i++) { + string newWord = word; // 用一个新单词替换word,因为每次置换一个字母 + for (int j = 0 ; j < 26; j++) { + newWord[i] = j + 'a'; + if (newWord == endWord) return path + 1; // 找到了end,返回path+1 + // wordSet出现了newWord,并且newWord没有被访问过 + if (wordSet.find(newWord) != wordSet.end() + && visitMap.find(newWord) == visitMap.end()) { + // 添加访问信息 + visitMap.insert(pair(newWord, path + 1)); + que.push(newWord); + } + } + } + } + return 0; + } +}; +``` +> 我是[程序员Carl](https://github.com/youngyangyang04),组队刷题可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),期待你的关注! + diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md new file mode 100644 index 00000000..b1d831f7 --- /dev/null +++ b/problems/0455.分发饼干.md @@ -0,0 +1,42 @@ + +## 链接 + +https://leetcode-cn.com/problems/assign-cookies/ + +## 思路 + +这道题目呢,其实可以用较大大的饼干优先满足可以满足的胃口大的小孩。 + +注意我这里用的是 可以满足的胃口大的小孩。这样就不会造成大饼干的浪费。 + +所以使用贪心策略,讲饼干数组和小孩数组排序。 + +然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量的大小就可以了。 + +如图: + + + + +C++代码整体如下: + +``` +class Solution { +public: + int findContentChildren(vector& g, vector& s) { + sort(g.begin(), g.end()); + sort(s.begin(), s.end()); + int index = s.size() - 1; // 饼干数组的下表 + int result = 0; + for (int i = g.size() - 1; i >= 0; i--) { + if (index >= 0 && s[index] >= g[i]) { + result++; + index--; + } + } + return result; + } +}; +``` +> 我是[程序员Carl](https://github.com/youngyangyang04),组队刷题可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),期待你的关注! +