Skip to content

Commit 70166f4

Browse files
author
lucifer
committed
2 parents c0290fd + 7cb20f9 commit 70166f4

File tree

3 files changed

+38
-84
lines changed

3 files changed

+38
-84
lines changed

problems/46.permutations.md

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ https://leetcode-cn.com/problems/permutations/
5050
5151
通用写法的具体代码见下方代码区。
5252

53+
[1,2,3] 为例,我们的逻辑是:
54+
55+
- 先从 [1,2,3] 选取一个数。
56+
- 然后继续从 [1,2,3] 选取一个数,并且这个数不能是已经选取过的数。
57+
- 重复这个过程直到选取的数字达到了 3。
58+
5359
## 关键点解析
5460

5561
- 回溯法
@@ -84,32 +90,6 @@ var permute = function (nums) {
8490

8591
Python3 Code:
8692

87-
```Python
88-
class Solution:
89-
def permute(self, nums: List[int]) -> List[List[int]]:
90-
"""itertools库内置了这个函数"""
91-
return itertools.permutations(nums)
92-
93-
def permute2(self, nums: List[int]) -> List[List[int]]:
94-
"""自己写回溯法"""
95-
res = []
96-
def _backtrace(nums, pre_list):
97-
if len(nums) <= 0:
98-
res.append(pre_list)
99-
else:
100-
for i in nums:
101-
# 注意copy一份新的调用,否则无法正常循环
102-
p_list = pre_list.copy()
103-
p_list.append(i)
104-
left_nums = nums.copy()
105-
left_nums.remove(i)
106-
_backtrace(left_nums, p_list)
107-
_backtrace(nums, [])
108-
return res
109-
```
110-
111-
Python Code:
112-
11393
```Python
11494
class Solution:
11595
def permute(self, nums: List[int]) -> List[List[int]]:
@@ -120,7 +100,7 @@ class Solution:
120100
def permute2(self, nums: List[int]) -> List[List[int]]:
121101
"""自己写回溯法"""
122102
res = []
123-
def _backtrace(nums, pre_list):
103+
def backtrack(nums, pre_list):
124104
if len(nums) <= 0:
125105
res.append(pre_list)
126106
else:
@@ -130,23 +110,23 @@ class Solution:
130110
p_list.append(i)
131111
left_nums = nums.copy()
132112
left_nums.remove(i)
133-
_backtrace(left_nums, p_list)
134-
_backtrace(nums, [])
113+
backtrack(left_nums, p_list)
114+
backtrack(nums, [])
135115
return res
136116

137117
def permute3(self, nums: List[int]) -> List[List[int]]:
138118
"""回溯的另一种写法"""
139119
res = []
140120
length = len(nums)
141-
def _backtrack(start=0):
121+
def backtrack(start=0):
142122
if start == length:
143123
# nums[:] 返回 nums 的一个副本,指向新的引用,这样后续的操作不会影响已经已知解
144124
res.append(nums[:])
145125
for i in range(start, length):
146126
nums[start], nums[i] = nums[i], nums[start]
147-
_backtrack(start+1)
127+
backtrack(start+1)
148128
nums[start], nums[i] = nums[i], nums[start]
149-
_backtrack()
129+
backtrack()
150130
return res
151131
```
152132

problems/47.permutations-ii.md

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,26 @@ https://leetcode-cn.com/problems/permutations-ii/
3434

3535
这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。
3636

37-
这种题目其实有一个通用的解法,就是回溯法。
38-
网上也有大神给出了这种回溯法解题的
39-
[通用写法](<https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)>),这里的所有的解法使用通用方法解答。
37+
这种题目其实有一个通用的解法,就是回溯法。网上也有大神给出了这种回溯法解题的
38+
[通用写法](<https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)>),这里的所有的解法都使用通用方法解答。
39+
4040
除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。
4141

42-
我们先来看下通用解法的解题思路,我画了一张图:
42+
这道题和第 46 题不一样的点在于其有重复元素。比如题目给的 [1,1,2]
43+
44+
如果按照 46 题的解法,那么就会有重复的排列。 回顾一下 46 题我们的逻辑。以 [1,1,2] 为例,我们的逻辑是:
45+
46+
- 先从 [1,1,2] 选取一个数。
47+
- 然后继续从 [1,1,2] 选取一个数,并且这个数不能是已经选取过的数。
48+
- 重复这个过程直到选取的数字达到了 3。
4349

44-
![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu6qwpr4j31190u0jw4.jpg)
50+
如果继续沿用上面的逻辑,那么我们是永远无法拿到三个数字的,因此我们的逻辑需要变化。 这里我的算法是记录每一个被选取的索引,而不是值,这就保证了**同一个数字不会被选取多次,并且可以选取所有数字了**
4551

46-
> 图是 [78.subsets](https://github.com/azl397985856/leetcode/blob/master/problems/78.subsets.md),都差不多,仅做参考
52+
不过这还是有一个问题。 仍然以 [1,1,2] 为例,如果第一次选取了第一个1,第二次选取了第二个1,这就产生了一个组合 [1,1,2]。 如果继续第一次选取了第二个 1,而第二次选取了第一个 1,那么又会产生组合 [1,1,2],可以看出这两个组合是重复的
4753

48-
通用写法的具体代码见下方代码区。
54+
一个解决方案是对 nums 进行一次排序,并规定如果 **i > 0 and nums[i] == nums[i - 1] and visited[i - 1]**, 则不进行选取即可。
55+
56+
经过这样的处理,每次实际上都是从后往前依次重复的数。仍然以上面的 [1,1,2] 为例。[1,1,2] 这个排列一定是先取的第二个 1,再取第一个 1,最后取的 2。因为如果先取的第一个 1,那么永远无法取到三个数,便形成了一个不可行解。
4957

5058
## 关键点解析
5159

@@ -61,39 +69,14 @@ https://leetcode-cn.com/problems/permutations-ii/
6169
* @lc app=leetcode id=47 lang=javascript
6270
*
6371
* [47] Permutations II
64-
*
65-
* https://leetcode.com/problems/permutations-ii/description/
66-
*
67-
* algorithms
68-
* Medium (39.29%)
69-
* Total Accepted: 234.1K
70-
* Total Submissions: 586.2K
71-
* Testcase Example: '[1,1,2]'
72-
*
73-
* Given a collection of numbers that might contain duplicates, return all
74-
* possible unique permutations.
75-
*
76-
* Example:
77-
*
78-
*
79-
* Input: [1,1,2]
80-
* Output:
81-
* [
82-
* ⁠ [1,1,2],
83-
* ⁠ [1,2,1],
84-
* ⁠ [2,1,1]
85-
* ]
86-
*
87-
*
8872
*/
8973
function backtrack(list, nums, tempList, visited) {
9074
if (tempList.length === nums.length) return list.push([...tempList]);
9175
for (let i = 0; i < nums.length; i++) {
9276
// 和46.permutations的区别是这道题的nums是可以重复的
9377
// 我们需要过滤这种情况
94-
if (visited[i]) continue; // 不能用tempList.includes(nums[i])了,因为有重复
95-
// visited[i - 1] 这个判断容易忽略
96-
if (i > 0 && nums[i] === nums[i - 1] && visited[i - 1]) continue;
78+
if (visited[i]) continue; // 同一个数字不能用两次
79+
if (i > 0 && nums[i] === nums[i - 1] && visited[i - 1]) continue; // 同样值的数字不能用两次
9780

9881
visited[i] = true;
9982
tempList.push(nums[i]);
@@ -128,23 +111,23 @@ class Solution:
128111

129112
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
130113
"""自己写回溯法,与46题相比,需要去重"""
131-
# 排序是为了去重
114+
# 排序是为了方便去重
132115
nums.sort()
133116
res = []
134-
def _backtrace(nums, pre_list):
117+
def backtrack(nums, pre_list):
135118
if len(nums) <= 0:
136119
res.append(pre_list)
137120
else:
138121
for i in range(len(nums)):
139-
# 如果是同样的数字,则之前一定已经生成了对应可能
122+
# 同样值的数字只能使用一次
140123
if i > 0 and nums[i] == nums[i-1]:
141124
continue
142125
p_list = pre_list.copy()
143126
p_list.append(nums[i])
144127
left_nums = nums.copy()
145128
left_nums.pop(i)
146-
_backtrace(left_nums, p_list)
147-
_backtrace(nums, [])
129+
backtrack(left_nums, p_list)
130+
backtrack(nums, [])
148131
return res
149132
```
150133

@@ -154,7 +137,7 @@ class Solution:
154137
- [39.combination-sum](./39.combination-sum.md)
155138
- [40.combination-sum-ii](./40.combination-sum-ii.md)
156139
- [46.permutations](./46.permutations.md)
157-
- [60.permutation-sequence](./60.permutation-sequence.md)(TODO)
140+
- [60.permutation-sequence](./60.permutation-sequence.md)
158141
- [78.subsets](./78.subsets.md)
159142
- [90.subsets-ii](./90.subsets-ii.md)
160143
- [113.path-sum-ii](./113.path-sum-ii.md)

problems/78.subsets.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ https://leetcode-cn.com/problems/subsets/
2828

2929
## 前置知识
3030

31-
- 回溯
31+
- [回溯](https://github.com/azl397985856/leetcode/blob/master/thinkings/backtrack.md)
3232

3333
## 公司
3434

@@ -39,18 +39,9 @@ https://leetcode-cn.com/problems/subsets/
3939

4040
## 思路
4141

42-
这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法
42+
回溯的基本思路清参考上方的回溯专题
4343

44-
这种题目其实有一个通用的解法,就是回溯法。网上也有大神给出了这种回溯法解题的[通用写法](<https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)>),这里的所有的解法使用通用方法解答。
45-
除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。
46-
47-
我们先来看下通用解法的解题思路,我画了一张图:
48-
49-
![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlu2o05lsj31190u0jw4.jpg)
50-
51-
> 每一层灰色的部分,表示当前有哪些节点是可以选择的, 红色部分则是选择路径。1,2,3,4,5,6 则分别表示我们的 6 个子集。
52-
53-
通用写法的具体代码见下方代码区。
44+
子集类题目和全排列题目不一样的点在于其需要在递归树的所有节点执行**加入结果集**,这一操作,而不像全排列需要在叶子节点执行**加入结果集**
5445

5546
## 关键点解析
5647

0 commit comments

Comments
 (0)