mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 23:28:29 +08:00
Update
This commit is contained in:
@ -39,17 +39,17 @@ https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
|
||||
**那么在来讲一讲回溯法,回溯法的模板如下:**
|
||||
|
||||
```
|
||||
回溯函数() {
|
||||
backtracking() {
|
||||
if (终止条件) {
|
||||
存放结果;
|
||||
}
|
||||
|
||||
for (枚举同一个位置的所有可能性,可以想成节点孩子的数量) {
|
||||
递归,处理下一个孩子;
|
||||
(递归的下面就是回溯的过程);
|
||||
递归,处理节点;
|
||||
backtracking();
|
||||
回溯,撤销处理结果
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
按照这个模板,不难写出如下代码:
|
||||
|
@ -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++代码
|
||||
```
|
||||
|
@ -39,7 +39,7 @@ https://leetcode-cn.com/problems/remove-element/
|
||||
|
||||
删除过程如下:
|
||||
|
||||
<img src='../../World/Writing/leetcode/video/27.移除元素-暴力解法.gif' width=600> </img></div>
|
||||
<img src='../video/27.移除元素-暴力解法.gif' width=600> </img></div>
|
||||
|
||||
很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。
|
||||
|
||||
@ -73,7 +73,7 @@ public:
|
||||
|
||||
删除过程如下:
|
||||
|
||||
<img src='../../World/Writing/leetcode/video/27.移除元素-双指针法.gif' width=600> </img></div>
|
||||
<img src='../video/27.移除元素-双指针法.gif' width=600> </img></div>
|
||||
|
||||
**双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。**
|
||||
|
||||
|
@ -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是路人**。
|
||||
|
||||
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开又闭的原则,这样这一圈才能按照统一的规则画下来。
|
||||
|
||||
那么我按照左闭右开的原则,来画一圈,大家看一下:
|
||||
|
||||
<img src='../pics/螺旋矩阵.png' width=600> </img></div>
|
||||
|
||||
很多同学,做这道题目之所以一直写不好,代码越写越乱,就是因为 在画每一条边的时候,没有保证统一原则
|
||||
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
|
||||
|
||||
例如:模拟矩阵上边的时候 左闭右开,然后模拟矩阵右列的时候又开始了左闭又闭,那岂能不乱
|
||||
这也是坚持了每条边左闭右开的原则。
|
||||
|
||||
## 解法
|
||||
一些同学做这道题目之所以一直写不好,代码越写越乱。
|
||||
|
||||
就是因为在画每一条边的时候,一会左开又闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
|
||||
|
||||
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
|
||||
|
||||
# C++代码
|
||||
|
||||
```C++
|
||||
class Solution {
|
||||
|
@ -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,来看一下动画效果:
|
||||
|
||||
<video src='../video/209.长度最小的子数组.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
|
||||
|
||||
代码如下:
|
||||
|
||||
## 暴力解法
|
||||
|
||||
```
|
||||
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,来看一下查找的过程:
|
||||
|
||||
<img src='../video/209.长度最小的子数组.gif' width=600> </img></div>
|
||||
|
||||
最后找到 4,3 是最短距离。
|
||||
|
||||
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
|
||||
|
||||
在本题中实现滑动窗口,主要确定如下三点:
|
||||
|
||||
* 窗口内是什么?
|
||||
* 如何移动窗口的起始位置?
|
||||
* 如何移动窗口的结束位置?
|
||||
|
||||
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
|
||||
|
||||
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
|
||||
|
||||
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。
|
||||
|
||||
解题的关键在于 窗口的起始位置如何移动,如图所示:
|
||||
|
||||
<img src='../pics/leetcode_209.png' width=600> </img></div>
|
||||
|
||||
可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。**
|
||||
|
||||
|
||||
# C++滑动窗口代码
|
||||
|
||||
```
|
||||
// 滑动窗口
|
||||
class Solution {
|
||||
public:
|
||||
int minSubArrayLen(int s, vector<int>& 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」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
||||
|
@ -94,15 +94,15 @@ class Solution {
|
||||
private:
|
||||
// unordered_map<出发城市, map<到达城市, 航班次数>> targets
|
||||
unordered_map<string, map<string, int>> targets;
|
||||
bool backtracking(int ticketNum, int index, vector<string>& result) {
|
||||
if (index == ticketNum + 1) {
|
||||
bool backtracking(int ticketNum, vector<string>& result) {
|
||||
if (result.size() == ticketNum + 1) {
|
||||
return true;
|
||||
}
|
||||
for (pair<const string, int>& 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;
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
38
problems/0657.机器人能否返回原点.md
Normal file
38
problems/0657.机器人能否返回原点.md
Normal file
@ -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)位置就可以了。
|
||||
|
||||
如图所示:
|
||||
<img src='../pics/657.机器人能否返回原点.png' width=600> </img></div>
|
||||
|
||||
## 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」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
Reference in New Issue
Block a user