mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-11 21:10:58 +08:00
Merge pull request #789 from jerryfishcode/master
添加 5. 31. 34. 42. 52. 70(完全背包版本). 84. 127. 129. 132. 141. 143. 205. 234. 283. 463. 649. 673. 684. 685. 724. 841. 844. 922. 925. 941. 1207. 1221. 1356. 1365. 1382.JavaScript版本
This commit is contained in:
@ -297,6 +297,117 @@ class Solution:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
//动态规划解法
|
||||||
|
var longestPalindrome = function(s) {
|
||||||
|
const len = s.length;
|
||||||
|
// 布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false
|
||||||
|
let dp = new Array(len).fill(false).map(() => new Array(len).fill(false));
|
||||||
|
// left起始位置 maxlenth回文串长度
|
||||||
|
let left = 0, maxlenth = 0;
|
||||||
|
for(let i = len - 1; i >= 0; i--){
|
||||||
|
for(let j = i; j < len; j++){
|
||||||
|
// 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串 j - i == 0
|
||||||
|
// 情况二:下标i 与 j相差为1,例如aa,也是文子串 j - i == 1
|
||||||
|
// 情况一和情况二 可以合并为 j - i <= 1
|
||||||
|
// 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]===true
|
||||||
|
if(s[i] === s[j] && (j - i <= 1 || dp[i + 1][j - 1])){
|
||||||
|
dp[i][j] = true;
|
||||||
|
}
|
||||||
|
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
|
||||||
|
if(dp[i][j] && j - i + 1 > maxlenth) {
|
||||||
|
maxlenth = j - i + 1; // 回文串长度
|
||||||
|
left = i; // 起始位置
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.substr(left, maxlenth); // 找到子串
|
||||||
|
};
|
||||||
|
|
||||||
|
//双指针
|
||||||
|
var longestPalindrome = function(s) {
|
||||||
|
let left = 0, right = 0, maxLength = 0;
|
||||||
|
const extend = (s, i, j, n) => {// s为字符串 i,j为双指针 n为字符串长度
|
||||||
|
while(i >= 0 && j < n && s[i] === s[j]){
|
||||||
|
if(j - i + 1 > maxLength){
|
||||||
|
left = i; // 更新开始位置
|
||||||
|
right = j; // 更新结尾位置
|
||||||
|
maxLength = j - i + 1; // 更新子串最大长度
|
||||||
|
}
|
||||||
|
// 指针移动
|
||||||
|
i--;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(let i = 0; i < s.length; i++){
|
||||||
|
extend(s, i, i, s.length); // 以i为中心
|
||||||
|
extend(s, i, i + 1, s.length); // 以i和i+1为中心
|
||||||
|
}
|
||||||
|
return s.substr(left, maxLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Manacher算法
|
||||||
|
var longestPalindrome = function(s) {
|
||||||
|
const len = s.length;
|
||||||
|
if(len < 2) return s;
|
||||||
|
let maxLength = 1, index = 0;
|
||||||
|
//Manacher算法,利用回文对称的性质,根据i在上一个回文中心的臂长里的位置去判断i的回文性
|
||||||
|
//需要知道上一个回文中心,以及其臂长
|
||||||
|
let center = 0;
|
||||||
|
//注意这里使用了maxRight的而不是真实的臂长length,因为之后需要判断i在臂长的什么位置
|
||||||
|
//如果这里臂长用了length,之后还要 计算i - center 去和 length比较,太繁琐
|
||||||
|
let maxRight = 0;
|
||||||
|
//考虑到回文串的长度是偶数的情况,所以这里预处理一下字符串,每个字符间插入特殊字符,把可能性都化为奇数
|
||||||
|
//这个处理把回文串长度的可能性都化为了奇数
|
||||||
|
//#c#b#b#a#
|
||||||
|
//#c#b#a#b#d#
|
||||||
|
let ss = "";
|
||||||
|
for(let i = 0; i < s.length; i++){
|
||||||
|
ss += "#"+s[i];
|
||||||
|
}
|
||||||
|
ss += "#";
|
||||||
|
//需要维护一个每个位置臂长的信息数组positionLength
|
||||||
|
const pl = new Array(ss.length).fill(0);
|
||||||
|
//这里需要注意参考的是i关于center对称的点i'的回文性
|
||||||
|
//i' = 2*center - i;
|
||||||
|
//所以列下情况:
|
||||||
|
//1.i>maxRight,找不到i',无法参考,自己算自己的
|
||||||
|
//2.i<=maxRight:
|
||||||
|
//2.1 i<maxRight-pl[i'],pl[i']的臂长没有超过center的臂长,根据对称性,pl[i] = pl[i']
|
||||||
|
//2.2 i=maxRight-pl[i'],pl[i']的臂长刚好等于center的臂长,根据对称性,pl[i] >= pl[i‘],大多少需要尝试扩散
|
||||||
|
//2.3 i>maxRight-pl[i'],pl[i']的臂长超过了center的臂长,根据对称性,i中心扩散到MaxRight处,
|
||||||
|
// s[2*i-maxRight] !== s[MaxRight]必不相等,所以pl[i] = maxRight-i;
|
||||||
|
//总结就是pl[i] = Math.min(maxRight-i,pl[i']);提示i<maxRight-pl[i'] 也可写成 pl[i']<maxRight-i
|
||||||
|
//0没有意义,从1开始计算
|
||||||
|
for(let i = 1; i < ss.length; i++){
|
||||||
|
if(i <= maxRight){//可以参考之前的
|
||||||
|
pl[i] = Math.min(maxRight - i, pl[2 * center - i]);
|
||||||
|
//尝试中心扩散
|
||||||
|
}
|
||||||
|
//注意到i<maxRight时都要尝试中心扩散,所以写else完全无意义,把中心扩散的代码写在下面
|
||||||
|
// else{//i不在之前回文中心的臂长范围里,之前的信息就完全无法参考,只能从i中心扩散把,然后去维护maxRight和center的定义
|
||||||
|
//尝试中心扩散
|
||||||
|
//这里不要动center和maxRight
|
||||||
|
// center = i;
|
||||||
|
// maxRight = pl[i] + i + 1;
|
||||||
|
let right = pl[i] + i + 1;
|
||||||
|
let left = i - pl[i] - 1;
|
||||||
|
while (left >= 0 && right<ss.length && ss[left] === ss[right]) {
|
||||||
|
right++;
|
||||||
|
left--;
|
||||||
|
pl[i]++;
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
if(pl[i] + i > maxRight){
|
||||||
|
center = i;
|
||||||
|
maxRight = pl[i] + i;
|
||||||
|
}
|
||||||
|
if (pl[i] * 2 + 1 > maxLength){
|
||||||
|
maxLength = pl[i]*2+1;
|
||||||
|
index = i - pl[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss.substr(index, maxLength).replace(/#/g,"");
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -132,6 +132,52 @@ class Solution {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
//卡尔的解法(吐槽一下JavaScript的sort和其他语言的不太一样,只想到了拷贝数组去排序再替换原数组来实现nums的[i + 1, nums.length)升序排序)
|
||||||
|
var nextPermutation = function(nums) {
|
||||||
|
for(let i = nums.length - 1; i >= 0; i--){
|
||||||
|
for(let j = nums.length - 1; j > i; j--){
|
||||||
|
if(nums[j] > nums[i]){
|
||||||
|
[nums[j],nums[i]] = [nums[i],nums[j]]; // 交换
|
||||||
|
// 深拷贝[i + 1, nums.length)部分到新数组arr
|
||||||
|
let arr = nums.slice(i+1);
|
||||||
|
// arr升序排序
|
||||||
|
arr.sort((a,b) => a - b);
|
||||||
|
// arr替换nums的[i + 1, nums.length)部分
|
||||||
|
nums.splice(i+1,nums.length - i, ...arr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nums.sort((a,b) => a - b); // 不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
|
||||||
|
};
|
||||||
|
|
||||||
|
//另一种
|
||||||
|
var nextPermutation = function(nums) {
|
||||||
|
let i = nums.length - 2;
|
||||||
|
// 从右往左遍历拿到第一个左边小于右边的 i,此时 i 右边的数组是从右往左递增的
|
||||||
|
while (i >= 0 && nums[i] >= nums[i+1]){
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (i >= 0){
|
||||||
|
let j = nums.length - 1;
|
||||||
|
// 从右往左遍历拿到第一个大于nums[i]的数,因为之前nums[i]是第一个小于他右边的数,所以他的右边一定有大于他的数
|
||||||
|
while (j >= 0 && nums[j] <= nums[i]){
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
// 交换两个数
|
||||||
|
[nums[j], nums[i]] = [nums[i], nums[j]];
|
||||||
|
}
|
||||||
|
// 对 i 右边的数进行交换
|
||||||
|
// 因为 i 右边的数原来是从右往左递增的,把一个较小的值交换过来之后,仍然维持单调递增特性
|
||||||
|
// 此时头尾交换并向中间逼近就能获得 i 右边序列的最小值
|
||||||
|
let l = i + 1;
|
||||||
|
let r = nums.length - 1;
|
||||||
|
while (l < r){
|
||||||
|
[nums[l], nums[r]] = [nums[r], nums[l]];
|
||||||
|
l++;
|
||||||
|
r--;
|
||||||
|
}
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -396,6 +396,46 @@ class Solution:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var searchRange = function(nums, target) {
|
||||||
|
const getLeftBorder = (nums, target) => {
|
||||||
|
let left = 0, right = nums.length - 1;
|
||||||
|
let leftBorder = -2;// 记录一下leftBorder没有被赋值的情况
|
||||||
|
while(left <= right){
|
||||||
|
let middle = left + ((right - left) >> 1);
|
||||||
|
if(nums[middle] >= target){ // 寻找左边界,nums[middle] == target的时候更新right
|
||||||
|
right = middle - 1;
|
||||||
|
leftBorder = right;
|
||||||
|
} else {
|
||||||
|
left = middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leftBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRightBorder = (nums, target) => {
|
||||||
|
let left = 0, right = nums.length - 1;
|
||||||
|
let rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
|
||||||
|
while (left <= right) {
|
||||||
|
let middle = left + ((right - left) >> 1);
|
||||||
|
if (nums[middle] > target) {
|
||||||
|
right = middle - 1;
|
||||||
|
} else { // 寻找右边界,nums[middle] == target的时候更新left
|
||||||
|
left = middle + 1;
|
||||||
|
rightBorder = left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rightBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
let leftBorder = getLeftBorder(nums, target);
|
||||||
|
let rightBorder = getRightBorder(nums, target);
|
||||||
|
// 情况一
|
||||||
|
if(leftBorder === -2 || rightBorder === -2) return [-1,-1];
|
||||||
|
// 情况三
|
||||||
|
if (rightBorder - leftBorder > 1) return [leftBorder + 1, rightBorder - 1];
|
||||||
|
// 情况二
|
||||||
|
return [-1, -1];
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -534,6 +534,105 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
//双指针
|
||||||
|
var trap = function(height) {
|
||||||
|
const len = height.length;
|
||||||
|
let sum = 0;
|
||||||
|
for(let i = 0; i < len; i++){
|
||||||
|
// 第一个柱子和最后一个柱子不接雨水
|
||||||
|
if(i == 0 || i == len - 1) continue;
|
||||||
|
let rHeight = height[i]; // 记录右边柱子的最高高度
|
||||||
|
let lHeight = height[i]; // 记录左边柱子的最高高度
|
||||||
|
for(let r = i + 1; r < len; r++){
|
||||||
|
if(height[r] > rHeight) rHeight = height[r];
|
||||||
|
}
|
||||||
|
for(let l = i - 1; l >= 0; l--){
|
||||||
|
if(height[l] > lHeight) lHeight = height[l];
|
||||||
|
}
|
||||||
|
let h = Math.min(lHeight, rHeight) - height[i];
|
||||||
|
if(h > 0) sum += h;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
//动态规划
|
||||||
|
var trap = function(height) {
|
||||||
|
const len = height.length;
|
||||||
|
if(len <= 2) return 0;
|
||||||
|
const maxLeft = new Array(len).fill(0);
|
||||||
|
const maxRight = new Array(len).fill(0);
|
||||||
|
// 记录每个柱子左边柱子最大高度
|
||||||
|
maxLeft[0] = height[0];
|
||||||
|
for(let i = 1; i < len; i++){
|
||||||
|
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
|
||||||
|
}
|
||||||
|
// 记录每个柱子右边柱子最大高度
|
||||||
|
maxRight[len - 1] = height[len - 1];
|
||||||
|
for(let i = len - 2; i >= 0; i--){
|
||||||
|
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
|
||||||
|
}
|
||||||
|
// 求和
|
||||||
|
let sum = 0;
|
||||||
|
for(let i = 0; i < len; i++){
|
||||||
|
let count = Math.min(maxLeft[i], maxRight[i]) - height[i];
|
||||||
|
if(count > 0) sum += count;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
//单调栈 js数组作为栈
|
||||||
|
var trap = function(height) {
|
||||||
|
const len = height.length;
|
||||||
|
if(len <= 2) return 0; // 可以不加
|
||||||
|
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
|
||||||
|
st.push(0);
|
||||||
|
let sum = 0;
|
||||||
|
for(let i = 1; i < len; i++){
|
||||||
|
if(height[i] < height[st[st.length - 1]]){ // 情况一
|
||||||
|
st.push(i);
|
||||||
|
}
|
||||||
|
if (height[i] == height[st[st.length - 1]]) { // 情况二
|
||||||
|
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
|
||||||
|
st.push(i);
|
||||||
|
} else { // 情况三
|
||||||
|
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
|
||||||
|
let mid = st[st.length - 1];
|
||||||
|
st.pop();
|
||||||
|
if (st.length !== 0) {
|
||||||
|
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
|
||||||
|
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
|
||||||
|
sum += h * w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
//单调栈 简洁版本 只处理情况三
|
||||||
|
var trap = function(height) {
|
||||||
|
const len = height.length;
|
||||||
|
if(len <= 2) return 0; // 可以不加
|
||||||
|
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
|
||||||
|
st.push(0);
|
||||||
|
let sum = 0;
|
||||||
|
for(let i = 1; i < len; i++){ // 只处理的情况三,其实是把情况一和情况二融合了
|
||||||
|
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
|
||||||
|
let mid = st[st.length - 1];
|
||||||
|
st.pop();
|
||||||
|
if (st.length !== 0) {
|
||||||
|
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
|
||||||
|
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
|
||||||
|
sum += h * w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st.push(i);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
C:
|
C:
|
||||||
|
|
||||||
|
@ -101,4 +101,48 @@ public:
|
|||||||
```
|
```
|
||||||
|
|
||||||
# 其他语言补充
|
# 其他语言补充
|
||||||
|
JavaScript
|
||||||
|
```javascript
|
||||||
|
var totalNQueens = function(n) {
|
||||||
|
let count = 0;
|
||||||
|
const backtracking = (n, row, chessboard) => {
|
||||||
|
if(row === n){
|
||||||
|
count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(let col = 0; col < n; col++){
|
||||||
|
if(isValid(row, col, chessboard, n)) { // 验证合法就可以放
|
||||||
|
chessboard[row][col] = 'Q'; // 放置皇后
|
||||||
|
backtracking(n, row + 1, chessboard);
|
||||||
|
chessboard[row][col] = '.'; // 回溯
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = (row, col, chessboard, n) => {
|
||||||
|
// 检查列
|
||||||
|
for(let i = 0; i < row; i++){ // 这是一个剪枝
|
||||||
|
if(chessboard[i][col] === 'Q'){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查 45度角是否有皇后
|
||||||
|
for(let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
|
||||||
|
if(chessboard[i][j] === 'Q'){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查 135度角是否有皇后
|
||||||
|
for(let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++){
|
||||||
|
if(chessboard[i][j] === 'Q'){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chessboard = new Array(n).fill([]).map(() => new Array(n).fill('.'));
|
||||||
|
backtracking(n, 0, chessboard);
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
@ -186,6 +186,20 @@ func climbStairs(n int) int {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var climbStairs = function(n) {
|
||||||
|
const dp = new Array(n+1).fill(0);
|
||||||
|
const weight = [1,2];
|
||||||
|
dp[0] = 1;
|
||||||
|
for(let i = 0; i <= n; i++){ //先遍历背包
|
||||||
|
for(let j = 0; j < weight.length; j++){ // 再遍历物品
|
||||||
|
if(i >= weight[j]) dp[i] += dp[i-weight[j]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[n];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -321,4 +321,79 @@ class Solution:
|
|||||||
return result
|
return result
|
||||||
```
|
```
|
||||||
|
|
||||||
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
//动态规划 js中运行速度最快
|
||||||
|
var largestRectangleArea = function(heights) {
|
||||||
|
const len = heights.length;
|
||||||
|
const minLeftIndex = new Array(len);
|
||||||
|
const maxRigthIndex = new Array(len);
|
||||||
|
// 记录每个柱子 左边第一个小于该柱子的下标
|
||||||
|
minLeftIndex[0] = -1; // 注意这里初始化,防止下面while死循环
|
||||||
|
for(let i = 1; i < len; i++) {
|
||||||
|
let t = i - 1;
|
||||||
|
// 这里不是用if,而是不断向左寻找的过程
|
||||||
|
while(t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
|
||||||
|
minLeftIndex[i] = t;
|
||||||
|
}
|
||||||
|
// 记录每个柱子 右边第一个小于该柱子的下标
|
||||||
|
maxRigthIndex[len - 1] = len; // 注意这里初始化,防止下面while死循环
|
||||||
|
for(let i = len - 2; i >= 0; i--){
|
||||||
|
let t = i + 1;
|
||||||
|
// 这里不是用if,而是不断向右寻找的过程
|
||||||
|
while(t < len && heights[t] >= heights[i]) t = maxRigthIndex[t];
|
||||||
|
maxRigthIndex[i] = t;
|
||||||
|
}
|
||||||
|
// 求和
|
||||||
|
let maxArea = 0;
|
||||||
|
for(let i = 0; i < len; i++){
|
||||||
|
let sum = heights[i] * (maxRigthIndex[i] - minLeftIndex[i] - 1);
|
||||||
|
maxArea = Math.max(maxArea , sum);
|
||||||
|
}
|
||||||
|
return maxArea;
|
||||||
|
};
|
||||||
|
|
||||||
|
//单调栈
|
||||||
|
var largestRectangleArea = function(heights) {
|
||||||
|
let maxArea = 0;
|
||||||
|
const stack = [];
|
||||||
|
heights = [0,...heights,0]; // 数组头部加入元素0 数组尾部加入元素0
|
||||||
|
for(let i = 0; i < heights.length; i++){
|
||||||
|
if(heights[i] > heights[stack[stack.length-1]]){ // 情况三
|
||||||
|
stack.push(i);
|
||||||
|
} else if(heights[i] === heights[stack[stack.length-1]]){ // 情况二
|
||||||
|
stack.pop(); // 这个可以加,可以不加,效果一样,思路不同
|
||||||
|
stack.push(i);
|
||||||
|
} else { // 情况一
|
||||||
|
while(heights[i] < heights[stack[stack.length-1]]){// 当前bar比栈顶bar矮
|
||||||
|
const stackTopIndex = stack.pop();// 栈顶元素出栈,并保存栈顶bar的索引
|
||||||
|
let w = i - stack[stack.length -1] - 1;
|
||||||
|
let h = heights[stackTopIndex]
|
||||||
|
// 计算面积,并取最大面积
|
||||||
|
maxArea = Math.max(maxArea, w * h);
|
||||||
|
}
|
||||||
|
stack.push(i);// 当前bar比栈顶bar高了,入栈
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxArea;
|
||||||
|
};
|
||||||
|
|
||||||
|
//单调栈 简洁
|
||||||
|
var largestRectangleArea = function(heights) {
|
||||||
|
let maxArea = 0;
|
||||||
|
const stack = [];
|
||||||
|
heights = [0,...heights,0]; // 数组头部加入元素0 数组尾部加入元素0
|
||||||
|
for(let i = 0; i < heights.length; i++){ // 只用考虑情况一 当前遍历的元素heights[i]小于栈顶元素heights[stack[stack.length-1]]]的情况
|
||||||
|
while(heights[i] < heights[stack[stack.length-1]]){// 当前bar比栈顶bar矮
|
||||||
|
const stackTopIndex = stack.pop();// 栈顶元素出栈,并保存栈顶bar的索引
|
||||||
|
let w = i - stack[stack.length -1] - 1;
|
||||||
|
let h = heights[stackTopIndex]
|
||||||
|
// 计算面积,并取最大面积
|
||||||
|
maxArea = Math.max(maxArea, w * h);
|
||||||
|
}
|
||||||
|
stack.push(i);// 当前bar比栈顶bar高了,入栈
|
||||||
|
}
|
||||||
|
return maxArea;
|
||||||
|
};
|
||||||
|
```
|
||||||
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>
|
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>
|
||||||
|
@ -141,6 +141,40 @@ public int ladderLength(String beginWord, String endWord, List<String> wordList)
|
|||||||
## Go
|
## Go
|
||||||
|
|
||||||
## JavaScript
|
## JavaScript
|
||||||
|
```javascript
|
||||||
|
var ladderLength = function(beginWord, endWord, wordList) {
|
||||||
|
// 将wordList转成Set,提高查询速度
|
||||||
|
const wordSet = new Set(wordList);
|
||||||
|
// Set元素个数为0 或者 endWord没有在wordSet出现,直接返回0
|
||||||
|
if (wordSet.size === 0 || !wordSet.has(endWord)) return 0;
|
||||||
|
// 记录word是否访问过
|
||||||
|
const visitMap = new Map();// <word, 查询到这个word路径长度>
|
||||||
|
// 初始化队列
|
||||||
|
const queue = [];
|
||||||
|
queue.push(beginWord);
|
||||||
|
// 初始化visitMap
|
||||||
|
visitMap.set(beginWord, 1);
|
||||||
|
|
||||||
|
while(queue.length !== 0){
|
||||||
|
let word = queue.shift(); // 删除队首元素,将它的值存放在word
|
||||||
|
let path = visitMap.get(word); // 这个word的路径长度
|
||||||
|
for(let i = 0; i < word.length; i++){ // 遍历单词的每个字符
|
||||||
|
for (let c = 97; c <= 122; c++) { // 对应26个字母ASCII值 从'a' 到 'z' 遍历替换
|
||||||
|
// 拼串得到新的字符串
|
||||||
|
let newWord = word.slice(0, i) + String.fromCharCode(c) + word.slice(i + 1);
|
||||||
|
if(newWord === endWord) return path + 1; // 找到了end,返回path+1
|
||||||
|
// wordSet出现了newWord,并且newWord没有被访问过
|
||||||
|
if(wordSet.has(newWord) && !visitMap.has(newWord)) {
|
||||||
|
// 添加访问信息
|
||||||
|
visitMap.set(newWord, path + 1);
|
||||||
|
queue.push(newWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -241,6 +241,48 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var sumNumbers = function(root) {
|
||||||
|
const listToInt = path => {
|
||||||
|
let sum = 0;
|
||||||
|
for(let num of path){
|
||||||
|
// sum * 10 表示进位
|
||||||
|
sum = sum * 10 + num;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
const recur = root =>{
|
||||||
|
if (root.left == null && root.right == null) {
|
||||||
|
// 当是叶子节点的时候,开始处理
|
||||||
|
res += listToInt(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.left != null){
|
||||||
|
// 注意有回溯
|
||||||
|
path.push(root.left.val);
|
||||||
|
recur(root.left);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
if (root.right != null){
|
||||||
|
// 注意有回溯
|
||||||
|
path.push(root.right.val);
|
||||||
|
recur(root.right);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const path = new Array();
|
||||||
|
let res = 0;
|
||||||
|
// 如果节点为0,那么就返回0
|
||||||
|
if (root == null) return 0;
|
||||||
|
// 首先将根节点放到集合中
|
||||||
|
path.push(root.val);
|
||||||
|
// 开始递归
|
||||||
|
recur(root);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -247,6 +247,42 @@ class Solution:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var minCut = function(s) {
|
||||||
|
const len = s.length;
|
||||||
|
// 二维数组isPalindromic来保存整个字符串的回文情况
|
||||||
|
const isPalindromic = new Array(len).fill(false).map(() => new Array(len).fill(false));
|
||||||
|
for(let i = len - 1; i >= 0; i--){
|
||||||
|
for(let j = i; j < len; j++){
|
||||||
|
if(s[i] === s[j] && (j - i <= 1 || isPalindromic[i + 1][j - 1])){
|
||||||
|
isPalindromic[i][j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dp[i]:范围是[0, i]的回文子串,最少分割次数是dp[i]
|
||||||
|
const dp = new Array(len).fill(0);
|
||||||
|
for(let i = 0; i < len; i++) dp[i] = i; // 初始化 dp[i]的最大值其实就是i,也就是把每个字符分割出来
|
||||||
|
for(let i = 1; i < len; i++){
|
||||||
|
if(isPalindromic[0][i]){ // 判断是不是回文子串
|
||||||
|
dp[i] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
如果要对长度为[0, i]的子串进行分割,分割点为j。
|
||||||
|
那么如果分割后,区间[j + 1, i]是回文子串,那么dp[i] 就等于 dp[j] + 1。
|
||||||
|
这里可能有同学就不明白了,为什么只看[j + 1, i]区间,不看[0, j]区间是不是回文子串呢?
|
||||||
|
那么在回顾一下dp[i]的定义: 范围是[0, i]的回文子串,最少分割次数是dp[i]。
|
||||||
|
[0, j]区间的最小切割数量,我们已经知道了就是dp[j]。
|
||||||
|
此时就找到了递推关系,当切割点j在[0, i] 之间时候,dp[i] = dp[j] + 1;
|
||||||
|
本题是要找到最少分割次数,所以遍历j的时候要取最小的dp[i]。dp[i] = Math.min(dp[i], dp[j] + 1);
|
||||||
|
*/
|
||||||
|
for(let j = 0; j < i; j++){
|
||||||
|
if(isPalindromic[j + 1][i]){
|
||||||
|
dp[i] = Math.min(dp[i], dp[j] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[len - 1];
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -114,6 +114,17 @@ class Solution:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var hasCycle = function(head) {
|
||||||
|
let fast = head;
|
||||||
|
let slow = head;
|
||||||
|
// 空链表、单节点链表一定不会有环
|
||||||
|
while(fast != null && fast.next != null){
|
||||||
|
fast = fast.next.next; // 快指针,一次移动两步
|
||||||
|
slow = slow.next; // 慢指针,一次移动一步
|
||||||
|
if(fast === slow) return true; // 快慢指针相遇,表明有环
|
||||||
|
}
|
||||||
|
return false; // 正常走到链表末尾,表明没有环
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -351,6 +351,110 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
// 方法一 使用数组存储节点
|
||||||
|
var reorderList = function(head, s = [], tmp) {
|
||||||
|
let cur = head;
|
||||||
|
// list是数组,可以使用下标随机访问
|
||||||
|
const list = [];
|
||||||
|
while(cur != null){
|
||||||
|
list.push(cur);
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
cur = head; // 重新回到头部
|
||||||
|
let l = 1, r = list.length - 1; // 注意左边是从1开始
|
||||||
|
let count = 0;
|
||||||
|
while(l <= r){
|
||||||
|
if(count % 2 == 0){
|
||||||
|
// even
|
||||||
|
cur.next = list[r];
|
||||||
|
r--;
|
||||||
|
} else {
|
||||||
|
// odd
|
||||||
|
cur.next = list[l];
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
// 每一次指针都需要移动
|
||||||
|
cur = cur.next;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
// 当是偶数的话,需要做额外处理
|
||||||
|
if(list.length % 2 == 0){
|
||||||
|
cur.next = list[l];
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
// 注意结尾要结束一波
|
||||||
|
cur.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法二 使用双端队列的方法来解决 js中运行很慢
|
||||||
|
var reorderList = function(head, s = [], tmp) {
|
||||||
|
// js数组作为双端队列
|
||||||
|
const deque = [];
|
||||||
|
// 这里是取head的下一个节点,head不需要再入队了,避免造成重复
|
||||||
|
let cur = head.next;
|
||||||
|
while(cur != null){
|
||||||
|
deque.push(cur);
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
cur = head; // 回到头部
|
||||||
|
let count = 0;
|
||||||
|
while(deque.length !== 0){
|
||||||
|
if(count % 2 == 0){
|
||||||
|
// even,取出队列右边尾部的值
|
||||||
|
cur.next = deque.pop();
|
||||||
|
} else {
|
||||||
|
// odd, 取出队列左边头部的值
|
||||||
|
cur.next = deque.shift();
|
||||||
|
}
|
||||||
|
cur = cur.next;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
cur.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//方法三 将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表
|
||||||
|
var reorderList = function(head, s = [], tmp) {
|
||||||
|
const reverseList = head => {
|
||||||
|
let headNode = new ListNode(0);
|
||||||
|
let cur = head;
|
||||||
|
let next = null;
|
||||||
|
while(cur != null){
|
||||||
|
next = cur.next;
|
||||||
|
cur.next = headNode.next;
|
||||||
|
headNode.next = cur;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
return headNode.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fast = head, slow = head;
|
||||||
|
//求出中点
|
||||||
|
while(fast.next != null && fast.next.next != null){
|
||||||
|
slow = slow.next;
|
||||||
|
fast = fast.next.next;
|
||||||
|
}
|
||||||
|
//right就是右半部分 12345 就是45 1234 就是34
|
||||||
|
let right = slow.next;
|
||||||
|
//断开左部分和右部分
|
||||||
|
slow.next = null;
|
||||||
|
//反转右部分 right就是反转后右部分的起点
|
||||||
|
right = reverseList(right);
|
||||||
|
//左部分的起点
|
||||||
|
let left = head;
|
||||||
|
//进行左右部分来回连接
|
||||||
|
//这里左部分的节点个数一定大于等于右部分的节点个数 因此只判断right即可
|
||||||
|
while (right != null) {
|
||||||
|
let curLeft = left.next;
|
||||||
|
left.next = right;
|
||||||
|
left = curLeft;
|
||||||
|
|
||||||
|
let curRight = right.next;
|
||||||
|
right.next = left;
|
||||||
|
right = curRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -119,6 +119,25 @@ func isIsomorphic(s string, t string) bool {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var isIsomorphic = function(s, t) {
|
||||||
|
let len = s.length;
|
||||||
|
if(len === 0) return true;
|
||||||
|
let maps = new Map();
|
||||||
|
let mapt = new Map();
|
||||||
|
for(let i = 0, j = 0; i < len; i++, j++){
|
||||||
|
if(!maps.has(s[i])){
|
||||||
|
maps.set(s[i],t[j]);// maps保存 s[i] 到 t[j]的映射
|
||||||
|
}
|
||||||
|
if(!mapt.has(t[j])){
|
||||||
|
mapt.set(t[j],s[i]);// mapt保存 t[j] 到 s[i]的映射
|
||||||
|
}
|
||||||
|
// 无法映射,返回 false
|
||||||
|
if(maps.get(s[i]) !== t[j] || mapt.get(t[j]) !== s[i]){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -284,7 +284,41 @@ class Solution:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var isPalindrome = function(head) {
|
||||||
|
const reverseList = head => {// 反转链表
|
||||||
|
let temp = null;
|
||||||
|
let pre = null;
|
||||||
|
while(head != null){
|
||||||
|
temp = head.next;
|
||||||
|
head.next = pre;
|
||||||
|
pre = head;
|
||||||
|
head = temp;
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}
|
||||||
|
// 如果为空或者仅有一个节点,返回true
|
||||||
|
if(!head && !head.next) return true;
|
||||||
|
let slow = head;
|
||||||
|
let fast = head;
|
||||||
|
let pre = head;
|
||||||
|
while(fast != null && fast.next != null){
|
||||||
|
pre = slow; // 记录slow的前一个结点
|
||||||
|
slow = slow.next;
|
||||||
|
fast = fast.next.next;
|
||||||
|
}
|
||||||
|
pre.next = null; // 分割两个链表
|
||||||
|
// 前半部分
|
||||||
|
let cur1 = head;
|
||||||
|
// 后半部分。这里使用了反转链表
|
||||||
|
let cur2 = reverseList(slow);
|
||||||
|
while(cur1 != null){
|
||||||
|
if(cur1.val != cur2.val) return false;
|
||||||
|
// 注意要移动两个结点
|
||||||
|
cur1 = cur1.next;
|
||||||
|
cur2 = cur2.next;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,6 +95,21 @@ Python:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var moveZeroes = function(nums) {
|
||||||
|
let slow = 0;
|
||||||
|
for(let fast = 0; fast < nums.length; fast++){
|
||||||
|
if(nums[fast] != 0){//找到非0的元素
|
||||||
|
nums[slow] = nums[fast];//把非0的元素赋值给数组慢指针指向的索引处的值
|
||||||
|
slow++;//慢指针向右移动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 后面的元素全变成 0
|
||||||
|
for(let j = slow; j < nums.length; j++){
|
||||||
|
nums[j] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
|
@ -120,6 +120,55 @@ Python:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
//解法一
|
||||||
|
var islandPerimeter = function(grid) {
|
||||||
|
// 上下左右 4 个方向
|
||||||
|
const dirx = [-1, 1, 0, 0], diry = [0, 0, -1, 1];
|
||||||
|
const m = grid.length, n = grid[0].length;
|
||||||
|
let res = 0; //岛屿周长
|
||||||
|
for(let i = 0; i < m; i++){
|
||||||
|
for(let j = 0; j < n; j++){
|
||||||
|
if(grid[i][j] === 1){
|
||||||
|
for(let k = 0; k < 4; k++){ //上下左右四个方向
|
||||||
|
// 计算周边坐标的x,y
|
||||||
|
let x = i + dirx[k];
|
||||||
|
let y = j + diry[k];
|
||||||
|
// 四个方向扩展的新位置是水域或者越界就会为周长贡献1
|
||||||
|
if(x < 0 // i在边界上
|
||||||
|
|| x >= m // i在边界上
|
||||||
|
|| y < 0 // j在边界上
|
||||||
|
|| y >= n // j在边界上
|
||||||
|
|| grid[x][y] === 0){ // (x,y)位置是水域
|
||||||
|
res++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
//解法二
|
||||||
|
var islandPerimeter = function(grid) {
|
||||||
|
let sum = 0; // 陆地数量
|
||||||
|
let cover = 0; // 相邻数量
|
||||||
|
for(let i = 0; i < grid.length; i++){
|
||||||
|
for(let j = 0; j <grid[0].length; j++){
|
||||||
|
if(grid[i][j] === 1){
|
||||||
|
sum++;
|
||||||
|
// 统计上边相邻陆地
|
||||||
|
if(i - 1 >= 0 && grid[i-1][j] === 1) cover++;
|
||||||
|
// 统计左边相邻陆地
|
||||||
|
if(j - 1 >= 0 && grid[i][j-1] === 1) cover++;
|
||||||
|
// 为什么没统计下边和右边? 因为避免重复计算
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum * 4 - cover * 2;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -219,6 +219,31 @@ func predictPartyVictory(senateStr string) string {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var predictPartyVictory = function(senateStr) {
|
||||||
|
// R = true表示本轮循环结束后,字符串里依然有R;D同理。
|
||||||
|
let R = true, D = true;
|
||||||
|
// 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R
|
||||||
|
let flag = 0;
|
||||||
|
let senate = senateStr.split('');
|
||||||
|
while(R && D){ // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了
|
||||||
|
R = false;
|
||||||
|
D = false;
|
||||||
|
for(let i = 0; i < senate.length; i++){
|
||||||
|
if(senate[i] === 'R'){
|
||||||
|
if(flag < 0) senate[i] = 0;// 消灭R,R此时为false
|
||||||
|
else R = true;// 如果没被消灭,本轮循环结束有R
|
||||||
|
flag++;
|
||||||
|
}
|
||||||
|
if(senate[i] === 'D'){
|
||||||
|
if(flag > 0) senate[i] = 0;
|
||||||
|
else D = true;
|
||||||
|
flag--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 循环结束之后,R和D只能有一个为true
|
||||||
|
return R ? "Radiant" : "Dire";
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -337,6 +337,28 @@ func findNumberOfLIS(nums []int) int {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var findNumberOfLIS = function(nums) {
|
||||||
|
const len = nums.length;
|
||||||
|
if(len <= 1) return len;
|
||||||
|
let dp = new Array(len).fill(1); // i之前(包括i)最长递增子序列的长度为dp[i]
|
||||||
|
let count = new Array(len).fill(1); // 以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]
|
||||||
|
let res = 0;
|
||||||
|
for(let i = 1; i < len; i++){
|
||||||
|
for(let j = 0; j < i; j++){
|
||||||
|
if(nums[i] > nums[j]){
|
||||||
|
if(dp[j] + 1 > dp[i]){ // 第 j 个数字为前一个数字的子序列是否更更长
|
||||||
|
dp[i] = dp[j] + 1; //更新 dp[i]
|
||||||
|
count[i] = count[j]; // 重置count[i]
|
||||||
|
} else if(dp[j] + 1 === dp[i]){ // 和原来一样长
|
||||||
|
count[i] += count[j]; //更新count[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let max = Math.max(...dp); //扩展运算符找到最大长度
|
||||||
|
for(let i = 0; i < len; i++) if(dp[i] === max) res += count[i]; // 累加
|
||||||
|
return res;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -303,6 +303,43 @@ func findRedundantConnection(edges [][]int) []int {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
const n = 1005;
|
||||||
|
const father = new Array(n);
|
||||||
|
// 并查集里寻根的过程
|
||||||
|
const find = u => {
|
||||||
|
return u == father[u] ? u : father[u] = find(father[u]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将v->u 这条边加入并查集
|
||||||
|
const join = (u, v) => {
|
||||||
|
u = find(u);
|
||||||
|
v = find(v);
|
||||||
|
if(u == v) return;
|
||||||
|
father[v] = u;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断 u 和 v是否找到同一个根,本题用不上
|
||||||
|
const same = (u, v) => {
|
||||||
|
u = find(u);
|
||||||
|
v = find(v);
|
||||||
|
return u == v;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[][]} edges
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
var findRedundantConnection = function(edges) {
|
||||||
|
// 并查集初始化
|
||||||
|
for(let i = 0; i < n; i++){
|
||||||
|
father[i] = i;
|
||||||
|
}
|
||||||
|
for(let i = 0; i < edges.length; i++){
|
||||||
|
if(same(edges[i][0], edges[i][1])) return edges[i];
|
||||||
|
else join(edges[i][0], edges[i][1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -515,6 +515,91 @@ func findRedundantDirectedConnection(edges [][]int) []int {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
const N = 1010; // 如题:二维数组大小的在3到1000范围内
|
||||||
|
const father = new Array(N);
|
||||||
|
let n; // 边的数量
|
||||||
|
|
||||||
|
// 并查集里寻根的过程
|
||||||
|
const find = u => {
|
||||||
|
return u == father[u] ? u : father[u] = find(father[u]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将v->u 这条边加入并查集
|
||||||
|
const join = (u, v) => {
|
||||||
|
u = find(u);
|
||||||
|
v = find(v);
|
||||||
|
if(u == v) return;
|
||||||
|
father[v] = u;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断 u 和 v是否找到同一个根
|
||||||
|
const same = (u, v) => {
|
||||||
|
u = find(u);
|
||||||
|
v = find(v);
|
||||||
|
return u == v;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 在有向图里找到删除的那条边,使其变成树
|
||||||
|
const getRemoveEdge = edges => {
|
||||||
|
// 初始化并查集
|
||||||
|
for (let i = 1; i <= n; i++) {
|
||||||
|
father[i] = i;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < n; i++) { // 遍历所有的边
|
||||||
|
if (same(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
|
||||||
|
return edges[i];
|
||||||
|
}
|
||||||
|
join(edges[i][0], edges[i][1]);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删一条边之后判断是不是树
|
||||||
|
const isTreeAfterRemoveEdge = (edges, deleteEdge) => {
|
||||||
|
// 初始化并查集
|
||||||
|
for (let i = 1; i <= n; i++) {
|
||||||
|
father[i] = i;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (i == deleteEdge) continue;
|
||||||
|
if (same(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
join(edges[i][0], edges[i][1]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[][]} edges
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
var findRedundantDirectedConnection = function(edges) {
|
||||||
|
n = edges.length;// 边的数量
|
||||||
|
const inDegree = new Array(n+1).fill(0); // 记录节点入度
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
inDegree[edges[i][1]]++; // 统计入度
|
||||||
|
}
|
||||||
|
let vec = [];// 记录入度为2的边(如果有的话就两条边)
|
||||||
|
// 找入度为2的节点所对应的边,注意要倒叙,因为优先返回最后出现在二维数组中的答案
|
||||||
|
for (let i = n - 1; i >= 0; i--) {
|
||||||
|
if (inDegree[edges[i][1]] == 2) {
|
||||||
|
vec.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理图中情况1 和 情况2
|
||||||
|
// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
|
||||||
|
if (vec.length > 0) {
|
||||||
|
if (isTreeAfterRemoveEdge(edges, vec[0])) {
|
||||||
|
return edges[vec[0]];
|
||||||
|
} else {
|
||||||
|
return edges[vec[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理图中情况3
|
||||||
|
// 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
|
||||||
|
return getRemoveEdge(edges);
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -129,6 +129,17 @@ func pivotIndex(nums []int) int {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var pivotIndex = function(nums) {
|
||||||
|
const sum = nums.reduce((a,b) => a + b);//求和
|
||||||
|
// 中心索引左半和 中心索引右半和
|
||||||
|
let leftSum = 0, rightSum = 0;
|
||||||
|
for(let i = 0; i < nums.length; i++){
|
||||||
|
leftSum += nums[i];
|
||||||
|
rightSum = sum - leftSum + nums[i];// leftSum 里面已经有 nums[i],多减了一次,所以加上
|
||||||
|
if(leftSum === rightSum) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -220,6 +220,55 @@ func canVisitAllRooms(rooms [][]int) bool {
|
|||||||
```
|
```
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
//DFS
|
||||||
|
var canVisitAllRooms = function(rooms) {
|
||||||
|
const dfs = (key, rooms, visited) => {
|
||||||
|
if(visited[key]) return;
|
||||||
|
visited[key] = 1;
|
||||||
|
for(let k of rooms[key]){
|
||||||
|
// 深度优先搜索遍历
|
||||||
|
dfs(k, rooms, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const visited = new Array(rooms.length).fill(false);
|
||||||
|
dfs(0, rooms, visited);
|
||||||
|
//检查是否都访问到了
|
||||||
|
for (let i of visited) {
|
||||||
|
if (!i) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
//BFS
|
||||||
|
var canVisitAllRooms = function(rooms) {
|
||||||
|
const bfs = rooms => {
|
||||||
|
const visited = new Array(rooms.length).fill(0); // 标记房间是否被访问过
|
||||||
|
visited[0] = 1; // 0 号房间开始
|
||||||
|
const queue = []; //js数组作为队列使用
|
||||||
|
queue.push(0); // 0 号房间开始
|
||||||
|
// 广度优先搜索的过程
|
||||||
|
while(queue.length !== 0){
|
||||||
|
let key = queue[0];
|
||||||
|
queue.shift();
|
||||||
|
for(let k of rooms[key]){
|
||||||
|
if(!visited[k]){
|
||||||
|
queue.push(k);
|
||||||
|
visited[k] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查房间是不是都遍历过了
|
||||||
|
for(let i of visited){
|
||||||
|
if(i === 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return bfs(rooms);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
|
@ -232,6 +232,64 @@ func backspaceCompare(s string, t string) bool {
|
|||||||
```
|
```
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
// 双栈
|
||||||
|
var backspaceCompare = function(s, t) {
|
||||||
|
const arrS = [], arrT = []; // 数组作为栈使用
|
||||||
|
for(let char of s){
|
||||||
|
char === '#' ? arrS.pop() : arrS.push(char);
|
||||||
|
}
|
||||||
|
for(let char of t){
|
||||||
|
char === '#' ? arrT.pop() : arrT.push(char);
|
||||||
|
}
|
||||||
|
return arrS.join('') === arrT.join(''); // 比较两个字符串是否相等
|
||||||
|
};
|
||||||
|
|
||||||
|
//双栈精简
|
||||||
|
var backspaceCompare = function(s, t) {
|
||||||
|
const getString = s => {
|
||||||
|
let arrS = [];
|
||||||
|
for(let char of s){
|
||||||
|
char === '#' ? arrS.pop() : arrS.push(char);
|
||||||
|
}
|
||||||
|
return arrS.join('');
|
||||||
|
}
|
||||||
|
return getString(s) === getString(t);
|
||||||
|
};
|
||||||
|
|
||||||
|
//双指针
|
||||||
|
var backspaceCompare = function(s, t) {
|
||||||
|
let sSkipNum = 0; // 记录s的#数量
|
||||||
|
let tSkipNum = 0; // 记录t的#数量
|
||||||
|
let i = s.length - 1, j = t.length - 1;
|
||||||
|
while(true) {
|
||||||
|
while(i >= 0){ // 从后向前,消除s的#
|
||||||
|
if(s[i] === '#') sSkipNum++;
|
||||||
|
else {
|
||||||
|
if (sSkipNum > 0) sSkipNum--;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
while (j >= 0) { // 从后向前,消除t的#
|
||||||
|
if (t[j] === '#') tSkipNum++;
|
||||||
|
else {
|
||||||
|
if (tSkipNum > 0) tSkipNum--;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
// 后半部分#消除完了,接下来比较s[i] != t[j]
|
||||||
|
if (i < 0 || j < 0) break; // s 或者t 遍历到头了
|
||||||
|
if (s[i] !== t[j]) return false;
|
||||||
|
i--;j--;
|
||||||
|
}
|
||||||
|
// 说明s和t同时遍历完毕
|
||||||
|
if (i == -1 && j == -1) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
|
@ -209,6 +209,57 @@ func sortArrayByParityII(nums []int) []int {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
//方法一
|
||||||
|
var sortArrayByParityII = function(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
// 分别存放 nums 中的奇数、偶数
|
||||||
|
let evenIndex = 0, oddIndex = 0;
|
||||||
|
// 初始化就确定数组大小,节省开销
|
||||||
|
const even = new Array(Math.floor(n/2));
|
||||||
|
const odd = new Array(Math.floor(n/2));
|
||||||
|
// 把A数组放进偶数数组,和奇数数组
|
||||||
|
for(let i = 0; i < n; i++){
|
||||||
|
if(nums[i] % 2 === 0) even[evenIndex++] = nums[i];
|
||||||
|
else odd[oddIndex++] = nums[i];
|
||||||
|
}
|
||||||
|
// 把奇偶数组重新存回 nums
|
||||||
|
let index = 0;
|
||||||
|
for(let i = 0; i < even.length; i++){
|
||||||
|
nums[index++] = even[i];
|
||||||
|
nums[index++] = odd[i];
|
||||||
|
}
|
||||||
|
return nums;
|
||||||
|
};
|
||||||
|
|
||||||
|
//方法二
|
||||||
|
var sortArrayByParityII = function(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
const result = new Array(n);
|
||||||
|
// 偶数下标 和 奇数下标
|
||||||
|
let evenIndex = 0, oddIndex = 1;
|
||||||
|
for(let i = 0; i < n; i++){
|
||||||
|
if(nums[i] % 2 === 0) {
|
||||||
|
result[evenIndex] = nums[i];
|
||||||
|
evenIndex += 2;
|
||||||
|
} else {
|
||||||
|
result[oddIndex] = nums[i];
|
||||||
|
oddIndex += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
//方法三
|
||||||
|
var sortArrayByParityII = function(nums) {
|
||||||
|
let oddIndex = 1;
|
||||||
|
for(let i = 0; i < nums.length; i += 2){
|
||||||
|
if(nums[i] % 2 === 1){ // 在偶数位遇到了奇数
|
||||||
|
while(nums[oddIndex] % 2 !== 0) oddIndex += 2;// 在奇数位找一个偶数
|
||||||
|
[nums[oddIndex], nums[i]] = [nums[i], nums[oddIndex]]; // 解构赋值交换
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nums;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -182,6 +182,34 @@ func isLongPressedName(name string, typed string) bool {
|
|||||||
```
|
```
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var isLongPressedName = function(name, typed) {
|
||||||
|
let i = 0, j = 0;
|
||||||
|
const m = name.length, n = typed.length;
|
||||||
|
while(i < m && j < n){
|
||||||
|
if(name[i] === typed[j]){ // 相同则同时向后匹配
|
||||||
|
i++; j++;
|
||||||
|
} else {
|
||||||
|
if(j === 0) return false; // 如果是第一位就不相同直接返回false
|
||||||
|
// 判断边界为n-1,若为n会越界,例如name:"kikcxmvzi" typed:"kiikcxxmmvvzzz"
|
||||||
|
while(j < n - 1 && typed[j] === typed[j-1]) j++;
|
||||||
|
if(name[i] === typed[j]){ // j跨越重复项之后再次和name[i]匹配,相同则同时向后匹配
|
||||||
|
i++; j++;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 说明name没有匹配完 例如 name:"pyplrzzzzdsfa" type:"ppyypllr"
|
||||||
|
if(i < m) return false;
|
||||||
|
// 说明type没有匹配完 例如 name:"alex" type:"alexxrrrrssda"
|
||||||
|
while(j < n) {
|
||||||
|
if(typed[j] === typed[j-1]) j++;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
|
@ -154,6 +154,16 @@ func validMountainArray(arr []int) bool {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var validMountainArray = function(arr) {
|
||||||
|
if(arr.length < 3) return false;// 一定不是山脉数组
|
||||||
|
let left = 0, right = arr.length - 1;// 双指针
|
||||||
|
// 注意防止越界
|
||||||
|
while(left < arr.length && arr[left] < arr[left+1]) left++;
|
||||||
|
while(right>0 && arr[right-1] > arr[right]) right--;
|
||||||
|
// 如果left或者right都在起始位置,说明不是山峰
|
||||||
|
if(left === right && left !== 0 && right !== arr.length - 1) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -118,7 +118,23 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
``` javascript
|
||||||
|
var uniqueOccurrences = function(arr) {
|
||||||
|
const count = new Array(2002).fill(0);// -1000 <= arr[i] <= 1000
|
||||||
|
for(let i = 0; i < arr.length; i++){
|
||||||
|
count[arr[i] + 1000]++;// 防止负数作为下标
|
||||||
|
}
|
||||||
|
// 标记相同频率是否重复出现
|
||||||
|
const fre = new Array(1002).fill(false);// 1 <= arr.length <= 1000
|
||||||
|
for(let i = 0; i <= 2000; i++){
|
||||||
|
if(count[i] > 0){//有i出现过
|
||||||
|
if(fre[count[i]] === false) fre[count[i]] = true;//之前未出现过,标记为出现
|
||||||
|
else return false;//之前就出现了,重复出现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
```
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||||
|
@ -108,6 +108,15 @@ public:
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var balancedStringSplit = function(s) {
|
||||||
|
let res = 0, total = 0;//res为平衡字符串数量 total为当前"R"字符和"L"字符的数量差
|
||||||
|
for(let c of s){// 遍历字符串每个字符
|
||||||
|
//因为开始字符数量差就是0,遍历的时候要先改变数量差,否则会影响结果数量
|
||||||
|
total += c === 'R' ? 1:-1;//遇到"R",total++;遇到"L",total--
|
||||||
|
if(total === 0) res++;//只要"R""L"数量一样就可以算是一个平衡字符串
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -166,6 +166,18 @@ class Solution {
|
|||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
var sortByBits = function(arr) {
|
||||||
|
const bitCount = n =>{// 计算n的二进制中1的数量
|
||||||
|
let count = 0;
|
||||||
|
while(n){
|
||||||
|
n &= (n - 1);// 清除最低位的1
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
// 如果有差,则按bits数排,如果无差,则按原值排
|
||||||
|
return arr.sort((a,b) => bitCount(a) - bitCount(b) || a - b);
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -155,7 +155,23 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var smallerNumbersThanCurrent = function(nums) {
|
||||||
|
const map = new Map();// 记录数字 nums[i] 有多少个比它小的数字
|
||||||
|
const res = nums.slice(0);//深拷贝nums
|
||||||
|
res.sort((a,b) => a - b);
|
||||||
|
for(let i = 0; i < res.length; i++){
|
||||||
|
if(!map.has(res[i])){// 遇到了相同的数字,那么不需要更新该 number 的情况
|
||||||
|
map.set(res[i],i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 此时map里保存的每一个元素数值 对应的 小于这个数值的个数
|
||||||
|
for(let i = 0; i < nums.length; i++){
|
||||||
|
res[i] = map.get(nums[i]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -126,6 +126,29 @@ class Solution:
|
|||||||
Go:
|
Go:
|
||||||
|
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
```javascript
|
||||||
|
var balanceBST = function(root) {
|
||||||
|
const res = [];
|
||||||
|
// 中序遍历转成有序数组
|
||||||
|
const travesal = cur => {
|
||||||
|
if(!cur) return;
|
||||||
|
travesal(cur.left);
|
||||||
|
res.push(cur.val);
|
||||||
|
travesal(cur.right);
|
||||||
|
}
|
||||||
|
// 有序数组转成平衡二叉树
|
||||||
|
const getTree = (nums, left, right) => {
|
||||||
|
if(left > right) return null;
|
||||||
|
let mid = left + ((right - left) >> 1);
|
||||||
|
let root = new TreeNode(nums[mid]);// 中心位置作为当前节点的值
|
||||||
|
root.left = getTree(nums, left, mid - 1);// 递归地将区间[left,mid−1] 作为当前节点的左子树
|
||||||
|
root.right = getTree(nums, mid + 1, right);// 递归地将区间[mid+1,right] 作为当前节点的左子树
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
travesal(root);
|
||||||
|
return getTree(res, 0, res.length - 1);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||||
|
Reference in New Issue
Block a user