提交 7f7094d5 编写于 作者: B brucecat

[javascript]

上级 ebe8d716
......@@ -430,41 +430,128 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
<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)
};
```
......@@ -214,14 +214,9 @@ int stoneGame(int[] piles) {
<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
......@@ -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];
......@@ -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];
}
```
......@@ -294,5 +294,132 @@ bool dp(string& s, int i, string& p, int j) {
<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;
}
};
```
......@@ -214,9 +214,73 @@ public int lengthOfLIS(int[] nums) {
<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++
......
......@@ -367,9 +367,10 @@ PS:为啥 `dp` 数组初始化为 `amount + 1` 呢,因为凑成 `amount` 金
<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];
}
```
......@@ -427,5 +427,385 @@ int maxProfit_k_any(int max_k, int[] prices) {
<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
......@@ -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]
};
```
......@@ -257,8 +257,16 @@ int[] dp(TreeNode root) {
<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]
}
```
......@@ -156,5 +156,38 @@ for (int i = 1; i < m; i++)
<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'>
......@@ -146,9 +146,10 @@ else:
<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++ 代码:
......@@ -184,6 +185,8 @@ public:
};
```
### java
[Shawn](https://github.com/Shawn-Hx) 提供 Java 代码:
......@@ -235,3 +238,103 @@ class Solution(object):
```
### 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]
};
```
......@@ -290,11 +290,13 @@ class Node {
<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'>
......@@ -157,9 +157,12 @@ int findMinArrowShots(int[][] intvs) {
<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;
};
```
......@@ -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;
};
```
......@@ -76,7 +76,7 @@ PS:这有点像 Big O 表示法计算​算法的复杂度。
**「选择」其实就是去选择哪层楼扔鸡蛋**。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。
现在明确了「状态」和「选择」,**动态规划的基本思路就形成了**:肯定是个二维的 `dp` 数组或者带有两个状态参数的 `dp` 函数来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新状态:
```python
# 当前状态为 K 个鸡蛋,面对 N 层楼
# 返回这个状态下的最优结果
......@@ -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);
};
```
......@@ -238,5 +238,122 @@ public Key delMax() {
<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'>
......@@ -313,6 +313,16 @@ void BST(TreeNode root, int target) {
======其他语言代码======
[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++
[dekunma](https://www.linkedin.com/in/dekun-ma-036a9b198/)提供第98题C++代码:
......@@ -488,3 +498,201 @@ class Solution:
node = node.right
return node
```
### 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
}
};
```
......@@ -180,9 +180,16 @@ vector<int> nextGreaterElements(vector<int>& nums) {
<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) 提供代码
......@@ -294,4 +301,300 @@ class Solution {
return res;
}
}
```
\ No newline at end of file
```
### 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;
}
```
部分做题规律如下,仅供做题套路参考,实际可以自由发挥。
当前项向左找第一个比自己大的位置:从左向右维护一个单调递减栈
当前项向左找第一个比自己小的位置:从左向右维护一个单调递增栈
当前项向右找第一个比自己大的位置:从右向左维护一个单调递减栈
当前项向右找第一个比自己小的位置:从右向左维护一个单调递增栈
......@@ -209,10 +209,11 @@ vector<int> maxSlidingWindow(vector<int>& nums, int k) {
<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;
};
```
......@@ -26,7 +26,7 @@
**-----------**
我们最终要实现的计算器功能如下:
1、输入一个字符串,可以包含`+ - * /`、数字、括号以及空格,你的算法返回运算结果。
2、要符合运算法则,括号的优先级最高,先乘除后加减。
......@@ -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
```
......@@ -301,9 +301,12 @@ PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件
<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)
```
......@@ -217,9 +217,12 @@ ListNode reverseBetween(ListNode head, int m, int n) {
<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++解法代码:
......@@ -272,34 +275,129 @@ public:
思路:递归。时间复杂度为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;
};
```
......@@ -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()
*/
```
......@@ -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;
}
```
......@@ -251,9 +251,16 @@ boolean equationsPossible(String[] equations) {
<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)
......@@ -329,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;
};
```
......@@ -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;
};
}
```
......@@ -184,14 +184,21 @@ int[] twoSum(int[] nums, int target) {
<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
```python
class Solution:
def twoSum(self, nums, target):
"""
......@@ -211,3 +218,85 @@ class Solution:
# 如果不存在的话继续处理剩余的数
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);
}
}
```
......@@ -507,8 +507,16 @@ int right_bound(int[] nums, int target) {
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[704.二分查找](https://leetcode-cn.com/problems/binary-search)
[34.在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
### python
======其他语言代码======
[MarineJoker](https://github.com/MarineJoker) 提供 Python3 代码
```python
......@@ -603,3 +611,207 @@ def right_bound(nums, target):
return -1
return right
```
### javascript
寻找左、右侧边界的二分搜索,两端闭合
```js
let binary_search = function (nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] === target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
let left_bound = function (nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
let mid = left + Math.floor((right - left) / 2);
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] === target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] !== target)
return -1;
return left;
}
// 两端都闭
let right_bound = function (nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
while (left <= right) {
let mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 区间变到[mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 区间变到[left, mid-1]
right = mid - 1;
} else if (nums[mid] === target) {
// 别返回,锁定右侧边界
// 区间变到[mid+1, right]
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] !== target)
return -1;
return right;
}
```
[704.二分查找](https://leetcode-cn.com/problems/binary-search)
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] === target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
};
```
[34.在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
按照本文的思路,可以很容易就写出答案,但会超时。
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let left = left_bound(nums, target);
let right = right_bound(nums, target)
return [left]
};
let left_bound = function (nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
let mid = left + Math.floor((right - left) / 2);
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] === target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] !== target)
return -1;
return left;
}
// 两端都闭
let right_bound = function (nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
while (left <= right) {
let mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 区间变到[mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 区间变到[left, mid-1]
right = mid - 1;
} else if (nums[mid] === target) {
// 别返回,锁定右侧边界
// 区间变到[mid+1, right]
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] !== target)
return -1;
return right;
}
```
现通过lower变量来确定,当前的二分法是向左找还是向右找。
```js
const binarySearch = (nums, target, lower) => {
let left = 0, right = nums.length - 1, ans = nums.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
var searchRange = function(nums, target) {
let ans = [-1, -1];
const leftIdx = binarySearch(nums, target, true);
const rightIdx = binarySearch(nums, target, false) - 1;
if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
ans = [leftIdx, rightIdx];
}
return ans;
};
```
......@@ -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);
};
```
......@@ -159,4 +159,80 @@ for (int i = 1; i < count.length; i++)
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[560.和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k)
### javascript
[560.和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k)
```js
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
let subarraySum = function (nums, k) {
let n = nums.length;
// 构造前缀和
let sum = new Array(n + 1);
sum[0] = 0;
for (let i = 0; i < n; i++) {
sum[i + 1] = sum[i] + nums[i];
}
let ans = 0;
// 穷举所有子数组
for (let i = 1; i <= n; i++)
for (let j = 0; j < i; j++)
// sum of nums[j..i-1]
if (sum[i] - sum[j] === k)
ans++;
return ans;
}
```
优化一下。
```js
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
let subarraySum = function (nums, k) {
let n = nums.length;
// map:前缀和 -> 该前缀和出现的次数
let preSum = new Map();
// base case
preSum.set(0, 1);
let ans = 0, sum0_i = 0;
for (let i = 0; i < n; i++) {
sum0_i += nums[i];
// 这是我们想找的前缀和 nums[0..j]
let sum0_j = sum0_i - k;
// 如果前面有这个前缀和,则直接更新答案
if (preSum.has(sum0_j))
ans += preSum.get(sum0_j);
// 把前缀和 nums[0..i] 加入并记录出现次数
if (preSum.has(sum0_i)) {
preSum.set(sum0_i,
preSum.get(sum0_i) + 1);
} else {
preSum.set(sum0_i, 1);
}
}
return ans;
}
```
......@@ -132,9 +132,16 @@ def intervalIntersection(A, B):
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[986.区间列表的交集](https://leetcode-cn.com/problems/interval-list-intersections)
### java
[KiraZh](https://github.com/KiraZh)提供第986题Java代码
```java
class Solution {
public int[][] intervalIntersection(int[][] A, int[][] B) {
......@@ -156,4 +163,46 @@ class Solution {
return res.toArray(new int[0][]);
}
}
```
\ No newline at end of file
```
### javascript
[986.区间列表的交集](https://leetcode-cn.com/problems/interval-list-intersections)
```js
/**
* @param {number[][]} firstList
* @param {number[][]} secondList
* @return {number[][]}
*/
var intervalIntersection = function (firstList, secondList) {
let i, j;
i = j = 0;
let res = [];
while (i < firstList.length && j < secondList.length) {
let a1 = firstList[i][0];
let a2 = firstList[i][1];
let b1 = secondList[j][0];
let b2 = secondList[j][1];
// 两个区间存在交集
if (b2 >= a1 && a2 >= b1) {
// 计算出交集,加入 res
res.push([Math.max(a1, b1), Math.min(a2, b2)])
}
// 指针前进
if (b2 < a2) {
j += 1;
} else {
i += 1
}
}
return res;
};
```
......@@ -92,6 +92,10 @@ def merge(intervals):
</p>
======其他语言代码======
[56.合并区间](https://leetcode-cn.com/problems/merge-intervals)
### java
```java
......@@ -190,3 +194,46 @@ public:
}
};
```
### javascript
[56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/)
```js
/**
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function (intervals) {
if (intervals.length < 1) {
return []
}
// 按区间的 start 升序排列
intervals.sort((a, b) => {
return a[0] - b[0]
})
const res = []
res.push(intervals[0].concat())
for (let i = 1; i < intervals.length; i++) {
let curr = intervals[i]
// res 中最后一个元素的引用
let last = res[res.length - 1]
if (curr[0] <= last[1]) {
// 找到最大的 end
last[1] = Math.max(last[1], curr[1])
} else {
// 处理下一个待合并区间
res.push(curr.concat())
}
}
return res
}
```
......@@ -234,9 +234,16 @@ void reverse(int[] nums) {
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[141.环形链表](https://leetcode-cn.com/problems/linked-list-cycle)
[142.环形链表II](https://leetcode-cn.com/problems/linked-list-cycle-ii)
[167.两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted)
### java
[zhengpj95](https://github.com/zhengpj95) 提供 Java 代码
......@@ -363,3 +370,229 @@ class Solution:
return False
```
### javascript
#### 一、快慢指针的常见算法
**1、判定链表中是否含有环**
[141.环形链表](https://leetcode-cn.com/problems/linked-list-cycle)
```js
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
let fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) return true;
}
return false;
};
```
**2、已知链表中含有环,返回这个环的起始位置**
[142.环形链表II](https://leetcode-cn.com/problems/linked-list-cycle-ii)
```js
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
let fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
// 上面的代码类似 hasCycle 函数
if (fast == null || fast.next == null) {
// fast 遇到空指针说明没有环
return null;
}
slow = head;
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
};
```
**3、寻找链表的中点**
[876. 链表的中间结点](https://leetcode-cn.com/problems/middle-of-the-linked-list/)
```js
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var middleNode = function(head) {
let fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// slow 就在中间位置
return slow;
};
```
**4、寻找链表的倒数第 k 个元素**
[剑指 Offer 22. 链表中倒数第k个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)
```js
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var getKthFromEnd = function(head, k) {
let slow, fast;
slow = fast = head;
while (k-- > 0)
fast = fast.next;
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
return slow;
};
```
#### 二、左右指针的常用算法
**1、二分查找**
[704. 二分查找](https://leetcode-cn.com/problems/binary-search/)
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
if (nums.length === 0) return -1;
let left = 0, right = nums.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] === target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
};
```
**2、两数之和**
[167.两数之和 II - 输入有序数组](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted)
```js
/**
* @param {number[]} numbers
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left < right) {
let sum = nums[left] + nums[right];
if (sum === target) {
// 题目要求的索引是从 1 开始的
return [left + 1, right + 1];
} else if (sum < target) {
left++; // 让 sum 大一点
} else if (sum > target) {
right--; // 让 sum 小一点
}
}
return [-1, -1];
};
```
**3、反转数组**
[344. 反转字符串](https://leetcode-cn.com/problems/reverse-string/)
```js
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
var reverseString = function(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
// swap(s[left], s[right])
let temp = s[left];
s[left] = s[right];
s[right] = temp;
left++; right--;
}
};
```
**4、滑动窗口算法**
详见[这篇文章](https://labuladong.gitbook.io/algo)
\ No newline at end of file
......@@ -308,9 +308,55 @@ def backtrack(...):
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[46.全排列](https://leetcode-cn.com/problems/permutations)
[51.N皇后](https://leetcode-cn.com/problems/n-queens)
### java
[46.全排列](https://leetcode-cn.com/problems/permutations)
```java
List<List<Integer>> res = new LinkedList<>();
/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}
```
[kepler-zc](https://github.com/kepler-zc) 提供 51.N皇后 Java 解法代码:
```java
class solution {
......@@ -372,4 +418,125 @@ class solution {
return path;
}
}
```
\ No newline at end of file
```
### javascript
[46.全排列](https://leetcode-cn.com/problems/permutations)
```js
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums){
// 1. 设置结果集
const result = [];
// 2. 回溯
const recursion = (path, set) => {
// 2.1 设置回溯终止条件
if (path.length === nums.length) {
// 2.1.1 推入结果集
result.push(path.concat());
// 2.1.2 终止递归
return;
}
// 2.2 遍历数组
for (let i = 0; i < nums.length; i++) {
// 2.2.1 必须是不存在 set 中的坐标
if (!set.has(i)) {
// 2.2.2 本地递归条件(用完记得删除)
path.push(nums[i]);
set.add(i);
// 2.2.3 进一步递归
recursion(path, set);
// 2.2.4 回溯:撤回 2.2.2 的操作
path.pop();
set.delete(i);
}
}
};
recursion([], new Set());
// 3. 返回结果
return result;
};
```
[ 51.N皇后](https://leetcode-cn.com/problems/n-queens)
检查斜线的时候,可以用两个坐标点的|(y1-y2)| == |(x1 - x2)|,如果true就是共斜线
```js
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function (n) {
const board = new Array(n);
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
for (let i = 0; i < n; i++) { // 棋盘的初始化
board[i] = new Array(n).fill('.');
}
const res = []
// 验证是否是有效的位置
const isValid = (row, col) => {
for (let i = 0; i < row; i++) { // 之前的行
for (let j = 0; j < n; j++) { // 所有的列
if (board[i][j] === 'Q' && // 发现了皇后,并且和自己同列/对角线
(j === col || i + j === row + col || i - j === row - col)) {
return false; // 不是合法的选择
}
}
}
return true;
};
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
const backtrack = (row) => {
// 触发结束条件
if (row === n) {
const stringsBoard = board.slice(); // 拷贝一份board
for (let i = 0; i < n; i++) {
stringsBoard[i] = stringsBoard[i].join(''); // 将每一行拼成字符串
}
res.push(stringsBoard); // 推入res数组
return;
}
for (let col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(row + 1);
// 撤销选择
board[row][col] = '.';
}
}
backtrack(0);
return res;
};
```
......@@ -21,7 +21,7 @@
**-----------**
对于比较小的数字,做运算可以直接使用编程语言提供的运算符,但是如果相乘的两个因数非常大,语言提供的数据类型可能就会溢出。一种替代方案就是,运算数以字符串的形式输入,然后模仿我们小学学习的乘法算术过程计算出结果,并且也用字符串表示。
![](../pictures/%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B9%98%E6%B3%95/title.png)
需要注意的是,`num1``num2` 可以非常长,所以不可以把他们直接转成整型然后运算,唯一的思路就是模仿我们手算乘法。
......@@ -100,9 +100,12 @@ string multiply(string num1, string num2) {
<p align='center'>
<img src="../pictures/qrcode.jpg" width=200 >
</p>
======其他语言代码======
[43.字符串相乘](https://leetcode-cn.com/problems/multiply-strings)
### python
[fengshuu](https://github.com/fengshuu) 提供 Python 解法代码:
......@@ -177,4 +180,38 @@ public String multiply(String num1, String num2) {
}
return ans.toString();
}
```
\ No newline at end of file
```
### javascript
```js
/**
* @param {string} num1
* @param {string} num2
* @return {string}
*/
const multiply = (num1, num2) => {
const m = num1.length;
const n = num2.length;
const pos = new Array(m + n).fill(0);
for (let i = m - 1; i >= 0; i--) {
const n1 = +num1[i];
for (let j = n - 1; j >= 0; j--) {
const n2 = +num2[j];
const multi = n1 * n2;
const sum = pos[i + j + 1] + multi;
pos[i + j + 1] = sum % 10;
pos[i + j] += sum / 10 | 0;
}
}
while (pos[0] === 0) {
pos.shift();
}
return pos.length ? pos.join('') : '0';
};
```
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册