diff --git a/README.md b/README.md index 43cc7ebe..9b345f32 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ * [哈希表:这道题目我做过?](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) +* [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) +* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) * 精选链表相关的面试题 * 精选字符串相关的面试题 * 精选栈与队列相关的面试题 @@ -82,6 +84,10 @@ * [0219.存在重复元素II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0219.存在重复元素II.md) * 0220.存在重复元素III +* 循环不变量原则 + * [0035.搜索插入位置](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) + * [0059.螺旋矩阵II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0059.螺旋矩阵II.md) + * 字符串经典题目 * [0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) * [0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) @@ -92,11 +98,12 @@ * [0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) * 双指针法经典题目 - * [0015.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md) - * [0018.四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md) + * [0027.移除元素](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) + * [0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) + * [0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) * [0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md) - * [0206.翻转链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0206.翻转链表.md) - * [0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) + * [0206.翻转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) + * [0142.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) * [0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) * [剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) @@ -400,7 +407,7 @@ int countNodes(TreeNode* root) { |[0205.同构字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0205.同构字符串.md) |哈希表 |简单| **哈希**| |[0206.翻转链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0206.翻转链表.md) |链表 |简单| **双指针法** **递归**| |[0209.长度最小的子数组](https://github.com/youngyangyang04/leetcode/blob/master/problems/0209.长度最小的子数组.md) |数组 |中等| **暴力** **滑动窗口**| -|[0216.组合总和III](https://github.com/youngyangyang04/leetcode/blob/master/problems/0216.组合总和III.md) |数组/回溯 |中等| **回溯**| +|[0216.组合总和III](https://github.com/youngyangyang04/leetcode/blob/master/problems/0216.组合总和III.md) |数组/回溯 |中等| **回溯算法**| |[0219.存在重复元素II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0219.存在重复元素II.md) | 哈希表 |简单| **哈希** | |[0222.完全二叉树的节点个数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0222.完全二叉树的节点个数.md) | 树 |简单| **递归** | |[0225.用队列实现栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0225.用队列实现栈.md) | 队列 |简单| **队列** | @@ -409,6 +416,7 @@ int countNodes(TreeNode* root) { |[0237.删除链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0237.删除链表中的节点.md) |链表 |简单| **原链表移除** **添加虚拟节点** 递归| |[0239.滑动窗口最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0239.滑动窗口最大值.md) |滑动窗口/队列 |困难| **单调队列**| |[0242.有效的字母异位词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0242.有效的字母异位词.md) |哈希表 |简单| **哈希**| +|[0332.重新安排行程](https://github.com/youngyangyang04/leetcode/blob/master/problems/0332.重新安排行程.md) |深度优先搜索/回溯 |中等| **深度优先搜索/回溯算法**| |[0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) |字符串 |简单| **双指针**| |[0347.前K个高频元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0347.前K个高频元素.md) |哈希/堆/优先级队列 |中等| **哈希/优先级队列**| |[0349.两个数组的交集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0349.两个数组的交集.md) |哈希表 |简单|**哈希**| @@ -418,7 +426,7 @@ int countNodes(TreeNode* root) { |[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**| |[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| |[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| -|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯**| +|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**| |[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**| |[0575.分糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0575.分糖果.md) |哈希表 |简单|**哈希**| |[0617.合并二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0617.合并二叉树.md) |树 |简单|**递归** **迭代**| diff --git a/pics/657.机器人能否返回原点.png b/pics/657.机器人能否返回原点.png new file mode 100644 index 00000000..6ea5b69b Binary files /dev/null and b/pics/657.机器人能否返回原点.png differ diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index e4257e24..c421c752 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -39,17 +39,17 @@ https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ **那么在来讲一讲回溯法,回溯法的模板如下:** ``` -回溯函数() { +backtracking() { if (终止条件) { 存放结果; } for (枚举同一个位置的所有可能性,可以想成节点孩子的数量) { - 递归,处理下一个孩子; - (递归的下面就是回溯的过程); + 递归,处理节点; + backtracking(); + 回溯,撤销处理结果 } } - ``` 按照这个模板,不难写出如下代码: diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md index ac1f699e..34430d1c 100644 --- a/problems/0018.四数之和.md +++ b/problems/0018.四数之和.md @@ -40,7 +40,22 @@ https://leetcode-cn.com/problems/4sum/ 而[四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! -大家解决一下这两道题目就能感受出来难度的差异。 +我们来回顾一下,几道题目使用了双指针法。 + +双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下: +* [0027.移除元素](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) +* [15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) +* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) + +双指针来记录前后指针实现链表反转: + +* [206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) + +使用双指针来确定有环: + +* [142题.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) + +双指针法在数组和链表中还有很多应用,后面还会介绍到。 # C++代码 ``` diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index 1ce8fe40..7700fcf3 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -39,7 +39,7 @@ https://leetcode-cn.com/problems/remove-element/ 删除过程如下: - + 很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。 @@ -73,7 +73,7 @@ public: 删除过程如下: - + **双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。** diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md index 48c4025c..c5c3301a 100644 --- a/problems/0059.螺旋矩阵II.md +++ b/problems/0059.螺旋矩阵II.md @@ -1,26 +1,66 @@ > 笔者在BAT从事技术研发多年,利用工作之余重刷leetcode,更多原创文章请关注公众号「代码随想录」。 -## 题目地址 +# 题目地址 https://leetcode-cn.com/problems/spiral-matrix-ii/ -## 思路 +> 一进循环深似海,从此offer是路人 -模拟顺时针画矩阵的过程 +# 题目59.螺旋矩阵II -填充上行从左到右 -填充右列从上到下 -填充下行从右到左 -填充左列从下到上 +给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 -在模拟的过程中最重要的思想是**保持画每一条边的原则一致,即每次都是左闭右开的原则**,如果所示 +示例: + +输入: 3 +输出: +[ + [ 1, 2, 3 ], + [ 8, 9, 4 ], + [ 7, 6, 5 ] +] + +# 思路 + +这道题目可以说在面试中出现频率较高的题目,**本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。** + +要如何画出这个螺旋排列的正方形矩阵呢? + +相信很多同学刚开始做这种题目的时候,上来就是一波判断猛如虎。 + +结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里哪里有问题,改了那里这里又跑不起来了。 + +大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。 + +而求解本题依然是要坚持循环不变量原则。 + +模拟顺时针画矩阵的过程: + +* 填充上行从左到右 +* 填充右列从上到下 +* 填充下行从右到左 +* 填充左列从下到上 + +由外向内一圈一圈这么画下去。 + +可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是**一进循环深似海,从此offer是路人**。 + +这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开又闭的原则,这样这一圈才能按照统一的规则画下来。 + +那么我按照左闭右开的原则,来画一圈,大家看一下: -很多同学,做这道题目之所以一直写不好,代码越写越乱,就是因为 在画每一条边的时候,没有保证统一原则 +这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。 -例如:模拟矩阵上边的时候 左闭右开,然后模拟矩阵右列的时候又开始了左闭又闭,那岂能不乱 +这也是坚持了每条边左闭右开的原则。 -## 解法 +一些同学做这道题目之所以一直写不好,代码越写越乱。 + +就是因为在画每一条边的时候,一会左开又闭,一会左闭右闭,一会又来左闭右开,岂能不乱。 + +代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。 + +# C++代码 ```C++ class Solution { diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index 47049a0a..f206d641 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -1,19 +1,25 @@ ## 题目地址 https://leetcode-cn.com/problems/minimum-size-subarray-sum/ -## 思路 +> 滑动窗口拯救了你 + +# 题目209.长度最小的子数组 + +给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 + +示例: + +输入:s = 7, nums = [2,3,1,2,4,3] +输出:2 +解释:子数组 [4,3] 是该条件下的长度最小的子数组。 + + +# 暴力解法 + 这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是O(n^2) 。 -还可以使用滑动窗口的细想来做这道题。所谓滑动窗口,**就是不断的调节子序列的起始位置,从而得出我们要想的结果** - -这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下动画效果: - - - 代码如下: -## 暴力解法 - ``` class Solution { public: @@ -27,7 +33,6 @@ public: sum += nums[j]; if (sum >= s) { // 一旦发现子序列和超过了s,更新result subLength = j - i + 1; // 取子序列的长度 - // result取 result和subLength最小的那个 result = result < subLength ? result : subLength; break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break } @@ -38,12 +43,45 @@ public: } }; ``` +时间复杂度:O(n^2) +空间复杂度:O(1) -## 滑动窗口 +# 滑动窗口 + +接下来就开始介绍数组操作中另一个重要的方法:**滑动窗口**。 + +所谓滑动窗口,**就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果**。 + +这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程: + + + +最后找到 4,3 是最短距离。 + +其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。 + +在本题中实现滑动窗口,主要确定如下三点: + +* 窗口内是什么? +* 如何移动窗口的起始位置? +* 如何移动窗口的结束位置? + +窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。 + +窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。 + +窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。 + +解题的关键在于 窗口的起始位置如何移动,如图所示: + +可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。** + + +# C++滑动窗口代码 + ``` -// 滑动窗口 class Solution { public: int minSubArrayLen(int s, vector& nums) { @@ -56,7 +94,6 @@ public: // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件 while (sum >= s) { subLength = (j - i + 1); // 取子序列的长度 - // result取 result和subLength最小的那个 result = result < subLength ? result : subLength; sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置) } @@ -67,5 +104,8 @@ public: }; ``` -> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 +时间复杂度:O(n) +空间复杂度:O(1) + +> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index ab2a9ee2..a2a318e4 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -94,15 +94,15 @@ class Solution { private: // unordered_map<出发城市, map<到达城市, 航班次数>> targets unordered_map> targets; -bool backtracking(int ticketNum, int index, vector& result) { - if (index == ticketNum + 1) { +bool backtracking(int ticketNum, vector& result) { + if (result.size() == ticketNum + 1) { return true; } for (pair& target : targets[result[result.size() - 1]]) { if (target.second > 0 ) { // 使用int字段来记录到达城市是否使用过了 result.push_back(target.first); target.second--; - if (backtracking(ticketNum, index + 1, result)) return true; + if (backtracking(ticketNum, result)) return true; result.pop_back(); target.second++; } @@ -116,9 +116,10 @@ public: targets[vec[0]][vec[1]]++; // 记录映射关系 } result.push_back("JFK"); - backtracking(tickets.size(), 1, result); + backtracking(tickets.size(), result); return result; } }; + ``` diff --git a/problems/0657.机器人能否返回原点.md b/problems/0657.机器人能否返回原点.md new file mode 100644 index 00000000..fb74c751 --- /dev/null +++ b/problems/0657.机器人能否返回原点.md @@ -0,0 +1,38 @@ +## 题目地址 +https://leetcode-cn.com/problems/robot-return-to-origin/ + +## 思路 + +这道题目还是挺简单的,大家不要想复杂了,一波哈希法又一波图论算法啥的,哈哈。 + +其实就是,x,y坐标,初始为0,然后: +* if (moves[i] == 'U') y++; +* if (moves[i] == 'D') y--; +* if (moves[i] == 'L') x--; +* if (moves[i] == 'R') x++; + +最后判断一下x,y是否回到了(0, 0)位置就可以了。 + +如图所示: + + +## C++代码 + +``` +class Solution { +public: + bool judgeCircle(string moves) { + int x = 0, y = 0; + for (int i = 0; i < moves.size(); i++) { + if (moves[i] == 'U') y++; + if (moves[i] == 'D') y--; + if (moves[i] == 'L') x--; + if (moves[i] == 'R') x++; + } + if (x == 0 && y == 0) return true; + return false; + } +}; +``` + +> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。