Merge branch 'master' into master

This commit is contained in:
程序员Carl
2021-06-29 09:28:44 +08:00
committed by GitHub
58 changed files with 1671 additions and 310 deletions

View File

@ -2,6 +2,7 @@
> 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者)
> 2. **PDF版本** [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。
> 3. **刷题顺序** README已经将刷题顺序排好了按照顺序一道一道刷就可以。
> 3. **学习社区** : 一起学习打卡/面试技巧/如何选择offer/大厂内推/职场规则/简历修改/技术分享/程序人生。欢迎加入[「代码随想录」学习社区](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 。
> 4. **提交代码**本项目统一使用C++语言进行讲解但已经有Java、Python、Go、JavaScript等等多语言版本感谢[这里的每一位贡献者](https://github.com/youngyangyang04/leetcode-master/graphs/contributors),如果你也想贡献代码点亮你的头像,[点击这里](https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A)了解提交代码的方式。
> 5. **转载须知** :以下所有文章皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
@ -41,7 +42,7 @@
对于刷题,我们都是想用最短的时间**按照循序渐进的难度顺序把经典题目都做一遍**,这样效率才是最高的!
所以我整理了leetcode刷题攻略一个超级详细的刷题顺序**每道题目都是我精心筛选,都是经典题目高频面试题**,大家只要按照这个顺序刷就可以了,**你没看错,就是题目顺序都排好了,文章顺序就是刷题顺序!挨个刷就可以,不用自己再去题海里选题了!**
所以我整理了leetcode刷题攻略一个超级详细的刷题顺序**每道题目都是我精心筛选,都是经典题目高频面试题**,大家只要按照这个顺序刷就可以了,**你没看错,README已经把题目顺序都排好了,文章顺序就是刷题顺序!挨个刷就可以,不用自己再去题海里选题了!**
而且每道题目我都写了的详细题解(图文并茂,难点配有视频),力扣上我的题解都是排在对应题目的首页,质量是有目共睹的。
@ -117,7 +118,7 @@
(持续更新中.....
## 备战秋招
## 知识星球精选
1. [选择方向的时候,我也迷茫了](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g)
2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg)
@ -125,6 +126,10 @@
4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA)
5. [Carl看了上百份简历总结了这些](https://mp.weixin.qq.com/s/sJa87MZD28piCOVMFkIbwQ)
6. [面试中遇到了发散性问题.....](https://mp.weixin.qq.com/s/SSonDxi2pjkSVwHNzZswng)
7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw)
8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg)
9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ)
## 数组

View File

@ -342,6 +342,46 @@ class Solution:
Go
> 主要在于递归中传递下一个数字
```go
func letterCombinations(digits string) []string {
lenth:=len(digits)
if lenth==0 ||lenth>4{
return nil
}
digitsMap:= [10]string{
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
}
res:=make([]string,0)
recursion("",digits,0,digitsMap,&res)
return res
}
func recursion(tempString ,digits string, Index int,digitsMap [10]string, res *[]string) {//index表示第几个数字
if len(tempString)==len(digits){//终止条件字符串长度等于digits的长度
*res=append(*res,tempString)
return
}
tmpK:=digits[Index]-'0' // 将index指向的数字转为int确定下一个数字
letter:=digitsMap[tmpK]// 取数字对应的字符集
for i:=0;i<len(letter);i++{
tempString=tempString+string(letter[i])//拼接结果
recursion(tempString,digits,Index+1,digitsMap,res)
tempString=tempString[:len(tempString)-1]//回溯
}
}
```
javaScript
```js

View File

@ -59,7 +59,7 @@ KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部
* 总结
读完本篇可以顺便把leetcode上28.实现strStr()题目做了。
读完本篇可以顺便把leetcode上28.实现strStr()题目做了。
# 什么是KMP
@ -126,16 +126,15 @@ next数组就是一个前缀表prefix table
# 最长公共前后缀?
文章中字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
文章中字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**
**正确理解什么是前缀什么是后缀很重要**
**正确理解什么是前缀什么是后缀很重要**!
那么网上清一色都说 “kmp 最长公共前后缀” 又是什么回事呢?
我查了一遍 算法导论 和 算法4里KMP的章节都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 准确一些。
我查了一遍 算法导论 和 算法4里KMP的章节都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 更准确一些。
**因为前缀表要求的就是相同前后缀的长度。**
@ -220,7 +219,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
# 使用next数组来匹配
以下我们以前缀表统一减一之后的next数组来做演示。
**以下我们以前缀表统一减一之后的next数组来做演示**
有了next数组就可以根据next数组来 匹配文本串s和模式串t了。
@ -236,7 +235,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
暴力的解法显而易见是O(n * m),所以**KMP在字符串匹配中极大的提高的搜索的效率。**
为了和[字符串KMP是时候上场了一文读懂系列](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)字符串命名统一方便大家理解以下文章统称haystack为文本串, needle为模式串。
为了和力扣题目28.实现strStr保持一致方便大家理解以下文章统称haystack为文本串, needle为模式串。
都知道使用KMP算法一定要构造next数组。
@ -402,7 +401,7 @@ for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
}
```
此时所有逻辑的代码都已经写出来了,本题整体代码如下:
此时所有逻辑的代码都已经写出来了,力扣 28.实现strStr 题目的整体代码如下:
# 前缀表统一减一 C++代码实现
@ -448,7 +447,9 @@ public:
# 前缀表不减一C++实现
那么前缀表就不减一了,也不右移的,到底行不行呢?行!
那么前缀表就不减一了,也不右移的,到底行不行呢?
**行!**
我之前说过这仅仅是KMP算法实现上的问题如果就直接使用前缀表可以换一种回退方式找j=next[j-1] 来进行回退。
@ -544,7 +545,7 @@ public:
我们介绍了什么是KMPKMP可以解决什么问题然后分析KMP算法里的next数组知道了next数组就是前缀表再分析为什么要是前缀表而不是什么其他表。
接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不同意减一得到的next数组仅仅是kmp的实现方式的不同。
接着从给出的模式串中我们一步一步的推导出了前缀表得出前缀表无论是统一减一还是不减一得到的next数组仅仅是kmp的实现方式的不同。
其中还分析了KMP算法的时间复杂度并且和暴力方法做了对比。

View File

@ -321,6 +321,59 @@ class Solution:
backtrack(board)
```
Python3:
```python3
class Solution:
def __init__(self) -> None:
self.board = []
def isValid(self, row: int, col: int, target: int) -> bool:
for idx in range(len(self.board)):
# 同列是否重复
if self.board[idx][col] == str(target):
return False
# 同行是否重复
if self.board[row][idx] == str(target):
return False
# 9宫格里是否重复
box_row, box_col = (row // 3) * 3 + idx // 3, (col // 3) * 3 + idx % 3
if self.board[box_row][box_col] == str(target):
return False
return True
def getPlace(self) -> List[int]:
for row in range(len(self.board)):
for col in range(len(self.board)):
if self.board[row][col] == ".":
return [row, col]
return [-1, -1]
def isSolved(self) -> bool:
row, col = self.getPlace() # 找个空位置
if row == -1 and col == -1: # 没有空位置,棋盘被填满的
return True
for i in range(1, 10):
if self.isValid(row, col, i): # 检查这个空位置放i是否合适
self.board[row][col] = str(i) # 放i
if self.isSolved(): # 合适,立刻返回, 填下一个空位置。
return True
self.board[row][col] = "." # 不合适,回溯
return False # 空位置没法解决
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
if board is None or len(board) == 0:
return
self.board = board
self.isSolved()
```
Go
Javascript:

View File

@ -286,6 +286,39 @@ class Solution:
```
Go
> 主要在于递归中传递下一个数字
```go
func combinationSum(candidates []int, target int) [][]int {
var trcak []int
var res [][]int
backtracking(0,0,target,candidates,trcak,&res)
return res
}
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){
//终止条件
if sum==target{
tmp:=make([]int,len(trcak))
copy(tmp,trcak)//拷贝
*res=append(*res,tmp)//放入结果集
return
}
if sum>target{return}
//回溯
for i:=startIndex;i<len(candidates);i++{
//更新路径集合和sum
trcak=append(trcak,candidates[i])
sum+=candidates[i]
//递归
backtracking(i,sum,target,candidates,trcak,res)
//回溯
trcak=trcak[:len(trcak)-1]
sum-=candidates[i]
}
}
```
JavaScript
```js

View File

@ -314,6 +314,48 @@ class Solution:
```
Go
> 主要在于如何在回溯中去重
```go
func combinationSum2(candidates []int, target int) [][]int {
var trcak []int
var res [][]int
var history map[int]bool
history=make(map[int]bool)
sort.Ints(candidates)
backtracking(0,0,target,candidates,trcak,&res,history)
return res
}
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int,history map[int]bool){
//终止条件
if sum==target{
tmp:=make([]int,len(trcak))
copy(tmp,trcak)//拷贝
*res=append(*res,tmp)//放入结果集
return
}
if sum>target{return}
//回溯
// used[i - 1] == true说明同一树支candidates[i - 1]使用过
// used[i - 1] == false说明同一树层candidates[i - 1]使用过
for i:=startIndex;i<len(candidates);i++{
if i>0&&candidates[i]==candidates[i-1]&&history[i-1]==false{
continue
}
//更新路径集合和sum
trcak=append(trcak,candidates[i])
sum+=candidates[i]
history[i]=true
//递归
backtracking(i+1,sum,target,candidates,trcak,res,history)
//回溯
trcak=trcak[:len(trcak)-1]
sum-=candidates[i]
history[i]=false
}
}
```
javaScript
```js

View File

@ -244,31 +244,35 @@ func backtrack(nums,pathNums []int,used []bool){
}
}
```
Javascript:
```javascript
```js
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let result = []
let path = []
function backtracing(used) {
if(path.length === nums.length) {
result.push(path.slice(0))
return
const res = [], path = [];
backtracking(nums, nums.length, []);
return res;
function backtracking(n, k, used) {
if(path.length === k) {
res.push(Array.from(path));
return;
}
for(let i = 0; i < nums.length; i++) {
if(used[nums[i]]) {
continue
}
used[nums[i]] = true
path.push(nums[i])
backtracing(used)
path.pop()
used[nums[i]] = false
for (let i = 0; i < k; i++ ) {
if(used[i]) continue;
path.push(n[i]);
used[i] = true; // 同支
backtracking(n, k, used);
path.pop();
used[i] = false;
}
}
backtracing([])
return result
};
```

View File

@ -353,14 +353,6 @@ class Solution {
}
```
## 其他语言版本
Java
Python
Go
```Go

View File

@ -279,9 +279,7 @@ Python
```python
class Solution: # 动态规划
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for i in range(n)] for j in range(m)]
for i in range(m): dp[i][0] = 1
for j in range(n): dp[0][j] = 1
dp = [[1 for i in range(n)] for j in range(m)]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i][j - 1] + dp[i - 1][j]

View File

@ -232,6 +232,38 @@ class Solution:
return dp[-1][-1]
```
```python
class Solution:
"""
使用一维dp数组
"""
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
# 初始化dp数组
# 该数组缓存当前行
curr = [0] * n
for j in range(n):
if obstacleGrid[0][j] == 1:
break
curr[j] = 1
for i in range(1, m): # 从第二行开始
for j in range(n): # 从第一列开始,因为第一列可能有障碍物
# 有障碍物处无法通行状态就设成0
if obstacleGrid[i][j] == 1:
curr[j] = 0
elif j > 0:
# 等价于
# dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
curr[j] = curr[j] + curr[j - 1]
# 隐含的状态更新
# dp[i][0] = dp[i - 1][0]
return curr[n - 1]
```
Go

View File

@ -338,6 +338,46 @@ class Solution(object):
return ans```
```
```python3
class Solution:
def __init__(self) -> None:
self.s = ""
self.res = []
def isVaild(self, s: str) -> bool:
if len(s) > 1 and s[0] == "0":
return False
if 0 <= int(s) <= 255:
return True
return False
def backTrack(self, path: List[str], start: int) -> None:
if start == len(self.s) and len(path) == 4:
self.res.append(".".join(path))
return
for end in range(start + 1, len(self.s) + 1):
# 剪枝
# 保证切割完s没有剩余的字符。
if len(self.s) - end > 3 * (4 - len(path) - 1):
continue
if self.isVaild(self.s[start:end]):
# 在参数处,更新状态,实则创建一个新的变量
# 不会影响当前的状态当前的path变量没有改变
# 因此递归完不用path.pop()
self.backTrack(path + [self.s[start:end]], end)
def restoreIpAddresses(self, s: str) -> List[str]:
# prune
if len(s) > 3 * 4:
return []
self.s = s
self.backTrack([], 0)
return self.res
```
JavaScript
```js
@ -367,6 +407,47 @@ var restoreIpAddresses = function(s) {
}
};
```
Go
> 回溯(对于前导 0的IP特别注意s[startIndex]=='0'的判断不应该写成s[startIndex]==0因为s截取出来不是数字
```go
func restoreIpAddresses(s string) []string {
var res,path []string
backTracking(s,path,0,&res)
return res
}
func backTracking(s string,path []string,startIndex int,res *[]string){
//终止条件
if startIndex==len(s)&&len(path)==4{
tmpIpString:=path[0]+"."+path[1]+"."+path[2]+"."+path[3]
*res=append(*res,tmpIpString)
}
for i:=startIndex;i<len(s);i++{
//处理
path:=append(path,s[startIndex:i+1])
if i-startIndex+1<=3&&len(path)<=4&&isNormalIp(s,startIndex,i){
//递归
backTracking(s,path,i+1,res)
}else {//如果首尾超过了3个或路径多余4个或前导为0或大于255直接回退
return
}
//回溯
path=path[:len(path)-1]
}
}
func isNormalIp(s string,startIndex,end int)bool{
checkInt,_:=strconv.Atoi(s[startIndex:end+1])
if end-startIndex+1>1&&s[startIndex]=='0'{//对于前导 0的IP特别注意s[startIndex]=='0'的判断不应该写成s[startIndex]==0因为s截取出来不是数字
return false
}
if checkInt>255{
return false
}
return true
}
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)

View File

@ -377,6 +377,75 @@ func isBST(root *TreeNode, min, max int) bool {
}
```
JavaScript版本
> 辅助数组解决
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isValidBST = function (root) {
let arr = [];
const buildArr = (root) => {
if (root) {
buildArr(root.left);
arr.push(root.val);
buildArr(root.right);
}
}
buildArr(root);
for (let i = 1; i < arr.length; ++i) {
if (arr[i] <= arr[i - 1])
return false;
}
return true;
};
```
> 递归中解决
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
let pre = null;
var isValidBST = function (root) {
let pre = null;
const inOrder = (root) => {
if (root === null)
return true;
let left = inOrder(root.left);
if (pre !== null && pre.val >= root.val)
return false;
pre = root;
let right = inOrder(root.right);
return left && right;
}
return inOrder(root);
};
```
-----------------------

View File

@ -98,7 +98,7 @@ class Solution:
out_list = []
while quene:
length = len(queue) # 这里一定要先求出队列的长度不能用range(len(queue))因为queue长度是变化的
length = len(queue)
in_list = []
for _ in range(length):
curnode = queue.pop(0) # (默认移除列表最后一个元素)这里需要移除队列最头上的那个
@ -627,6 +627,27 @@ public:
}
};
```
python代码
```python
class Solution:
def largestValues(self, root: TreeNode) -> List[int]:
if root is None:
return []
queue = [root]
out_list = []
while queue:
length = len(queue)
in_list = []
for _ in range(length):
curnode = queue.pop(0)
in_list.append(curnode.val)
if curnode.left: queue.append(curnode.left)
if curnode.right: queue.append(curnode.right)
out_list.append(max(in_list))
return out_list
```
javascript代码
```javascript
@ -712,6 +733,42 @@ public:
};
```
python代码
```python
# 层序遍历解法
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return None
queue = [root]
while queue:
n = len(queue)
for i in range(n):
node = queue.pop(0)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if i == n - 1:
break
node.next = queue[0]
return root
# 链表解法
class Solution:
def connect(self, root: 'Node') -> 'Node':
first = root
while first:
cur = first
while cur: # 遍历每一层的节点
if cur.left: cur.left.next = cur.right # 找左节点的next
if cur.right and cur.next: cur.right.next = cur.next.left # 找右节点的next
cur = cur.next # cur同层移动到下一节点
first = first.left # 从本层扩展到下一层
return root
```
## 117.填充每个节点的下一个右侧节点指针II
题目地址https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/
@ -753,7 +810,48 @@ public:
}
};
```
python代码
```python
# 层序遍历解法
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return None
queue = [root]
while queue: # 遍历每一层
length = len(queue)
tail = None # 每一层维护一个尾节点
for i in range(length): # 遍历当前层
curnode = queue.pop(0)
if tail:
tail.next = curnode # 让尾节点指向当前节点
tail = curnode # 让当前节点成为尾节点
if curnode.left : queue.append(curnode.left)
if curnode.right: queue.append(curnode.right)
return root
# 链表解法
class Solution:
def connect(self, root: 'Node') -> 'Node':
if not root:
return None
first = root
while first: # 遍历每一层
dummyHead = Node(None) # 为下一行创建一个虚拟头节点,相当于下一行所有节点链表的头结点(每一层都会创建)
tail = dummyHead # 为下一行维护一个尾节点指针(初始化是虚拟节点)
cur = first
while cur: # 遍历当前层的节点
if cur.left: # 链接下一行的节点
tail.next = cur.left
tail = tail.next
if cur.right:
tail.next = cur.right
tail = tail.next
cur = cur.next # cur同层移动到下一节点
first = dummyHead.next # 此处为换行操作,更新到下一行
return root
```
## 总结

View File

@ -775,6 +775,20 @@ var buildTree = function(inorder, postorder) {
};
```
从前序与中序遍历序列构造二叉树
```javascript
var buildTree = function(preorder, inorder) {
if(!preorder.length)
return null;
let root = new TreeNode(preorder[0]);
let mid = inorder.findIndex((number) => number === root.val);
root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid));
root.right = buildTree(preorder.slice(mid + 1, preorder.length), inorder.slice(mid + 1, inorder.length));
return root;
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)

View File

@ -54,7 +54,7 @@
如下两棵树,都是这个数组的平衡二叉搜索树:
![108.将有序数组转换为二叉搜索树](https://code-thinking.cdn.bcebos.com/pics/108.%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.png)
![108.将有序数组转换为二叉搜索树](https://code-thinking.cdn.bcebos.com/pics/108.将有序数组转换为二叉搜索树.png)
如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1取右边元素就是树2。
@ -258,6 +258,29 @@ class Solution:
Go
> 递归(隐含回溯)
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//递归(隐含回溯)
func sortedArrayToBST(nums []int) *TreeNode {
if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回
root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点从中间构造节点
root.Left=sortedArrayToBST(nums[:len(nums)/2])//数组的左边为左子树
root.Right=sortedArrayToBST(nums[len(nums)/2+1:])//数字的右边为右子树
return root
}
```
-----------------------

View File

@ -117,7 +117,7 @@ if (cur->left) { // 左
}
if (cur->right) { // 右
count -= cur->right->val;
if (traversal(cur->right, count - cur->right->val)) return true;
if (traversal(cur->right, count)) return true;
count += cur->right->val;
}
return false;

View File

@ -312,6 +312,50 @@ class Solution:
```
Go
> 注意切片go切片是披着值类型外衣的引用类型
```go
func partition(s string) [][]string {
var tmpString []string//切割字符串集合
var res [][]string//结果集合
backTracking(s,tmpString,0,&res)
return res
}
func backTracking(s string,tmpString []string,startIndex int,res *[][]string){
if startIndex==len(s){//到达字符串末尾了
//进行一次切片拷贝怕之后的操作影响tmpString切片内的值
t := make([]string, len(tmpString))
copy(t, tmpString)
*res=append(*res,t)
}
for i:=startIndex;i<len(s);i++{
//处理首先通过startIndex和i判断切割的区间进而判断该区间的字符串是否为回文若为回文则加入到tmpString否则继续后移找到回文区间这里为一层处理
if isPartition(s,startIndex,i){
tmpString=append(tmpString,s[startIndex:i+1])
}else{
continue
}
//递归
backTracking(s,tmpString,i+1,res)
//回溯
tmpString=tmpString[:len(tmpString)-1]
}
}
//判断是否为回文
func isPartition(s string,startIndex,end int)bool{
left:=startIndex
right:=end
for ;left<right;{
if s[left]!=s[right]{
return false
}
//移动左右指针
left++
right--
}
return true
}
```
javaScript

View File

@ -50,12 +50,15 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/
* 将整个字符串反转
* 将每个单词反转
如动画所示:
举个例子,源字符串为:"the sky is blue "
![151翻转字符串里的单词](https://tva1.sinaimg.cn/large/008eGmZEly1gp0kv5gl4mg30gy0c4nbp.gif)
* 移除多余空格 : "the sky is blue"
* 字符串反转:"eulb si yks eht"
* 单词反转:"blue is sky the"
这样我们就完成了翻转字符串里的单词。
思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码:
```C++
@ -80,13 +83,13 @@ void removeExtraSpaces(string& s) {
如果不仔细琢磨一下erase的时间复杂读还以为以上的代码是O(n)的时间复杂度呢。
想一下真正的时间复杂度是多少一个erase本来就是O(n)的操作erase实现原理题目[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)最优的算法来移除元素也要O(n)。
想一下真正的时间复杂度是多少一个erase本来就是O(n)的操作erase实现原理题目[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)最优的算法来移除元素也要O(n)。
erase操作上面还套了一个for循环那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。
那么使用双指针法来去移除空格最后resize重新设置一下字符串的大小就可以做到O(n)的时间复杂度。
如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)是如何移除元素的。
如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)是如何移除元素的。
那么使用双指针来移除冗余空格代码如下: fastIndex走的快slowIndex走的慢最后slowIndex就标记着移除多余空格后新字符串的长度。
@ -122,7 +125,7 @@ void removeExtraSpaces(string& s) {
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)和[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)里已经讲过了。
还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)和[541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)里已经讲过了。
代码如下:
@ -135,11 +138,8 @@ void reverse(string& s, int start, int end) {
}
```
## 本题C++整体代码
本题C++整体代码
效率:
<img src='https://code-thinking.cdn.bcebos.com/pics/151_%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8C%E7%9A%84%E5%8D%95%E8%AF%8D.png' width=600> </img></div>
```C++
// 版本一
@ -203,6 +203,7 @@ public:
return s;
}
// 当然这里的主函数reverseWords写的有一些冗余的可以精简一些精简之后的主函数为
/* 主函数简单写法
string reverseWords(string s) {
removeExtraSpaces(s);
@ -220,25 +221,8 @@ public:
};
```
当然这里的主函数reverseWords写的有一些冗余的可以精简一些精简之后的主函数为
```C++
// 注意这里仅仅是主函数,其他函数和版本一一致
string reverseWords(string s) {
removeExtraSpaces(s);
reverse(s, 0, s.size() - 1);
for(int i = 0; i < s.size(); i++) {
int j = i;
// 查找单词间的空格,翻转单词
while(j < s.size() && s[j] != ' ') j++;
reverse(s, i, j - 1);
i = j;
}
return s;
}
```
效率
<img src='https://code-thinking.cdn.bcebos.com/pics/151_翻转字符串里的单词.png' width=600> </img></div>
@ -316,7 +300,57 @@ class Solution {
}
```
Python
```Python3
class Solution:
#1.去除多余的空格
def trim_spaces(self,s):
n=len(s)
left=0
right=n-1
while left<=right and s[left]==' ': #去除开头的空格
left+=1
while left<=right and s[right]==' ': #去除结尾的空格
right=right-1
tmp=[]
while left<=right: #去除单词中间多余的空格
if s[left]!=' ':
tmp.append(s[left])
elif tmp[-1]!=' ': #当前位置是空格,但是相邻的上一个位置不是空格,则该空格是合理的
tmp.append(s[left])
left+=1
return tmp
#2.翻转字符数组
def reverse_string(self,nums,left,right):
while left<right:
nums[left], nums[right]=nums[right],nums[left]
left+=1
right-=1
return None
#3.翻转每个单词
def reverse_each_word(self, nums):
start=0
end=0
n=len(nums)
while start<n:
while end<n and nums[end]!=' ':
end+=1
self.reverse_string(nums,start,end-1)
start=end+1
end+=1
return None
#4.翻转字符串里的单词
def reverseWords(self, s): #测试用例:"the sky is blue"
l = self.trim_spaces(s) #输出:['t', 'h', 'e', ' ', 's', 'k', 'y', ' ', 'i', 's', ' ', 'b', 'l', 'u', 'e'
self.reverse_string( l, 0, len(l) - 1) #输出:['e', 'u', 'l', 'b', ' ', 's', 'i', ' ', 'y', 'k', 's', ' ', 'e', 'h', 't']
self.reverse_each_word(l) #输出:['b', 'l', 'u', 'e', ' ', 'i', 's', ' ', 's', 'k', 'y', ' ', 't', 'h', 'e']
return ''.join(l) #输出blue is sky the
'''
Go

View File

@ -0,0 +1,2 @@
同:[链表:链表相交](./面试题02.07.链表相交.md)

View File

@ -142,7 +142,7 @@ class Solution {
}
```
Python
Python迭代法
```python
#双指针
# Definition for singly-linked list.
@ -163,6 +163,32 @@ class Solution:
return pre
```
Python递归法
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def reverse(pre,cur):
if not cur:
return pre
tmp = cur.next
cur.next = pre
return reverse(cur,tmp)
return reverse(None,head)
```
Go
```go

View File

@ -118,8 +118,8 @@ public:
## 相关题目推荐
* 904.水果成篮
* 76.最小覆盖子串
* [904.水果成篮](https://leetcode-cn.com/problems/fruit-into-baskets/)
* [76.最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)

View File

@ -284,6 +284,37 @@ class Solution:
Go
> 回溯+减枝
```go
func combinationSum3(k int, n int) [][]int {
var track []int// 遍历路径
var result [][]int// 存放结果集
backTree(n,k,1,&track,&result)
return result
}
func backTree(n,k,startIndex int,track *[]int,result *[][]int){
if len(*track)==k{
var sum int
tmp:=make([]int,k)
for k,v:=range *track{
sum+=v
tmp[k]=v
}
if sum==n{
*result=append(*result,tmp)
}
return
}
for i:=startIndex;i<=9-(k-len(*track))+1;i++{//减枝k-len(*track)表示还剩多少个可填充的元素)
*track=append(*track,i)//记录路径
backTree(n,k,i+1,track,result)//递归
*track=(*track)[:len(*track)-1]//回溯
}
}
```
javaScript:
```js

View File

@ -335,6 +335,30 @@ func countNodes(root *TreeNode) int {
}
```
利用完全二叉树特性的递归解法
```go
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
leftH, rightH := 0, 0
leftNode := root.Left
rightNode := root.Right
for leftNode != nil {
leftNode = leftNode.Left
leftH++
}
for rightNode != nil {
rightNode = rightNode.Right
rightH++
}
if leftH == rightH {
return (2 << leftH) - 1
}
return countNodes(root.Left) + countNodes(root.Right) + 1
}
```
JavaScript:

View File

@ -57,7 +57,7 @@ queue.pop();**注意此时的输出栈的操作**
queue.pop();
queue.empty();
![232.用栈实现队列版本2](https://code-thinking.cdn.bcebos.com/gifs/232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97%E7%89%88%E6%9C%AC2.gif)
![232.用栈实现队列版本2](https://code-thinking.cdn.bcebos.com/gifs/232.用栈实现队列版本2.gif)
在push数据的时候只要数据放进输入栈就好**但在pop的时候操作就复杂一些输出栈如果为空就把进栈数据全部导入进来注意是全部导入**,再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
@ -125,8 +125,6 @@ public:
## 其他语言版本
Java

View File

@ -312,6 +312,7 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
}else {return findLeft}
}
```
JavaScript版本
1. 使用递归的方法
```javascript

View File

@ -310,6 +310,7 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
return nil
}
```
JavaScript版本
```javascript
var lowestCommonAncestor = function(root, p, q) {
@ -337,6 +338,7 @@ var lowestCommonAncestor = function(root, p, q) {
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
* B站视频[代码随想录](https://space.bilibili.com/525438321)

View File

@ -208,6 +208,7 @@ public:
Java
```Java
//解法一
//自定义数组
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
@ -260,6 +261,40 @@ class Solution {
return res;
}
}
//解法二
//利用双端队列手动实现单调队列
/**
* 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
* 单调队列类似 tail --> 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
*/
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意i为nums下标是要在[i - k + 1, k] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, k]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 因为单调当i增长到符合第一个k范围的时候每滑动一步都将队列头节点放入结果就行了
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res;
}
}
```
Python

View File

@ -351,6 +351,29 @@ class Solution:
```
Go
```go
func binaryTreePaths(root *TreeNode) []string {
res := make([]string, 0)
var travel func(node *TreeNode, s string)
travel = func(node *TreeNode, s string) {
if node.Left == nil && node.Right == nil {
v := s + strconv.Itoa(node.Val)
res = append(res, v)
return
}
s = s + strconv.Itoa(node.Val) + "->"
if node.Left != nil {
travel(node.Left, s)
}
if node.Right != nil {
travel(node.Right, s)
}
}
travel(root, "")
return res
}
```
JavaScript:
1.递归版本
```javascript

View File

@ -287,24 +287,85 @@ class Solution {
Python
> 动态规划
```python
> 暴力递归
```python3
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: TreeNode) -> int:
result = self.robTree(root)
if root is None:
return 0
if root.left is None and root.right is None:
return root.val
# 偷父节点
val1 = root.val
if root.left:
val1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
val1 += self.rob(root.right.left) + self.rob(root.right.right)
# 不偷父节点
val2 = self.rob(root.left) + self.rob(root.right)
return max(val1, val2)
```
> 记忆化递归
```python3
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
memory = {}
def rob(self, root: TreeNode) -> int:
if root is None:
return 0
if root.left is None and root.right is None:
return root.val
if self.memory.get(root) is not None:
return self.memory[root]
# 偷父节点
val1 = root.val
if root.left:
val1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
val1 += self.rob(root.right.left) + self.rob(root.right.right)
# 不偷父节点
val2 = self.rob(root.left) + self.rob(root.right)
self.memory[root] = max(val1, val2)
return max(val1, val2)
```
> 动态规划
```python3
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: TreeNode) -> int:
result = self.rob_tree(root)
return max(result[0], result[1])
#长度为2的数组0不偷1
def robTree(self, cur):
if not cur:
return (0, 0) #这里返回tuple, 也可以返回list
left = self.robTree(cur.left)
right = self.robTree(cur.right)
#偷cur
val1 = cur.val + left[0] + right[0]
#不偷cur
val2 = max(left[0], left[1]) + max(right[0], right[1])
return (val2, val1)
def rob_tree(self, node):
if node is None:
return (0, 0) # (偷当前节点金额,不偷当前节点金额)
left = self.rob_tree(node.left)
right = self.rob_tree(node.right)
val1 = node.val + left[1] + right[1] # 偷当前节点,不能偷子节点
val2 = max(left[0], left[1]) + max(right[0], right[1]) # 不偷当前节点,可偷可不偷子节点
return (val1, val2)
```
Go

View File

@ -21,11 +21,10 @@ https://leetcode-cn.com/problems/reverse-string/
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2
示例 2
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
@ -56,7 +55,7 @@ https://leetcode-cn.com/problems/reverse-string/
接下来再来讲一下如何解决反转字符串的问题。
大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)。
大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)。
在反转链表中,使用了双指针的方法。
@ -64,7 +63,7 @@ https://leetcode-cn.com/problems/reverse-string/
因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg)。
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。
对于字符串,我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
@ -119,8 +118,7 @@ s[i] ^= s[j];
相信大家本着我所讲述的原则来做字符串相关的题目,在选择库函数的角度上会有所原则,也会有所收获。
## C++代码
C++代码如下:
```C++
class Solution {
@ -157,7 +155,7 @@ class Solution {
```
Python
```python3
```python
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
@ -168,6 +166,17 @@ class Solution:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
# 下面的写法更加简洁,但是都是同样的算法
# class Solution:
# def reverseString(self, s: List[str]) -> None:
# """
# Do not return anything, modify s in-place instead.
# """
# 不需要判别是偶数个还是奇数个序列,因为奇数个的时候,中间那个不需要交换就可
# for i in range(len(s)//2):
# s[i], s[len(s)-1-i] = s[len(s)-1-i], s[i]
# return s
```
Go

View File

@ -140,8 +140,29 @@ public:
## 其他语言版本
Java
Java:
```
class Solution {
public boolean isSubsequence(String s, String t) {
int length1 = s.length(); int length2 = t.length();
int[][] dp = new int[length1+1][length2+1];
for(int i = 1; i <= length1; i++){
for(int j = 1; j <= length2; j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i][j-1];
}
}
}
if(dp[length1][length2] == length1){
return true;
}else{
return false;
}
}
}
```
Python
```python

View File

@ -214,12 +214,9 @@ Python
```python
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key=lambda x: (x[0], -x[1]), reverse=True)
people.sort(key=lambda x: (-x[0], x[1]))
que = []
for p in people:
if p[1] > len(que):
que.append(p)
else:
que.insert(p[1], p)
return que
```

View File

@ -67,7 +67,6 @@ if (root == nullptr) return root;
第五种情况有点难以理解,看下面动画:
![450.删除二叉搜索树中的节点](https://tva1.sinaimg.cn/large/008eGmZEly1gnbj3k596mg30dq0aigyz.gif)
<img src='../video/450.删除二叉搜索树中的节点.gif' width=600> </img></div>
动画中颗二叉搜索树中删除元素7 那么删除节点元素7的左孩子就是5删除节点元素7的右子树的最左面节点是元素8。
@ -359,6 +358,51 @@ func deleteNode1(root *TreeNode)*TreeNode{
}
```
JavaScript版本
> 递归
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} key
* @return {TreeNode}
*/
var deleteNode = function (root, key) {
if (root === null)
return root;
if (root.val === key) {
if (!root.left)
return root.right;
else if (!root.right)
return root.left;
else {
let cur = root.right;
while (cur.left) {
cur = cur.left;
}
cur.left = root.left;
let temp = root;
root = root.right;
delete root;
return root;
}
}
if (root.val > key)
root.left = deleteNode(root.left, key);
if (root.val < key)
root.right = deleteNode(root.right, key);
return root;
};
```

View File

@ -41,13 +41,11 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/
* [帮你把KMP算法学个通透求next数组代码篇](https://www.bilibili.com/video/BV1M5411j7Xx)
如果KMP还不够了解可以看我的这个视频[帮你把KMP算法学个通透B站](https://www.bilibili.com/video/BV1PD4y1o7nd/)
我们在[字符串都来看看KMP的看家本领](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)里提到了在一个串中查找是否出现过另一个串这是KMP的看家本领。
我们在[字符串KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)里提到了在一个串中查找是否出现过另一个串这是KMP的看家本领。
那么寻找重复子串怎么也涉及到KMP算法了呢
这里就要说一说next数组了next 数组记录的就是最长相同前后缀( [字符串:听说你对KMP有这些疑问](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀) 如果 next[len - 1] != -1则说明字符串有最长相同的前后缀就是字符串里的前缀子串和后缀子串相同的最长长度
这里就要说一说next数组了next 数组记录的就是最长相同前后缀( [字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀) 如果 next[len - 1] != -1则说明字符串有最长相同的前后缀就是字符串里的前缀子串和后缀子串相同的最长长度
最长相等前后缀的长度为next[len - 1] + 1。
@ -62,7 +60,7 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/
如图:
![459.重复的子字符串_1](https://code-thinking.cdn.bcebos.com/pics/459.%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2_1.png)
![459.重复的子字符串_1](https://code-thinking.cdn.bcebos.com/pics/459.重复的子字符串_1.png)
next[len - 1] = 7next[len - 1] + 1 = 88就是此时字符串asdfasdfasdf的最长相同前后缀的长度。
@ -70,7 +68,7 @@ next[len - 1] = 7next[len - 1] + 1 = 88就是此时字符串asdfasdfasdf
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4 4正好可以被 12(字符串的长度) 整除所以说明有重复的子字符串asdf
代码如下:(这里使用了前缀表统一减一的实现方式)
C++代码如下:(这里使用了前缀表统一减一的实现方式)
```C++
class Solution {
@ -104,7 +102,7 @@ public:
```
前缀表(不减一)的代码实现
前缀表(不减一)的C++代码实现
```C++
class Solution {
@ -139,12 +137,11 @@ public:
# 拓展
此时我们已经分享了三篇KMP的文章首先是[字符串KMP是时候上场了一文读懂系列](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)讲解KMP算法的基础理论给出next数组究竟是如何来了前缀表又是怎么回事为什么要选择前缀表。
在[字符串KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)讲解KMP算法的基础理论给出next数组究竟是如何来了前缀表又是怎么回事为什么要选择前缀表。
然后通过[字符串都来看看KMP的看家本领](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)讲解一道KMP的经典题目判断文本串里是否出现过模式串这里涉及到构造next数组的代码实现以及使用next数组完成模式串与文本串的匹配过程。
后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串听说你对KMP有这些疑问](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ)中又给出了详细的讲解。
讲解一道KMP的经典题目力扣28. 实现 strStr()判断文本串里是否出现过模式串这里涉及到构造next数组的代码实现以及使用next数组完成模式串与文本串的匹配过程。
后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)给出了详细的讲解。
## 其他语言版本

View File

@ -158,7 +158,7 @@ dp[j] 表示填满j包括j这么大容积的包有dp[i]种方法
那么只需要搞到一个2nums[i]有dp[3]方法可以凑齐容量为3的背包相应的就有多少种方法可以凑齐容量为5的背包。
那么需要把 这些方法累加起来就可以了dp[i] += dp[j - nums[i]]
那么需要把 这些方法累加起来就可以了dp[j] += dp[j - nums[i]]
所以求组合类问题的公式,都是类似这种:

View File

@ -522,6 +522,7 @@ func traversal(root *TreeNode,result *[]int,pre *TreeNode){//遍历统计
}
}
```
JavaScript版本
使用额外空间map的方法
```javascript

View File

@ -256,6 +256,39 @@ func findMIn(root *TreeNode,res *[]int){
}
```
JavaScript版本
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var getMinimumDifference = function (root) {
let arr = [];
const buildArr = (root) => {
if (root) {
buildArr(root.left);
arr.push(root.val);
buildArr(root.right);
}
}
buildArr(root);
let diff = arr[arr.length - 1];
for (let i = 1; i < arr.length; ++i) {
if (diff > arr[i] - arr[i - 1])
diff = arr[i] - arr[i - 1];
}
return diff;
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)

View File

@ -219,6 +219,26 @@ class Solution:
Go
> 弄一个sum暂存其和值
```go
//右中左
func bstToGst(root *TreeNode) *TreeNode {
var sum int
RightMLeft(root,&sum)
return root
}
func RightMLeft(root *TreeNode,sum *int) *TreeNode {
if root==nil{return nil}//终止条件,遇到空节点就返回
RightMLeft(root.Right,sum)//先遍历右边
temp:=*sum//暂存总和值
*sum+=root.Val//将总和值变更
root.Val+=temp//更新节点值
RightMLeft(root.Left,sum)//遍历左节点
return root
}
```
-----------------------

View File

@ -38,7 +38,7 @@ https://leetcode-cn.com/problems/reverse-string-ii/
**所以当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。**
性能如下:
<img src='https://code-thinking.cdn.bcebos.com/pics/541_%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2II.png' width=600> </img></div>
<img src='https://code-thinking.cdn.bcebos.com/pics/541_反转字符串II.png' width=600> </img></div>
那么这里具体反转的逻辑我们要不要使用库函数呢其实用不用都可以使用reverse来实现反转也没毛病毕竟不是解题关键部分。
@ -65,7 +65,7 @@ public:
};
```
那么我们也可以实现自己的reverse函数其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)道理是一样的。
那么我们也可以实现自己的reverse函数其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)道理是一样的。
下面我实现的reverse函数区间是左闭右闭区间代码如下
@ -103,6 +103,7 @@ public:
Java
```Java
//解法一
class Solution {
public String reverseStr(String s, int k) {
StringBuffer res = new StringBuffer();
@ -128,6 +129,28 @@ class Solution {
return res.toString();
}
}
//解法二(似乎更容易理解点)
//题目的意思其实概括为 每隔2k个反转前k个尾数不够k个时候全部反转
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
for(int i = 0; i < ch.length; i += 2 * k){
int start = i;
//这里是判断尾数够不够k个来取决end指针的位置
int end = Math.min(ch.length - 1, start + k - 1);
//用异或运算反转
while(start < end){
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
return new String(ch);
}
}
```
Python

View File

@ -368,8 +368,52 @@ func mergeTrees(t1 *TreeNode, t2 *TreeNode) *TreeNode {
Right: mergeTrees(t1.Right,t2.Right)}
return root
}
// 前序遍历简洁版
func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
if root1 == nil {
return root2
}
if root2 == nil {
return root1
}
root1.Val += root2.Val
root1.Left = mergeTrees(root1.Left, root2.Left)
root1.Right = mergeTrees(root1.Right, root2.Right)
return root1
}
```
JavaScript:
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root1
* @param {TreeNode} root2
* @return {TreeNode}
*/
var mergeTrees = function (root1, root2) {
const preOrder = (root1, root2) => {
if (!root1)
return root2
if (!root2)
return root1;
root1.val += root2.val;
root1.left = preOrder(root1.left, root2.left);
root1.right = preOrder(root1.right, root2.right);
return root1;
}
return preOrder(root1, root2);
};
```
-----------------------

View File

@ -311,6 +311,42 @@ func findMax(nums []int) (index int){
}
```
JavaScript版本
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} nums
* @return {TreeNode}
*/
var constructMaximumBinaryTree = function (nums) {
const BuildTree = (arr, left, right) => {
if (left > right)
return null;
let maxValue = -1;
let maxIndex = -1;
for (let i = left; i <= right; ++i) {
if (arr[i] > maxValue) {
maxValue = arr[i];
maxIndex = i;
}
}
let root = new TreeNode(maxValue);
root.left = BuildTree(arr, left, maxIndex - 1);
root.right = BuildTree(arr, maxIndex + 1, right);
return root;
}
let root = BuildTree(nums, 0, nums.length - 1);
return root;
};
```

View File

@ -286,7 +286,33 @@ class Solution:
return root
```
Go
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func trimBST(root *TreeNode, low int, high int) *TreeNode {
if root==nil{
return nil
}
if root.Val<low{//如果该节点值小于最小值,则该节点更换为该节点的右节点值,继续遍历
right:=trimBST(root.Right,low,high)
return right
}
if root.Val>high{//如果该节点的值大于最大值,则该节点更换为该节点的左节点值,继续遍历
left:=trimBST(root.Left,low,high)
return left
}
root.Left=trimBST(root.Left,low,high)
root.Right=trimBST(root.Right,low,high)
return root
}
```

View File

@ -290,7 +290,64 @@ func searchBST(root *TreeNode, val int) *TreeNode {
}
```
JavaScript版本
> 递归
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var searchBST = function (root, val) {
if (!root || root.val === val) {
return root;
}
if (root.val > val)
return searchBST(root.left, val);
if (root.val < val)
return searchBST(root.right, val);
return null;
};
```
> 迭代
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var searchBST = function (root, val) {
while (root !== null) {
if (root.val > val)
root = root.left;
else if (root.val < val)
root = root.right;
else
return root;
}
return root;
};
```

View File

@ -286,8 +286,118 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode {
}
```
JavaScript版本
> 有返回值的递归写法
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var insertIntoBST = function (root, val) {
const setInOrder = (root, val) => {
if (root === null) {
let node = new TreeNode(val);
return node;
}
if (root.val > val)
root.left = setInOrder(root.left, val);
else if (root.val < val)
root.right = setInOrder(root.right, val);
return root;
}
return setInOrder(root, val);
};
```
> 无返回值的递归
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var insertIntoBST = function (root, val) {
let parent = new TreeNode(0);
const preOrder = (cur, val) => {
if (cur === null) {
let node = new TreeNode(val);
if (parent.val > val)
parent.left = node;
else
parent.right = node;
return;
}
parent = cur;
if (cur.val > val)
preOrder(cur.left, val);
if (cur.val < val)
preOrder(cur.right, val);
}
if (root === null)
root = new TreeNode(val);
preOrder(root, val);
return root;
};
```
> 迭代
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var insertIntoBST = function (root, val) {
if (root === null) {
root = new TreeNode(val);
} else {
let parent = new TreeNode(0);
let cur = root;
while (cur) {
parent = cur;
if (cur.val > val)
cur = cur.left;
else
cur = cur.right;
}
let node = new TreeNode(val);
if (parent.val > val)
parent.left = node;
else
parent.right = node;
}
return root;
};
```
-----------------------
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)

View File

@ -217,7 +217,29 @@ class Solution: # 贪心思路
Go
Javascript:
```Javascript
// 贪心思路
var maxProfit = function(prices, fee) {
let result = 0
let minPrice = prices[0]
for(let i = 1; i < prices.length; i++) {
if(prices[i] < minPrice) {
minPrice = prices[i]
}
if(prices[i] >= minPrice && prices[i] <= minPrice + fee) {
continue
}
if(prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee
// 买入和卖出只需要支付一次手续费
minPrice = prices[i] -fee
}
}
return result
};
```
-----------------------

View File

@ -147,23 +147,43 @@ class Solution {
Python
```python
```python3
class Solution:
def monotoneIncreasingDigits(self, n: int) -> int:
strNum = list(str(n))
flag = len(strNum)
for i in range(len(strNum) - 1, 0, -1):
if int(strNum[i]) < int(strNum[i - 1]):
strNum[i - 1] = str(int(strNum[i - 1]) - 1)
flag = i
for i in range(flag, len(strNum)):
strNum[i] = '9'
return int("".join(strNum))
a = list(str(n))
for i in range(len(a)-1,0,-1):
if int(a[i]) < int(a[i-1]):
a[i-1] = str(int(a[i-1]) - 1)
a[i:] = '9' * (len(a) - i) #python不需要设置flag值直接按长度给9就好了
return int("".join(a))
```
Go
Javascript:
```Javascript
var monotoneIncreasingDigits = function(n) {
n = n.toString()
n = n.split('').map(item => {
return +item
})
let flag = Infinity
for(let i = n.length - 1; i > 0; i--) {
if(n [i - 1] > n[i]) {
flag = i
n[i - 1] = n[i - 1] - 1
n[i] = 9
}
}
for(let i = flag; i < n.length; i++) {
n[i] = 9
}
n = n.join('')
return +n
};
```
-----------------------

View File

@ -217,47 +217,46 @@ Go
> 暴力法
```go
func dailyTemperatures(temperatures []int) []int {
length:=len(temperatures)
res:=make([]int,length)
for i:=0;i<length;i++{
func dailyTemperatures(t []int) []int {
var res []int
for i := 0; i < len(t)-1; i++ {
j := i + 1
if i==length-1{
res[i]=0
}
for j<length&&temperatures[i]>=temperatures[j]{//大于等于
j++
}
if j<length&&temperatures[i]<temperatures[j]{
res[i]=j-i
}
if j==length{
res[i]=0
for ; j < len(t); j++ {
// 如果之后出现更高,说明找到了
if t[j] > t[i] {
res = append(res, j-i)
break
}
}
return res
// 找完了都没找到
if j == len(t) {
res = append(res, 0)
}
}
// 最后一个肯定是 0
return append(res, 0)
}
```
> 单调栈法
```go
func dailyTemperatures(temperatures []int) []int {
length:=len(temperatures)
res:=make([]int,length)
// 单调递减栈
func dailyTemperatures(num []int) []int {
ans := make([]int, len(num))
stack := []int{}
for i:=0;i<length;i++{
//如果当前栈中存在元素,新来的元素大于栈顶的元素,则计算差值并弹出栈顶元素
for len(stack)>0&&temperatures[i]>temperatures[stack[len(stack)-1]]{
res[stack[len(stack)-1]]=i-stack[len(stack)-1]//存放结果集
stack=stack[:len(stack)-1]//删除stack[len(stack)-1]的元素
for i, v := range num {
// 栈不空,且当前遍历元素 v 破坏了栈的单调性
for len(stack) != 0 && v > num[stack[len(stack)-1]] {
// pop
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ans[top] = i - top
}
//如果栈顶元素大于等于新来的元素则加入到栈中。当栈内元素个数为0时直接入栈
if len(stack)==0||temperatures[i]<=temperatures[stack[len(stack)-1]]{
stack = append(stack, i)
}
}
return res
return ans
}
```

View File

@ -128,6 +128,33 @@ class Solution:
Go
```go
func partitionLabels(s string) []int {
var res []int;
var marks [26]int;
size, left, right := len(s), 0, 0;
for i := 0; i < size; i++ {
marks[s[i] - 'a'] = i;
}
for i := 0; i < size; i++ {
right = max(right, marks[s[i] - 'a']);
if i == right {
res = append(res, right - left + 1);
left = i + 1;
}
}
return res;
}
func max(a, b int) int {
if a < b {
a = b;
}
return a;
}
```
Javascript:
```Javascript
var partitionLabels = function(s) {

View File

@ -369,7 +369,42 @@ class Solution:
```
Go
Javascript:
```Javascript
var minCameraCover = function(root) {
let result = 0
function traversal(cur) {
if(cur === null) {
return 2
}
let left = traversal(cur.left)
let right = traversal(cur.right)
if(left === 2 && right === 2) {
return 0
}
if(left === 0 || right === 0) {
result++
return 1
}
if(left === 1 || right === 1) {
return 2
}
return -1
}
if(traversal(root) === 0) {
result++
}
return result
};
```
-----------------------

View File

@ -26,7 +26,8 @@
拿示例一A = [1,4,2], B = [1,2,4]为例,相交情况如图:
![1035.不相交的线](https://img-blog.csdnimg.cn/20210321164517460.png)
![](https://gitee.com/programmercarl/pics/raw/master/pic1/20210527113415.png)
其实也就是说A和B的最长公共子序列是[1,4]长度为2。 这个公共子序列指的是相对顺序不变即数字4在字符串A中数字1的后面那么数字4也应该在字符串B数字1的后面

View File

@ -127,7 +127,9 @@ Java
```Java
class Solution {
public String removeDuplicates(String S) {
Deque<Character> deque = new LinkedList<>();
//ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
//参考https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for (int i = 0; i < S.length(); i++) {
ch = S.charAt(i);
@ -171,6 +173,29 @@ class Solution {
}
```
拓展:双指针
```java
class Solution {
public String removeDuplicates(String s) {
char[] ch = s.toCharArray();
int fast = 0;
int slow = 0;
while(fast < s.length()){
// 直接用fast指针覆盖slow指针的值
ch[slow] = ch[fast];
// 遇到前后相同值的就跳过即slow指针后退一步下次循环就可以直接被覆盖掉了
if(slow > 0 && ch[slow] == ch[slow - 1]){
slow--;
}else{
slow++;
}
fast++;
}
return new String(ch,0,slow);
}
}
```
Python
```python3
class Solution:

View File

@ -155,9 +155,82 @@ public:
## 其他语言版本
Java
```java
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
```
Python
```python3

View File

@ -42,9 +42,9 @@ i指向新长度的末尾j指向旧长度的末尾。
时间复杂度空间复杂度均超过100%的用户。
<img src='https://code-thinking.cdn.bcebos.com/pics/%E5%89%91%E6%8C%87Offer05.%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.png' width=600> </img></div>
<img src='https://code-thinking.cdn.bcebos.com/pics/剑指Offer05.替换空格.png' width=600> </img></div>
## C++代码
C++代码如下:
```C++
class Solution {
@ -76,17 +76,17 @@ public:
};
```
时间复杂度O(n)
空间复杂度O(1)
* 时间复杂度O(n)
* 空间复杂度O(1)
此时算上本题,我们已经做了七道双指针相关的题目了分别是:
* [27.移除元素](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)
* [344.反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
* [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)
# 拓展
@ -121,10 +121,6 @@ for (int i = 0; i < a.size(); i++) {
所以想处理字符串我们还是会定义一个string类型。
## 其他语言版本
@ -150,8 +146,6 @@ public static String replaceSpace(StringBuffer str) {
}
```
Python
Go
```go

View File

@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
不能使用额外空间的话模拟在本串操作要实现左旋转字符串的功能还是有点困难的
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的
这道题目也非常类似依然可以通过局部反转+整体反转 达到左旋转的目的
@ -50,13 +50,13 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
如图
<img src='https://code-thinking.cdn.bcebos.com/pics/%E5%89%91%E6%8C%87Offer58-II.%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.png' width=600> </img></div>
<img src='https://code-thinking.cdn.bcebos.com/pics/剑指Offer58-II.左旋转字符串.png' width=600> </img></div>
最终得到左旋2个单元的字符串cdefgab
思路明确之后,那么代码实现就很简单了
# C++代码
C++代码如下:
```C++
class Solution {
@ -73,15 +73,16 @@ public:
# 总结
此时我们已经反转好多次字符串了,来一起回顾一下吧。
在这篇文章[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
在这篇文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。
然后发现[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章。
后来在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
后来在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
最后再讲到本,本题则是先局部反转再 整体反转,与[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)类似,但是也是一种新的思路。
最后再讲到本,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)类似,但是也是一种新的思路。
好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。
@ -93,7 +94,6 @@ public:
**如果想让这套题目有意义,就不要申请额外空间。**
## 其他语言版本
Java
@ -117,7 +117,6 @@ class Solution {
}
}
```
Python
Go

View File

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

View File

@ -43,9 +43,10 @@ for (int i = 0; i < a.size(); i++) {
所以想处理字符串我们还是会定义一个string类型。
# 要不要使用库函数
在文章[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)中强调了**打基础的时候,不要太迷恋于库函数。**
在文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中强调了**打基础的时候,不要太迷恋于库函数。**
甚至一些同学习惯于调用substrsplitreverse之类的库函数却不知道其实现原理也不知道其时间复杂度这样实现出来的代码如果在面试现场面试官问“分析其时间复杂度”的话一定会一脸懵逼
@ -56,15 +57,15 @@ for (int i = 0; i < a.size(); i++) {
# 双指针法
在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。**
在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。**
接着在[字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
接着在[字符串:替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA)同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
那么针对数组删除操作的问题,其实在[数组:就移除元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)中就已经提到了使用双指针法进行移除操作。
那么针对数组删除操作的问题,其实在[27. 移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中就已经提到了使用双指针法进行移除操作。
同样的道理在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
同样的道理在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
一些同学会使用for循环里调用库函数erase来移除元素这其实是O(n^2)的操作因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。
@ -72,7 +73,7 @@ for (int i = 0; i < a.size(); i++) {
在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。
[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)中一些同学可能为了处理逻辑每隔2k个字符的前k的字符写了一堆逻辑代码或者再搞一个计数器来统计2k再统计前k个字符。
[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)中一些同学可能为了处理逻辑每隔2k个字符的前k的字符写了一堆逻辑代码或者再搞一个计数器来统计2k再统计前k个字符。
其实**当需要固定规律一段一段去处理字符串的时候要想想在在for循环的表达式上做做文章**。
@ -80,34 +81,34 @@ for (int i = 0; i < a.size(); i++) {
因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。
在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
这道题目通过 **先整体反转再局部反转**,实现了反转字符串里的单词。
后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。
在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)中,我们通过**先局部反转再整体反转**达到了左旋的效果。
在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们通过**先局部反转再整体反转**达到了左旋的效果。
# KMP
KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。**
KMP的精髓所在就是前缀表在[字符串KMP是时候上场了一文读懂系列](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)中提到了什么是KMP什么是前缀表以及为什么要用前缀表。
KMP的精髓所在就是前缀表在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中提到了什么是KMP什么是前缀表以及为什么要用前缀表。
前缀表起始位置到下表i之前包括i的子串中有多大长度的相同前缀后缀。
那么使用KMP可以解决两类经典问题
1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)
2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ)
1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)
2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/32Pve4j8IWvdgxYEZdTeFg)
在[字符串听说你对KMP有这些疑问](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ) 强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。
再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。
前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。
然后**针对前缀表到底要不要减一这其实是不同KMP实现的方式**,我们在[字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw)中针对之前两个问题分别给出了两个不同版本的的KMP实现。
然后**针对前缀表到底要不要减一这其实是不同KMP实现的方式**,我们在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中针对之前两个问题分别给出了两个不同版本的的KMP实现。
其中主要**理解j=next[x]这一步最为关键!**
@ -123,18 +124,6 @@ KMP算法是字符串查找最重要的算法但彻底理解KMP并不容易
## 其他语言版本
Java
Python
Go
-----------------------

View File

@ -85,20 +85,7 @@ std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
所以STL 队列也不被归类为容器而被归类为container adapter 容器适配器)。
我这里讲的都是clckC++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖起内部原理,才能夯实基础。
## 其他语言版本
Java
Python
Go
我这里讲的都是C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖起内部原理,才能夯实基础。

View File

@ -5,6 +5,7 @@
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
</p>
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 动态规划关于01背包问题你该了解这些滚动数组
昨天[动态规划关于01背包问题你该了解这些](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中是用二维dp数组来讲解01背包。
@ -35,7 +36,7 @@
**其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上表达式完全可以是dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);**
**其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了**只用dp[j](一维数组,也可以理解是一个滚动数组)。
**其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了**只用dp[j](一维数组,也可以理解是一个滚动数组)。
这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。
@ -242,7 +243,24 @@ Java
Python
```python
def test_1_wei_bag_problem():
weight = [1, 3, 4]
value = [15, 20, 30]
bag_weight = 4
# 初始化: 全为0
dp = [0] * (bag_weight + 1)
# 先遍历物品, 再遍历背包容量
for i in range(len(weight)):
for j in range(bag_weight, weight[i] - 1, -1):
# 递归公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
print(dp)
test_1_wei_bag_problem()
```
Go
```go