Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d1cf776

Browse files
Update
1 parent 604fd07 commit d1cf776

10 files changed

+350
-41
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
|[0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md) |数组 |简单|**暴力** **快慢指针/快慢指针** |
308308
|[0027.移除元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0027.移除元素.md) |数组 |简单| **暴力** **双指针/快慢指针/双指针**|
309309
|[0028.实现strStr()](https://github.com/youngyangyang04/leetcode/blob/master/problems/0028.实现strStr().md) |字符串 |简单| **KMP** |
310+
|[0031.下一个排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0031.下一个排列.md) |数组 |中等| **模拟** 这道题目还是有难度的|
310311
|[0035.搜索插入位置](https://github.com/youngyangyang04/leetcode/blob/master/problems/0035.搜索插入位置.md) |数组 |简单| **暴力** **二分**|
311312
|[0037.解数独](https://github.com/youngyangyang04/leetcode/blob/master/problems/0037.解数独.md) |回溯 |困难| **回溯**|
312313
|[0039.组合总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0039.组合总和.md) |数组/回溯 |中等| **回溯**|

pics/31.下一个排列.png

77.9 KB
Loading

pics/435.无重叠区间.png

23.2 KB
Loading

pics/46.全排列.png

227 KB
Loading

problems/0031.下一个排列.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
## 思路
3+
4+
一些同学可能手动写排列的顺序,都没有写对,那么写程序的话思路一定是有问题的了,我这里以1234为例子,把全排列都列出来。可以参考一下规律所在:
5+
6+
```
7+
1 2 3 4
8+
1 2 4 3
9+
1 3 2 4
10+
1 3 4 2
11+
1 4 2 3
12+
1 4 3 2
13+
2 1 3 4
14+
2 1 4 3
15+
2 3 1 4
16+
2 3 4 1
17+
2 4 1 3
18+
2 4 3 1
19+
3 1 2 4
20+
3 1 4 2
21+
3 2 1 4
22+
3 2 4 1
23+
3 4 1 2
24+
3 4 2 1
25+
4 1 2 3
26+
4 1 3 2
27+
4 2 1 3
28+
4 2 3 1
29+
4 3 1 2
30+
4 3 2 1
31+
```
32+
33+
如图:
34+
35+
以求1243为例,流程如图:
36+
37+
<img src='../pics/31.下一个排列.png' width=600> </img></div>
38+
39+
对应的C++代码如下:
40+
41+
```
42+
class Solution {
43+
public:
44+
void nextPermutation(vector<int>& nums) {
45+
for (int i = nums.size() - 1; i >= 0; i--) {
46+
for (int j = nums.size() - 1; j > i; j--) {
47+
if (nums[j] > nums[i]) {
48+
swap(nums[j], nums[i]);
49+
sort(nums.begin() + i + 1, nums.end());
50+
return;
51+
}
52+
}
53+
}
54+
// 到这里了说明整个数组都是倒叙了,反转一下便可
55+
reverse(nums.begin(), nums.end());
56+
}
57+
};
58+
```

problems/0046.全排列.md

Lines changed: 118 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,142 @@
11

2-
## 题目地址
3-
https://leetcode-cn.com/problems/permutations/
2+
> 开始排列问题
3+
> 通知:现在已经将所有历史文章,汇总到一起,有一个整体的目录,方便录友们从前面系列开始卡了,就在公众号左下角「算法汇总」,大家去瞅瞅哈
4+
5+
# 46.全排列
6+
7+
题目链接:https://leetcode-cn.com/problems/permutations/
8+
9+
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
10+
11+
示例:
12+
输入: [1,2,3]
13+
输出:
14+
[
15+
[1,2,3],
16+
[1,3,2],
17+
[2,1,3],
18+
[2,3,1],
19+
[3,1,2],
20+
[3,2,1]
21+
]
422

523
## 思路
624

7-
先写逻辑,再确认参数,先把for循环写出来,在写 结束语句,在写 函数参数。
25+
此时我们已经学习了[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。
26+
27+
相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。
28+
29+
所以正如我们在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)所讲的为什么回溯法是暴力搜索,效率这么低,还要用它?
30+
31+
**因为一些问题能暴力搜出来就已经很不错了!**
32+
33+
我以[1,2,3]为例,抽象成树形结构如下:
34+
35+
<img src='../pics/46.全排列.png' width=600> </img></div>
36+
37+
## 回溯三部曲
38+
39+
* 递归函数参数
40+
41+
**首先排列是有序的,也就是说[1,2][2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方**
42+
43+
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
44+
45+
但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:
46+
47+
<img src='../pics/46.全排列.png' width=600> </img></div>
48+
49+
代码如下:
50+
51+
```
52+
vector<vector<int>> result;
53+
vector<int> path;
54+
void backtracking (vector<int>& nums, vector<bool>& used)
55+
```
56+
57+
* 递归终止条件
58+
59+
<img src='../pics/46.全排列.png' width=600> </img></div>
60+
61+
可以看出叶子节点,就是收割结果的地方。
62+
63+
那么什么时候,算是到达叶子节点呢?
64+
65+
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
66+
67+
代码如下:
68+
69+
```
70+
// 此时说明找到了一组
71+
if (path.size() == nums.size()) {
72+
result.push_back(path);
73+
return;
74+
}
75+
```
76+
77+
* 单层搜索的逻辑
78+
79+
这里和[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。
880

9-
这道题目树形结构还不太一样
81+
因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
1082

11-
## 解法
83+
**而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次**
84+
85+
代码如下:
86+
87+
```
88+
for (int i = 0; i < nums.size(); i++) {
89+
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
90+
used[i] = true;
91+
path.push_back(nums[i]);
92+
backtracking(nums, used);
93+
path.pop_back();
94+
used[i] = false;
95+
}
96+
```
97+
98+
整体C++代码如下:
99+
100+
## C++代码
12101

13102
```
14103
class Solution {
15104
public:
16105
vector<vector<int>> result;
17-
void backtracking (vector<int>& nums, vector<int>& vec, vector<bool>& used) {
106+
vector<int> path;
107+
void backtracking (vector<int>& nums, vector<bool>& used) {
18108
// 此时说明找到了一组
19-
if (vec.size() == nums.size()) {
20-
result.push_back(vec);
109+
if (path.size() == nums.size()) {
110+
result.push_back(path);
21111
return;
22112
}
23-
24113
for (int i = 0; i < nums.size(); i++) {
25-
if (used[i] == false) {
26-
used[i] = true;
27-
vec.push_back(nums[i]);
28-
backtracking(nums, vec, used);
29-
vec.pop_back();
30-
used[i] = false;
31-
}
114+
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
115+
used[i] = true;
116+
path.push_back(nums[i]);
117+
backtracking(nums, used);
118+
path.pop_back();
119+
used[i] = false;
32120
}
33121
}
34122
vector<vector<int>> permute(vector<int>& nums) {
123+
result.clear();
124+
path.clear();
35125
vector<bool> used(nums.size(), false);
36-
vector<int> vec;
37-
backtracking(nums, vec, used);
126+
backtracking(nums, used);
38127
return result;
39128
}
40129
};
41130
```
42131

43-
这是一个思路:
44-
class Solution {
45-
public:
46-
vector<vector<int>> res;
47-
vector<vector<int>> permute(vector<int>& nums) {
48-
solve(nums, 0);
49-
return res;
50-
}
51-
void solve(vector<int> &nums, int idx) {
52-
if(idx == nums.size()-1 || nums.size() == 0){
53-
res.push_back(nums);
54-
return;
55-
}
56-
for(int i = idx; i < nums.size(); i++){
57-
swap(nums[idx], nums[i]);
58-
solve(nums, idx+1);
59-
swap(nums[idx], nums[i]);
60-
}
61-
}
62-
};
132+
# 总结
133+
134+
大家此时可以感受出排列问题的不同:
135+
136+
* 每层都是从0开始搜索而不是startIndex
137+
* 需要used数组记录path里都放了哪些元素了
138+
139+
排列问题是回溯算法解决的经典题目,大家可以好好体会体会。
140+
141+
就酱,如果感觉「代码随想录」诚意满满,就帮Carl宣传一波吧!
142+

problems/0090.子集II.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public:
8181

8282
其实这道题目的知识点,我们之前都讲过了,如果之前讲过的子集问题和去重问题都掌握的好,这道题目应该分分钟AC。
8383

84+
85+
这道题目去重的逻辑,也可以这么写
86+
```
87+
if (i > startIndex && nums[i] == nums[i - 1] ) {
88+
continue;
89+
}
90+
```
91+
8492
**就酱,如果感觉融会贯通了,就把「代码随想录」介绍给自己的同学朋友吧,也许他们也需要!**
8593

8694
> 我是[程序员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),期待你的关注!

problems/0435.无重叠区间.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,30 @@
55

66
**相信很多同学看到这道题目都冥冥之中感觉要排序,但是究竟是按照右边界排序,还是按照左边界排序呢?**
77

8-
按照右边界排序,那么右边界越小越好,因为右边界越小,留给下一个区间的空间就越大,所以可以从左向右遍历,优先选右边界小的。
8+
按照右边界排序,从左向右遍历,右边界越小越好,因为右边界越小,留给下一个区间的空间就越大,所以可以从左向右遍历,优先选右边界小的。
99

10-
按照左边界排序,那么就是左边界越大越好,这样就给前一个区间的空间就越大,所以可以从右向左遍历。
10+
按照左边界排序,那么就是从右向左遍历,左边界数值越大越好(越靠右),这样就给前一个区间的空间就越大,所以可以从右向左遍历。
1111

1212
如果按照左边界排序,还从左向右遍历的话,要处理各个区间右边界的各种情况,就比较复杂了,这其实也就不是贪心了。
1313

1414

15+
**我来按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了**
1516

16-
在每次选择中,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
17+
这里记录非交叉区间的个数还是有技巧的,如图:
1718

19+
<img src='../pics/435.无重叠区间.png' width=600> </img></div>
20+
21+
区间,1,2,3,4,5,6都按照右边界排好序。
22+
23+
每次取非交叉区间的时候,都是可右边界最小的来做分割点(这样留给下一个区间的空间就越大),所以第一条分割线就是区间1结束的位置。
24+
25+
接下来就是找大于区间1结束位置的区间,是从区间4开始。**那有同学问了为什么不从区间5开始?别忘已经是按照右边界排序的了**
26+
27+
区间4结束之后,在找到区间6,所以一共记录非交叉区间的个数是三个。
28+
29+
总共区间个数为6,减去非交叉区间的个数(3),为3。移除区间的最小数量就是3。
30+
31+
C++代码如下:
1832

1933
```
2034
class Solution {
@@ -38,3 +52,6 @@ public:
3852
}
3953
};
4054
```
55+
56+
> 我是[程序员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),期待你的关注!
57+

problems/回溯总结.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@
2020

2121

2222
# 组合 子集问题,used[i-1] = false 来去重复, 啥问题 used[i-1] = true也是可以的来着 排列问题
23+
24+
# 时间复杂度分析

0 commit comments

Comments
 (0)