未验证 提交 c2952f59 编写于 作者: B BruceCat 提交者: GitHub

Merge branch 'master' into master

......@@ -181,7 +181,7 @@ Gitee Pages 地址:https://labuladong.gitee.io/algo
[PaperJets](https://github.com/PaperJets),
[qy-yang](https://github.com/qy-yang),
[realism0331](https://github.com/realism0331),
[SCUhzs](https://github.com/HuangZiSheng001),
[SCUhzs](https://github.com/brucecat),
[Seaworth](https://github.com/Seaworth),
[shazi4399](https://github.com/shazi4399),
[ShuozheLi](https://github.com/ShuoZheLi/),
......@@ -190,7 +190,7 @@ Gitee Pages 地址:https://labuladong.gitee.io/algo
[Tianhao Zhou](https://github.com/tianhaoz95),
[timmmGZ](https://github.com/timmmGZ),
[tommytim0515](https://github.com/tommytim0515),
[upbin](https://github.com/upbin),
[ucsk](https://github.com/ucsk),
[wadegrc](https://github.com/wadegrc),
[walsvid](https://github.com/walsvid),
[warmingkkk](https://github.com/warmingkkk),
......
pictures/qrcode.jpg

17.2 KB | W: | H:

pictures/qrcode.jpg

26.6 KB | W: | H:

pictures/qrcode.jpg
pictures/qrcode.jpg
pictures/qrcode.jpg
pictures/qrcode.jpg
  • 2-up
  • Swipe
  • Onion skin
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [经典动态规划:最长公共子序列](https://labuladong.gitbook.io/algo)
* [特殊数据结构:单调栈](https://labuladong.gitbook.io/algo)
* [经典动态规划:最长公共子序列](https://labuladong.gitbook.io/algo/)
* [特殊数据结构:单调栈](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -423,48 +423,135 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[28.实现 strStr()](https://leetcode-cn.com/problems/implement-strstr)
### python
[MoguCloud](https://github.com/MoguCloud) 提供 实现 strStr() 的 Python 完整代码:
```py
```python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
# 边界条件判断
if not needle:
return 0
pat = needle
txt = haystack
M = len(pat)
# dp[状态][字符] = 下个状态
dp = [[0 for _ in range(256)] for _ in pat]
# base case
dp[0][ord(pat[0])] = 1
# 影子状态 X 初始化为 0
X = 0
for j in range(1, M):
for c in range(256):
dp[j][c] = dp[X][c]
dp[j][ord(pat[j])] = j + 1
# 更新影子状态
X = dp[X][ord(pat[j])]
def strStr(self, haystack: str, needle: str) -> int:
# 边界条件判断
if not needle:
return 0
pat = needle
txt = haystack
M = len(pat)
# dp[状态][字符] = 下个状态
dp = [[0 for _ in range(256)] for _ in pat]
# base case
dp[0][ord(pat[0])] = 1
# 影子状态 X 初始化为 0
X = 0
for j in range(1, M):
for c in range(256):
dp[j][c] = dp[X][c]
dp[j][ord(pat[j])] = j + 1
# 更新影子状态
X = dp[X][ord(pat[j])]
N = len(txt)
# pat 初始状态为 0
j = 0
for i in range(N):
# 计算 pat 的下一个状态
j = dp[j][ord(txt[i])]
# 到达终止态,返回结果
if j == M:
return i - M + 1
# 没到达终止态,匹配失败
return -1
# 计算 pat 的下一个状态
j = dp[j][ord(txt[i])]
# 到达终止态,返回结果
if j == M:
return i - M + 1
# 没到达终止态,匹配失败
return -1
```
### javascript
```js
class KMP {
constructor(pat) {
this.pat = pat;
let m = pat.length;
// dp[状态][字符] = 下个状态 初始化一个m*256的整数矩阵
this.dp = new Array(m);
for (let i = 0; i < m; i++) {
this.dp[i] = new Array(256);
this.dp[i].fill(0, 0, 256);
}
// base case
this.dp[0][this.pat[0].charCodeAt()] = 1;
// 影子状态X 初始为0
let x = 0;
// 构建状态转移图
for (let j = 1; j < m; j++) {
for (let c = 0; c < 256; c++) {
this.dp[j][c] = this.dp[x][c];
}
// dp[][对应的ASCII码]
this.dp[j][this.pat[j].charCodeAt()] = j + 1;
// 更新影子状态
x = this.dp[x][this.pat[j].charCodeAt()]
}
}
search(txt) {
let m = this.pat.length;
let n = txt.length;
// pat的初始态为0
let j = 0;
for (let i = 0; i < n; i++) {
// 计算pat的下一个状态
j = this.dp[j][txt[i].charCodeAt()];
// 到达终止态 返回结果
if (j === m) return i - m + 1;
}
// 没到终止态 匹配失败
return -1;
}
}
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function(haystack, needle) {
if(haystack === ""){
if(needle !== ""){
return -1;
}
return 0;
}
if(needle === ""){
return 0;
}
let kmp = new KMP(needle);
return kmp.search(haystack)
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [40张图解:TCP三次握手和四次挥手面试题](https://labuladong.gitbook.io/algo)
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo)
* [40张图解:TCP三次握手和四次挥手面试题](https://labuladong.gitbook.io/algo/)
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -20,7 +20,7 @@
**-----------**
上一篇文章 [几道智力题](https://labuladong.gitbook.io/algo) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
上一篇文章 [几道智力题](https://labuladong.gitbook.io/algo/) 中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
博弈类问题的套路都差不多,下文参考 [这个 YouTube 视频](https://www.youtube.com/watch?v=WxpIHvsu1RI) 的思路讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
......@@ -207,21 +207,16 @@ int stoneGame(int[] piles) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
* python3版本
### python
[SCUHZS](https://github.com/brucecat)提供
......@@ -287,7 +282,9 @@ class Solution:
```
* C++ 版本
### C++ 版本
[TCeason](https://github.com/TCeason) 提供
......@@ -328,3 +325,112 @@ public:
```
### javascript
[SCUHZS](https://github.com/brucecat)提供
**1、暴力递归解**
```js
/**
* 返回[i,j]上先手所能取得的最优决策的值
* @param piles
* @param i
* @param j
* @return {number|*}
*/
var f=function(piles,i,j) {
if(i===j){ //如果i===j,只有一个元素,那么先手只能选它
return piles[i]
}
//否则 有2种情况:
//1 先选i,之后在[i+1,j]上后手进行最优选择
//2 先选j,之后在[i,j-1]上后手进行最优选择
return Math.max(piles[i]+s(i+1,j),piles[j]+s(i,j-1))
}
/**
*返回[i,j]上后手所能取得的最优决策的值
* @param piles
* @param i
* @param j
* @return {number}
*/
var s=function(piles,i,j) {
if(i===j){ //如果i===j,只有一个元素,那么后手没有选,只能为0
return 0
}
//对于这种双方都是绝顶聪明的人,数据一开始对于双方都是可见的,那么数据一确定,先后手一确定,那么结果就已经确定了
//先手选的人会把最优解选了,那么剩给后手的只有最差的情况
//所以后手的人虽然能从剩下的之中进行最优决策,但结果确是命中注定的了,只能是最差的
//所以返回[i+1,j] [i,j-1]上进行最优选择的最小值
//这也说明了先手的人在大概率下会赢得游戏(在某些情况下先手必赢,比如本题的情况:具体分析看官方解析)
return Math.min(f(i+1,j),f(i,j-1))
}
/**
*
* @param piles
* @return {boolean}
*/
var stoneGame = function(piles) {
return f(0,piles.length-1)>s(0,piles.length-1) //亚历克斯先选和李后选得到的最大值做比较
};
```
**2、动态规划dp做法**
这里采取的是三维的做法
```js
var stoneGame = function (piles) {
let n = piles.length;
// 初始化一个n*n的矩阵 dp数组
let dp = []
for (let i = 0; i < n; i++) {
dp[i] = []
}
// 在三角区域填充
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
dp[i][j] = [0, 0]
}
}
// 填入base case
for (let i = 0; i < n; i++) {
dp[i][i][0] = piles[i];
dp[i][i][1] = 0;
}
// 斜着遍历数组
for (let l = 2; l <= n; l++) {
for (let i = 0; i <= n - 1; i++) {
let j = l + i - 1;
// 先手选择最左边或最右边的分数
let left = piles[i] + dp[i + 1][j][1];
let right = piles[j] + dp[i][j - 1][1];
// 套用状态转移方程
if (left > right) {
dp[i][j][0] = left;
dp[i][j][1] = dp[i + 1][j][0];
} else {
dp[i][j][0] = right;
dp[i][j][1] = dp[i][j - 1][0];
}
}
}
let res = dp[0][n - 1];
return res[0] - res[1]
};
```
###
\ No newline at end of file
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [如何高效寻找素数](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
* [如何高效寻找素数](https://labuladong.gitbook.io/algo/)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -146,7 +146,7 @@ dp[i] = dp[i - 1] + 1;
但是,如果要按 `C-V`,还要考虑之前是在哪里 `C-A C-C` 的。
**刚才说了,最优的操作序列一定是 `C-A C-C` 接着若干 `C-V`,所以我们用一个变量 `j` 作为若干 `C-V` 的起点**。那么 `j` 之前的 2 个操作就应该是 `C-A C-C` 了:
```java
public int maxA(int N) {
int[] dp = new int[N + 1];
......@@ -192,7 +192,7 @@ def dp(n, a_num, copy):
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -200,4 +200,60 @@ def dp(n, a_num, copy):
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
### javascript
[651.四键键盘](https://leetcode-cn.com/problems/4-keys-keyboard)
**1、第一种思路**
```js
let maxA = function (N) {
// 备忘录
let memo = {}
let dp = function (n, a_num, copy) {
if (n <= 0) {
return a_num;
}
let key = n + ',' + a_num + ',' + copy
// 避免计算重叠子问题
if (memo[key] !== undefined) {
return memo[key]
}
memo[key] = Math.max(
dp(n - 1, a_num + 1, copy), // A
dp(n - 1, a_num + copy, copy), // C-V
dp(n - 2, a_num, a_num) // C-A C-C
)
return memo[key]
}
return dp(N, 0, 0)
}
```
**2、第二种思路**
```js
var maxA = function (N) {
let dp = new Array(N + 1);
dp[0] = 0;
for (let i = 1; i <= N; i++) {
// 按A键盘
dp[i] = dp[i - 1] + 1;
for (let j = 2; j < i; j++) {
// 全选 & 复制 dp[j-2],连续粘贴 i - j 次
// 屏幕上共 dp[j - 2] * (i - j + 1) 个 A
dp[i] = Math.max(dp[i], dp[j - 2] * (i - (j - 2) - 1));
}
}
// N 次按键之后最多有几个 A?
return dp[N];
}
```
......@@ -10,8 +10,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [我写了首诗,把滑动窗口算法算法变成了默写题](https://labuladong.gitbook.io/algo)
* [二分查找高效判定子序列](https://labuladong.gitbook.io/algo)
* [我写了首诗,把滑动窗口算法算法变成了默写题](https://labuladong.gitbook.io/algo/)
* [二分查找高效判定子序列](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -287,12 +287,139 @@ bool dp(string& s, int i, string& p, int j) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**。
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**。
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### javascript
[10.正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching/)
```js
var isMatch = function (s, p) {
// 备忘录
let memo = {}
let dp = function (s, i, p, j) {
let m = s.length, n = p.length;
// base case
if (j === n) {
return i === m;
}
if (i === m) {
if ((n - j) % 2 === 1) {
return false;
}
for (; j + 1 < n; j += 2) {
if (p[j + 1] !== '*') {
return false;
}
}
return true;
}
// 记录状态(i,j),消除重叠子问题
let key = i + ',' + j
if (memo[key] !== undefined) {
return memo[key];
}
let res = false;
if (s[i] === p[j] || p[j] === '.') {
// 匹配
if (j < n - 1 && p[j + 1] === '*') {
// 1.1 通配符匹配 0 次或多次
res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j);
} else {
// 1.2 常规匹配1次
res = dp(s, i + 1, p, j + 1);
}
} else {
// 不匹配
if (j < n - 1 && p[j + 1] === '*') {
// 2.1 通配符匹配0次
res = dp(s, i, p, j + 2)
} else {
// 2.2 无法继续匹配
res = false
}
}
// 将当前结果记入备忘录
memo[key] = res;
return res;
}
// 指针 i,j 从索引 0 开始移动
return dp(s, 0, p, 0);
};
```
### C++
```c++
class Solution {
public:
map<string, bool> memo;
bool isMatch(string s, string p) {
// 指针 i,j 从索引 0 开始移动
return dp(s, 0, p, 0);
}
/* 计算 p[j..] 是否匹配 s[i..] */
bool dp(string& s, int i, string& p, int j) {
int m = s.size(), n = p.size();
// base case
if (j == n) {
return i == m;
}
if (i == m) {
if ((n - j) % 2 == 1) {
return false;
}
for (; j + 1 < n; j += 2) {
if (p[j + 1] != '*') {
return false;
}
}
return true;
}
// 记录状态 (i, j),消除重叠子问题
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
bool res = false;
if (s[i] == p[j] || p[j] == '.') {
if (j < n - 1 && p[j + 1] == '*') {
res = dp(s, i, p, j + 2)
|| dp(s, i + 1, p, j);
} else {
res = dp(s, i + 1, p, j + 1);
}
} else {
if (j < n - 1 && p[j + 1] == '*') {
res = dp(s, i, p, j + 2);
} else {
res = false;
}
}
// 将当前结果记入备忘录
memo[key] = res;
return res;
}
};
```
......@@ -10,8 +10,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo)
* [一文学会递归解题](https://labuladong.gitbook.io/algo)
* [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo/)
* [一文学会递归解题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -19,7 +19,7 @@
**-----------**
也许有读者看了前文 [动态规划详解](https://labuladong.gitbook.io/algo),学会了动态规划的套路:找到了问题的「状态」,明确了 `dp` 数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
也许有读者看了前文 [动态规划详解](https://labuladong.gitbook.io/algo/),学会了动态规划的套路:找到了问题的「状态」,明确了 `dp` 数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
不要担心,动态规划的难点本来就在于寻找正确的状态转移方程,本文就借助经典的「最长递增子序列问题」来讲一讲设计动态规划的通用技巧:**数学归纳思想**
......@@ -43,7 +43,7 @@
**我们的定义是这样的:`dp[i]` 表示以 `nums[i]` 这个数结尾的最长递增子序列的长度。**
PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文[动态规划之子序列问题解题模板](https://labuladong.gitbook.io/algo) 总结了几种常见套路。你读完本章所有的动态规划问题,就会发现 `dp` 数组的定义方法也就那几种。
PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文[动态规划之子序列问题解题模板](https://labuladong.gitbook.io/algo/) 总结了几种常见套路。你读完本章所有的动态规划问题,就会发现 `dp` 数组的定义方法也就那几种。
根据这个定义,我们就可以推出 base case:`dp[i]` 初始值为 1,因为以 `nums[i]` 结尾的最长递增子序列起码要包含它自己。
......@@ -164,7 +164,7 @@ public int lengthOfLIS(int[] nums) {
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是**有序**吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
PS:旧文[二分查找算法详解](https://labuladong.gitbook.io/algo)详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
PS:旧文[二分查找算法详解](https://labuladong.gitbook.io/algo/)详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
```java
public int lengthOfLIS(int[] nums) {
......@@ -207,16 +207,80 @@ public int lengthOfLIS(int[] nums) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### javascript
[scuhzs](https://github.com/brucecat)提供[300.最长上升子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence)
动态规划做法如下:
```javascript
let lengthOfLIS = function (nums) {
// 用1填满dp数组
let dp = [];
dp.fill(1, 0, nums.length);
for (let i = 1; i < nums.length; i++)
for (let j = 0; j < i; j++)
nums[i] > nums[j] && (dp[i] = Math.max(dp[i], dp[j] + 1))
return nums.length < 2 ? nums.length : Math.max(...dp)
};
```
二分法做法如下:
```javascript
let lengthOfLIS01 = function (nums) {
let top = new Array(nums.length);
for (let i = 0; i < nums.length; i++) {
top[i] = 0;
}
// 牌堆数初始化为 0
let piles = 0;
for (let i = 0; i < nums.length; i++) {
// 要处理的扑克牌
let poker = nums[i];
/***** 搜索左侧边界的二分查找 *****/
let left = 0, right = piles;
while (left < right) {
// 记住这里要向下取整
let mid = Math.floor((left + right) / 2);
if (top[mid] > poker) {
right = mid;
} else if (top[mid] < poker) {
left = mid + 1;
} else {
right = mid;
}
}
/*********************************/
// 没找到合适的牌堆,新建一堆
left === piles && piles++;
// 把这张牌放到牌堆顶
top[left] = poker;
}
// 牌堆数就是 LIS 长度
return piles;
}
```
### python
```python 动态规划
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
......@@ -262,6 +326,9 @@ class Solution:
```
### c++
[Kian](https://github.com/KianKw/) 提供 C++ 代码
```c++
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo)
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo)
* [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo/)
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -216,7 +216,7 @@ int coinChange(int[] coins, int amount);
回到凑零钱问题,为什么说它符合最优子结构呢?比如你想求 `amount = 11` 时的最少硬币数(原问题),如果你知道凑出 `amount = 10` 的最少硬币数(子问题),你只需要把子问题的答案加一(再选一枚面值为 1 的硬币)就是原问题的答案。因为硬币的数量是没有限制的,所以子问题之间没有相互制,是互相独立的。
PS:关于最优子结构的问题,后文[动态规划答疑篇](https://labuladong.gitbook.io/algo) 还会再举例探讨。
PS:关于最优子结构的问题,后文[动态规划答疑篇](https://labuladong.gitbook.io/algo/) 还会再举例探讨。
那么,既然知道了这是个动态规划问题,就要思考**如何列出正确的状态转移方程**
......@@ -360,16 +360,17 @@ PS:为啥 `dp` 数组初始化为 `amount + 1` 呢,因为凑成 `amount` 金
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### python
[DapangLiu](https://github.com/DapangLiu) 提供 509. 斐波那契数 Python3 解法代码:
递归写法
......@@ -409,4 +410,176 @@ class Solution:
for _ in range(n):
dp_0, dp_1 = dp_1, dp_0 + dp_1
return dp_0
```
\ No newline at end of file
```
### javascript
#### 一、斐波那契数
**1、暴力递归**
```js
let fib = function (n) {
if (n === 1 || n === 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
};
```
**2、带备忘录的递归**
```js
let fib = function (n) {
if (n < 1) return 0;
// 备忘录全初始化为 0
let memo = new Array(n + 1);
memo.fill(0, 0, n + 1);
// 进行带备忘录的递归
return helper(memo, n);
}
let helper = function (memo, n) {
// base case
if (n === 1 || n === 2) return 1;
// 已经计算过
if (memo[n] !== 0) return memo[n];
memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
return memo[n];
}
```
**3、dp 数组的迭代解法**
```js
let fib = function (n) {
if (n === 0) return 0;
if (n === 1) return 1;
let dp = new Array(n + 1);
dp.fill(0, 0, n + 1);
// base case
dp[1] = dp[2] = 1;
for (let i = 3; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
}
```
##### 4、dp数组 状态压缩
```js
let fib = function (n) {
if (n === 2 || n === 1)
return 1;
let prev = 1, curr = 1;
for (let i = 3; i <= n; i++) {
let sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}
```
#### 二、凑零钱
1、**递归写法**
```js
var coinChange = function (coins, amount) {
let dp = function (n) {
// base case
if (n === 0) {
return 0
}
if (n < 0) {
return -1
}
// 求最小值,所以初始化为正无穷 或者是amount+1
let res = amount + 1
for (let coin of coins) {
let subproblem = dp(n - coin)
// 子问题无解,跳过
if (subproblem === -1)
continue;
res = Math.min(res, 1 + subproblem)
}
return res !== amount + 1 ? res : -1;
}
return dp(amount)
};
```
**2、带备忘录的递归写法**
```js
var coinChange = function (coins, amount) {
let memo = {};
this.dp = function (amount) {
if (memo[amount] !== undefined) {
return memo[amount];
}
if (amount === 0) {
return 0;
}
if (amount < 0) {
return -1;
}
let result = Infinity;
for (let coin of coins) {
// 计算子问题
const subResult = dp(amount - coin);
// 子问题无解
if (subResult === -1) {
continue;
}
// 个数为 1 + 子问题的解
result = Math.min(result, 1 + subResult);
}
// 备忘录
memo[amount] = result == Infinity ? -1 : result;
return memo[amount];
};
return dp(amount);
}
```
**3、dp 数组的迭代解法**
```js
var coinChange = function (coins, amount) {
// 数组大小为 amount + 1,初始值也为 amount + 1
let dp = new Array(amount + 1);
dp.fill(amount + 1, 0, amount + 1);
// base case
dp[0] = 0;
// 外层 for 循环在遍历所有状态的所有取值
for (let i = 0; i < dp.length; i++) {
// 内层 for 循环在求所有选择的最小值
for (let coin of coins) {
// 子问题无解,跳过
if (i - coin < 0) continue;
dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
}
}
return (dp[amount] === amount + 1) ? -1 : dp[amount];
}
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [动态规划之KMP字符匹配算法](https://labuladong.gitbook.io/algo)
* [如何判断回文链表](https://labuladong.gitbook.io/algo)
* [动态规划之KMP字符匹配算法](https://labuladong.gitbook.io/algo/)
* [如何判断回文链表](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -420,12 +420,392 @@ int maxProfit_k_any(int max_k, int[] prices) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock)
[买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
[买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
[买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
[最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
[买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
### javascript
**第一题**
[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock),相当于`k=1`的情形。
```js
var maxProfit = function (prices) {
let n = prices.length;
if (n <= 1) {
return 0;
}
let dp = new Array(n);
dp.fill([0, 0], 0, n)
// base case
// 解释:
// dp[i][0]
// = max(dp[-1][0], dp[-1][1] + prices[i])
// = max(0, -infinity + prices[i]) = 0
// dp[0][0] = 0;
// 解释:
// dp[i][1]
// = max(dp[-1][1], dp[-1][0] - prices[i])
// = max(-infinity, 0 - prices[i])
// = -prices[i]
dp[0][1] = -prices[0];
// 状态转移
for (let i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], -prices[i])
}
return dp[n - 1][0]
};
```
状态压缩
```js
var maxProfit = function (prices) {
let n = prices.length;
// base case
let dp_i_0 = 0, dp_i_1 = -prices[0];
for (let i = 1; i < n; i++) {
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
// dp[i][1] = max(dp[i-1][1], -prices[i])
dp_i_1 = Math.max(dp_i_1, -prices[i]);
}
return dp_i_0;
}
```
**第二题**
[买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/),相当于`k = +infinity`的情形。
```js
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let n = prices.length;
let dp = new Array(n);
dp.fill([0, 0], 0, n)
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (let i = 1; i < n; i++) {
dp[i][0] = Math.max(
dp[i - 1][0],
dp[i - 1][1] + prices[i]
)
dp[i][1] = Math.max(
dp[i - 1][1],
dp[i - 1][0] - prices[i]
)
}
return dp[n - 1][0]
};
```
状态压缩
```js
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let n = prices.length;
// base case
let dp_i_0 = 0, dp_i_1 = -prices[0];
for (let i = 0; i < n; i++) {
// dp[i][0] = Math.max(
// dp[i - 1][0],
// dp[i - 1][1] + prices[i]
// )
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
// dp[i][1] = Math.max(
// dp[i - 1][1],
// dp[i - 1][0] - prices[i]
// )
dp_i_1 = Math.max(dp_i_1, dp_i_0 - prices[i])
}
return dp_i_0;
};
```
**第三题**
[最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/),相当于`k = +infinity with cooldown`的情形。
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
```
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。
```
```js
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let n = prices.length;
if (n < 2) {
return 0;
}
if (n === 2) {
return Math.max(prices[1] - prices[0], 0)
}
let dp = new Array(n);
for (let i = 0; i < n; i++) {
dp[i] = [0, 0]
}
// base case
// dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = Math.max(
dp[0][0],
dp[0][1] + prices[1]
)
dp[1][1] = Math.max(
dp[0][1],
dp[0][0] - prices[1]
);
// 状态转移
for (let i = 2; i < n; i++) {
dp[i][0] = Math.max(
dp[i - 1][0],
dp[i - 1][1] + prices[i]
)
dp[i][1] = Math.max(
dp[i - 1][1],
dp[i - 2][0] - prices[i] // 买被限制在卖一天后了
)
}
return dp[n - 1][0];
};
```
状态压缩
```js
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(prices) {
let n = prices.length;
let dp_i_0 = 0;
let dp_i_1 = -Infinity; // 还未买入
let dp_pre_0 = 0; // 代表 dp[i-2][0]
for (let i = 0; i < n; i++) {
let temp = dp_i_0;
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]);
dp_pre_0 = temp;
}
return dp_i_0;
};
```
**第四题**
[买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)`k = +infinity with fee`的情形。
每次交易要支付手续费,只要把手续费从利润中减去即可。
```
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i] - fee)
解释:相当于买入股票的价格升高了。
在第一个式子里减也是一样的,相当于卖出股票的价格减小了。
```
```js
/**
* @param {number[]} prices
* @param {number} fee
* @return {number}
*/
var maxProfit = function(prices, fee) {
let n = prices.length;
let dp = new Array(n);
for (let i = 0; i < n; i++) {
dp[i] = [0, 0]
}
// base case
// dp[0][0] = 0;
dp[0][1] = -prices[0] - fee;
// 状态转移
for (let i = 1; i < n; i++) {
dp[i][0] = Math.max(
dp[i - 1][0],
dp[i - 1][1] + prices[i]
)
dp[i][1] = Math.max(
dp[i - 1][1],
dp[i - 1][0] - prices[i] - fee // 相当于买入股票的价格升高了
)
}
return dp[n - 1][0];
};
```
状态压缩
```js
var maxProfit = function (prices, fee) {
let n = prices.length;
// base case
let dp_i_0 = 0, dp_i_1 = -prices[0] - fee;
for (let i = 0; i < n; i++) {
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = Math.max(dp_i_1, dp_i_0 - prices[i] - fee)
}
return dp_i_0;
};
```
**第五题**
[买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)`k = 2 `的情形。
```
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )
解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )
解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
```
```js
var maxProfit = function(prices) {
//第一次 买入, 卖出的利润
let profit_1_in = -prices[0], profit_1_out = 0;
//继第一次之后,第二次买入卖出的利润
let profit_2_in = -prices[0], profit_2_out = 0;
let n = prices.length;
for (let i = 1; i < n; i++){
profit_2_out = Math.max(profit_2_out, profit_2_in + prices[i]);
//第二次买入后的利润, 第一次卖出的利润 - prices[i]
profit_2_in = Math.max(profit_2_in, profit_1_out - prices[i]);
profit_1_out = Math.max(profit_1_out, profit_1_in + prices[i]);
//第一次买入后,利润为 -prices[i]
profit_1_in = Math.max(profit_1_in, -prices[i]);
}
return profit_2_out;
};
```
**第六题**
[买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)。k = any integer的情形。
```js
/**
* @param {number} k
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function(k, prices) {
if (!prices.length) {
return 0;
}
const n = prices.length;
k = Math.min(k, Math.floor(n / 2));
const buy = new Array(k + 1).fill(0);
const sell = new Array(k + 1).fill(0);
buy[0]= -prices[0]
sell[0] = 0
for (let i = 1; i < k + 1; ++i) {
buy[i] = sell[i] = -Number.MAX_VALUE;
}
for (let i = 1; i < n; ++i) {
buy[0] = Math.max(buy[0], sell[0] - prices[i]);
for (let j = 1; j < k + 1; ++j) {
buy[j] = Math.max(buy[j], sell[j] - prices[i]);
sell[j] = Math.max(sell[j], buy[j - 1] + prices[i]);
}
}
return Math.max(...sell)
};
```
======其他语言代码======
\ No newline at end of file
......@@ -12,8 +12,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [洗牌算法](https://labuladong.gitbook.io/algo)
* [twoSum问题的核心思想](https://labuladong.gitbook.io/algo)
* [洗牌算法](https://labuladong.gitbook.io/algo/)
* [twoSum问题的核心思想](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -166,7 +166,7 @@ int longestPalindromeSubseq(string s) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,labuladong 带你搞定 LeetCode**
......@@ -174,4 +174,46 @@ int longestPalindromeSubseq(string s) {
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[516.最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence)
### javascript
```js
/**
* @param {string} s
* @return {number}
*/
var longestPalindromeSubseq = function (s) {
let l = s.length;
if (l <= 1) {
return l;
}
// 初始化一个 dp[l][l]
let dp = new Array(l);
for (let i = 0; i < l; i++) {
dp[i] = new Array(l);
dp[i].fill(0, 0, l)
// // base case
dp[i][i] = 1
}
// 从右下角开始,逐渐往上推
for (let i = l - 2; i >= 0; i--) {
for (let j = i + 1; j <= l - 1; j++) {
if (s[i] === s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(
dp[i + 1][j],
dp[i][j - 1]
)
}
}
}
return dp[0][l - 1]
};
```
......@@ -10,7 +10,7 @@
![](../pictures/souyisou.png)
相关推荐:
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo)
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo/)
* [经典动态规划:子集背包问题](/https://labuladong.gitbook.io/algo)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -250,15 +250,23 @@ int[] dp(TreeNode root) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[198.打家劫舍](https://leetcode-cn.com/problems/house-robber)
[213.打家劫舍II](https://leetcode-cn.com/problems/house-robber-ii)
[337.打家劫舍III](https://leetcode-cn.com/problems/house-robber-iii)
### python
[Shantom](https://github.com/Shantom) 提供 198. House Robber I Python3 解法代码:
```Python
......@@ -321,3 +329,157 @@ class Solution:
return max(dp(root))
```
### javascript
#### House Robber I
自顶向下
```js
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function (nums) {
let memo = new Array(nums.length);
memo.fill(-1, 0, nums.length)
// 返回nums[start..]能抢到的最大值
let dp = function (nums, start) {
if (start >= nums.length) {
return 0;
}
// 避免重复计算
if (memo[start] !== -1) return memo[start];
let res = Math.max(
// 不抢,去下一家
dp(nums, start + 1),
// 抢, 然后去下下家抢
nums[start] + dp(nums, start + 2)
)
// 记入备忘录
memo[start] = res;
return res;
}
// 强盗从第 0 间房子开始决定抢劫哪家
return dp(nums, 0)
};
```
自底向上
```js
var rob = function (nums) {
let n = nums.length;
// dp[i] = x 表示:
// 从第 i 间房子开始抢劫,最多能抢到的钱为 x
// base case: dp[n] = 0
let dp = new Array(n + 2);
dp.fill(0, 0, n + 2)
for (let i = n - 1; i >= 0; i--) {
dp[i] = Math.max(
dp[i + 1],
nums[i] + dp[i + 2]
)
}
// 强盗从第 0 间房子开始决定抢劫哪家
return dp[0]
};
```
自底向上 + 状态压缩
```js
var rob = function (nums) {
let n = nums.length;
// 记录 dp[i+1] 和 dp[i+2]
let dp_i_1 = 0, dp_i_2 = 0;
// 记录 dp[i]
let dp_i = 0;
for (let i = n - 1; i >= 0; i--) {
dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i;
};
```
#### House Robber II
```js
var rob = function (nums) {
let n = nums.length;
if (n === 1) return nums[0];
// 仅计算闭区间 [start,end] 的最优结果
let robRange = function (nums, start, end) {
let dp_i_1 = 0, dp_i_2 = 0;
let dp_i = 0;
for (let i = end; i >= start; i--) {
dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i;
}
return Math.max(
robRange(nums, 0, n - 2),
robRange(nums, 1, n - 1)
)
};
```
#### House Robber III
```js
var rob = function (root) {
let res = dp(root);
return Math.max(res[0], res[1]);
};
var dp = function (root){
if(root == null){
return [0,0];
}
let left = dp(root.left);
let right = dp(root.right);
// 抢,下家就不能抢了
let rob = root.val + left[0] + right[0];
// 不抢,下家可抢可不抢,取决于收益大小
let not_rob = Math.max(left[0], left[1]) + + Math.max(right[0], right[1]);
return [not_rob, rob]
}
```
......@@ -12,8 +12,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [搜索引擎背后的经典数据结构和算法](https://labuladong.gitbook.io/algo)
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo)
* [搜索引擎背后的经典数据结构和算法](https://labuladong.gitbook.io/algo/)
* [动态规划之四键键盘](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -54,7 +54,7 @@ return result;
当然,上面这个例子太简单了,不过请读者回顾一下,我们做动态规划问题,是不是一直在求各种最值,本质跟我们举的例子没啥区别,无非需要处理一下重叠子问题。
前文不[同定义不同解法](https://labuladong.gitbook.io/algo)[高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo) 就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
前文不[同定义不同解法](https://labuladong.gitbook.io/algo/)[高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo/) 就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
再举个常见但也十分简单的例子,求一棵二叉树的最大值,不难吧(简单起见,假设节点中的值都是非负数):
......@@ -149,12 +149,45 @@ for (int i = 1; i < m; i++)
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### javascript
正向遍历
```js
// 构建m*n的矩阵
let dp = new Array(m).fill(new Array(n))
for (let i = 0; i < m; i++)
for (let j = 0; j < n; j++)
// 计算 dp[i][j]
```
反向遍历
```js
for (let i = m - 1; i >= 0; i--)
for (let j = n - 1; j >= 0; j--)
// 计算 dp[i][j]
```
斜向遍历
```js
// 斜着遍历数组
for (let l = 2; l <= n; l++) {
for (let i = 0; i <= n - l; i++) {
let j = l + i - 1;
// 计算 dp[i][j]
}
}
```
======其他语言代码======
\ No newline at end of file
# 最长公共子序列
# 最长公共子序列
<p align='center'>
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo)
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo/)
* [经典动态规划:高楼扔鸡蛋(进阶)](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -139,16 +139,18 @@ else:
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[1143.最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence)
### c++
[Edwenc](https://github.com/Edwenc) 提供 C++ 代码:
......@@ -183,6 +185,10 @@ public:
};
```
### java
[Shawn](https://github.com/Shawn-Hx) 提供 Java 代码:
```java
......@@ -207,4 +213,128 @@ public int longestCommonSubsequence(String text1, String text2) {
}
```
### python
[lo-tp](http://blog.lotp.xyz/) 提供 Python 代码:
```python
class Solution(object):
def longestCommonSubsequence(self, text1, text2):
# calculate the size of the first and second string
sz1, sz2 = len(text1), len(text2)
# since to calculate dp(i,j) we only need dp(i-1,j-1), dp(i-1,j), dp(i,j-1)
# we don't have to save data before i-1
# we use dp to save dp(i-1, 0), dp(i-1, 1)....dp(i-1, sz2)
# we use tmp to save dp(i, 0), dp(i,1)....(dpi-1, sz2)
tmp, dp = [0]*(sz2+1), [0]*(sz2+1)
for i in range(0, sz1):
for j in range(0, sz2):
tmp[j+1] = dp[j] + \
1 if text1[i] == text2[j] else max(tmp[j], dp[j+1])
# In the next iteration, we will calculate dp(i+1,0),dp(i+1, 1)....dp(i+1,sz2)
# So we exchange dp and tmp
tmp, dp = dp, tmp
return dp[-1]
```
### javascript
**暴力解法**
```js
var longestCommonSubsequence = function (text1, text2) {
let s1 = text1.length;
let s2 = text2.length;
let dp = function (i, j) {
// 空串的base case
if (i === -1 || j === -1) {
return 0;
}
if (text1[i] === text2[j]) {
// 这边找到一个 lcs 的元素,继续往前找
return dp(i - 1, j - 1) + 1
} else {
// 谁能让lcs最长,就听谁的
return Math.max(dp(i - 1, j), dp(i, j - 1))
}
}
// i 和 j 初始化为最后一个索引
return dp(s1 - 1, s2 - 1)
};
```
**暴力解法+备忘录优化**
```js
var longestCommonSubsequence = function (text1, text2) {
let s1 = text1.length;
let s2 = text2.length;
let memo = new Map();
let dp = function (i, j) {
// 空串的base case
if (i === -1 || j === -1) {
return 0;
}
// 查询一下备忘录,防止重复计算
let key = i + "," + j
if (memo.has(key)) {
return memo.get(key)
}
let res;
if (text1[i] === text2[j]) {
// 这边找到一个 lcs 的元素,继续往前找
// 记入备忘录
res = dp(i - 1, j - 1) + 1
memo.set(key, res)
} else {
// 谁能让lcs最长,就听谁的
res = Math.max(dp(i - 1, j), dp(i, j - 1))
memo.set(key, res)
}
return res;
}
// i 和 j 初始化为最后一个索引
return dp(s1 - 1, s2 - 1)
};
```
**DPtable优化**
```js
var longestCommonSubsequence = function (text1, text2) {
let s1 = text1.length;
let s2 = text2.length;
// 构建 DP table 和 base case
// 初始化一个 (s1+1)*(s2+1)的dp表
let dp = new Array(s1 + 1);
for (let i = 0; i < s1 + 1; i++) {
dp[i] = new Array(s2 + 1);
dp[i].fill(0, 0, s2 + 1)
}
// 进行状态转移
for (let i = 1; i < s1 + 1; i++) {
for (let j = 1; j < s2 + 1; j++) {
if (text1[i - 1] === text2[j - 1]) {
// 找到一个lcs中的字符
dp[i][j] = 1 + dp[i - 1][j - 1]
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
}
}
// i 和 j 初始化为最后一个索引
return dp[s1][s2]
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [labuladong优质作者扶持计划](https://labuladong.gitbook.io/algo)
* [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo)
* [labuladong优质作者扶持计划](https://labuladong.gitbook.io/algo/)
* [动态规划设计:最大子数组](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -283,18 +283,20 @@ class Node {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[ChenjieXu](https://github.com/ChenjieXu) 提供Python版本代码:
```python3
### python
[ChenjieXu](https://github.com/ChenjieXu) 提供Python版本[72.编辑距离](https://leetcode-cn.com/problems/edit-distance)代码:
```python
def minDistance(word1, word2):
m, n = len(word1), len(word2)
# 创建 DP 数组
......@@ -318,4 +320,92 @@ def minDistance(word1, word2):
dp[i - 1][j - 1] + 1)
# 储存着整个 word1 和 word2 的最小编辑距离
return dp[m][n]
````
\ No newline at end of file
````
### javascript
[SCUHZS](https://github.com/brucecat)提供[72.编辑距离](https://leetcode-cn.com/problems/edit-distance)
```javascript
let minDistance = function (s1, s2) {
let m = s1.length, n = s2.length;
// 初始化一个 (m+1) * (n+1)大小的数组
let dp = new Array(m + 1);
for (let i = 0; i < m + 1; i++) {
dp[i] = new Array(n + 1).fill(0)
}
for (let i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= n; j++)
dp[0][j] = j;
// 自底向上求解
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i - 1] === s2[j - 1])
dp[i][j] = dp[i - 1][j - 1]
else
dp[i][j] = Math.min(
dp[i - 1][j] + 1, // 删除
dp[i][j - 1] + 1, // 插入
dp[i - 1][j - 1] + 1 // 替换
)
}
}
// 储存着整个 s1 和 s2 的最小编辑距离
return dp[m][n];
};
```
上面的代码还可以进行状态压缩优化,我们还需要一个额外的变量 pre 来时刻保存 (i-1,j-1) 的值。推导公式就可以从二维的:
```
dp[i][j] = min(dp[i-1][j] , dp[i-1][j-1] , dp[i][j-1]) + 1
```
转化为一维的:
```
dp[i] = min(dp[i-1], pre, dp[i]) + 1
```
完整代码如下:
```js
let minDistance = function (word1, word2) {
let m = word1.length, n = word2.length;
// 初始化一个数组
let dp = new Array(Math.max(m,n) + 1)
// dp[0...n]的初始值
for (let j = 0; j <= n; j++)
dp[j] = j;
// dp[j] = min(dp[j-1], pre, dp[j]) + 1
for (let i = 1; i <= m; i++) {
let temp = dp[0];
// 相当于初始化
dp[0] = i;
for (let j = 1; j <= n; j++) {
// pre 相当于之前的 dp[i-1][j-1]
let pre = temp;
temp = dp[j];
// 如果 word1[i] 与 word2[j] 相等。第 i 个字符对应下标是 i-1
if (word1[i - 1] === word2[j - 1]) {
dp[j] = pre;
} else {
dp[j] = Math.min(Math.min(dp[j - 1], pre), dp[j]) + 1;
}
}
}
return dp[n];
};
```
# 贪心算法之区间调度问题
# 贪心算法之区间调度问题
<p align='center'>
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [如何判定括号合法性](https://labuladong.gitbook.io/algo)
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo)
* [如何判定括号合法性](https://labuladong.gitbook.io/algo/)
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -150,16 +150,19 @@ int findMinArrowShots(int[][] intvs) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)
[452.用最少数量的箭引爆气球](https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons)
### python
Edwenc 提供 第435题的python3 代码:
......@@ -197,4 +200,116 @@ class Solution:
# 最后返回的是 需要移除的区间个数
return n-count
```
\ No newline at end of file
```
### javascript
**区间调度实现**
```js
var intervalSchedule = function (intvs) {
if (intvs.length === 0) return 0;
// 按end升序排序
intvs.sort((a, b) => {
if (a[1] < b[1])
return -1;
else if (a[1] > b[1])
return 1;
else return 0;
})
// 至少有一个区间不相交
let count = 1;
// 排序后,第一个区间就是 x
let x_end = intvs[0][1];
for (let interval of intvs) {
let start = interval[0];
if (start >= x_end) {
// 找到下一个选择的区间了
count++;
x_end = interval[1];
}
}
return count;
}
```
**第435题 无重叠区间**
```js
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function (intervals) {
let n = intervals.length;
// 我们已经会求最多有几个区间不会重叠了,那么剩下的不就是至少需要去除的区间吗?
return n - intervalSchedule(intervals);
};
var intervalSchedule = function (intvs) {
if (intvs.length === 0) return 0;
// 按end升序排序
intvs.sort((a, b) => {
if (a[1] < b[1])
return -1;
else if (a[1] > b[1])
return 1;
else return 0;
})
// 至少有一个区间不相交
let count = 1;
// 排序后,第一个区间就是 x
let x_end = intvs[0][1];
for (let interval of intvs) {
let start = interval[0];
if (start >= x_end) {
// 找到下一个选择的区间了
count++;
x_end = interval[1];
}
}
return count;
}
```
**第452题 用最少数量的箭引爆气球**
```js
/**
* @param {number[][]} points
* @return {number}
*/
var findMinArrowShots = function (intvs) {
if (intvs.length === 0) return 0;
// 按end升序排序
intvs.sort((a, b) => {
if (a[1] < b[1])
return -1;
else if (a[1] > b[1])
return 1;
else return 0;
})
// 至少有一个区间不相交
let count = 1;
// 排序后,第一个区间就是 x
let x_end = intvs[0][1];
for (let interval of intvs) {
let start = interval[0];
if (start > x_end) {
// 找到下一个选择的区间了
count++;
x_end = interval[1];
}
}
return count;
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第二期)](https://labuladong.gitbook.io/algo)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
* [手把手带你刷二叉树(第二期)](https://labuladong.gitbook.io/algo/)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -287,7 +287,7 @@ while (lo < hi) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -295,4 +295,40 @@ while (lo < hi) {
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/)
### javascript
```js
/**
* @param {number} K
* @param {number} N
* @return {number}
*/
var superEggDrop = function (K, N) {
// m 最多不会超过 N 次(线性扫描)
// 初始化一个 (K+1)(N+1) 的矩阵
let dp = new Array(K + 1);
// base case:
// dp[0][..] = 0
// dp[..][0] = 0
// 初始化数组都为 0
for (let i = 0; i < K + 1; i++) {
dp[i] = new Array(N + 1);
dp[i].fill(0, 0, N + 1);
}
let m = 0;
while (dp[K][m] < N) {
m++;
for (let k = 1; k <= K; k++) {
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
}
}
return m;
};
```
......@@ -12,8 +12,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [如何判断回文链表](https://labuladong.gitbook.io/algo)
* [SQL进阶技巧](https://labuladong.gitbook.io/algo)
* [如何判断回文链表](https://labuladong.gitbook.io/algo/)
* [SQL进阶技巧](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -76,7 +76,7 @@ PS:这有点像 Big O 表示法计算​算法的复杂度。
**「选择」其实就是去选择哪层楼扔鸡蛋**。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。
现在明确了「状态」和「选择」,**动态规划的基本思路就形成了**:肯定是个二维的 `dp` 数组或者带有两个状态参数的 `dp` 函数来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新状态:
```python
# 当前状态为 K 个鸡蛋,面对 N 层楼
# 返回这个状态下的最优结果
......@@ -243,7 +243,7 @@ def superEggDrop(self, K: int, N: int) -> int:
return dp(K, N)
```
这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo)
这里就不展开其他解法了,留在下一篇文章 [高楼扔鸡蛋进阶](https://labuladong.gitbook.io/algo/)
我觉得吧,我们这种解法就够了:找状态,做选择,足够清晰易懂,可流程化,可举一反三。掌握这套框架学有余力的话,再去考虑那些奇技淫巧也不迟。
......@@ -253,7 +253,7 @@ def superEggDrop(self, K: int, N: int) -> int:
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -261,4 +261,110 @@ def superEggDrop(self, K: int, N: int) -> int:
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[887.鸡蛋掉落](https://leetcode-cn.com/problems/super-egg-drop/)
### javascript
**dp状态转移 + 备忘录**
```js
var superEggDrop = function (K, N) {
let memo = {}
let dp = function (K, N) {
// base case
if (K === 1) return N;
if (N === 0) return 0;
// 避免重复计算
let key = K + ',' + N
if (memo[key] !== undefined) {
return memo[key];
}
// 正无穷
let res = Infinity;
// 穷举所有的可能的选择
for (let i = 1; i < N + 1; i++) {
res = Math.min(
res,
Math.max(
dp(K, N - i),
dp(K - 1, i - 1)
) + 1
)
}
// 记入备忘录
memo[key] = res;
return res;
}
return dp(K, N);
};
```
**dp状态转移 + 备忘录 + 二分法**
```js
var superEggDrop = function (K, N) {
let memo = {}
let dp = function (K, N) {
// base case
if (K === 1) return N;
if (N === 0) return 0;
// 避免重复计算
let key = K + ',' + N
if (memo[key] !== undefined) {
return memo[key];
}
// 正无穷
let res = Infinity;
// 穷举所有的可能的选择
// for (let i = 1; i < N + 1; i++) {
// res = Math.min(
// res,
// Math.max(
// dp(K, N - i),
// dp(K - 1, i - 1)
// ) + 1
// )
// }
// 用二分搜索代替线性搜索
let lo = 1, hi = N;
while (lo <= hi) {
let mid = Math.floor((lo + hi) / 2);
let broken = dp(K - 1, mid - 1) // 碎
let not_broken = dp(K, N - mid) // 没碎
// res = min(max(碎,没碎) + 1)
if (broken > not_broken) {
hi = mid - 1
res = Math.min(res, broken + 1)
} else {
lo = mid + 1
res = Math.min(res, not_broken + 1)
}
}
// 记入备忘录
memo[key] = res;
return res;
}
return dp(K, N);
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo/)
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -103,10 +103,10 @@ $ logout
类似的,还有一种后台运行常用的做法是这样:
```shell
$ nohub some_cmd &
$ nohup some_cmd &
```
`nohub`命令也是类似的原理,不过通过我的测试,还是`(cmd &)`这种形式更加稳定。
`nohup`命令也是类似的原理,不过通过我的测试,还是`(cmd &)`这种形式更加稳定。
### 三、单引号和双引号的区别
......@@ -149,7 +149,7 @@ $ sudo /home/fdl/bin/connect.sh
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -157,4 +157,4 @@ $ sudo /home/fdl/bin/connect.sh
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo)
* [Union-Find算法详解](https://labuladong.gitbook.io/algo)
* [一文解决三道区间问题](https://labuladong.gitbook.io/algo/)
* [Union-Find算法详解](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -133,7 +133,7 @@ $ cmd1 | cmd2 | cmd3
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [烧饼排序](https://labuladong.gitbook.io/algo)
* [动态规划之正则表达](https://labuladong.gitbook.io/algo)
* [烧饼排序](https://labuladong.gitbook.io/algo/)
* [动态规划之正则表达](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -98,7 +98,7 @@ Redis 监听的默认端口是 6379,我们设置它接收网卡 127.0.0.1 的
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo)
* [Linux的进程、线程、文件描述符是什么](https://labuladong.gitbook.io/algo)
* [如何计算完全二叉树的节点数](https://labuladong.gitbook.io/algo/)
* [Linux的进程、线程、文件描述符是什么](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -147,7 +147,7 @@ https://github.com/astaxie/build-web-application-with-golang
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [区间调度之区间合并问题](https://labuladong.gitbook.io/algo)
* [别再说你不懂Linux内存管理了,10张图给你安排的明明白白](https://labuladong.gitbook.io/algo)
* [区间调度之区间合并问题](https://labuladong.gitbook.io/algo/)
* [别再说你不懂Linux内存管理了,10张图给你安排的明明白白](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -111,7 +111,7 @@ https://sqlzoo.net/
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo)
* [一个方法团灭 nSum 问题](https://labuladong.gitbook.io/algo)
* [我用四个命令概括了 Git 的所有套路](https://labuladong.gitbook.io/algo/)
* [一个方法团灭 nSum 问题](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -194,7 +194,7 @@ HTTPS 协议中的 SSL/TLS 安全层会组合使用以上几种加密方式,**
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -203,4 +203,4 @@ HTTPS 协议中的 SSL/TLS 安全层会组合使用以上几种加密方式,**
</p>
======其他语言代码======
[test ad](https://labuladong.gitbook.io/algo)
\ No newline at end of file
[test ad](https://labuladong.gitbook.io/algo/)
\ No newline at end of file
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [算法就像搭乐高:带你手撸 LRU 算法](https://labuladong.gitbook.io/algo)
* [拆解复杂问题:实现计算器](https://labuladong.gitbook.io/algo)
* [算法就像搭乐高:带你手撸 LRU 算法](https://labuladong.gitbook.io/algo/)
* [拆解复杂问题:实现计算器](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -231,12 +231,129 @@ public Key delMax() {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### javascript
```js
/**
* 最大堆
*/
function left(i) {
return i * 2 + 1;
}
function right(i) {
return i * 2 + 2;
}
function swap(A, i, j) {
const t = A[i];
A[i] = A[j];
A[j] = t;
}
class Heap {
constructor(arr) {
this.data = [...arr];
this.size = this.data.length;
}
/**
* 重构堆
*/
rebuildHeap() {
const L = Math.floor(this.size / 2);
for (let i = L - 1; i >= 0; i--) {
this.maxHeapify(i);
}
}
isHeap() {
const L = Math.floor(this.size / 2);
for (let i = L - 1; i >= 0; i++) {
const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER;
const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER;
const max = Math.max(this.data[i], l, r);
if (max !== this.data[i]) {
return false;
}
return true;
}
}
sort() {
for (let i = this.size - 1; i > 0; i--) {
swap(this.data, 0, i);
this.size--;
this.maxHeapify(0);
}
}
insert(key) {
this.data[this.size++] = key;
if (this.isHeap()) {
return;
}
this.rebuildHeap();
}
delete(index) {
if (index >= this.size) {
return;
}
this.data.splice(index, 1);
this.size--;
if (this.isHeap()) {
return;
}
this.rebuildHeap();
}
/**
* 堆的其他地方都满足性质
* 唯独跟节点,重构堆性质
* @param {*} i
*/
maxHeapify(i) {
let max = i;
if (i >= this.size) {
return;
}
// 求左右节点中较大的序号
const l = left(i);
const r = right(i);
if (l < this.size && this.data[l] > this.data[max]) {
max = l;
}
if (r < this.size && this.data[r] > this.data[max]) {
max = r;
}
// 如果当前节点最大,已经是最大堆
if (max === i) {
return;
}
swap(this.data, i, max);
// 递归向下继续执行
return this.maxHeapify(max);
}
}
module.exports = Heap;
```
======其他语言代码======
\ No newline at end of file
# 二叉搜索树操作集锦
# 二叉搜索树操作集锦
<p align='center'>
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo/)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -28,7 +28,7 @@
**-----------**
通过之前的文章[框架思维](https://labuladong.gitbook.io/algo),二叉树的遍历框架应该已经印到你的脑子里了,这篇文章就来实操一下,看看框架思维是怎么灵活运用,秒杀一切二叉树问题的。
通过之前的文章[框架思维](https://labuladong.gitbook.io/algo/),二叉树的遍历框架应该已经印到你的脑子里了,这篇文章就来实操一下,看看框架思维是怎么灵活运用,秒杀一切二叉树问题的。
二叉树算法的设计的总路线:明确一个节点要做的事情,然后剩下的事抛给框架。
......@@ -301,7 +301,7 @@ void BST(TreeNode root, int target) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -309,7 +309,18 @@ void BST(TreeNode root, int target) {
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
======其他语言代码======
[100.相同的树](https://leetcode-cn.com/problems/same-tree)
[450.删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst)
[701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree)
[700.二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree)
[98.验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree)
### c++
......@@ -347,6 +358,43 @@ public:
};
```
[yanggg1997](https://github.com/yanggg1997)提供第100题C++代码:
``` c++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
// 若当前节点均为空,则此处相同
if(!p && !q) return true;
// 若当前节点在一棵树上有而另一棵树上为空,则两棵树不同
if(!p && q) return false;
if(p && !q) return false;
// 若当前节点在两棵树上均存在。
if(p->val != q->val)
{
return false;
}
else
{
// 向左右子树分别递归判断
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
}
};
```
### python
[ChenjieXu](https://github.com/ChenjieXu)提供第98题Python3代码:
......@@ -399,6 +447,7 @@ class Solution:
return p.val==q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
```
[Edwenc](https://github.com/Edwenc) 提供 leetcode第450题的python3 代码:
```python
......@@ -472,3 +521,200 @@ public boolean isValidBST(TreeNode root) {
return leftRes && rightRes;
}
```
### javascript
1. 如何把二叉树所有的节点中的值加一?
热热身,体会体会二叉树的递归思想。
```js
let plusOne = function(root) {
if (root == null) return;
root.val += 1;
plusOne(root.left);
plusOne(root.right);
}
```
2. 如何判断两棵二叉树是否完全相同?
[100.相同的树](https://leetcode-cn.com/problems/same-tree)
```js
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(p == null && q == null)
return true;
if(p == null || q == null)
return false;
if(p.val != q.val)
return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
};
```
零、判断 BST 的合法性
[98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)
```js
/**
* 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) {
return helper(root, null, null);
};
var helper = function (root, min, max) {
if (root == null) return true;
if (min != null && root.val <= min.val) return false;
if (max != null && root.val >= max.val) return false;
return helper(root.left, min, root)
&& helper(root.right, root, max);
}
```
一、在BST 中查找一个数是否存在
[700.二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree)
```js
/**
* 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, target) {
if (root == null) return null;
if (root.val === target)
return root;
if (root.val < target)
return searchBST(root.right, target);
if (root.val > target)
return searchBST(root.left, target);
// root 该做的事做完了,顺带把框架也完成了,妙
};
```
二、在 BST 中插入一个数
[701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree)
```js
/**
* 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) return new TreeNode(val);
// if (root.val == val)
// BST 中一般不会插入已存在元素
if (root.val < val)
root.right = insertIntoBST(root.right, val);
if (root.val > val)
root.left = insertIntoBST(root.left, val);
return root;
};
```
三、在 BST 中删除一个数
[450.删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst)
```js
/**
* 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) return null
// if key > root.val, delete node in root.right. Otherwise delete node in root.left.
if (key > root.val) {
const rightNode = deleteNode(root.right, key)
root.right = rightNode
return root
} else if (key < root.val) {
const leftNode = deleteNode(root.left, key)
root.left = leftNode
return root
} else {
// now root.val === key
if (!root.left) {
return root.right
}
if (!root.right) {
return root.left
}
// 将删除元素的左下方元素替代删除元素;
// 将左下方元素的右侧最下方子元素衔接删除元素的右下方子元素;
const rightChild = root.right
let newRightChild = root.left
while (newRightChild.right) {
newRightChild = newRightChild.right
}
newRightChild.right = rightChild
return root.left
}
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo)
* [回溯算法解题套路框架](https://labuladong.gitbook.io/algo/)
* [动态规划解题套路框架](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -173,16 +173,23 @@ vector<int> nextGreaterElements(vector<int>& nums) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[496.下一个更大元素I](https://leetcode-cn.com/problems/next-greater-element-i)
[503.下一个更大元素II](https://leetcode-cn.com/problems/next-greater-element-ii)
[739.每日温度](https://leetcode-cn.com/problems/daily-temperatures/)
### java
[ZakAnun](https://github.com/ZakAnun) 提供代码
......@@ -239,6 +246,7 @@ public int[] nextGreaterElement(int[] nums1, int[] nums2) {
}
```
[ZakAnun](https://github.com/ZakAnun) 提供代码
```java
// 739. Daily Temperatures
......@@ -258,4 +266,335 @@ class Solution {
return ans;
}
}
```
\ No newline at end of file
```
[JiangangZhao](https://github.com/JiangangZhao)提供【503.下一个更大元素II】【java】
```java
class Solution {
public int[] nextGreaterElements(int[] nums) {
//数组长度
int n = nums.length;
//逻辑拼接,数组长度翻倍
int len = n*2 - 1;
//存储结果数组
int[] res = new int[n];
//存放索引,不是元素
LinkedList<Integer> s = new LinkedList<>();
//从前往后遍历
for (int i = 0; i < len; ++i) {
//索引要取模
int val = nums[i % n];
//当前元素比栈顶元素大,即是栈顶元素的下一个更大的元素
while (!s.isEmpty() && val > nums[s.peek()]) {
res[s.pop()] = val;
}
//i<n时入栈
if (i < n) {
s.push(i);
}
}
//栈中剩余的索引不存在下一个更大的元素,赋值-1
while (!s.isEmpty()) {
res[s.pop()] = -1;
}
return res;
}
}
```
### javascript
单调栈模板
[496.下一个更大元素I](https://leetcode-cn.com/problems/next-greater-element-i)
这里需要用一个map记录nums2中各项的下一个更大值,为何?注意读题。
- nums1和nums2中所有整数 互不相同
- nums1 中的所有整数同样出现在 nums2 中
如果还是用数组的话,num1中元素在nums2中的位置并不好找,所以这里使用map来维护。
其它核心思想和上文中的大抵相同。值得注意的是,入栈顺序可以有正着入栈和倒着入栈,顺序不同,维护的动作也不同,详见下文。
正着入栈如下。
```js
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var nextGreaterElement = function (nums1, nums2) {
let len1 = nums1.length;
let len2 = nums2.length;
// base case
if (len1 < 1 || len2 < 1 || len1 > len2) {
return [];
}
let res = new Array(len1); // 存放答案的数组
let stack = [];
let map = {};
// 启动条件
stack.push(nums2[0]);
// 右边数字入栈
for (let j = 1; j < len2; j++) {
let currNum = nums2[j];
// 单调栈栈顶元素和当前数组元素作比较
// 找到下一个更大元素
while (stack.length !== 0 && currNum > stack[stack.length - 1]) {
map[stack.pop()] = currNum;
}
stack.push(currNum);
}
// 栈不为空 这些元素都是找不到下一个更大值的
while (stack.length !== 0) {
map[stack.pop()] = -1;
}
for (let i = 0; i < len1; i++) {
res[i] = map[nums1[i]];
}
return res;
};
```
接下来是倒着入栈,就是上文中提到的排队思路。
抽象思路,nums2看做排队找后面第一个比自己高的高个子。
```js
var nextGreaterElement = function(nums1, nums2) {
// 把此类问题比作排队看后面第一个比自己高的
// 从后面开始遍历往前面看,就能很好的避免不知道后面什么情况了
let stack = []
let res = []
let map = new Map()
for(let i = nums2.length - 1; i >= 0; i--){
// 矮个子起开,要你也没用,反正看不见你
while(stack.length && nums2[i] >= stack[stack.length - 1]){
stack.pop()
}
//有比我个子高的吗?有就是你了,没有就是-1
map.set(nums2[i], stack.length ? stack[stack.length - 1] : -1)
stack.push(nums2[i])
}
nums1.forEach(item => {
res.push(map.get(item))
})
return res;
};
```
解决了这道题,后面的题就很容易理解了。在这道题的基础上,让单调栈中存放的元素是下标而不是值,因为有的题目需要根据下标计算,这样泛化性更好。
正着入栈,存储下标。
```js
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var nextGreaterElement = function (nums1, nums2) {
let len1 = nums1.length;
let len2 = nums2.length;
// base case
if (len1 < 1 || len2 < 1 || len1 > len2) {
return [];
}
let map = new Map()
let res = []; // 存放结果
let stack = []
for (let i = 0; i < len2; i++) {
//栈顶元素存在,并且当前的元素大于栈顶
while (stack.length && nums2[i] > nums2[stack[stack.length - 1]]) {
// 关键步骤1
let index = stack.pop();
map.set(nums2[index], nums2[i])
}
// 关键步骤2 下标入栈
stack.push(i)
}
//栈内还有元素,说明后面没有比自己小的了
while (stack.length) {
let index = stack.pop();
map.set(nums2[index], -1)
}
// 最后导出结果
nums1.forEach(item => {
res.push(map.get(item))
})
return res
};
```
倒着入栈,存储下标。
```js
// 存储的是下标
var nextGreaterElement = function (nums1, nums2) {
// 把此类问题比作排队看后面第一个比自己高的
// 从后面开始遍历往前面看,就能很好的避免不知道后面什么情况了
let stack = []
let res = []
let map = new Map()
for (let i = nums2.length - 1; i >= 0; i--) {
// 矮个子起开,要你也没用,反正看不见你
while (stack.length && nums2[i] >= nums2[stack[stack.length - 1]]) {
stack.pop()
}
//有比我个子高的吗?有就是你了,没有就是-1
map.set(nums2[i], stack.length ? nums2[stack[stack.length - 1]] : -1)
// 关键步骤:存储的是下标
stack.push(i)
}
nums1.forEach(item => {
res.push(map.get(item))
})
return res;
};
```
进一步而谈,其实map也可以转化成使用index对应index,不过这种情况的题比较少见,了解即可,不必抛开框架深入追究细节。
```js
nums1:[4,1,2]
nums2:[1,3,4,2]
直接num1的value对nums2的value 前提value唯一
{
4: -1
1: 3
2: -1
}
num1的index对nums2的index
这里也可以用数组来做自由发挥吧,这里只提供一些思路
{
0-1
11
2-1
}
```
**[503.下一个更大元素II](https://leetcode-cn.com/problems/next-greater-element-ii)**
因为是环形数组,所以最后一个数的下一个最大的数不是-1,而是要把再数组从头开始遍历到末尾得出这个数,可以把数组扩大两倍解决。
- 把数组扩大两倍逆序遍历依次放入栈中,栈中的栈顶元素代表下一个迭代的数的后面第一个最大的数;
- 当前数比栈顶元素大时,出栈;
- 此时栈有值时,栈顶元素即为当前数的下一个最大的数,把它存入结果数组对应的下标中;
- 把当前数入栈
这里用的和上文一样,还是反着入栈,相信读者可以自己悟出正着入栈怎么写了吧。
```js
/**
* @param {number[]} nums
* @return {number[]}
*/
var nextGreaterElements = function (nums) {
let n = nums.length;
let res = [];
let stack = [];
// 假装这个数组长度翻倍了
for (let i = 2 * n - 1; i >= 0; i--) {
// 索引要求模,其他的和模板一样
while (stack.length && stack[stack.length - 1] <= nums[i % n])
stack.pop();
res[i % n] = stack.length ? stack[stack.length - 1] : -1;
stack.push(nums[i % n]);
}
return res;
};
```
**[739.每日温度](https://leetcode-cn.com/problems/daily-temperatures/)**
很简单,就是第一个next greater的变形而已,存储的是索引。
倒着入栈。
```js
/**
* @param {number[]} T
* @return {number[]}
*/
var dailyTemperatures = function (T) {
let res = new Array(T.length).fill(0);
// 这里放元素索引,而不是元素
let stack = [];
/* 单调栈模板 */
for (let i = T.length - 1; i >= 0; i--) {
while (stack.length !== 0 && T[stack[stack.length - 1]] <= T[i]) {
stack.pop();
}
// 得到索引间距
res[i] = stack.length === 0 ? 0 : (stack[stack.length - 1] - i);
// 将索引入栈,而不是元素
stack.push(i);
}
return res;
};
```
正着入栈,es6写法。
```js
const dailyTemperatures = (T) => {
const res = new Array(T.length).fill(0);
for (let i = 0; i < T.length; i++) {
for (let j = i + 1; j < T.length; j++) {
if (T[j] > T[i]) {
res[i] = j - i;
break;
}
}
}
return res;
}
```
部分做题规律如下,仅供做题套路参考,实际可以自由发挥。
当前项向左找第一个比自己大的位置:从左向右维护一个单调递减栈
当前项向左找第一个比自己小的位置:从左向右维护一个单调递增栈
当前项向右找第一个比自己大的位置:从右向左维护一个单调递减栈
当前项向右找第一个比自己小的位置:从右向左维护一个单调递增栈
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [几个反直觉的概率问题](https://labuladong.gitbook.io/algo)
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo)
* [几个反直觉的概率问题](https://labuladong.gitbook.io/algo/)
* [Git/SQL/正则表达式的在线练习平台](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -202,17 +202,18 @@ vector<int> maxSlidingWindow(vector<int>& nums, int k) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
### python3
[239.滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum)
### python
[SCUHZS](ttps://github.com/brucecat)提供
......@@ -312,3 +313,62 @@ class Solution {
}
}
```
### javascript
这里用js实现的思路和上文中一样,都是自己实现一个单调队列,注意,这里的单调队列和优先级队列(大小堆)不是同一个概念。
```js
let MonotonicQueue = function () {
// 模拟一个deque双端队列
this.data = [];
// 在队尾添加元素 n
this.push = function (n) {
while (this.data.length !== 0 && this.data[this.data.length - 1] < n)
this.data.pop();
this.data.push(n);
}
// 返回当前队列中的最大值
this.max = function () {
return this.data[0];
};
// 队头元素如果是 n,删除它
this.pop = function (n) {
if (this.data.length !== 0 && this.data[0] === n)
this.data.shift();
};
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function (nums, k) {
let window = new MonotonicQueue();
let res = []
for (let i = 0; i < nums.length; i++) {
if (i < k - 1) { //先把窗口的前 k - 1 填满
window.push(nums[i]);
} else {
// 窗口开始向前滑动
window.push(nums[i]);
res.push(window.max());
window.pop(nums[i - k + 1]);
// nums[i - k + 1] 就是窗口最后的元素
}
}
return res;
};
```
......@@ -12,8 +12,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo/)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -26,7 +26,7 @@
**-----------**
我们最终要实现的计算器功能如下:
1、输入一个字符串,可以包含`+ - * /`、数字、括号以及空格,你的算法返回运算结果。
2、要符合运算法则,括号的优先级最高,先乘除后加减。
......@@ -298,7 +298,7 @@ def calculate(s: str) -> int:
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -306,4 +306,108 @@ def calculate(s: str) -> int:
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[224.基本计算器](https://leetcode-cn.com/problems/basic-calculator)
[227.基本计算器II](https://leetcode-cn.com/problems/basic-calculator-ii)
[772.基本计算器III](https://leetcode-cn.com/problems/basic-calculator-iii)
### javascript
说实话,用js的话,你完全可以妙用eval来秒杀这类题。
```js
var calculate = function(s) {
var Fn = Function;
return new Fn('return ' + s)()
};
```
不过相信看该作者文章的读者,都是奔着学习框架思想来的,下面用js来还原上文代码。
[224.基本计算器](https://leetcode-cn.com/problems/basic-calculator)
```js
/**
* @param {string} s
* @return {number}
*/
var calculate = function(s) {
var q = [], n = '', f = '+', a = typeof s === 'string' ? Array.from(s).reverse() : s
while(a.length || n) {
var p = a.pop()
if (p === ' ') continue
if (p === '(') {
n = calculate(a)
} else if (/\D/.test(p)) {
switch (f) {
case '+':
q.push(n)
break;
case '-':
q.push(-n)
break;
case '*':
q.push(q.pop() * n)
break;
case '/':
q.push(q.pop() / n | 0)
}
if (p === ')') break
f = p, n = ''
} else n += p
}
return q.reduce((p, v) => p + (v | 0), 0)
};
```
[227.基本计算器II](https://leetcode-cn.com/problems/basic-calculator-ii)
- 从左向右遍历字符串,符号标识`f`,初始`+`
- `空格`,忽视。`数字`,当字符串拼接。`非数字`,根据`f`运算
- `+``-`入栈,`*``/`和栈`第一位`运算,结果入栈
- 返回栈的累加和
```js
/**
* @param {string} s
* @return {number}
*/
var calculate = function(s) {
var q = [], n = '', f = '+'
for (var i = 0; i < s.length || n; i++) {
if (s[i] === ' ') continue
if (/\D/.test(s[i])) {
switch (f) {
case '+':
q.push(n)
break;
case '-':
q.push(-n)
break;
case '*':
q.push(q.pop() * n)
break;
case '/':
q.push(q.pop() / n | 0)
}
f = s[i], n = ''
} else n += s[i]
}
return q.reduce((p, v) => p + (v | 0), 0)
};
```
[772.基本计算器III](https://leetcode-cn.com/problems/basic-calculator-iii)
要会员才能做这道题,打扰了。
```js
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [面试官:你说对MySQL事务很熟?那我问你10个问题](https://labuladong.gitbook.io/algo)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo)
* [面试官:你说对MySQL事务很熟?那我问你10个问题](https://labuladong.gitbook.io/algo/)
* [一行代码就能解决的算法题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -294,16 +294,19 @@ PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[355.设计推特](https://leetcode-cn.com/problems/design-twitter)
### c++
[happy-yuxuan](https://github.com/happy-yuxuan) 提供 C++ 代码:
```c++
......@@ -419,4 +422,178 @@ public:
userMap[followerId]->unfollow(followeeId);
}
};
```
\ No newline at end of file
```
### javascript
由于js没有大小堆相关的内置库,所以可以考虑使用其它的方式类似实现链表+优先级队列的功能。
followMap:用户关注列表, 用 Set 数据类型不需要去处理重复数据,取消关注(从列表删除)也会更方便;
postMap:用户推文列表;
latestPostId:推文的自增id,用于后续获取推文列表时排序;
- 在 postTweet 函数中,将新增的 推文 { tweetId, postTime } 放到列表的最前面,并确保 latestPostId 自增;
- 在 follow 函数中,先检查 followMap 是否已存在 followerId 数据,若已存在,直接 add(followeeId), 若不存在,新增 new Set([followeeId]);
- 在 unfollow 函数中,直接检查是否存在 followMap[followerId] 列表,若存在直接delete(followeeId);
- 在 getNewsFeed 函数中,因为要取用户和用户关注的用户的最新 10 条推文,所以只需要把这些用户的前10条推文取出来,再根据postTime去排序,然后取最新10条推文。
```js
/**
* Initialize your data structure here.
*/
var Twitter = function() {
this.followMap = {}
this.postMap = new Map()
this.latestPostId = 0
}
/**
* Compose a new tweet.
* @param {number} userId
* @param {number} tweetId
* @return {void}
*/
Twitter.prototype.postTweet = function(userId, tweetId) {
const postTime = this.latestPostId++
let tweeList = [{ tweetId, postTime }]
if (this.postMap.has(userId)) {
tweeList = tweeList.concat(this.postMap.get(userId))
}
this.postMap.set(userId, tweeList)
}
/**
* Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
* @param {number} userId
* @return {number[]}
*/
Twitter.prototype.getNewsFeed = function(userId) {
const followeeIdList = this.followMap[userId] ? [...this.followMap[userId]] : []
const tweeList = []
const userIds = [...new Set(followeeIdList.concat([userId]))]
userIds.forEach(uid => {
if (this.postMap.has(uid)) {
tweeList.push(...this.postMap.get(uid).slice(0, 10))
}
})
tweeList.sort((a, b) => b.postTime - a.postTime)
return tweeList.slice(0, 10).map(item => item.tweetId)
}
/**
* Follower follows a followee. If the operation is invalid, it should be a no-op.
* @param {number} followerId
* @param {number} followeeId
* @return {void}
*/
Twitter.prototype.follow = function(followerId, followeeId) {
if (this.followMap[followerId]) {
this.followMap[followerId].add(followeeId)
} else {
this.followMap[followerId] = new Set([followeeId])
}
}
/**
* Follower unfollows a followee. If the operation is invalid, it should be a no-op.
* @param {number} followerId
* @param {number} followeeId
* @return {void}
*/
Twitter.prototype.unfollow = function(followerId, followeeId) {
if (this.followMap[followerId]) {
this.followMap[followerId].delete(followeeId)
}
}
```
### python
```python
import heapq
class Tweet:
def __init__(self, tid: int, time: int) -> None:
self.tid = tid
self.time = time
self.next = None
class User:
def __init__(self, uid: int):
self.uid = uid
self.following = set()
self.tweetlst = None
self.follow(uid)
def post(self, tid: int, time: int) -> None:
tweet = Tweet(tid, time)
tweet.next = self.tweetlst
self.tweetlst = tweet
def follow(self, uid: int) -> None:
if uid not in self.following:
self.following.add(uid)
def unfollow(self, uid: int) -> None:
# one cannot unfollow itself
if uid != self.uid and uid in self.following:
self.following.remove(uid)
class Twitter:
def __init__(self):
"""
Initialize your data structure here.
"""
self.id2user = {}
self.timestamp = 0
def postTweet(self, userId: int, tweetId: int) -> None:
"""
Compose a new tweet.
"""
if userId not in self.id2user: self.id2user[userId] = User(userId)
user = self.id2user[userId]
user.post(tweetId, self.timestamp)
self.timestamp += 1
def getNewsFeed(self, userId: int) -> List[int]:
"""
Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
"""
heap, user = [], self.id2user.get(userId)
if user:
for uid in user.following:
tweets = self.id2user[uid].tweetlst
while tweets:
heap.append(tweets)
tweets = tweets.next
return [twt.tid for twt in heapq.nlargest(10, heap, key= lambda twt: twt.time)]
else: return []
def follow(self, followerId: int, followeeId: int) -> None:
"""
Follower follows a followee. If the operation is invalid, it should be a no-op.
"""
if followerId not in self.id2user:
self.id2user[followerId] = User(followerId)
if followeeId not in self.id2user:
self.id2user[followeeId] = User(followeeId)
self.id2user[followerId].follow(followeeId)
def unfollow(self, followerId: int, followeeId: int) -> None:
"""
Follower unfollows a followee. If the operation is invalid, it should be a no-op.
"""
if followerId in self.id2user:
self.id2user[followerId].unfollow(followeeId)
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo)
* [回溯算法最佳实践:括号生成](https://labuladong.gitbook.io/algo)
* [特殊数据结构:单调队列](https://labuladong.gitbook.io/algo/)
* [回溯算法最佳实践:括号生成](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -210,48 +210,194 @@ ListNode reverseBetween(ListNode head, int m, int n) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[92.反转链表II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)
### c++
[shilei](https://github.com/ShileiGuo) 提供C++解法代码:
思想:
1.head表示需要反转的头节点,pre表示需要反转头节点的前驱节点
2.对于从m到n的节点反转,需要反转n-m次,将head的next节点移动到需要反转链表部分的首部,需要反转链表部分剩余节点依旧保持相对顺序即可
3.示例 当m=2, n=5时
第一次反转:1(pre) 2(head) 3(next) 4 5 反转为 1 3 2 4 5
第二次反转:1(pre) 3 2(head) 4(next) 5 反转为 1 4 3 2 5
第三次发转:1(pre) 4 3 2(head) 5(next) 反转为 1 5 4 3 2
```CPP
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
//初始化哨兵节点
ListNode* dummy=new ListNode(-1);
//初始化待反转区间的前一个节点
ListNode* pre=dummy;
//哨兵节点下一个节点指向head头节点
dummy->next=head;
//获取待反转节点的前一个节点
for(int i=0;i<m-1;i++)
pre=pre->next;
//获取待反转节点的第一个节点
head=pre->next;
//迭代反转n-m次,将head的next节点移动到需要反转链表部分的首部
for(int i=m;i<n;i++){
ListNode* t=head->next;
head->next=t->next;
t->next=pre->next;
pre->next=t;
}
//返回哨兵节点
return dummy->next;
}
};
```
### python
[DiamondI](https://github.com/DiamondI) 提供python3版本代码:
思路:递归。时间复杂度为O(n),由于递归调用需要借助栈的空间,因此空间复杂度亦为O(n)。
```python3
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def __init__(self):
self.__successor = None
def __init__(self):
self.__successor = None
def __reverseN(self, head: ListNode, n: int) -> ListNode:
if n == 1:
# 记录第 n + 1 个节点
self.__successor = head.next;
return head;
# 以 head.next 为起点,需要反转前 n - 1 个节点
last = self.__reverseN(head.next, n - 1);
head.next.next = head;
# 让反转之后的 head 节点和后面的节点连起来
head.next = self.__successor;
return last;
if n == 1:
# 记录第 n + 1 个节点
self.__successor = head.next;
return head;
# 以 head.next 为起点,需要反转前 n - 1 个节点
last = self.__reverseN(head.next, n - 1);
head.next.next = head;
# 让反转之后的 head 节点和后面的节点连起来
head.next = self.__successor;
return last;
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
# base case
if m == 1:
return self.__reverseN(head, n);
# 前进到反转的起点触发 base case
head.next = self.reverseBetween(head.next, m - 1, n - 1);
# base case
if m == 1:
return self.__reverseN(head, n);
# 前进到反转的起点触发 base case
head.next = self.reverseBetween(head.next, m - 1, n - 1);
return head;
```
### javascript
**递归反转整个链表**
[206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)
```js
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
if (head == null || head.next == null) {
return head;
}
const last = reverseList(head.next);
head.next.next = head;
head.next = null;
return last;
};
```
**反转链表前 N 个节点**
这题貌似在leetcode上没找到,就不贴链接了。
```js
let successor = null; // 后驱节点
let reverseListN = function(head, n) {
if (n === 1) {
// 记录第 n + 1 个节点
successor = head.next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
let last = reverseListN(head.next, n - 1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = successor;
return last;
};
```
**反转链表的一部分**
现在解决我们最开始提出的问题,给一个索引区间 `[m,n]`(索引从 1 开始),仅仅反转区间中的链表元素。
```js
let successor = null; // 后驱节点
let reverseListN = function(head, n) {
if (n === 1) {
// 记录第 n + 1 个节点
successor = head.next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
let last = reverseListN(head.next, n - 1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = successor;
return last;
};
/**
* @param {ListNode} head
* @param {number} m
* @param {number} n
* @return {ListNode}
*/
let reverseBetween = function(head, m, n) {
// base case
if (m === 1) {
return reverseListN(head, n);
}
// 前进到反转的起点触发 base case
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo)
* [高性能短链设计](https://labuladong.gitbook.io/algo)
* [手把手带你刷二叉树(第三期)](https://labuladong.gitbook.io/algo/)
* [高性能短链设计](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -222,7 +222,7 @@ public boolean empty() {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -230,4 +230,147 @@ public boolean empty() {
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[232.用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks)
[225.用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues)
### javascript
[232.用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks)
```js
/**
* Initialize your data structure here.
*/
var MyQueue = function () {
this.inStack = [];
this.outStack = [];
};
/**
* Push element x to the back of queue.
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function (x) {
this.inStack.push(x);
};
/**
* 检查outStack
*/
MyQueue.prototype.checkOutStack = function () {
// // 把 inStack 元素压入 outStack
if (!this.outStack.length) {
while (this.inStack.length) {
this.outStack.push(this.inStack.pop());
}
}
};
/**
* Removes the element from in front of queue and returns that element.
* @return {number}
*/
MyQueue.prototype.pop = function () {
this.checkOutStack();
return this.outStack.pop();
};
/**
* Get the front element.
* @return {number}
*/
MyQueue.prototype.peek = function () {
this.checkOutStack();
return this.outStack[this.outStack.length - 1];
};
/**
* Returns whether the queue is empty.
* @return {boolean}
*/
MyQueue.prototype.empty = function () {
return (!this.inStack.length && !this.outStack.length);
};
```
[225.用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues)
```js
// --------------------------------
/**
* Initialize your data structure here.
*/
var MyStack = function () {
this.q = []
this.top_elem = 0;
};
/**
* Push element x onto stack.
* @param {number} x
* @return {void}
*/
MyStack.prototype.push = function (x) {
// x 是队列的队尾,是栈的栈顶
this.q.push(x);
this.top_elem = x;
};
/**
* Removes the element on top of the stack and returns that element.
* @return {number}
*/
MyStack.prototype.pop = function () {
let size = this.q.length;
// 留下队尾 2 个元素
while (size > 2) {
// peek()都是用来返回队列的头元素, 相当于[0]
// poll()都是用来从队列头部删除一个元素 相当于js的shift()
this.q.push(this.q.shift());
size--;
}
// 记录新的队尾元素
this.top_elem = this.q[0];
this.q.push(this.q.shift());
// 删除之前的队尾元素
return this.q.shift();
};
/**
* Get the top element.
* @return {number}
*/
MyStack.prototype.top = function () {
return this.top_elem;
};
/**
* Returns whether the stack is empty.
* @return {boolean}
*/
MyStack.prototype.empty = function () {
return !this.q.length;
};
/**
* Your MyStack object will be instantiated and called as such:
* var obj = new MyStack()
* obj.push(x)
* var param_2 = obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.empty()
*/
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [如何高效进行模幂运算](https://labuladong.gitbook.io/algo)
* [经典动态规划:0-1 背包问题](https://labuladong.gitbook.io/algo)
* [如何高效进行模幂运算](https://labuladong.gitbook.io/algo/)
* [经典动态规划:0-1 背包问题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -126,7 +126,7 @@ image[x][y] = newColor;
完全 OK,这也是处理「图」的一种常用手段。不过对于此题,不用开数组,我们有一种更好的方法,那就是回溯算法。
前文 [回溯算法框架套路](https://labuladong.gitbook.io/algo)讲过,这里不再赘述,直接套回溯算法框架:
前文 [回溯算法框架套路](https://labuladong.gitbook.io/algo/)讲过,这里不再赘述,直接套回溯算法框架:
```java
void fill(int[][] image, int x, int y,
......@@ -231,7 +231,7 @@ int fill(int[][] image, int x, int y,
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -239,4 +239,89 @@ int fill(int[][] image, int x, int y,
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[733.图像渲染](https://leetcode-cn.com/problems/flood-fill)
### javascript
**BFS**
从起始像素向上下左右扩散,只要相邻的点存在并和起始点颜色相同,就染成新的颜色,并继续扩散。
借助一个队列去遍历节点,考察出列的节点,带出满足条件的节点入列。已经染成新色的节点不会入列,避免重复访问节点。
时间复杂度:O(n)。空间复杂度:O(n)
```js
const floodFill = (image, sr, sc, newColor) => {
const m = image.length;
const n = image[0].length;
const oldColor = image[sr][sc];
if (oldColor == newColor) return image;
const fill = (i, j) => {
if (i < 0 || i >= m || j < 0 || j >= n || image[i][j] != oldColor) {
return;
}
image[i][j] = newColor;
fill(i - 1, j);
fill(i + 1, j);
fill(i, j - 1);
fill(i, j + 1);
};
fill(sr, sc);
return image;
};
```
**DFS**
思路与上文相同。
```js
/**
* @param {number[][]} image
* @param {number} sr
* @param {number} sc
* @param {number} newColor
* @return {number[][]}
*/
let floodFill = function (image, sr, sc, newColor) {
let origColor = image[sr][sc];
fill(image, sr, sc, origColor, newColor);
return image;
}
let fill = function (image, x, y, origColor, newColor) {
// 出界:超出边界索引
if (!inArea(image, x, y)) return;
// 碰壁:遇到其他颜色,超出 origColor 区域
if (image[x][y] !== origColor) return;
// 已探索过的 origColor 区域
if (image[x][y] === -1) return;
// 打标记 避免重复
image[x][y] = -1;
fill(image, x, y + 1, origColor, newColor);
fill(image, x, y - 1, origColor, newColor);
fill(image, x - 1, y, origColor, newColor);
fill(image, x + 1, y, origColor, newColor);
// un choose:将标记替换为 newColor
image[x][y] = newColor;
}
let inArea = function (image, x, y) {
return x >= 0 && x < image.length
&& y >= 0 && y < image[0].length;
}
```
......@@ -11,14 +11,14 @@
![](../pictures/souyisou.png)
相关推荐:
* [手把手带你刷二叉树(第一期)](https://labuladong.gitbook.io/algo)
* [二分查找详解](https://labuladong.gitbook.io/algo)
* [手把手带你刷二叉树(第一期)](https://labuladong.gitbook.io/algo/)
* [二分查找详解](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions)
[990.等式方程的可满足性](https://leetcode-cn.com/problems/surrounded-regions)
[990.等式方程的可满足性](https://leetcode-cn.com/problems/satisfiability-of-equality-equations)
[261.以图判树](https://leetcode-cn.com/problems/graph-valid-tree/)
......@@ -244,16 +244,25 @@ boolean equationsPossible(String[] equations) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions)
[990.等式方程的可满足性](https://leetcode-cn.com/problems/satisfiability-of-equality-equations)
[261.以图判树](https://leetcode-cn.com/problems/graph-valid-tree/)
### java
第261题的Java代码(提供:[LEODPEN](https://github.com/LEODPEN)
```java
......@@ -327,3 +336,237 @@ class Solution {
}
}
```
### javascript
[130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions)
```js
class UF {
// 记录连通分量
count;
// 节点 x 的根节点是 parent[x]
parent;
// 记录树的“重量”
size;
constructor(n) {
// 一开始互不连通
this.count = n;
// 父节点指针初始指向自己
this.parent = new Array(n);
this.size = new Array(n);
for (let i = 0; i < n; i++) {
this.parent[i] = i;
this.size[i] = 1;
}
}
/* 返回某个节点 x 的根节点 */
find(x) {
// 根节点的 parent[x] == x
while (this.parent[x] !== x) {
// 进行路径压缩
this.parent[x] = this.parent[this.parent[x]];
x = this.parent[x];
}
return x;
}
/* 将 p 和 q 连接 */
union(p, q) {
// 如果某两个节点被连通,则让其中的(任意)
// 一个节点的根节点接到另一个节点的根节点上
let rootP = this.find(p);
let rootQ = this.find(q);
if (rootP === rootQ) return;
// 小树接到大树下面,较平衡
if (this.size[rootP] > this.size[rootQ]) {
this.parent[rootQ] = rootP;
this.size[rootP] += this.size[rootQ];
} else {
this.parent[rootP] = rootQ;
this.size[rootQ] += this.size[rootP];
}
this.count--; // 两个分量合二为一
}
/* 判断 p 和 q 是否连通 */
connected(p, q) {
let rootP = this.find(p);
let rootQ = this.find(q);
return rootP === rootQ;
};
/* 返回图中有多少个连通分量 */
getCount() {
return this.count;
};
}
/**
* @param {[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
let solve = function (board) {
if (board.length === 0) return;
let m = board.length;
let n = board[0].length;
// 给 dummy 留一个额外位置
let uf = new UF(m * n + 1);
let dummy = m * n;
// 将首列和末列的 O 与 dummy 连通
for (let i = 0; i < m; i++) {
if (board[i][0] === 'O')
uf.union(i * n, dummy);
if (board[i][n - 1] === 'O')
uf.union(i * n + n - 1, dummy);
}
// 将首行和末行的 O 与 dummy 连通
for (let j = 0; j < n; j++) {
if (board[0][j] === 'O')
uf.union(j, dummy);
if (board[m - 1][j] === 'O')
uf.union(n * (m - 1) + j, dummy);
}
// 方向数组 d 是上下左右搜索的常用手法
let d = [[1, 0], [0, 1], [0, -1], [-1, 0]];
for (let i = 1; i < m - 1; i++)
for (let j = 1; j < n - 1; j++)
if (board[i][j] === 'O')
// 将此 O 与上下左右的 O 连通
for (let k = 0; k < 4; k++) {
let x = i + d[k][0];
let y = j + d[k][1];
if (board[x][y] === 'O')
uf.union(x * n + y, i * n + j);
}
// 所有不和 dummy 连通的 O,都要被替换
for (let i = 1; i < m - 1; i++)
for (let j = 1; j < n - 1; j++)
if (!uf.connected(dummy, i * n + j))
board[i][j] = 'X';
}
```
[990.等式方程的可满足性](https://leetcode-cn.com/problems/surrounded-regions)
需要注意的点主要为js字符与ASCII码互转。
在java、c这些语言中,字符串直接相减,得到的是ASCII码的差值,结果为整数;而js中`"a" - "b"`的结果为NaN,所以需要使用`charCodeAt(index)`方法来获取字符的ASCII码,index不填时,默认结果为第一个字符的ASCII码。
```js
class UF {
// 记录连通分量
count;
// 节点 x 的根节点是 parent[x]
parent;
// 记录树的“重量”
size;
constructor(n) {
// 一开始互不连通
this.count = n;
// 父节点指针初始指向自己
this.parent = new Array(n);
this.size = new Array(n);
for (let i = 0; i < n; i++) {
this.parent[i] = i;
this.size[i] = 1;
}
}
/* 返回某个节点 x 的根节点 */
find(x) {
// 根节点的 parent[x] == x
while (this.parent[x] !== x) {
// 进行路径压缩
this.parent[x] = this.parent[this.parent[x]];
x = this.parent[x];
}
return x;
}
/* 将 p 和 q 连接 */
union(p, q) {
// 如果某两个节点被连通,则让其中的(任意)
// 一个节点的根节点接到另一个节点的根节点上
let rootP = this.find(p);
let rootQ = this.find(q);
if (rootP === rootQ) return;
// 小树接到大树下面,较平衡
if (this.size[rootP] > this.size[rootQ]) {
this.parent[rootQ] = rootP;
this.size[rootP] += this.size[rootQ];
} else {
this.parent[rootP] = rootQ;
this.size[rootQ] += this.size[rootP];
}
this.count--; // 两个分量合二为一
}
/* 判断 p 和 q 是否连通 */
connected(p, q) {
let rootP = this.find(p);
let rootQ = this.find(q);
return rootP === rootQ;
};
/* 返回图中有多少个连通分量 */
getCount() {
return this.count;
};
}
/**
* @param {string[]} equations
* @return {boolean}
*/
let equationsPossible = function (equations) {
// 26 个英文字母
let uf = new UF(26);
// 先让相等的字母形成连通分量
for (let eq of equations) {
if (eq[1] === '=') {
let x = eq[0];
let y = eq[3];
// 'a'.charCodeAt() 为 97
uf.union(x.charCodeAt(0) - 97, y.charCodeAt(0) - 97);
}
}
// 检查不等关系是否打破相等关系的连通性
for (let eq of equations) {
if (eq[1] === '!') {
let x = eq[0];
let y = eq[3];
// 如果相等关系成立,就是逻辑冲突
if (uf.connected(x.charCodeAt(0) - 97, y.charCodeAt(0) - 97))
return false;
}
}
return true;
};
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [一文秒杀四道原地修改数组的算法题](https://labuladong.gitbook.io/algo)
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo)
* [一文秒杀四道原地修改数组的算法题](https://labuladong.gitbook.io/algo/)
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -309,7 +309,7 @@ Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -317,4 +317,138 @@ Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
### javascript
```js
class UF {
// 记录连通分量
count;
// 节点 x 的根节点是 parent[x]
parent;
constructor(n) {
// 一开始互不连通
this.count = n;
// 父节点指针初始指向自己
this.parent = new Array(n);
for (let i = 0; i < n; i++)
this.parent[i] = i;
}
/* 返回某个节点 x 的根节点 */
find(x) {
// 根节点的 parent[x] == x
while (this.parent[x] !== x)
x = this.parent[x];
return x;
}
/* 将 p 和 q 连接 */
union(p, q) {
// 如果某两个节点被连通,则让其中的(任意)
// 一个节点的根节点接到另一个节点的根节点上
let rootP = this.find(p);
let rootQ = this.find(q);
if (rootP === rootQ) return;
// 将两棵树合并为一棵
parent[rootP] = rootQ;
// parent[rootQ] = rootP 也一样
count--; // 两个分量合二为一
}
/* 判断 p 和 q 是否连通 */
connected(p, q) {
let rootP = this.find(p);
let rootQ = this.find(q);
return rootP === rootQ;
};
/* 返回图中有多少个连通分量 */
getCount() {
return this.count;
};
}
```
引入size属性,更好地平衡森林。
```js
class UF {
// 记录连通分量
count;
// 节点 x 的根节点是 parent[x]
parent;
// 记录树的“重量”
size;
constructor(n) {
// 一开始互不连通
this.count = n;
// 父节点指针初始指向自己
this.parent = new Array(n);
this.size = new Array(n);
for (let i = 0; i < n; i++) {
this.parent[i] = i;
this.size[i] = 1;
}
}
/* 返回某个节点 x 的根节点 */
find(x) {
// 根节点的 parent[x] == x
while (this.parent[x] !== x) {
// 进行路径压缩
this.parent[x] = this.parent[this.parent[x]];
x = this.parent[x];
}
return x;
}
/* 将 p 和 q 连接 */
union(p, q) {
// 如果某两个节点被连通,则让其中的(任意)
// 一个节点的根节点接到另一个节点的根节点上
let rootP = this.find(p);
let rootQ = this.find(q);
if (rootP === rootQ) return;
// 小树接到大树下面,较平衡
if (this.size[rootP] > this.size[rootQ]) {
this.parent[rootQ] = rootP;
this.size[rootP] += this.size[rootQ];
} else {
this.parent[rootP] = rootQ;
this.size[rootQ] += this.size[rootP];
}
this.count--; // 两个分量合二为一
}
/* 判断 p 和 q 是否连通 */
connected(p, q) {
let rootP = this.find(p);
let rootQ = this.find(q);
return rootP === rootQ;
};
/* 返回图中有多少个连通分量 */
getCount() {
return this.count;
};
}
```
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo)
* [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo)
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo/)
* [经典动态规划:完全背包问题](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -177,12 +177,126 @@ int[] twoSum(int[] nums, int target) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[1.两数之和](https://leetcode-cn.com/problems/two-sum)
[170.两数之和 III - 数据结构设计](https://leetcode-cn.com/problems/two-sum-iii-data-structure-design)
### python
[JodyZ0203](https://github.com/JodyZ0203)提供 1. Two Sums Python3 解法代码:
只用一个哈希表
```python
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
# 提前构造一个哈希表
hashTable = {}
# 寻找两个目标数值
for i, n in enumerate(nums):
other_num = target - n
# 如果存在这个余数 other_num
if other_num in hashTable.keys():
# 查看是否存在哈希表里,如果存在的话就返回数组
return [i, hashTable[other_num]]
# 如果不存在的话继续处理剩余的数
hashTable[n] = i
```
### javascript
[1.两数之和](https://leetcode-cn.com/problems/two-sum)
穷举
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
for (let i = 0; i < nums.length; i++)
for (let j = i + 1; j < nums.length; j++)
if (nums[j] === target - nums[i])
return [i, j];
// 不存在这么两个数
return [-1, -1];
};
```
备忘录
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
let n = nums.length;
let index = new Map();
// 构造一个哈希表:元素映射到相应的索引
for (let i = 0; i < n; i++)
index.set(nums[i], i);
for (let i = 0; i < n; i++) {
let other = target - nums[i];
// 如果 other 存在且不是 nums[i] 本身
if (index.has(other) && index.get(other) !== i)
return [i, index.get(other)];
}
// 不存在这么两个数
return [-1, -1];
};
```
[170.两数之和 III - 数据结构设计](https://leetcode-cn.com/problems/two-sum-iii-data-structure-design)
哈希集合优化。
```js
class TwoSum {
constructor() {
this.sum = new Set();
this.nums = [];
}
// 向数据结构中添加一个数 number
add(number) {
// 记录所有可能组成的和
for (let n of this.nums) {
this.sum.push(n + number)
}
this.nums.add(number);
}
// 寻找当前数据结构中是否存在两个数的和为 value
find(value) {
return this.sum.has(value);
}
}
```
======其他语言代码======
\ No newline at end of file
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [递归反转链表的一部分](https://labuladong.gitbook.io/algo)
* [25 张图解:键入网址后,到网页显示,其间发生了什么](https://labuladong.gitbook.io/algo)
* [递归反转链表的一部分](https://labuladong.gitbook.io/algo/)
* [25 张图解:键入网址后,到网页显示,其间发生了什么](https://labuladong.gitbook.io/algo/)
**-----------**
......@@ -93,7 +93,7 @@
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
......@@ -11,8 +11,8 @@
![](../pictures/souyisou.png)
相关推荐:
* [讲两道常考的阶乘算法题](https://labuladong.gitbook.io/algo)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo)
* [讲两道常考的阶乘算法题](https://labuladong.gitbook.io/algo/)
* [状态压缩:对动态规划进行降维打击](https://labuladong.gitbook.io/algo/)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
......@@ -22,7 +22,7 @@
很多算法问题都需要排序技巧,其难点不在于排序本身,而是需要巧妙地排序进行预处理,将算法问题进行转换,为之后的操作打下基础。
信封嵌套问题就需要先按特定的规则排序,之后就转换为一个 [最长递增子序列问题](https://labuladong.gitbook.io/algo),可以用前文 [二分查找详解](https://labuladong.gitbook.io/algo) 的技巧来解决了。
信封嵌套问题就需要先按特定的规则排序,之后就转换为一个 [最长递增子序列问题](https://labuladong.gitbook.io/algo/),可以用前文 [二分查找详解](https://labuladong.gitbook.io/algo/) 的技巧来解决了。
### 一、题目概述
......@@ -127,7 +127,7 @@ public int lengthOfLIS(int[] nums) {
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......@@ -135,4 +135,123 @@ public int lengthOfLIS(int[] nums) {
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[354.俄罗斯套娃信封问题](https://leetcode-cn.com/problems/russian-doll-envelopes)
### javascript
[300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
```js
/**
* @param {number[]} nums
* @return {number}
*/
let lengthOfLIS = function(nums) {
let top = new Array(nums.length);
// 牌堆数初始化为 0
let piles = 0;
for (let i = 0; i < nums.length; i++) {
// 要处理的扑克牌
let poker = nums[i];
/***** 搜索左侧边界的二分查找 *****/
let left = 0, right = piles;
while (left < right) {
let mid = (left + right) / 2;
if (top[mid] > poker) {
right = mid;
} else if (top[mid] < poker) {
left = mid + 1;
} else {
right = mid;
}
}
/*********************************/
// 没找到合适的牌堆,新建一堆
if (left === piles) piles++;
// 把这张牌放到牌堆顶
top[left] = poker;
}
// 牌堆数就是 LIS 长度
return piles;
};
```
[354.俄罗斯套娃信封问题](https://leetcode-cn.com/problems/russian-doll-envelopes)
```js
/**
* @param {number[]} nums
* @return {number}
*/
let lengthOfLIS = function(nums) {
let top = new Array(nums.length);
// 牌堆数初始化为 0
let piles = 0;
for (let i = 0; i < nums.length; i++) {
// 要处理的扑克牌
let poker = nums[i];
/***** 搜索左侧边界的二分查找 *****/
let left = 0, right = piles;
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (top[mid] > poker) {
right = mid;
} else if (top[mid] < poker) {
left = mid + 1;
} else {
right = mid;
}
}
/*********************************/
// 没找到合适的牌堆,新建一堆
if (left === piles) piles++;
// 把这张牌放到牌堆顶
top[left] = poker;
}
// 牌堆数就是 LIS 长度
return piles;
};
/**
* @param {number[][]} envelopes
* @return {number}
*/
var maxEnvelopes = function (envelopes) {
let n = envelopes.length;
// 按宽度升序排列,如果宽度一样,则按高度降序排列
envelopes.sort((a, b) => {
return a[0] === b[0] ? b[1] - a[1] : a[0] - b[0];
})
// 对高度数组寻找 LIS
let height = new Array(n);
for (let i = 0; i < n; i++)
height[i] = envelopes[i][1];
return lengthOfLIS(height);
};
```
......@@ -11,12 +11,12 @@
![](../pictures/souyisou.png)
相关推荐:
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo)
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo)
* [学习算法和数据结构的思路指南](https://labuladong.gitbook.io/algo/)
* [我写了首诗,让你闭着眼睛也能写对二分搜索](https://labuladong.gitbook.io/algo/)
**-----------**
上篇文章 [洗牌算法详解](https://labuladong.gitbook.io/algo) 讲到了验证概率算法的蒙特卡罗方法,今天聊点轻松的内容:几个和概率相关的有趣问题。
上篇文章 [洗牌算法详解](https://labuladong.gitbook.io/algo/) 讲到了验证概率算法的蒙特卡罗方法,今天聊点轻松的内容:几个和概率相关的有趣问题。
计算概率有下面两个最简单的原则:
......@@ -133,7 +133,7 @@
**_____________**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo) 持续更新最新文章**
**刷算法,学套路,认准 labuladong,公众号和 [在线电子书](https://labuladong.gitbook.io/algo/) 持续更新最新文章**
**本小抄即将出版,微信扫码关注公众号,后台回复「小抄」限时免费获取,回复「进群」可进刷题群一起刷题,带你搞定 LeetCode**
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册