Merge branch 'youngyangyang04:master' into master

This commit is contained in:
Wen Liang
2021-08-26 13:27:10 +08:00
committed by GitHub
19 changed files with 231 additions and 119 deletions

View File

@ -63,7 +63,7 @@ candidates 中的每个数字在每个组合中只能使用一次。
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。**
那么问题来了,我们是要同一树层上使用过,还是一树枝上使用过呢?
那么问题来了,我们是要同一树层上使用过,还是一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

View File

@ -21,18 +21,27 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q..", // 解法 1
输出:
解法 1
[
[".Q..",
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
解法 2
["..Q.",
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
提示:

View File

@ -22,15 +22,22 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
解法 1
[
 [".Q..",  // 解法 1
 [".Q..",
  "...Q",
  "Q...",
  "..Q."],
 ["..Q.",  // 解法 2
解法 2
 ["..Q.",
  "Q...",
  "...Q",
  ".Q.."]

View File

@ -309,7 +309,35 @@ class Solution {
```
python版本
```python
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
res = []
path = [] # 存放分割后的字符
# 判断数组中的数字是否合法
def isValid(p):
if p == '0': return True # 解决"0000"
if p[0] == '0': return False
if int(p) > 0 and int(p) <256: return True
return False
def backtrack(s, startIndex):
if len(s) > 12: return # 字符串长度最大为12
if len(path) == 4 and startIndex == len(s): # 确保切割完且切割后的长度为4
res.append(".".join(path[:])) # 字符拼接
return
for i in range(startIndex, len(s)):
if len(s) - startIndex > 3*(4 - len(path)): continue # 剪枝,剩下的字符串大于允许的最大长度则跳过
p = s[startIndex:i+1] # 分割字符
if isValid(p): # 判断字符是否有效
path.append(p)
else: continue
backtrack(s, i + 1) # 寻找i+1为起始位置的子串
path.pop()
backtrack(s, 0)
return res
```
```python
class Solution(object):
def restoreIpAddresses(self, s):

View File

@ -1528,6 +1528,29 @@ public:
```
Java
```Java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> que = new LinkedList<>();
que.offer(root);
int depth = 0;
while (!que.isEmpty())
{
int len = que.size();
while (len > 0)
{
TreeNode node = que.poll();
if (node.left != null) que.offer(node.left);
if (node.right != null) que.offer(node.right);
len--;
}
depth++;
}
return depth;
}
}
```
Python

View File

@ -292,7 +292,8 @@ class Solution {
```
Python
```py
```python
# 版本一
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
@ -310,7 +311,36 @@ class Solution:
return res
```
```python
# 版本二
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
path = [] #放已经回文的子串
# 双指针法判断是否是回文串
def isPalindrome(s):
n = len(s)
i, j = 0, n - 1
while i < j:
if s[i] != s[j]:return False
i += 1
j -= 1
return True
def backtrack(s, startIndex):
if startIndex >= len(s): # 如果起始位置已经大于s的大小说明已经找到了一组分割方案了
res.append(path[:])
return
for i in range(startIndex, len(s)):
p = s[startIndex:i+1] # 获取[startIndex,i+1]在s中的子串
if isPalindrome(p): # 是回文子串
path.append(p)
else: continue #不是回文,跳过
backtrack(s, i + 1)
path.pop() #回溯过程弹出本次已经填在path的子串
backtrack(s, 0)
return res
```
Go
> 注意切片go切片是披着值类型外衣的引用类型

View File

@ -131,6 +131,22 @@ class Solution:
## JavaScript
```js
var rotate = function (nums, k) {
function reverse(nums, i, j) {
while (i < j) {
[nums[i],nums[j]] = [nums[j],nums[i]]; // 解构赋值
i++;
j--;
}
}
let n = nums.length;
k %= n;
if (k) {
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
}
};
```
-----------------------

View File

@ -184,7 +184,7 @@ public:
这份代码在leetcode上提交要比版本一耗时要好的多。
**所以正如在[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)中说的那样数组setmap都可以做哈希表而且数组干的活map和set都能干但如数值范围小的话能用数组尽量用数组**
**所以正如在[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)中说的那样数组setmap都可以做哈希表而且数组干的活map和set都能干但如数值范围小的话能用数组尽量用数组**

View File

@ -9,7 +9,7 @@
# 题目剑指Offer 05.替换空格
https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/
[力扣题目链接](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/)
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
@ -81,12 +81,12 @@ public:
此时算上本题,我们已经做了七道双指针相关的题目了分别是:
* [27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)
* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)
* [18.四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)
* [206.翻转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)
* [142.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)
* [344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)
* [27.移除元素](https://programmercarl.com/0027.移除元素.html)
* [15.三数之和](https://programmercarl.com/0015.三数之和.html)
* [18.四数之和](https://programmercarl.com/0018.四数之和.html)
* [206.翻转链表](https://programmercarl.com/0206.翻转链表.html)
* [142.环形链表II](https://programmercarl.com/0142.环形链表II.html)
* [344.反转字符串](https://programmercarl.com/0344.反转字符串.html)
# 拓展

View File

@ -11,11 +11,10 @@
# 题目剑指Offer58-II.左旋转字符串
https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
[力扣题目链接](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/)
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1
输入: s = "abcdefg", k = 2
输出: "cdefgab"
@ -34,7 +33,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
不能使用额外空间的话模拟在本串操作要实现左旋转字符串的功能还是有点困难的
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的
这道题目也非常类似依然可以通过局部反转+整体反转 达到左旋转的目的
@ -76,13 +75,13 @@ public:
此时我们已经反转好多次字符串了,来一起回顾一下吧。
在这篇文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。
然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。
后来在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)类似,但是也是一种新的思路。
最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)类似,但是也是一种新的思路。
好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。

View File

@ -12,7 +12,7 @@
# 数组篇
在[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。
在[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。
一些同学可能会写出如下代码(伪代码):
@ -30,11 +30,11 @@ for (int i = 0; i < array.size(); i++) {
# 字符串篇
在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。
在[字符串:这道题目,使用库函数一行代码搞定](https://programmercarl.com/0344.反转字符串.html)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。
使用双指针法,**定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。**时间复杂度是O(n)。
在[替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了!
在[替换空格](https://programmercarl.com/剑指Offer05.替换空格.html) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了!
思路就是**首先扩充数组到每个空格替换成"%20"之后的大小。然后双指针从后向前替换空格。**
@ -44,7 +44,7 @@ for (int i = 0; i < array.size(); i++) {
**其实很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
那么在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中我们使用双指针法用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。
那么在[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用双指针法用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。
**在删除冗余空格的过程中如果不注意代码效率很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。**
@ -54,19 +54,19 @@ for (int i = 0; i < array.size(); i++) {
翻转链表是现场面试,白纸写代码的好题,考察了候选者对链表以及指针的熟悉程度,而且代码也不长,适合在白纸上写。
在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向直接将链表反转 ,而不用重新定义一个新的链表。**
在[链表:听说过两天反转链表又写不出来了?](https://programmercarl.com/0206.翻转链表.html)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向直接将链表反转 ,而不用重新定义一个新的链表。**
思路还是很简单的代码也不长但是想在白纸上一次性写出bugfree的代码并不是容易的事情。
在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。
在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。
**使用快慢指针(双指针法),分别定义 fast 和 slow指针从头结点出发fast指针每次移动两个节点slow指针每次移动一个节点如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。**
那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)。
那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)。
# N数之和篇
在[哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中讲到使用哈希法可以解决1.两数之和的问题
在[哈希表:解决了两数之和,那么能解决三数之和么?](https://programmercarl.com/0015.三数之和.html)中讲到使用哈希法可以解决1.两数之和的问题
其实使用双指针也可以解决1.两数之和的问题只不过1.两数之和求的是两个元素的下标没法用双指针如果改成求具体两个元素的数值就可以了大家可以尝试用双指针做一个leetcode上两数之和的题目就可以体会到我说的意思了。
@ -82,7 +82,7 @@ for (int i = 0; i < array.size(); i++) {
只用双指针法时间复杂度为O(n^2)但比哈希法的O(n^2)效率高得多哈希法在使用两层for循环的时候能做的剪枝操作很有限。
在[双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环依然是使用双指针法。**
在[双指针法:一样的道理,能解决四数之和](https://programmercarl.com/0018.四数之和.html)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环依然是使用双指针法。**
对于三数之和使用双指针法就是将原本暴力O(n^3)的解法降为O(n^2)的解法四数之和的双指针解法就是将原本暴力O(n^4)的解法降为O(n^3)的解法。

View File

@ -12,7 +12,7 @@
# 哈希表理论基础
在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。
在[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。
**一般来说哈希表都是用来快速判断一个元素是否出现集合里**
@ -28,7 +28,7 @@
* set集合
* map映射
在C++语言中set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中我给出了详细分析,这一知识点很重要!
在C++语言中set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)中我给出了详细分析,这一知识点很重要!
例如什么时候用std::set什么时候用std::multiset什么时候用std::unordered_set都是很有考究的。
@ -40,13 +40,13 @@
一些应用场景就是为数组量身定做的。
在[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!
在[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!
这道题目包含小写字母,那么使用数组来做哈希最合适不过。
在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!
在[383.赎金信](https://programmercarl.com/0383.赎金信.html)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!
本题和[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)是求 字符串a 和 字符串b 是否可以相互组成,在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中是求字符串a能否组成字符串b而不用管字符串b 能不能组成字符串a。
本题和[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)很像,[242.有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)是求 字符串a 和 字符串b 是否可以相互组成,在[383.赎金信](https://programmercarl.com/0383.赎金信.html)中是求字符串a能否组成字符串b而不用管字符串b 能不能组成字符串a。
一些同学可能想用数组干啥都用map不就完事了。
@ -55,7 +55,7 @@
## set作为哈希表
在[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)中我们给出了什么时候用数组就不行了需要用set。
在[349. 两个数组的交集](https://programmercarl.com/0349.两个数组的交集.html)中我们给出了什么时候用数组就不行了需要用set。
这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
@ -66,7 +66,7 @@
所以此时一样的做映射的话就可以使用set了。
关于setC++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)
关于setC++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)
* std::set
* std::multiset
@ -74,12 +74,12 @@
std::set和std::multiset底层实现都是红黑树std::unordered_set的底层实现是哈希 使用unordered_set 读写效率是最高的本题并不需要对数据进行排序而且还不要让数据重复所以选择unordered_set。
在[202.快乐数](https://mp.weixin.qq.com/s/n5q0ujxxrjQS3xuh3dgqBQ)中我们再次使用了unordered_set来判断一个数是否重复出现过。
在[202.快乐数](https://programmercarl.com/0202.快乐数.html)中我们再次使用了unordered_set来判断一个数是否重复出现过。
## map作为哈希表
在[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中map正式登场。
在[1.两数之和](https://programmercarl.com/0001.两数之和.html)中map正式登场。
来说一说使用数组和set来做哈希法的局限。
@ -88,7 +88,7 @@ std::set和std::multiset底层实现都是红黑树std::unordered_set的底
map是一种`<key, value>`的结构本题可以用key保存数值用value在保存数值所在的下表。所以使用map最为合适。
C++提供如下三种map详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)
C++提供如下三种map详情请看[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)
* std::map
* std::multimap
@ -96,19 +96,19 @@ C++提供如下三种map详情请看[关于哈希表,你该了解这
std::unordered_map 底层实现为哈希std::map 和std::multimap 的底层实现是红黑树。
同理std::map 和std::multimap 的key也是有序的这个问题也经常作为面试题考察对语言容器底层的理解[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中并不需要key有序选择std::unordered_map 效率更高!
同理std::map 和std::multimap 的key也是有序的这个问题也经常作为面试题考察对语言容器底层的理解[1.两数之和](https://programmercarl.com/0001.两数之和.html)中并不需要key有序选择std::unordered_map 效率更高!
在[454.四数相加](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)中我们提到了其实需要哈希的地方都能找到map的身影。
在[454.四数相加](https://programmercarl.com/0454.四数相加II.html)中我们提到了其实需要哈希的地方都能找到map的身影。
本题咋眼一看好像和[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)差不多,其实差很多!
本题咋眼一看好像和[18. 四数之和](https://programmercarl.com/0018.四数之和.html)[15.三数之和](https://programmercarl.com/0015.三数之和.html)差不多,其实差很多!
**关键差别是本题为四个独立的数组只要找到A[i] + B[j] + C[k] + D[l] = 0就可以不用考虑重复问题而[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个数组集合里找到和为0的组合可就难很多了**
**关键差别是本题为四个独立的数组只要找到A[i] + B[j] + C[k] + D[l] = 0就可以不用考虑重复问题而[18. 四数之和](https://programmercarl.com/0018.四数之和.html)[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个数组集合里找到和为0的组合可就难很多了**
用哈希法解决了两数之和,很多同学会感觉用哈希法也可以解决三数之和,四数之和。
其实是可以解决,但是非常麻烦,需要去重导致代码效率很低。
在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。
在[15.三数之和](https://programmercarl.com/0015.三数之和.html)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。
所以18. 四数之和15.三数之和都推荐使用双指针法!

View File

@ -14,7 +14,7 @@
关于回溯算法理论基础我录了一期B站视频[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM)如果对回溯算法还不了解的话,可以看一下。
在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中我们详细的介绍了回溯算法的理论知识,不同于教科书般的讲解,这里介绍的回溯法的效率,解决的问题以及模板都是在刷题的过程中非常实用!
在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中我们详细的介绍了回溯算法的理论知识,不同于教科书般的讲解,这里介绍的回溯法的效率,解决的问题以及模板都是在刷题的过程中非常实用!
**回溯是递归的副产品,只要有递归就会有回溯**,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。
@ -32,7 +32,7 @@
回溯法确实不好理解,所以需要把回溯法抽象为一个图形来理解就容易多了,**在后面的每一道回溯法的题目我都将遍历过程抽象为树形结构方便大家的理解**。
在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)还用了回溯三部曲来分析回溯算法,并给出了回溯法的模板:
在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)还用了回溯三部曲来分析回溯算法,并给出了回溯法的模板:
```
void backtracking(参数) {
@ -55,7 +55,7 @@ void backtracking(参数) {
## 组合问题
在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们开始用回溯法解决第一道题目:组合问题。
在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目:组合问题。
我在文中开始的时候给大家列举k层for循环例子进而得出都是同样是暴利解法为什么要用回溯法
@ -71,13 +71,13 @@ void backtracking(参数) {
**所以,录友们刚开始学回溯法,起跑姿势就很标准了!**
优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中把回溯法代码做了剪枝优化,树形结构如图:
优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,树形结构如图:
![77.组合4](https://img-blog.csdnimg.cn/20201118153133458.png)
大家可以一目了然剪的究竟是哪里。
**[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)剪枝精髓是for循环在寻找起点的时候要有一个范围如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了就没有必要搜索了**。
**[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)剪枝精髓是for循环在寻找起点的时候要有一个范围如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了就没有必要搜索了**。
**在for循环上做剪枝操作是回溯法剪枝的常见套路** 后面的题目还会经常用到。
@ -86,7 +86,7 @@ void backtracking(参数) {
### 组合总和(一)
在[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中,相当于 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)加了一个元素总和的限制。
在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。
树形结构如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201118201921245.png)
@ -95,21 +95,21 @@ void backtracking(参数) {
![216.组合总和III1](https://img-blog.csdnimg.cn/20201118202038240.png)
在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中提到的对for循环选择的起始范围的剪枝。
在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的对for循环选择的起始范围的剪枝。
所以剪枝的代码可以在for循环加上 `i <= 9 - (k - path.size()) + 1` 的限制!
### 组合总和(二)
在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中讲解的组合总和问题,和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中讲解的组合总和问题,和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
不少同学都是看到可以重复选择就义无反顾的把startIndex去掉了。
**本题还需要startIndex来控制for循环的起始位置对于组合问题什么时候需要startIndex呢**
我举过例子如果是一个集合来求组合的话就需要startIndex例如[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。
我举过例子如果是一个集合来求组合的话就需要startIndex例如[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。
如果是多个集合取组合各个集合之间相互不影响那么就不用startIndex例如[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
如果是多个集合取组合各个集合之间相互不影响那么就不用startIndex例如[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路**
@ -130,7 +130,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
### 组合总和(三)
在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中集合元素会有重复,但要求解集不能包含重复的组合。
在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)中集合元素会有重复,但要求解集不能包含重复的组合。
**所以难就难在去重问题上了**
@ -153,11 +153,11 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
## 多个集合求组合
在[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。
在[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。
例如这里for循环可不像是在 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的。
例如这里for循环可不像是在 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的。
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!**
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!**
树形结构如下:
@ -169,7 +169,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
# 切割问题
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。
我列出如下几个难点:
@ -196,7 +196,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
## 子集问题(一)
在[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。
在[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。
如图:
@ -221,9 +221,9 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
## 子集问题(二)
在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中,开始针对子集问题进行去重。
在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中,开始针对子集问题进行去重。
本题就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了,一样的套路。
本题就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了,一样的套路。
树形结构如下:
@ -231,15 +231,15 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
## 递增子序列
在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨!
在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨!
树形结构如下:
![491. 递增子序列1](https://img-blog.csdnimg.cn/20201112170832333.png)
很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)混在一起。
很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混在一起。
**[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)也可以使用set针对同一父节点本层去重但子集问题一定要排序为什么呢**
**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重但子集问题一定要排序为什么呢**
我用没有排序的集合{2,1,2,2}来举个例子画一个图,如下:
@ -251,7 +251,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
## 排列问题(一)
[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw) 又不一样了。
[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html) 又不一样了。
排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
@ -268,7 +268,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
## 排列问题(二)
排列问题也要去重了,在[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)中又一次强调了“树层去重”和“树枝去重”。
排列问题也要去重了,在[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html)中又一次强调了“树层去重”和“树枝去重”。
树形结构如下:
@ -294,17 +294,17 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
以上我都是统一使用used数组来去重的其实使用set也可以用来去重
在[本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码并纠正一些同学的常见错误写法。
在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码并纠正一些同学的常见错误写法。
同时详细分析了 使用used数组去重 和 使用set去重 两种写法的性能差异:
**使用set去重的版本相对于used数组的版本效率都要低很多**大家在leetcode上提交能明显发现。
原因在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中也分析过主要是因为程序运行的时候对unordered_set 频繁的insertunordered_set需要做哈希映射也就是把key通过hash function映射为唯一的哈希值相对费时间而且insert的时候其底层的符号表也要做相应的扩充也是费时的。
原因在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中也分析过主要是因为程序运行的时候对unordered_set 频繁的insertunordered_set需要做哈希映射也就是把key通过hash function映射为唯一的哈希值相对费时间而且insert的时候其底层的符号表也要做相应的扩充也是费时的。
**而使用used数组在时间复杂度上几乎没有额外负担**
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
@ -314,7 +314,7 @@ used数组可是全局变量每层与每层之间公用一个used数组
之前说过,有递归的地方就有回溯,深度优先搜索也是用递归来实现的,所以往往伴随着回溯。
在[回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA)其实也算是图论里深搜的题目,但是我用回溯法的套路来讲解这道题目,算是给大家拓展一下思路,原来回溯法还可以这么玩!
在[回溯算法:重新安排行程](https://programmercarl.com/0332.重新安排行程.html)其实也算是图论里深搜的题目,但是我用回溯法的套路来讲解这道题目,算是给大家拓展一下思路,原来回溯法还可以这么玩!
以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下:
@ -331,7 +331,7 @@ used数组可是全局变量每层与每层之间公用一个used数组
## N皇后问题
在[回溯算法N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)中终于迎来了传说中的N皇后。
在[回溯算法N皇后问题](https://programmercarl.com/0051.N皇后.html)中终于迎来了传说中的N皇后。
下面我用一个3 * 3 的棋牌,将搜索过程抽象为一颗树,如图:
@ -345,19 +345,19 @@ used数组可是全局变量每层与每层之间公用一个used数组
**这里我明确给出了棋盘的宽度就是for循环的长度递归的深度就是棋盘的高度这样就可以套进回溯法的模板里了**
相信看完本篇[回溯算法N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)也没那么难了,传说已经不是传说了,哈哈。
相信看完本篇[回溯算法N皇后问题](https://programmercarl.com/0051.N皇后.html)也没那么难了,传说已经不是传说了,哈哈。
## 解数独问题
在[回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)中要征服回溯法的最后一道山峰。
在[回溯算法:解数独](https://programmercarl.com/0037.解数独.html)中要征服回溯法的最后一道山峰。
解数独应该是棋盘很难的题目了比N皇后还要复杂一些但只要理解 “二维递归”这个过程,其实发现就没那么难了。
大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[131.分割回文串(分割问题)](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)[78.子集(子集问题)](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)[46.全排列(排列问题)](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及[51.N皇后N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg),其实这些题目都是一维递归。
大家已经跟着「代码随想录」刷过了如下回溯法题目,例如:[77.组合(组合问题)](https://programmercarl.com/0077.组合.html)[131.分割回文串(分割问题)](https://programmercarl.com/0131.分割回文串.html)[78.子集(子集问题)](https://programmercarl.com/0078.子集.html)[46.全排列(排列问题)](https://programmercarl.com/0046.全排列.html),以及[51.N皇后N皇后问题](https://programmercarl.com/0051.N皇后.html),其实这些题目都是一维递归。
其中[N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg)是因为每一行每一列只放一个皇后只需要一层for循环遍历一行递归来遍历列然后一行一列确定皇后的唯一位置。
其中[N皇后问题](https://programmercarl.com/0051.N皇后.html)是因为每一行每一列只放一个皇后只需要一层for循环遍历一行递归来遍历列然后一行一列确定皇后的唯一位置。
本题就不一样了,**本题中棋盘的每一个位置都要放一个数字并检查数字是否合法解数独的树形结构要比N皇后更宽更深**。
@ -367,7 +367,7 @@ used数组可是全局变量每层与每层之间公用一个used数组
解数独可以说是非常难的题目了,如果还一直停留在一维递归的逻辑中,这道题目可以让大家瞬间崩溃。
**所以我在[回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ)中开篇就提到了二维递归,这也是我自创词汇**,希望可以帮助大家理解解数独的搜索过程。
**所以我在[回溯算法:解数独](https://programmercarl.com/0037.解数独.html)中开篇就提到了二维递归,这也是我自创词汇**,希望可以帮助大家理解解数独的搜索过程。
一波分析之后,在看代码会发现其实也不难,唯一难点就是理解**二维递归**的思维逻辑。

View File

@ -8,13 +8,13 @@
# 回溯算法去重问题的另一种写法
> 在 [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈!
> 在 [本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈!
接下来我再把这块再讲一下。
在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中的去重和 [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中的去重 都是 同一父节点下本层的去重。
在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中的去重和 [回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中的去重 都是 同一父节点下本层的去重。
[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)也可以使用set针对同一父节点本层去重但子集问题一定要排序为什么呢
[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重但子集问题一定要排序为什么呢
我用没有排序的集合{2,1,2,2}来举例子画一个图,如图:
@ -22,11 +22,11 @@
图中,大家就很明显的看到,子集重复了。
那么下面我针对[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) 给出使用set来对本层去重的代码实现。
那么下面我针对[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html) 给出使用set来对本层去重的代码实现。
## 90.子集II
used数组去重版本 [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)
used数组去重版本 [回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)
使用set去重的版本如下
@ -138,7 +138,7 @@ uset已经是全局变量本层的uset记录了一个元素然后进入下
## 40. 组合总和 II
使用used数组去重版本[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)
使用used数组去重版本[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)
使用set去重的版本如下
@ -179,7 +179,7 @@ public:
## 47. 全排列 II
使用used数组去重版本[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)
使用used数组去重版本[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html)
使用set去重的版本如下
@ -224,11 +224,11 @@ public:
需要注意的是:**使用set去重的版本相对于used数组的版本效率都要低很多**大家在leetcode上提交能明显发现。
原因在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中也分析过主要是因为程序运行的时候对unordered_set 频繁的insertunordered_set需要做哈希映射也就是把key通过hash function映射为唯一的哈希值相对费时间而且insert的时候其底层的符号表也要做相应的扩充也是费时的。
原因在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中也分析过主要是因为程序运行的时候对unordered_set 频繁的insertunordered_set需要做哈希映射也就是把key通过hash function映射为唯一的哈希值相对费时间而且insert的时候其底层的符号表也要做相应的扩充也是费时的。
**而使用used数组在时间复杂度上几乎没有额外负担**
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
@ -236,7 +236,7 @@ used数组可是全局变量每层与每层之间公用一个used数组
## 总结
本篇本打算是对[本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA)的一个点做一下纠正,没想到又写出来这么多!
本篇本打算是对[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)的一个点做一下纠正,没想到又写出来这么多!
**这个点都源于一位录友的疑问,然后我思考总结了一下,就写着这一篇,所以还是得多交流啊!**

View File

@ -13,7 +13,7 @@
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
在二叉树系列中,我们已经不止一次,提到了回溯,例如[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)。
在二叉树系列中,我们已经不止一次,提到了回溯,例如[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)。
回溯是递归的副产品,只要有递归就会有回溯。
@ -67,7 +67,7 @@
这里给出Carl总结的回溯算法模板。
在讲[二叉树的递归](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)中我们说了递归三部曲,这里我再给大家列出回溯三部曲。
在讲[二叉树的递归](https://programmercarl.com/二叉树的递归遍历.html)中我们说了递归三部曲,这里我再给大家列出回溯三部曲。
* 回溯函数模板返回值以及参数
@ -87,7 +87,7 @@ void backtracking(参数)
* 回溯函数终止条件
既然是树形结构,那么我们在讲解[二叉树的递归](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)的时候,就知道遍历树形结构一定要有终止条件。
既然是树形结构,那么我们在讲解[二叉树的递归](https://programmercarl.com/二叉树的递归遍历.html)的时候,就知道遍历树形结构一定要有终止条件。
所以回溯也有要终止条件。

View File

@ -63,7 +63,7 @@
## 二分法
[数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q)
[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html)
这道题目呢,考察的数据的基本操作,思路很简单,但是在通过率在简单题里并不高,不要轻敌。
@ -79,7 +79,7 @@
## 双指针法
* [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)
* [数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)
双指针法(快慢指针法):**通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
@ -95,7 +95,7 @@
## 滑动窗口
* [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg)
* [数组:滑动窗口拯救了你](https://programmercarl.com/0209.长度最小的子数组.html)
本题介绍了数组操作中的另一个重要思想:滑动窗口。
@ -111,7 +111,7 @@
## 模拟行为
* [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg)
* [数组:这个循环可以转懵很多人!](https://programmercarl.com/0059.螺旋矩阵II.html)
模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。

View File

@ -7,7 +7,7 @@
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 贪心算法:根据身高重建队列(续集)
在讲解[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中我们提到了使用vectorC++中的动态数组来进行insert操作是费时的。
在讲解[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中我们提到了使用vectorC++中的动态数组来进行insert操作是费时的。
但是在解释的过程中有不恰当的地方,所以来专门写一篇文章来详细说一说这个问题。
@ -99,9 +99,9 @@ for (int i = 0; i < vec.size(); i++) {
**同时也注意此时capicity和size的变化关键的地方我都标红了**
而在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中我们使用vector来做insert的操作此时大家可会发现**虽然表面上复杂度是O(n^2)但是其底层都不知道额外做了多少次全量拷贝了所以算上vector的底层拷贝整体时间复杂度可以认为是O(n^2 + t * n)级别的t是底层拷贝的次数**。
而在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中我们使用vector来做insert的操作此时大家可会发现**虽然表面上复杂度是O(n^2)但是其底层都不知道额外做了多少次全量拷贝了所以算上vector的底层拷贝整体时间复杂度可以认为是O(n^2 + t * n)级别的t是底层拷贝的次数**。
那么是不是可以直接确定好vector的大小不让它在动态扩容了例如在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中已经给出了有people.size这么多的人可以定义好一个固定大小的vector这样我们就可以控制vector不让它底层动态扩容。
那么是不是可以直接确定好vector的大小不让它在动态扩容了例如在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中已经给出了有people.size这么多的人可以定义好一个固定大小的vector这样我们就可以控制vector不让它底层动态扩容。
这种方法需要自己模拟插入的操作不仅没有直接调用insert接口那么方便需要手动模拟插入操作而且效率也不高
@ -147,7 +147,7 @@ public:
所以对于两种使用数组的方法一和方法三,也不好确定谁优,但一定都没有使用方法二链表的效率高!
一波分析之后,对于[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw) ,大家就安心使用链表吧!别折腾了,哈哈,相当于我替大家折腾了一下。
一波分析之后,对于[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html) ,大家就安心使用链表吧!别折腾了,哈哈,相当于我替大家折腾了一下。
## 总结

View File

@ -10,7 +10,7 @@
## 链表的理论基础
在这篇文章[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)中,介绍了如下几点:
在这篇文章[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html)中,介绍了如下几点:
* 链表的种类主要为:单链表,双链表,循环链表
* 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
@ -23,17 +23,17 @@
### 虚拟头结点
在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。
在[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。
**每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题**
在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。
在[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。
### 链表的基本操作
在[链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g)中,我们通设计链表把链表常见的五个操作练习了一遍。
在[链表:一道题目考察了常见的五个操作!](https://programmercarl.com/0707.设计链表.html)中,我们通设计链表把链表常见的五个操作练习了一遍。
这是练习链表基础操作的非常好的一道题目,考察了:
@ -49,13 +49,13 @@
### 反转链表
在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲解了如何反转链表。
在[链表:听说过两天反转链表又写不出来了?](https://programmercarl.com/0206.翻转链表.html)中,讲解了如何反转链表。
因为反转链表的代码相对简单,有的同学可能直接背下来了,但一写还是容易出问题。
反转链表是面试中高频题目,很考察面试者对链表操作的熟练程度。
我在[文章](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,给出了两种反转的方式,迭代法和递归法。
我在[文章](https://programmercarl.com/0206.翻转链表.html)中,给出了两种反转的方式,迭代法和递归法。
建议大家先学透迭代法,然后再看递归法,因为递归法比较绕,如果迭代还写不明白,递归基本也写不明白了。
@ -63,16 +63,16 @@
### 删除倒数第N个节点
在[链表删除链表倒数第N个节点怎么删](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。
在[链表删除链表倒数第N个节点怎么删](https://programmercarl.com/0019.删除链表的倒数第N个节点.html)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。
### 链表相交
[链表:链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)
[链表:链表相交](https://programmercarl.com/面试题02.07.链表相交.html)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)
## 环形链表
在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中,讲解了在链表如何找环,以及如何找环的入口位置。
在[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中,讲解了在链表如何找环,以及如何找环的入口位置。
这道题目可以说是链表的比较难的题目了。 但代码却十分简洁,主要在于一些数学证明。
@ -80,15 +80,15 @@
考察链表的操作其实就是考察指针的操作,是面试中的常见类型。
链表篇中开头介绍[链表理论知识](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA),然后分别通过经典题目介绍了如下知识点:
链表篇中开头介绍[链表理论知识](https://programmercarl.com/0203.移除链表元素.html),然后分别通过经典题目介绍了如下知识点:
1. [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)
2. [虚拟头结点的技巧](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)
3. [链表的增删改查](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g)
4. [反转一个链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)
5. [删除倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg)
6. [链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw)
7. [有否环形,以及环的入口](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)
1. [关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html)
2. [虚拟头结点的技巧](https://programmercarl.com/0203.移除链表元素.html)
3. [链表的增删改查](https://programmercarl.com/0707.设计链表.html)
4. [反转一个链表](https://programmercarl.com/0206.翻转链表.html)
5. [删除倒数第N个节点](https://programmercarl.com/0019.删除链表的倒数第N个节点.html)
6. [链表相交](https://programmercarl.com/面试题02.07.链表相交.html)
7. [有否环形,以及环的入口](https://programmercarl.com/0142.环形链表II.html)

View File

@ -9,7 +9,7 @@
## 面试题 02.07. 链表相交
题目链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/
[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/)
给定两个单向链表判定它们是否相交并返回交点。请注意相交的定义基于节点的引用而不是基于节点的值。换句话说如果一个链表的第k个节点与另一个链表的第j个节点是同一节点引用完全相同则这两个链表相交。