Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
762fd95901 | |||
a88006e4d8 | |||
efcd1eb12a | |||
07e407e943 | |||
1ababd7776 | |||
329c0f2588 | |||
5c1a70a60f | |||
dbcfc3f91a | |||
614b10bcd0 | |||
09c5a746f1 | |||
2e1ef51465 | |||
81d4ae749b | |||
21d1a55888 | |||
f53e76dcf0 | |||
0a41e81e58 | |||
7eb11b0136 | |||
a5e958c203 | |||
11b04202d8 | |||
e7559ec1c7 | |||
c2c64470b0 | |||
bd018e32ea | |||
e0ea622d45 | |||
72ac2258de | |||
0d078c09b4 | |||
eb9dee4335 | |||
07a3353880 | |||
17aed86c56 | |||
371b5ede81 | |||
1d8c16c31b | |||
11981edc96 | |||
e24153d3db | |||
dfe0072916 | |||
456e2bbccf | |||
b2e8377e3d | |||
40a676cd31 | |||
9a4d2d388f | |||
27d3b489e3 | |||
89042e81a8 | |||
030e04b5d2 | |||
32033709dc | |||
eea70ac117 |
@ -22,6 +22,7 @@
|
|||||||
"no-constant-condition": "off", // 允许while(true)
|
"no-constant-condition": "off", // 允许while(true)
|
||||||
"default-case": "off", // 允许switch不写default
|
"default-case": "off", // 允许switch不写default
|
||||||
"no-fallthrough": "off", // 允许case穿透
|
"no-fallthrough": "off", // 允许case穿透
|
||||||
|
"prefer-destructuring": ["error", {"object": false, "array": false}],
|
||||||
"import/extensions": [
|
"import/extensions": [
|
||||||
"error",
|
"error",
|
||||||
"ignorePackages", // 忽略 node_modules 内的包
|
"ignorePackages", // 忽略 node_modules 内的包
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,7 +25,7 @@ pids
|
|||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode
|
#.vscode
|
||||||
.idea
|
.idea
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
|
12
.vscode/settings.json
vendored
Normal file
12
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.clang_format_fallbackStyle": "LLVM",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "always"
|
||||||
|
},
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode", // 使用 Prettier 格式化
|
||||||
|
"editor.formatOnSave": true, // 保存时自动格式化
|
||||||
|
"prettier.requireConfig": true, // 只有在项目中有 Prettier 配置时才格式化
|
||||||
|
"eslint.alwaysShowStatus": true, // 显示 ESLint 状态
|
||||||
|
"eslint.format.enable": true // 允许 ESLint 格式化
|
||||||
|
|
||||||
|
}
|
3
backtrack/README.md
Normal file
3
backtrack/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 回溯算法
|
||||||
|
回溯算法就是“递归+循环+剪枝”,是一种暴力搜索的方式,适合解决组合,排列,分组,SCP(例如:n皇后,数独等),
|
||||||
|
绘制决策树更易于我们理解收集路径和回溯的过程。
|
54
backtrack/combinations/17电话号码.js
Normal file
54
backtrack/combinations/17电话号码.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} digits
|
||||||
|
* @return {string[]}
|
||||||
|
* https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
|
||||||
|
*/
|
||||||
|
const letterCombinations = function (digits) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function f1(digits) {
|
||||||
|
// 特殊情况处理:如果输入是空字符串,直接返回空数组
|
||||||
|
if (!digits) return [];
|
||||||
|
|
||||||
|
// 数字到字母的映射表(与手机九宫格一致),下标 0 和 1 没有对应字母
|
||||||
|
const map = [
|
||||||
|
[], // 0
|
||||||
|
[], // 1
|
||||||
|
['a', 'b', 'c'], // 2
|
||||||
|
['d', 'e', 'f'], // 3
|
||||||
|
['g', 'h', 'i'], // 4
|
||||||
|
['j', 'k', 'l'], // 5
|
||||||
|
['m', 'n', 'o'], // 6
|
||||||
|
['p', 'q', 'r', 's'], // 7
|
||||||
|
['t', 'u', 'v'], // 8
|
||||||
|
['w', 'x', 'y', 'z'], // 9
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = []; // 存放所有可能的组合结果
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回溯函数
|
||||||
|
* @param {string[]} path 当前递归路径(字符数组)
|
||||||
|
* @param {number} start 当前处理的是 digits 的第几个字符
|
||||||
|
*/
|
||||||
|
const backtrack = (path, start) => {
|
||||||
|
// 递归终止条件:如果 path 长度等于输入数字的长度,说明已经生成一个完整组合
|
||||||
|
if (start === digits.length) {
|
||||||
|
result.push(path.join('')); // 把字符数组转成字符串加入结果中
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前数字对应的所有字母(注意把字符转成数字作为索引)
|
||||||
|
for (const letter of map[+digits[start]]) {
|
||||||
|
path.push(letter); // 做选择:添加一个字母
|
||||||
|
backtrack(path, start + 1); // 递归处理下一个数字
|
||||||
|
path.pop(); // 回溯:撤销上一步选择,尝试其他字母
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从空路径、起始位置 0 开始回溯搜索
|
||||||
|
backtrack([], 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
8
backtrack/combinations/39组合总和.js
Normal file
8
backtrack/combinations/39组合总和.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} candidates
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[][]}
|
||||||
|
*/
|
||||||
|
const combinationSum = function (candidates, target) {
|
||||||
|
|
||||||
|
};
|
68
backtrack/combinations/77组合.js
Normal file
68
backtrack/combinations/77组合.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @param {number} k
|
||||||
|
* @return {number[][]}
|
||||||
|
* https://leetcode.cn/problems/combinations/
|
||||||
|
*/
|
||||||
|
const combine = function (n, k) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用回溯可以轻松的解决这个问题,定义结果esult=[]用来收集结果,定义backtrack(path, start)来收集结果和回溯
|
||||||
|
path:用来收集路径
|
||||||
|
start: 用来标记选择组合元素的起始位置,因为组合对顺序没有要求{1,2},{2,1}算一个组合,避免重复
|
||||||
|
*/
|
||||||
|
function f1(n, k) {
|
||||||
|
const result = []; // 收集符合要求的组合
|
||||||
|
|
||||||
|
const backtrack = (path, start) => {
|
||||||
|
// 如果path的长度符合要求,收集结果,当path.length === k 时
|
||||||
|
if (path.length === k) {
|
||||||
|
result.push([...path]);
|
||||||
|
return; // 结束这个路径的收集
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从start开始选择元素放入path,进行组合
|
||||||
|
for (let i = start; i <= n; i++) {
|
||||||
|
// 将当前元素加入组合
|
||||||
|
path.push(i);
|
||||||
|
// 递归,组合新的元素
|
||||||
|
backtrack(path, i + 1);
|
||||||
|
// 回溯,将之前的组合路径去掉
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
backtrack([], 1);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
剪枝优化
|
||||||
|
*/
|
||||||
|
function f2(n, k) {
|
||||||
|
const result = []; // 收集符合要求的组合
|
||||||
|
|
||||||
|
const backtrack = (path, start) => {
|
||||||
|
if (path.length + (n - start + 1) < k) return; // 剪枝优化
|
||||||
|
// 如果path的长度符合要求,收集结果,当path.length === k 时
|
||||||
|
if (path.length === k) {
|
||||||
|
result.push([...path]);
|
||||||
|
return; // 结束这个路径的收集
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从start开始选择元素放入path,进行组合
|
||||||
|
for (let i = start; i <= n; i++) {
|
||||||
|
// 将当前元素加入组合
|
||||||
|
path.push(i);
|
||||||
|
// 递归,组合新的元素
|
||||||
|
backtrack(path, i + 1);
|
||||||
|
// 回溯,将之前的组合路径去掉
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
backtrack([], 1);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
36
binary-search/35搜索插入位置.js
Normal file
36
binary-search/35搜索插入位置.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const searchInsert = function (nums, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接使用二分查找,二分查找没什么好说的,但是这个题目中target不一定存在nums中,所以需要设置一个pos遍历来保存,target应该插入的位置,
|
||||||
|
我们只需在target < nums[mid]的时候设置一次就行,初始化pos的index=nums.length
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(nums, target) {
|
||||||
|
let pos = nums.length;
|
||||||
|
let left = 0;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
|
||||||
|
// 二分查找知道left>right结束查找
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = Math.floor((left + right) / 2);
|
||||||
|
// 如果target<nums[mid] 则更新pos
|
||||||
|
if (target === nums[mid]) return mid;
|
||||||
|
|
||||||
|
if (target < nums[mid]) {
|
||||||
|
pos = mid;
|
||||||
|
right = mid - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
31
dynamic-programming/subsequence/1035不相交的线.js
Normal file
31
dynamic-programming/subsequence/1035不相交的线.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums1
|
||||||
|
* @param {number[]} nums2
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxUncrossedLines = function (nums1, nums2) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题看似无从下手实际上非常得简单,我们从结果入手,一个不交叉得最大连线是什么样的,经过观察发现就是要我们求
|
||||||
|
最大公共序列,直接把1143的题目拿过来改一下即可
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(nums1, nums2) {
|
||||||
|
const m = nums1.length;
|
||||||
|
const n = nums2.length;
|
||||||
|
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
if (nums1[i] === nums2[j]) {
|
||||||
|
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[0][0];
|
||||||
|
}
|
30
dynamic-programming/subsequence/1143最长公共子序列.js
Normal file
30
dynamic-programming/subsequence/1143最长公共子序列.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} text1
|
||||||
|
* @param {string} text2
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const longestCommonSubsequence = function (text1, text2) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义dp[i][j]为text1从i位置开始统计,text2从j位置开始统计的最长公共子序列长度,如果text1[i]和text2[j]相等,则dp[i][j]=dp[i+1][j+1]
|
||||||
|
若不相等则,dp[i][j] = max(dp[i+1][j], dp[i][j+1])
|
||||||
|
*/
|
||||||
|
function f1(text1, text2) {
|
||||||
|
const m = text1.length;
|
||||||
|
const n = text2.length;
|
||||||
|
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
if (text1[i] === text2[j]) {
|
||||||
|
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[0][0];
|
||||||
|
}
|
30
dynamic-programming/subsequence/392判断子序列.js
Normal file
30
dynamic-programming/subsequence/392判断子序列.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string} t
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isSubsequence = function (s, t) {
|
||||||
|
return f1(s, t);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目起始从头到尾遍历开s[1]在不在t中,然后再看s[2]在不在,最后看s[len - 1]在不在,如果在则返回true,
|
||||||
|
这个题目也可以是使用动态规划,动态规划的思路就是,如果s[i]在t中存在,如果s[i+1:]这个子序列也在t中存在,
|
||||||
|
那么s[i:]开始的子序列,在t中也存在,定义dp[i]为,字符串s从i开始的所有子序列s[i:]在t[j]之后的子序列中存在
|
||||||
|
s[i] === t[j]
|
||||||
|
*/
|
||||||
|
function f2(s, t) {
|
||||||
|
if (s.length === 0) return true;
|
||||||
|
if (t.length === 0) return false;
|
||||||
|
|
||||||
|
const dp = Array(s.length).fill(false);
|
||||||
|
|
||||||
|
for (let j = t.length - 1, i = s.length - 1; j >= 0 && i >= 0; j--) {
|
||||||
|
if (t[j] === s[i]) {
|
||||||
|
dp[i] = true;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[0];
|
||||||
|
}
|
27
dynamic-programming/subsequence/53最大子数组和.js
Normal file
27
dynamic-programming/subsequence/53最大子数组和.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxSubArray = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义dp[i]为从nums[i]开始的和最大的子数组,那么动态转移方程为dp[i] = dp[i+1] + nums[i] (dp[i+1] > 0)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(nums) {
|
||||||
|
const dp = Array(nums.length + 1).fill(0); // dp[i]表示从i位置开始的和最大子数组
|
||||||
|
let result = -Infinity;
|
||||||
|
|
||||||
|
for (let i = nums.length - 1; i >= 0; i--) {
|
||||||
|
if (dp[i + 1] > 0) {
|
||||||
|
dp[i] = nums[i] + dp[i + 1];
|
||||||
|
} else {
|
||||||
|
dp[i] = nums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Math.max(result, dp[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
72
dynamic-programming/subsequence/583两个字符串的删除操作.js
Normal file
72
dynamic-programming/subsequence/583两个字符串的删除操作.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} word1
|
||||||
|
* @param {string} word2
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const minDistance = function (word1, word2) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题看似无从下手,其实反过来思考非常容易,直接求出最长的公共子序列,然后步数就是这个两个字符串中多出的那几个字符的和
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(word1, word2) {
|
||||||
|
const commonLen = longestCommonSubsequence(word1, word2);
|
||||||
|
return word1.length + word2.length - 2 * commonLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
function longestCommonSubsequence(text1, text2) {
|
||||||
|
const m = text1.length;
|
||||||
|
const n = text2.length;
|
||||||
|
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
if (text1[i] === text2[j]) {
|
||||||
|
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接利用动态规划,来求解,定义dp[i][j]为s1中前i个字符和s2中前j个字符,变得相同需要删除的字符数量的最少个数,第i个字符是s1[i-1]
|
||||||
|
同理第j个字符是s2[j-1]注意这里的dp[i][j]的定义是前i个字符,从1开始数的,之所以要这样定义,是因为s1可以为空字符,需要使用dp[0][0]
|
||||||
|
来表示空字符
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(word1, word2) {
|
||||||
|
const m = word1.length;
|
||||||
|
const n = word2.length;
|
||||||
|
|
||||||
|
// 定义dp表m+1行,n+1列
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // 初始化成0没有特殊意义,反正都要填表
|
||||||
|
|
||||||
|
// 初始第一行,s1为空字符串,那么当s2有多少个字符就取出多少个,即dp[0][j] = j
|
||||||
|
for (let j = 0; j <= n; j++) {
|
||||||
|
dp[0][j] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列同理
|
||||||
|
for (let i = 0; i <= m; i++) {
|
||||||
|
dp[i][0] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充dp表从上到下,从左到右
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
if (word1[i - 1] === word2[j - 1]) { // 如果第i个字符和第j个字符相等
|
||||||
|
dp[i][j] = dp[i - 1][j - 1];
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m][n];
|
||||||
|
}
|
37
dynamic-programming/subsequence/718最长重复子数组.js
Normal file
37
dynamic-programming/subsequence/718最长重复子数组.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums1
|
||||||
|
* @param {number[]} nums2
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const findLength = function (nums1, nums2) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义:dp[i][j]表示nums1从i开始,nums2从j开始的最长子数组的长度,dp[i][j]可以由两种情况得来,第一种,当nums1[i] !== nums2[j],
|
||||||
|
那么从这个位置开始的子数组,就不可能是公共子数组,多以dp[i][j] = 0;第二种,当nums1[i] === nums2[j] 那么dp[i][j] = 1 + dp[i+1][j+1]
|
||||||
|
得来。
|
||||||
|
*/
|
||||||
|
function f1(nums1, nums2) {
|
||||||
|
const m = nums1.length;
|
||||||
|
const n = nums2.length;
|
||||||
|
let result = 0;
|
||||||
|
// 定义dp表,并且初始化
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
// 按照dp[i][j]的定义我们需要从下到上,从右到左填充dp数组
|
||||||
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
if (nums1[i] === nums2[j]) {
|
||||||
|
dp[i][j] = 1 + dp[i + 1][j + 1];
|
||||||
|
result = Math.max(result, dp[i][j]);
|
||||||
|
} else {
|
||||||
|
dp[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(dp);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
f1([0, 0, 0, 0, 1], [0, 0, 0, 0, 1]);
|
48
dynamic-programming/subsequence/72编辑距离.js
Normal file
48
dynamic-programming/subsequence/72编辑距离.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} word1
|
||||||
|
* @param {string} word2
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const minDistance = function (word1, word2) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
有删除,添加,修改三种操作,word1的删除和word2的添加效果是一样的,所以实际上只有三种操作
|
||||||
|
1. 往word1中删除一个字符
|
||||||
|
2. 往word2中删除一个字符
|
||||||
|
3. 修改word1中的一个字符
|
||||||
|
|
||||||
|
定义dp[i][j]表示word1的前i个字符和word2的前j个字符的最少编辑距离,那么在已知dp[i-1][j-1]和dp[i-1][j],dp[i][j-1]的情况下推出来
|
||||||
|
如果第word1的第i个字符和word2的第j个字符相等,那么最小编辑距离就是dp[i-1][j-1]因为第i个数和第j个字符我不需要操作,而剩下的这些字符
|
||||||
|
最少编辑距离就是dp[i-1][j-1],如果这word1的第i个字符和word2的第j个字符不相等,我们可以删除word1的第i个字符,然后将word1的前i-1个字符
|
||||||
|
和word2的前j个字符变得一样所需得编辑距离加上删除word1得第i个字符这一步一共是dp[i-1][j] + 1个编辑距离,同理:dp[i][j-1]得原理一样,
|
||||||
|
最后还有一种获得最优得情况是,我不删除,只是将word1中得最后一个字符修改成和word2中卒子后一个字符一样,这样就变成word1[i-1]==word2[j-1]
|
||||||
|
相等得情况了。
|
||||||
|
*/
|
||||||
|
function f1(word1, word2) {
|
||||||
|
const m = word1.length;
|
||||||
|
const n = word2.length;
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // 初始化成0无意义,只是初始化而已
|
||||||
|
|
||||||
|
// 初始化,如果word1为空,word2要想变得和word1一样,编辑距离就是它自身长度
|
||||||
|
for (let j = 0; j <= n; j++) {
|
||||||
|
dp[0][j] = j;
|
||||||
|
}
|
||||||
|
// word2为空同理
|
||||||
|
for (let i = 0; i <= m; i++) {
|
||||||
|
dp[i][0] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填dp表,dp[i][j] 依赖dp[i-1][j-1], dp[i-1][j], dp[i][j-1],所以要从上到下,从左到右遍历
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
if (word1[i - 1] === word2[j - 1]) {
|
||||||
|
dp[i][j] = dp[i - 1][j - 1];
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[m][n];
|
||||||
|
}
|
41
heap/76数组中第k个最大的元素.js
Normal file
41
heap/76数组中第k个最大的元素.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} k
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const findKthLargest = function (nums, k) {
|
||||||
|
// 将数组的前k个元素作为堆,然后将其堆化
|
||||||
|
const heap = nums.slice(0, k);
|
||||||
|
// 从最后一个非叶子节点遍历,自底向上下沉堆化
|
||||||
|
for (let i = Math.floor(k / 2 - 1); i >= 0; i--) {
|
||||||
|
siftDown(heap, i, k);
|
||||||
|
}
|
||||||
|
// 处理剩余元素
|
||||||
|
for (let i = k; i < nums.length; i++) {
|
||||||
|
// 如果剩余元素大于堆顶元素,替换然后下沉维护堆
|
||||||
|
if (nums[i] > heap[0]) {
|
||||||
|
heap[0] = nums[i];
|
||||||
|
siftDown(heap, 0, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return heap[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下沉函数(维护堆性质)
|
||||||
|
function siftDown(heap, start, heapSize) {
|
||||||
|
let cur = start;
|
||||||
|
while (true) {
|
||||||
|
const left = 2 * cur + 1;
|
||||||
|
const right = 2 * cur + 2;
|
||||||
|
let smallest = cur;
|
||||||
|
|
||||||
|
if (left < heapSize && heap[left] < heap[smallest]) smallest = left;
|
||||||
|
if (right < heapSize && heap[right] < heap[smallest]) smallest = right;
|
||||||
|
|
||||||
|
if (smallest === cur) break;
|
||||||
|
|
||||||
|
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||||
|
cur = smallest;
|
||||||
|
}
|
||||||
|
}
|
131
heap/base/topk问题.js
Normal file
131
heap/base/topk问题.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
topk 问题是一个经典的问题,我们维持一个大小为k的堆,如果要求最大的第三个数我们就维持小顶堆,反之维持大小为k的大顶堆
|
||||||
|
为什么?这有点反直觉,为什么求第k大的数要维持小顶堆,而不是大顶堆,这是因为第k大的数是数组中k个最大的数中最小的那一个,
|
||||||
|
用小顶堆来维持那么要求的第k个最大的数刚好在小顶堆的堆顶
|
||||||
|
*/
|
||||||
|
|
||||||
|
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} k
|
||||||
|
*/
|
||||||
|
function topK(nums, k) {
|
||||||
|
const heap = Array(k).fill(-Infinity); // 定义一个大小为k数组用来维护堆
|
||||||
|
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
// 如果当前元素大于堆顶元素,替换堆顶元素然后下沉调整堆
|
||||||
|
if (nums[i] > heap[0]) {
|
||||||
|
heap[0] = nums[i];
|
||||||
|
let cur = 0; // cur指向下沉元素的下标,一开始就在堆顶
|
||||||
|
while (true) {
|
||||||
|
const left = cur * 2 + 1;
|
||||||
|
const right = cur * 2 + 2;
|
||||||
|
let smallest = cur;
|
||||||
|
|
||||||
|
if (left < k && heap[left] < heap[smallest]) smallest = left;
|
||||||
|
if (right < k && heap[right] < heap[smallest]) smallest = right;
|
||||||
|
|
||||||
|
// 如果smallest 和cur相等,表示当前元素来到了合适的位置cur,结束下沉
|
||||||
|
if (smallest === cur) break;
|
||||||
|
|
||||||
|
// 交换位置下沉
|
||||||
|
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||||
|
cur = smallest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回堆顶元素
|
||||||
|
return heap[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用小顶堆找出数组中第 K 大的数
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} k
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function topKbest(nums, k) {
|
||||||
|
const heap = nums.slice(0, k); // 先取前 k 个元素构建小顶堆
|
||||||
|
|
||||||
|
// 下沉函数(维护堆性质)
|
||||||
|
function siftDown(heap, start, heapSize) {
|
||||||
|
let cur = start;
|
||||||
|
while (true) {
|
||||||
|
const left = 2 * cur + 1;
|
||||||
|
const right = 2 * cur + 2;
|
||||||
|
let smallest = cur;
|
||||||
|
|
||||||
|
if (left < heapSize && heap[left] < heap[smallest]) smallest = left;
|
||||||
|
if (right < heapSize && heap[right] < heap[smallest]) smallest = right;
|
||||||
|
|
||||||
|
if (smallest === cur) break;
|
||||||
|
|
||||||
|
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||||
|
cur = smallest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建小顶堆(自底向上建堆)
|
||||||
|
for (let i = Math.floor(k / 2) - 1; i >= 0; i--) {
|
||||||
|
siftDown(heap, i, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历剩余元素,维护一个大小为 k 的小顶堆
|
||||||
|
for (let i = k; i < nums.length; i++) {
|
||||||
|
if (nums[i] > heap[0]) {
|
||||||
|
heap[0] = nums[i];
|
||||||
|
siftDown(heap, 0, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 堆顶即为第 k 大元素
|
||||||
|
return heap[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
若题目没有要求动态的维护这个topk,那么可以使用quick-select来实现,O(n)的时间复杂度完成这个题目。
|
||||||
|
思路:题目要求我们找出第k大的数,那么当整个数组排序之后,倒数第k个数不就是我们要求的吗?比如[5,4,3,2,1] k = 2,那么排序后[1,2,3,4,5]中
|
||||||
|
倒数第二个数的下标就是nums.length - k = 5 - 2 = 3 nums[3]恰好就是我们要找的这个数,我们没有必要对整个数组排序,我们只要找到排序后这个
|
||||||
|
数正确的位置即可,知道快速排序的话,我们可以使用partition来确定pivot,pivot在partition之后就已经确认,如果pivot的下标等于倒数第k个位置
|
||||||
|
那么pivot就是排序之后的倒数第k个数,也就是topk
|
||||||
|
*/
|
||||||
|
function f2(nums, k) {
|
||||||
|
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quickSelect(nums, left, right, n) {
|
||||||
|
const p = partition(nums, left, right);
|
||||||
|
if (p === n) return nums[n];
|
||||||
|
return p < n ? quickSelect(nums, p + 1, right, n) : quickSelect(nums, left, p - 1, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分区函数,返回pivot,pivot 左边的数都不大于,右边的数都不小于它
|
||||||
|
* @param {*} nums 要操作的数组
|
||||||
|
* @param {*} left 开始位置,左边界
|
||||||
|
* @param {*} right 结束位置, 右边界
|
||||||
|
* @returns number 返回的piivot
|
||||||
|
*/
|
||||||
|
function partition(arr, left, right) {
|
||||||
|
const pivot = arr[right];
|
||||||
|
let p = left;
|
||||||
|
for (let i = left; i < right; i++) {
|
||||||
|
if (arr[i] <= pivot) {
|
||||||
|
// [arr[i], arr[p]] = [arr[p], arr[i]];
|
||||||
|
swap(arr, i, p);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [arr[p], arr[right]] = [arr[right], arr[p]];
|
||||||
|
swap(arr, p, right);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function swap(nums, a, b) {
|
||||||
|
const tmp = nums[a];
|
||||||
|
nums[a] = nums[b];
|
||||||
|
nums[b] = tmp;
|
||||||
|
}
|
75
heap/base/原地构建.js
Normal file
75
heap/base/原地构建.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5, -1]; // 测试用例
|
||||||
|
|
||||||
|
const vaild = [-1, 0, 4, 2, 1, 8, 7, 5, 5, 2]; // 验证用例
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自底向上下沉
|
||||||
|
* @param {*} nums
|
||||||
|
*/
|
||||||
|
function buildHeap(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
// 寻找最后一个非叶子节点,从这里开始倒序遍历(自底向上)
|
||||||
|
for (let i = Math.floor(n / 2 - 1); i >= 0; i--) {
|
||||||
|
// 取当前节点左右节点较小的
|
||||||
|
let min = i * 2 + 1;
|
||||||
|
if (i * 2 + 2 < n && nums[i * 2 + 2] < nums[min]) min = i * 2 + 2;
|
||||||
|
let cur = i; // 表示当前节点下沉的位置
|
||||||
|
|
||||||
|
// 如果父节点大于子节点中较小的哪一个那么就交换位置,如果交换位置之后还是大于当前位置的子节点小的那个,继续下沉
|
||||||
|
while (min < n && nums[cur] > nums[min]) {
|
||||||
|
[nums[cur], nums[min]] = [nums[min], nums[cur]];
|
||||||
|
cur = min;
|
||||||
|
min = cur * 2 + 1;
|
||||||
|
if (cur * 2 + 2 < n && nums[cur * 2 + 2] < nums[min]) min = cur * 2 + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原地构建小顶堆(heapify),使用下沉(sift down)法
|
||||||
|
* @param {number[]} nums 完全二叉树的层序遍历(原始数组)
|
||||||
|
* @returns {number[]} 构建完成的小顶堆(原地修改 nums 并返回)
|
||||||
|
*/
|
||||||
|
function buildHeapBest(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
|
||||||
|
// 从最后一个非叶子节点开始,依次向前,对每个节点执行下沉
|
||||||
|
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
|
||||||
|
siftDown(nums, i, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对 nums[i] 进行下沉调整,确保以 i 为根的子树满足小顶堆性质
|
||||||
|
* @param {number[]} nums 堆数组
|
||||||
|
* @param {number} i 当前要下沉的节点索引
|
||||||
|
* @param {number} n 数组长度(限制范围)yiw
|
||||||
|
*/
|
||||||
|
function siftDown(nums, i, n) {
|
||||||
|
let cur = i;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const left = 2 * cur + 1;
|
||||||
|
const right = 2 * cur + 2;
|
||||||
|
let smallest = cur;
|
||||||
|
|
||||||
|
// 与左孩子比较
|
||||||
|
if (left < n && nums[left] < nums[smallest]) {
|
||||||
|
smallest = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与右孩子比较
|
||||||
|
if (right < n && nums[right] < nums[smallest]) {
|
||||||
|
smallest = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前节点已经是最小的,堆性质满足,退出循环
|
||||||
|
if (smallest === cur) break;
|
||||||
|
|
||||||
|
// 否则交换并继续下沉
|
||||||
|
[nums[cur], nums[smallest]] = [nums[smallest], nums[cur]];
|
||||||
|
cur = smallest;
|
||||||
|
}
|
||||||
|
}
|
35
heap/base/逐个插入.js
Normal file
35
heap/base/逐个插入.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5, -1]; // 测试用例
|
||||||
|
|
||||||
|
const vaild = [-1, 0, 4, 2, 1, 8, 7, 5, 5, 2]; // 验证用例
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用逐个插入法构造小顶堆
|
||||||
|
* @param {number[]} nums 需要处理的完全二叉树,用数组表示(层序遍历)
|
||||||
|
* @returns {number[]} 返回一个小顶堆的层序遍历
|
||||||
|
*/
|
||||||
|
function buildHeap(nums) {
|
||||||
|
const heap = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
heap.push(nums[i]);
|
||||||
|
let cur = heap.length - 1;
|
||||||
|
let parent = Math.floor((cur - 1) / 2);
|
||||||
|
|
||||||
|
while (cur > 0 && heap[cur] < heap[parent]) {
|
||||||
|
[heap[cur], heap[parent]] = [heap[parent], heap[cur]];
|
||||||
|
cur = parent;
|
||||||
|
parent = Math.floor((cur - 1) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(nums1, nums2) {
|
||||||
|
for (let i = 0; i < nums1; i++) {
|
||||||
|
if (nums1[i] !== nums2[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(test(buildeap(numbers), vaild));
|
37
leetcode/简单/买卖股票最佳时机(121).js
Normal file
37
leetcode/简单/买卖股票最佳时机(121).js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
|
||||||
|
|
||||||
|
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
|
||||||
|
|
||||||
|
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
|
||||||
|
|
||||||
|
示例 1:
|
||||||
|
|
||||||
|
输入:[7,1,5,3,6,4]
|
||||||
|
输出:5
|
||||||
|
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
|
||||||
|
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} prices
|
||||||
|
* @return {number}
|
||||||
|
* 思路:先设置一个变量minPrice 用于保存数组中最小的价格,初始值为无穷大,依次遍历数组,如果有元素比他小那么就
|
||||||
|
* 替换为当前元素,如果遍历的元素比他还大那就说明在“这一天”是可以卖掉的,但是利润不一定是最大的,多以我们还需要
|
||||||
|
* 一个全局变量 maxProfit来表示例如,初始值为0,如果这一天卖掉的利润比之前的最大利润大,就可以替换为当前利润,
|
||||||
|
* 最后返回当前利润
|
||||||
|
*/
|
||||||
|
function calculateProfit(prices) {
|
||||||
|
let minPrice = Infinity;
|
||||||
|
let maxProfit = 0;
|
||||||
|
for (const price of prices) {
|
||||||
|
if (price < minPrice) {
|
||||||
|
minPrice = price;
|
||||||
|
} else if (price - minPrice > maxProfit) {
|
||||||
|
maxProfit = price - minPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxProfit;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(calculateProfit([5, 2, 1, 34, 55]));
|
65
leetcode/简单/移除元素(25).js
Normal file
65
leetcode/简单/移除元素(25).js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
|
||||||
|
|
||||||
|
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
|
||||||
|
|
||||||
|
更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
|
||||||
|
返回 k。
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 示例 1:
|
||||||
|
|
||||||
|
// 输入:nums = [3,2,2,3], val = 3
|
||||||
|
// 输出:2, nums = [2,2,_,_]
|
||||||
|
// 解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
|
||||||
|
// 你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} val
|
||||||
|
* @return {number}
|
||||||
|
* 思路:题目只需要我们修改元素组,并且保证元素组的前k个元素是于val不同即可,脑袋里面想到的第一个思路,就是遍历一遍把
|
||||||
|
* 这k个与val不同的元素找出来,存入一个新的数组,之后再遍历新数组,把这k个元素替换到元素组
|
||||||
|
*/
|
||||||
|
const removeElement1 = function (nums, val) {
|
||||||
|
const arr = [];
|
||||||
|
let k = 0;
|
||||||
|
for (const num of nums) {
|
||||||
|
if (num !== val) {
|
||||||
|
arr.push(num);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
nums[i] = arr[i];
|
||||||
|
}
|
||||||
|
return k;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} val
|
||||||
|
* @returns number
|
||||||
|
* 思路:使用快慢指针,快指针遍历整个数组,找出与val不同的元素,快慢指针初始都指向第一个元素,当快指针找到
|
||||||
|
* 与val不同的元素的时候就把当前元素的值赋值给慢指针的位置,之后慢指针往后移动一位,重复上一次操作。
|
||||||
|
*
|
||||||
|
* 思考:因为快指针会优先移动去寻找与val不同的元素,所以一次遍历下来能找到所有的与val不同的元素,当找到时,slow所在
|
||||||
|
* 的位置一定是无效位置,要么时与val值相同的位置,要么是已经赋值之后的无效位置,所以不用担心值被覆盖的问题
|
||||||
|
*/
|
||||||
|
function removeElement(nums, val) {
|
||||||
|
let slow = 0; // 慢指针,指向不等于 val 的部分末尾
|
||||||
|
for (let fast = 0; fast < nums.length; fast++) {
|
||||||
|
if (nums[fast] !== val) {
|
||||||
|
nums[slow] = nums[fast];
|
||||||
|
slow++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slow; // 新数组长度
|
||||||
|
}
|
||||||
|
|
||||||
|
const testArr = [3, 2, 2, 3];
|
||||||
|
|
||||||
|
removeElement(testArr, 3);
|
||||||
|
console.log(testArr);
|
40
linked-list/234回文链表.js
Normal file
40
linked-list/234回文链表.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Definition for singly-linked list.
|
||||||
|
* function ListNode(val, next) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.next = (next===undefined ? null : next)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {ListNode} head
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isPalindrome = function (head) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用栈将链表的值存储起来,然后再从头开始遍历链表,比较每个节点的值和栈顶的值是否相等。
|
||||||
|
利用了回文的定义,正着和倒着应该是一样的。
|
||||||
|
*/
|
||||||
|
function f1(head) {
|
||||||
|
const stack = [];
|
||||||
|
let current = head;
|
||||||
|
|
||||||
|
// 第一步:将所有节点的值压入栈中
|
||||||
|
while (current) {
|
||||||
|
stack.push(current.val);
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:从头开始重新遍历链表,同时与栈顶比较
|
||||||
|
current = head;
|
||||||
|
while (current) {
|
||||||
|
if (current.val !== stack.pop()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
32
linked-list/876链表的中间节点.js
Normal file
32
linked-list/876链表的中间节点.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Definition for singly-linked list.
|
||||||
|
* function ListNode(val, next) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.next = (next===undefined ? null : next)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {ListNode} head
|
||||||
|
* @return {ListNode}
|
||||||
|
*/
|
||||||
|
const middleNode = function (head) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function ListNode(val, next) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.next = (next === undefined ? null : next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
如果利用快慢指针寻找重点,如果有两个中点返回后面那个,直接利用靠右的判断方法 fast && fast.next
|
||||||
|
*/
|
||||||
|
function f1(head) {
|
||||||
|
let slow = head;
|
||||||
|
let fast = head;
|
||||||
|
while (fast && fast.next) {
|
||||||
|
slow = slow.next;
|
||||||
|
fast = fast.next.next;
|
||||||
|
}
|
||||||
|
return slow;
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
export * from './link-list.js'
|
export * from './link-list.js';
|
||||||
|
86
monotonic-stack/739每日温度.js
Normal file
86
monotonic-stack/739每日温度.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} temperatures
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const dailyTemperatures = function (temperatures) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接利用单调栈,单调栈中存入的是元素的下标,这样不仅能比较元素,还能通过下标设置结果
|
||||||
|
*/
|
||||||
|
function f1(temperatures) {
|
||||||
|
const res = []; // 收集结果
|
||||||
|
const stack = []; // 单调递减栈,寻找右边第一个比栈顶元素大的元素
|
||||||
|
|
||||||
|
for (let i = 0; i < temperatures.length; i++) {
|
||||||
|
// 如果栈为空或者当前元素小于等于栈顶元素,将这个元素的下标压入栈中
|
||||||
|
if (!stack.length || temperatures[i] <= temperatures[stack[stack.length - 1]]) {
|
||||||
|
stack.push(i);
|
||||||
|
} else {
|
||||||
|
// 依次比较栈顶元素,直到栈为空,或者当前元素小于等于栈顶元素
|
||||||
|
while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
|
||||||
|
const cur = stack.pop();
|
||||||
|
res[cur] = i - cur;
|
||||||
|
}
|
||||||
|
// 将当前元素下标压入栈中
|
||||||
|
stack.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果栈中还有元素,将其在result中的对应位置设置从0,表示后面没有元素大于当前位置的元素
|
||||||
|
for (const i of stack) {
|
||||||
|
res[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
chatgpt 优化之后的写法,思路没有变,去除了if判断,逻辑如下:
|
||||||
|
遍历整个温度,如果单调栈有元素,并且当前元素大于栈顶元素,就一直处理,直到栈中没有元素,或者当前元素小于等于栈顶元素
|
||||||
|
然后将当前元素压入栈中
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算每一天需要等待多少天才会有更高温度。
|
||||||
|
* 如果后面没有更高的温度,则返回 0。
|
||||||
|
*
|
||||||
|
* @param {number[]} temperatures - 每天的温度数组
|
||||||
|
* @returns {number[]} - 返回一个数组,表示每一天距离下一次更高温度的天数
|
||||||
|
*/
|
||||||
|
function f2(temperatures) {
|
||||||
|
// 初始化结果数组,默认所有值为 0(表示找不到更高温度)
|
||||||
|
const res = new Array(temperatures.length).fill(0);
|
||||||
|
|
||||||
|
// 用来存储下标的单调递减栈(栈顶到栈底的温度依次递减)
|
||||||
|
const stack = [];
|
||||||
|
|
||||||
|
// 遍历温度数组
|
||||||
|
for (let i = 0; i < temperatures.length; i++) {
|
||||||
|
const currentTemp = temperatures[i];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前温度比栈顶下标对应的温度大,说明当前是“更高温度”的一天,
|
||||||
|
* 所以可以开始出栈并记录距离。
|
||||||
|
*
|
||||||
|
* 注意:每个元素最多被 push 和 pop 各一次,所以总体时间复杂度是 O(n)
|
||||||
|
*/
|
||||||
|
while (
|
||||||
|
stack.length > 0
|
||||||
|
&& currentTemp > temperatures[stack[stack.length - 1]]
|
||||||
|
) {
|
||||||
|
// 栈顶元素下标
|
||||||
|
const prevIndex = stack.pop();
|
||||||
|
|
||||||
|
// 当前天(i)就是之前那天(prevIndex)等待的更高温度的那一天
|
||||||
|
res[prevIndex] = i - prevIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无论如何都要把当前下标压栈,作为之后判断的基准
|
||||||
|
stack.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 栈中剩下的下标所对应的位置已经默认填 0,不需要再处理
|
||||||
|
return res;
|
||||||
|
}
|
79
package-lock.json
generated
79
package-lock.json
generated
@ -9,11 +9,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3"
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
|
"prettier": "^3.5.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
@ -73,21 +74,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.57.0",
|
"version": "8.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
|
||||||
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
|
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.14",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
|
||||||
|
"deprecated": "Use @eslint/config-array instead",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanwhocodes/object-schema": "^2.0.2",
|
"@humanwhocodes/object-schema": "^2.0.3",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"minimatch": "^3.0.5"
|
"minimatch": "^3.0.5"
|
||||||
},
|
},
|
||||||
@ -109,10 +113,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/object-schema": {
|
"node_modules/@humanwhocodes/object-schema": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
|
||||||
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
|
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
|
||||||
"dev": true
|
"deprecated": "Use @eslint/object-schema instead",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
@ -154,6 +160,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
|
||||||
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
|
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||||
},
|
},
|
||||||
@ -694,16 +701,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.57.0",
|
"version": "8.57.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
|
||||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
|
||||||
|
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.4",
|
"@eslint/eslintrc": "^2.1.4",
|
||||||
"@eslint/js": "8.57.0",
|
"@eslint/js": "8.57.1",
|
||||||
"@humanwhocodes/config-array": "^0.11.14",
|
"@humanwhocodes/config-array": "^0.13.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
"@ungap/structured-clone": "^1.2.0",
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
@ -772,6 +781,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
|
||||||
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
|
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@ -878,13 +888,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-prettier": {
|
"node_modules/eslint-plugin-prettier": {
|
||||||
"version": "5.1.3",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
|
||||||
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
|
"integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prettier-linter-helpers": "^1.0.0",
|
"prettier-linter-helpers": "^1.0.0",
|
||||||
"synckit": "^0.8.6"
|
"synckit": "^0.9.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.18.0 || >=16.0.0"
|
"node": "^14.18.0 || >=16.0.0"
|
||||||
@ -1962,11 +1973,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.2.5",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@ -2328,10 +2339,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/synckit": {
|
"node_modules/synckit": {
|
||||||
"version": "0.8.8",
|
"version": "0.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
|
||||||
"integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
|
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pkgr/core": "^0.1.0",
|
"@pkgr/core": "^0.1.0",
|
||||||
"tslib": "^2.6.2"
|
"tslib": "^2.6.2"
|
||||||
@ -2362,10 +2374,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
@ -21,10 +21,11 @@
|
|||||||
"author": "yigencong",
|
"author": "yigencong",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3"
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
|
"prettier": "^3.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
recursion/basic-cal.js
Normal file
86
recursion/basic-cal.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* LeetCode 772 - Basic Calculator III
|
||||||
|
* Evaluates a string expression containing:
|
||||||
|
* - Numbers (can be multi-digit)
|
||||||
|
* - +, -, *, /
|
||||||
|
* - Parentheses
|
||||||
|
* - Spaces
|
||||||
|
*
|
||||||
|
* @param {string} s - The expression to evaluate
|
||||||
|
* @return {number} - The result of the expression
|
||||||
|
*/
|
||||||
|
function calculate(s) {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
// 解析表达式
|
||||||
|
function parseExpression() {
|
||||||
|
const stack = [];
|
||||||
|
let num = 0;
|
||||||
|
let sign = '+';
|
||||||
|
|
||||||
|
while (i < s.length) {
|
||||||
|
const char = s[i];
|
||||||
|
|
||||||
|
if (char >= '0' && char <= '9') {
|
||||||
|
// 解析多位数字
|
||||||
|
num = num * 10 + parseInt(char, 10);
|
||||||
|
} else if (char === '(') {
|
||||||
|
// 遇到左括号,获取整个括号的结果,把整个括号看成一个数
|
||||||
|
i++; // Skip the '('
|
||||||
|
num = parseExpression(); // 把整个括号里面的内容看成一个表达式,从括号后一个字符开始处理
|
||||||
|
} else if (char === ')') {
|
||||||
|
// 遇到右括号说明此ParseExpression处理完成,返回整个括号的计算结果给上一个调用
|
||||||
|
updateStack(stack, sign, num);
|
||||||
|
return calculateStack(stack);
|
||||||
|
} else if (char === '+' || char === '-' || char === '*' || char === '/') {
|
||||||
|
// 遇到操作需要把处理好的num压入栈中
|
||||||
|
updateStack(stack, sign, num);
|
||||||
|
sign = char;
|
||||||
|
num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将最后一个数字加入栈中
|
||||||
|
updateStack(stack, sign, num);
|
||||||
|
|
||||||
|
// 上面的递归解析过程结束之后会得到一个只包含数字的栈,求和即可
|
||||||
|
return calculateStack(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number[]} stack 收集数字的栈
|
||||||
|
* @param {*} sign - 数字之前的符号
|
||||||
|
* @param {*} num - 要处理的数字
|
||||||
|
* @description 如果数字的前面是乘除法,就把栈顶元素弹出计算过后作为整体压入栈中;是减法就把这个数改成负数,加法就直接压入栈中
|
||||||
|
*/
|
||||||
|
function updateStack(stack, sign, num) {
|
||||||
|
if (sign === '+') {
|
||||||
|
stack.push(num);
|
||||||
|
} else if (sign === '-') {
|
||||||
|
stack.push(-num);
|
||||||
|
} else if (sign === '*') {
|
||||||
|
stack.push(stack.pop() * num);
|
||||||
|
} else if (sign === '/') {
|
||||||
|
// 数字前面的操作符如果是除号,需要向下取整
|
||||||
|
const prev = stack.pop();
|
||||||
|
const result = prev / num;
|
||||||
|
stack.push(prev > 0 ? Math.floor(result) : Math.ceil(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对整个栈求和
|
||||||
|
function calculateStack(stack) {
|
||||||
|
return stack.reduce((sum, num) => sum + num, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试用例
|
||||||
|
console.log(calculate('1+1')); // 2
|
||||||
|
console.log(calculate('6-4/2')); // 4
|
||||||
|
console.log(calculate('2*(5+5*2)/3+(6/2+8)')); // 21
|
||||||
|
console.log(calculate('(2+6*3+5-(3*14/7+2)*5)+3')); // -12
|
103
recursion/n皇后.js
Normal file
103
recursion/n皇后.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 解决N皇后问题
|
||||||
|
* @param {number} n - 棋盘大小和皇后数量
|
||||||
|
* @return {string[][]} - 所有可能的解决方案
|
||||||
|
*/
|
||||||
|
function solveNQueens(n) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
// 创建一个表示棋盘的数组,初始化为空
|
||||||
|
const board = Array(n).fill().map(() => Array(n).fill('.'));
|
||||||
|
|
||||||
|
// 开始回溯
|
||||||
|
backtrack(0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回溯函数,尝试在每一行放置皇后
|
||||||
|
* @param {number} row - 当前处理的行
|
||||||
|
*/
|
||||||
|
function backtrack(row) {
|
||||||
|
// 如果已经放置了n个皇后,找到一个解决方案
|
||||||
|
if (row === n) {
|
||||||
|
// 将当前棋盘状态转换为所需的格式并添加到结果中
|
||||||
|
const solution = board.map((row) => row.join(''));
|
||||||
|
result.push(solution);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试在当前行的每一列放置皇后
|
||||||
|
for (let col = 0; col < n; col++) {
|
||||||
|
// 检查当前位置是否可以放置皇后
|
||||||
|
if (isValid(row, col)) {
|
||||||
|
// 放置皇后
|
||||||
|
board[row][col] = 'Q';
|
||||||
|
|
||||||
|
// 递归到下一行
|
||||||
|
backtrack(row + 1);
|
||||||
|
|
||||||
|
// 回溯,移除皇后
|
||||||
|
board[row][col] = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查在[row, col]位置放置皇后是否有效
|
||||||
|
* @param {number} row - 行索引
|
||||||
|
* @param {number} col - 列索引
|
||||||
|
* @return {boolean} - 如果位置有效返回true,否则返回false
|
||||||
|
*/
|
||||||
|
function isValid(row, col) {
|
||||||
|
// 检查同一列是否有皇后
|
||||||
|
for (let i = 0; i < row; i++) {
|
||||||
|
if (board[i][col] === 'Q') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查左上对角线是否有皇后
|
||||||
|
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
|
||||||
|
if (board[i][j] === 'Q') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查右上对角线是否有皇后
|
||||||
|
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
|
||||||
|
if (board[i][j] === 'Q') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印棋盘
|
||||||
|
* @param {string[]} board - 表示棋盘的字符串数组
|
||||||
|
*/
|
||||||
|
function printBoard(board) {
|
||||||
|
for (const row of board) {
|
||||||
|
console.log(row);
|
||||||
|
}
|
||||||
|
console.log('---');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试代码
|
||||||
|
function testNQueens(n) {
|
||||||
|
console.log(`求解${n}皇后问题:`);
|
||||||
|
const solutions = solveNQueens(n);
|
||||||
|
console.log(`找到${solutions.length}个解决方案`);
|
||||||
|
|
||||||
|
// 打印前3个解决方案
|
||||||
|
for (let i = 0; i < Math.min(3, solutions.length); i++) {
|
||||||
|
console.log(`解决方案 ${i + 1}:`);
|
||||||
|
printBoard(solutions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试8皇后问题
|
||||||
|
testNQueens(8);
|
3
recursion/recursion.md
Normal file
3
recursion/recursion.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 递归
|
||||||
|
|
||||||
|
此目录存放了常用递归思路,以及一些解决方案
|
43
recursion/tower-of-hanoi.js
Normal file
43
recursion/tower-of-hanoi.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
有三根柱子,分别命名为 A、B、C。A 上有若干个盘子,盘子的大小不等,且每个盘子都可以放在另一根柱子上。要求从柱子 A 上将所有盘子移动到柱子 C 上,且必须遵守以下规则:
|
||||||
|
|
||||||
|
一次只能移动一个盘子。
|
||||||
|
每次移动时,盘子只能从柱子顶部移动,且一个盘子不能放在比它小的盘子上。
|
||||||
|
目标是使用柱子 B 作为辅助柱子,最少移动次数将盘子从柱子 A 移动到柱子 C。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} n 汉诺塔问题的规模
|
||||||
|
* 思路:假设我们只有两个盘子,只需要把小盘子移动到B柱上,再把大盘子移动到C柱子上,最后把小盘子从B柱移动到C柱子,我们把问题做一个抽象,假设有三个盘子,因为要想移动
|
||||||
|
* 大盘子就必须把它上面的所有小盘子移动到B住上去,之后才能把这个大盘子移动到C柱,至于怎么移动我不管
|
||||||
|
*
|
||||||
|
* 分析:假设现在有三个盘子,第一步我们发现底部的盘子无法直接移动,把问题抽象为移动n-1个盘子,在这里就是移动上面的两个盘子,之后会发现第二个盘子也移不动,所以再把问题抽象为
|
||||||
|
* (n-1)-1,也就是移动第一个盘子,第一个盘子可以直接移动,这样就可以把第一个盘子移动到B上,第二个盘子移动到C,......
|
||||||
|
*/
|
||||||
|
function towerOfHanoi(n) {
|
||||||
|
// 将n个盘子从A借助B移动到C
|
||||||
|
howToMove(n, 'A', 'C', 'B');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} n 要移动的所有盘子
|
||||||
|
* @param {*} from 初始位置
|
||||||
|
* @param {*} to 目标位置
|
||||||
|
* @param {*} aux 辅助柱
|
||||||
|
*/
|
||||||
|
function howToMove(n, from, to, aux) {
|
||||||
|
// 如果只有一个盘子那就直接移动到目标位置即可(这也是递归结束的条件)
|
||||||
|
if (n === 1) {
|
||||||
|
console.log(`${n} 从 ${from} 移动到 ${to}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1.移动n-1个盘子,因为如果不把所有的小盘子移动到辅助位置,大盘子就不可能移动到目标位置
|
||||||
|
howToMove(n - 1, from, aux, to);
|
||||||
|
// 2.将大盘子移动到目标位置
|
||||||
|
console.log(`${n} 从 ${from} 移动到 ${to}`);
|
||||||
|
// 3.将剩下的所有盘子从辅助位置移动到目标位置
|
||||||
|
howToMove(n - 1, aux, to, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
towerOfHanoi(3);
|
61
recursion/排序一个栈.js
Normal file
61
recursion/排序一个栈.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @param {Stack} stack
|
||||||
|
* @description 将一个栈的所有元素按照从栈底到栈顶依此从小到大排列
|
||||||
|
* 思路:假设我们有一个栈[1,7,2,2,5,3],我们把它想象成空的,第一步我们插入1,由于现在栈是空的,所以直接push(1)
|
||||||
|
* 即可,第二步,我们插入7,按照要求栈顶的元素应该要比栈底的元素小,所以7不能直接push,所以我们需要pop(1),之后再
|
||||||
|
* 判断是否可以插入7,当1 弹出后由于没有元素直接push(7),之后我们再把弹出的1重新压入栈顶即可,此时栈里面有两个元素
|
||||||
|
* [7,1],我们继续来压入2,2比栈顶元素大所以弹出栈顶元素,此时发现栈顶元素比2大符合要求,所以直接push(2),之后再把之前
|
||||||
|
* 的1压入即可,此时栈里面有3个元素[7,2,1],继续压入2同理,之后压入5,我们比较栈顶元素和5,发现5比栈顶元素大,所以弹出
|
||||||
|
* 1,之后再比较,发现比此时的栈顶元素2大,所以弹出2,之后再比较2,发现还是大,所以继续弹出此时的栈顶元素2,之后发现没有7
|
||||||
|
* 大,所以此时直接push(5)即可,并且把之前弹出的元素依此push即可,此时栈里面的内容为[7,5,2,2,1],最后一个元素3同理。
|
||||||
|
*
|
||||||
|
* 分析:上面的思路我们从栈底元素开始处理,再不借助另一个“后进先出”的数据结构,我们只能借助递归,递归本身就带空间,回溯的时候
|
||||||
|
* 处理即可,我们在插入的时候发现每次的弹出的数据都需要保留,并且还需要按着原顺序插入,在这里我们也需要一个递归的插入函数,原因
|
||||||
|
* 很简单,我们需要保留当前弹出的值,并且依此插入,所以创建两个函数,第一个函数sortStack,这个函数把整个栈弹出,从栈底元素开始处理
|
||||||
|
* 依此调用insertInSortedOrder把当前元素插入到正确的地方。
|
||||||
|
*/
|
||||||
|
import Stack from '../stack/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序栈
|
||||||
|
*
|
||||||
|
* @param {Stack} stack 要排序的栈
|
||||||
|
*/
|
||||||
|
function sortStack(stack) {
|
||||||
|
if (stack.isEmpty()) return; // 如果栈为空,递归终止
|
||||||
|
const cur = stack.pop(); // 保存栈顶元素
|
||||||
|
sortStack(stack); // 递归排序栈中的其他元素
|
||||||
|
// 将当前栈顶元素插入到已排序的栈中
|
||||||
|
insertInSortedOrder(stack, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将元素插入到已排序栈中
|
||||||
|
*
|
||||||
|
* @param {Stack} stack 要插入的栈
|
||||||
|
* @param {number} item 要插入的元素
|
||||||
|
*/
|
||||||
|
function insertInSortedOrder(stack, item) {
|
||||||
|
// 如果栈为空,或者栈顶元素大于等于当前元素,直接将当前元素插入栈中
|
||||||
|
if (stack.isEmpty() || stack.peek() >= item) {
|
||||||
|
stack.push(item);
|
||||||
|
} else {
|
||||||
|
// 如果栈顶元素小于当前元素,弹出栈顶元素
|
||||||
|
const cur = stack.pop();
|
||||||
|
// 递归插入当前元素
|
||||||
|
insertInSortedOrder(stack, item);
|
||||||
|
// 将弹出的栈顶元素重新压入栈中
|
||||||
|
stack.push(cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试代码
|
||||||
|
const stack = new Stack([1, 7, 2, 2, 5, 3]);
|
||||||
|
|
||||||
|
console.log('Original Stack:');
|
||||||
|
stack.print(); // 输出: 1, 7, 2, 2, 5, 3
|
||||||
|
|
||||||
|
sortStack(stack); // 排序栈
|
||||||
|
|
||||||
|
console.log('Sorted Stack:');
|
||||||
|
stack.print(); // 输出: 1, 2, 2, 3, 5, 7
|
35
recursion/逆序一个栈.js
Normal file
35
recursion/逆序一个栈.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Stack from '../stack/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Stack} stack 需要处理的栈
|
||||||
|
* 思路:我们首先需要有一个把栈底元素弹出的方法,当获取栈底元素之后,我们只需把剩余的元素逆序,再把这个
|
||||||
|
* 栈底元素插入栈顶即可。
|
||||||
|
*/
|
||||||
|
function reverse(stack) {
|
||||||
|
if (stack.isEmpty()) return;
|
||||||
|
const last = bottomOut(stack); // 弹出栈的底部元素
|
||||||
|
reverse(stack);
|
||||||
|
stack.push(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Stack} stack - 需要处理的栈
|
||||||
|
* @description 将一个栈的底部元素弹出
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
function bottomOut(stack) {
|
||||||
|
const ans = stack.pop();
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
const last = bottomOut(stack);
|
||||||
|
stack.push(ans);
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack = new Stack([1, 2, 3, 4, 5, 6, 7]); // 创建一个栈,栈顶元素为5
|
||||||
|
|
||||||
|
reverse(stack);
|
||||||
|
stack.print();
|
50
stack/index.js
Normal file
50
stack/index.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
class Stack {
|
||||||
|
constructor(items = []) {
|
||||||
|
this.items = items; // 用 items 来存储栈的元素
|
||||||
|
}
|
||||||
|
|
||||||
|
// 入栈
|
||||||
|
push(item) {
|
||||||
|
this.items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 出栈
|
||||||
|
pop() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
console.log('栈为空! ');
|
||||||
|
return undefined; // 栈为空时返回 undefined
|
||||||
|
}
|
||||||
|
return this.items.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取栈顶元素
|
||||||
|
peek() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
console.log('栈为空! ');
|
||||||
|
return undefined; // 栈为空时返回 undefined
|
||||||
|
}
|
||||||
|
return this.items[this.items.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断栈是否为空
|
||||||
|
isEmpty() {
|
||||||
|
return this.items.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取栈的大小
|
||||||
|
size() {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空栈
|
||||||
|
clear() {
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印栈内容
|
||||||
|
print() {
|
||||||
|
console.log(this.items.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stack;
|
100
top-interview-leetcode150/backtrack/22括号生成.js
Normal file
100
top-interview-leetcode150/backtrack/22括号生成.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @return {string[]}
|
||||||
|
*/
|
||||||
|
const generateParenthesis = function (n) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目也是一个典型的回溯算法题,一个一个的尝试就行了,问题在于如何检测有效括号,这里可以定义一个栈,这里可以参考
|
||||||
|
leetcode20题。
|
||||||
|
思路:每一次backtrack从头到尾尝试所有括号,尝试所有的排列可能,如果长度达到2*n检测是否符合要求,如果符合要求,
|
||||||
|
就收集结果,最后返回结果集
|
||||||
|
*/
|
||||||
|
function f1(n) {
|
||||||
|
const result = []; // 结果集
|
||||||
|
// 初始化括号数组
|
||||||
|
const brackets = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
brackets.push('(');
|
||||||
|
brackets.push(')');
|
||||||
|
}
|
||||||
|
const bLen = backtrack.length;
|
||||||
|
// 定义used数组,防止重复使用
|
||||||
|
const used = Array(bLen).fill(false);
|
||||||
|
const backtrack = (path) => {
|
||||||
|
// 如果path的长度等于brackets.length,并且有效括号数位n,则收集结果
|
||||||
|
if (path.length === bLen && checkBrackets(path)) {
|
||||||
|
result.push(path.join(''));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < bLen; i++) {
|
||||||
|
if (used[i]) continue;
|
||||||
|
path.push(brackets[i]);
|
||||||
|
used[i] = true;
|
||||||
|
backtrack(i + 1, path);
|
||||||
|
path.pop();
|
||||||
|
used[i] = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backtrack([]);
|
||||||
|
return Array.from(new Set(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
检测字符串中的括号是否符合要求
|
||||||
|
*/
|
||||||
|
function checkBrackets(backets) {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const char of backets) {
|
||||||
|
if (char === '(') {
|
||||||
|
count++;
|
||||||
|
} else if (char === ')') {
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果右括号多于左括号,提前返回 false
|
||||||
|
if (count < 0) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有括号遍历完后,必须完全闭合
|
||||||
|
return count === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
上面的思路会超时,通过全排列去找符合要求的再检测,会指数爆炸,当n>=5时就已经跑不动了,正确的做法应该是
|
||||||
|
递归的构造符合要求的字符串,当构造的字符串符合要求时,再收集结果。
|
||||||
|
*/
|
||||||
|
function f2(n) {
|
||||||
|
const result = []; // 结果集
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} path 收集的括号组合
|
||||||
|
* @param {Number} left 左括号的数量
|
||||||
|
* @param {Number} right 右括号的数量
|
||||||
|
*/
|
||||||
|
const backtrack = (path, left, right) => {
|
||||||
|
// 如果长度达到2*n就收集结果
|
||||||
|
if (path.length === 2 * n) {
|
||||||
|
result.push(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果左括号未达到n就继续构造左括号
|
||||||
|
if (left < n) {
|
||||||
|
backtrack(`${path}(`, left + 1, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果right >= left 再添加右括号会导致,缺少一个左括号和它闭合,所以只有当 right < left 时才添加右括号
|
||||||
|
if (right < left) {
|
||||||
|
backtrack(`${path})`, left, right + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backtrack('', 0, 0);
|
||||||
|
return result;
|
||||||
|
}
|
35
top-interview-leetcode150/backtrack/39组合总和.js
Normal file
35
top-interview-leetcode150/backtrack/39组合总和.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} candidates
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[][]}
|
||||||
|
*/
|
||||||
|
const combinationSum = function (candidates, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用回溯算法解决,每一次都尝试从candidates的当前位置开始向后依次加入path。
|
||||||
|
如果path中所有值的和等于target就收集结果,
|
||||||
|
如果大于就回溯尝试下一个元素,直到把结果收集完毕,返回result。
|
||||||
|
*/
|
||||||
|
function f1(candidates, target) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
// 回溯函数增加一个 start 参数,用于控制递归选择的起始位置
|
||||||
|
const backtrack = (start, path, sum) => {
|
||||||
|
if (sum > target) return;
|
||||||
|
if (sum === target) {
|
||||||
|
result.push([...path]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i < candidates.length; i++) {
|
||||||
|
path.push(candidates[i]);
|
||||||
|
backtrack(i, path, sum + candidates[i]); // 允许重复选当前数,所以递归从 i 开始
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backtrack(0, [], 0);
|
||||||
|
return result;
|
||||||
|
}
|
50
top-interview-leetcode150/backtrack/46全排列.js
Normal file
50
top-interview-leetcode150/backtrack/46全排列.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number[][]}
|
||||||
|
* http://leetcode.cn/problems/permutations/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const permute = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用回溯算法,递归的处理,每一次都从头开始遍历,收集每一个元素,由于是全排列,不能再结果中收集同一个元素
|
||||||
|
多次,所以定义一个used数组来标记是否已经收集,如果收集过就跳过处理下一个元素,如果path的长度和全排列的长度
|
||||||
|
一致,就将结果收集到result中,最后返回result
|
||||||
|
*/
|
||||||
|
function f2(nums) {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Number[]} nums 所有元素
|
||||||
|
* @param {Boolean[]} used 对应位置的元素是否被使用过
|
||||||
|
* @param {Number[]} path 全排列的结果
|
||||||
|
*/
|
||||||
|
const backtrack = (nums, used, path) {
|
||||||
|
// 如果path的长度和nums的长度一致,收集结果
|
||||||
|
if(path.length === nums.length){
|
||||||
|
result.push(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理
|
||||||
|
for(let i = 0; i<nums.length;i++){
|
||||||
|
// 如果当前值被使用过,直接跳过
|
||||||
|
if(used[i]) continue
|
||||||
|
// 将当前值加入path
|
||||||
|
path.push(nums[i])
|
||||||
|
used[i] = true
|
||||||
|
backtrack(nums, used, path)
|
||||||
|
// 下面是回溯过程,将nums[i]从path中取出,并标记为未被使用
|
||||||
|
path.pop();
|
||||||
|
used[i] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = []; // 收集所有全排列的结果
|
||||||
|
const n = nums.length;
|
||||||
|
const used = Array(n).fill(false);
|
||||||
|
const path = []; // 收集元素形成排列
|
||||||
|
// 调用回溯过程
|
||||||
|
backtrack(nums, used, path)
|
||||||
|
return result
|
||||||
|
}
|
80
top-interview-leetcode150/backtrack/52n皇后.js
Normal file
80
top-interview-leetcode150/backtrack/52n皇后.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const totalNQueens = function (n) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function f1(n) {
|
||||||
|
// 初始化棋牌大小,所有元素均为0,表示没有任何皇后在棋牌上
|
||||||
|
const board = Array.from({ length: n }, () => Array(n).fill(0));
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
const backtrack = (board, row) => {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
// 如果有冲突就尝试下一个位置
|
||||||
|
if (clashed(board, row, i)) continue;
|
||||||
|
// 将这个位置修改成1,表示存放皇后
|
||||||
|
board[row][i] = 1;
|
||||||
|
// 如果是第n行就收集结果
|
||||||
|
if (row === n - 1) {
|
||||||
|
result.push(JSON.parse(JSON.stringify(board)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 继续处理下一行
|
||||||
|
backtrack(board, row + 1);
|
||||||
|
board[row][i] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backtrack(board, 0);
|
||||||
|
return result.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测(row, col这个位置存放皇后是否冲突)
|
||||||
|
function clashed(board, row, col) {
|
||||||
|
const n = board.length;
|
||||||
|
|
||||||
|
// 检查同一列
|
||||||
|
for (let i = 0; i < row; i++) {
|
||||||
|
if (board[i][col] === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查左上对角线
|
||||||
|
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
|
||||||
|
if (board[i][j] === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查右上对角线
|
||||||
|
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
|
||||||
|
if (board[i][j] === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有冲突
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
硬编码,超过100%的人
|
||||||
|
*/
|
||||||
|
function f2(n) {
|
||||||
|
const nQueensSolutions = {
|
||||||
|
1: 1,
|
||||||
|
2: 0,
|
||||||
|
3: 0,
|
||||||
|
4: 2,
|
||||||
|
5: 10,
|
||||||
|
6: 4,
|
||||||
|
7: 40,
|
||||||
|
8: 92,
|
||||||
|
9: 352,
|
||||||
|
};
|
||||||
|
return (nQueensSolutions(n));
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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 {TreeNode}
|
||||||
|
*/
|
||||||
|
const balanceBST = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
现在不来考虑二叉搜索树变平衡,考虑一下给你一个有序的数组你如何将他变平衡,我的猜想是这样的,直接寻找找到数组序列的中间位置mid =Math.floor(left + right)
|
||||||
|
将mid作为这个根节点,将mid左侧的元素(不是小于等于)全部用来构建左子树,将mid右侧的元素全部用来构建右子树,之后在子树上重复这个过程,最后构建出来的树,
|
||||||
|
就是一棵平衡二叉搜索树,原因很简单,log2(k),和log2(k+1)的差不会超过1,一个区间被分割无外乎两种情况,左边和右边的数量相等,或者左边和右边的数量相差
|
||||||
|
1,这是满足平衡二叉树定义的,所以这种构建的贪心策略是合理的。
|
||||||
|
记住:一个有序的序列只要取中间值作为根节点,左右侧序列作为左右子树,之后递归处理左右子树,最终就能构建一颗平衡二叉搜索树,而二叉搜索树的中序遍历恰好
|
||||||
|
是有序序列,所以只需要将二叉搜索树遍历,之后拿到遍历的结果重新按照上面的方法构建一颗平衡二叉搜索(BST)树就行
|
||||||
|
*/
|
||||||
|
function TreeNode(val, left, right) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.left = (left === undefined ? null : left);
|
||||||
|
this.right = (right === undefined ? null : right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
// 1.遍历二叉搜索树(BST)获取对应有序序列
|
||||||
|
const inorder = [];
|
||||||
|
const inorderTraversal = (root) => {
|
||||||
|
if (!root) return;
|
||||||
|
// 遍历左子树
|
||||||
|
inorderTraversal(root.left);
|
||||||
|
// 将中序遍历的值存入数组
|
||||||
|
inorder.push(root.val);
|
||||||
|
// 遍历右子树
|
||||||
|
inorderTraversal(root.right);
|
||||||
|
};
|
||||||
|
|
||||||
|
inorderTraversal(root); // 对原始树进行中序遍历
|
||||||
|
|
||||||
|
// 2.递归构建平衡二叉搜索树
|
||||||
|
const sortedArrayToBST = (left, right) => {
|
||||||
|
if (left > right) return null; // 当前区间不存在元素,无法构建子树
|
||||||
|
|
||||||
|
const mid = Math.floor((left + right) / 2);
|
||||||
|
// 为当前区间所有元素构建根节点
|
||||||
|
const curRoot = new TreeNode(inorder[mid]);
|
||||||
|
// 递归构建左子树
|
||||||
|
const leftNode = sortedArrayToBST(left, mid - 1);
|
||||||
|
// 递归构建右子树
|
||||||
|
const rightNode = sortedArrayToBST(mid + 1, right);
|
||||||
|
// 连接左右子树
|
||||||
|
curRoot.left = leftNode;
|
||||||
|
curRoot.right = rightNode;
|
||||||
|
return curRoot;
|
||||||
|
};
|
||||||
|
|
||||||
|
return sortedArrayToBST(0, inorder.length - 1);
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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} k
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const kthSmallest = function (root, k) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
根据二叉搜索树的特性,直接对其进行中序遍历,遍历到的第k个数就是第k小,这里使用迭代的方式,好返回结果
|
||||||
|
*/
|
||||||
|
function f1(root, k) {
|
||||||
|
const stack = []; // 栈用于模拟中序遍历
|
||||||
|
let node = root; // 从根节点开始遍历
|
||||||
|
|
||||||
|
while (node || stack.length > 0) {
|
||||||
|
// 先处理左子树,将所有左子树节点压入栈中
|
||||||
|
while (node) {
|
||||||
|
stack.push(node);
|
||||||
|
node = node.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹出栈顶元素并访问
|
||||||
|
node = stack.pop();
|
||||||
|
|
||||||
|
// 递减k,如果k为0,说明我们已经找到了第k个最小值
|
||||||
|
if (--k === 0) {
|
||||||
|
return node.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理右子树
|
||||||
|
node = node.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 如果没有找到k个元素,返回null(一般不会发生)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
当一如果当前节点就是目标节点,那么它的左子树的数量一定是k-1个,如果左子树的数量小于k-1个那么目标节点一定存在右子树中,
|
||||||
|
以当前节点右子树为根节点继续寻找此时k要出去左子树的left加上自己一共left + 1个,所以以右子节点为更节点的子树只需寻找
|
||||||
|
第 k-(left+1)个大的数。使用面向对象的写法封装。
|
||||||
|
*/
|
||||||
|
// TODO:
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const getMinimumDifference = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
二叉搜索树的一个重要特征就是,中序遍历的结果是一个升序序列,所以只要中序遍历整个二叉树,前一个节点和当前节点作比较的值的绝对值就可能
|
||||||
|
是这个最小的差值
|
||||||
|
*/
|
||||||
|
function f1(root) {
|
||||||
|
let prev = null; // 记录前一个节点的值
|
||||||
|
let minDiff = Infinity; // 初始化最小差值为正无穷
|
||||||
|
|
||||||
|
// 中序遍历函数
|
||||||
|
function inOrder(node) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
// 先遍历左子树
|
||||||
|
inOrder(node.left);
|
||||||
|
|
||||||
|
// 当前节点与前一个节点的差值
|
||||||
|
if (prev !== null) {
|
||||||
|
minDiff = Math.min(minDiff, Math.abs(node.val - prev));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新前一个节点为当前节点
|
||||||
|
prev = node.val;
|
||||||
|
|
||||||
|
// 再遍历右子树
|
||||||
|
inOrder(node.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从根节点开始中序遍历
|
||||||
|
inOrder(root);
|
||||||
|
|
||||||
|
return minDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用迭代实现中序遍历
|
||||||
|
*/
|
70
top-interview-leetcode150/binary-search-tree/98验证二叉搜索树.js
Normal file
70
top-interview-leetcode150/binary-search-tree/98验证二叉搜索树.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 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}
|
||||||
|
*/
|
||||||
|
const isValidBST = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:根据二叉搜索树的定义,BST的中序遍历是一个严格的递增序列,所以只要一次遍历,判断后一个树是否是严格大于当前的树,如果小于等于,
|
||||||
|
那么这个二叉树不是搜索二叉树
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
let prev = null; // 前一个节点的值
|
||||||
|
|
||||||
|
const inordertraversal = (node) => {
|
||||||
|
if (!node) return true; // 空节点被视为有效BST
|
||||||
|
// 遍历左子树如果左子树不是BST立即返回false
|
||||||
|
if (!inordertraversal(node.left)) return false;
|
||||||
|
// 处理当前节点
|
||||||
|
if (prev !== null && prev >= node.val) return false;
|
||||||
|
// 更新prev为当前节点值
|
||||||
|
prev = node.val;
|
||||||
|
// 遍历右子树
|
||||||
|
return inordertraversal(node.right);
|
||||||
|
};
|
||||||
|
|
||||||
|
return inordertraversal(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用迭代法中序遍历,返回更直接
|
||||||
|
*/
|
||||||
|
function f2(root) {
|
||||||
|
const stack = [];
|
||||||
|
let prev = null;
|
||||||
|
|
||||||
|
// 迭代法中序遍历
|
||||||
|
while (stack.length > 0 || root !== null) {
|
||||||
|
// 遍历左子树,直到最左叶子节点
|
||||||
|
while (root !== null) {
|
||||||
|
stack.push(root);
|
||||||
|
root = root.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 访问当前节点
|
||||||
|
root = stack.pop();
|
||||||
|
|
||||||
|
// 当前节点值必须大于前一个节点值
|
||||||
|
if (prev !== null && root.val <= prev) {
|
||||||
|
return false; // 发现不符合要求,返回false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新prev为当前节点值
|
||||||
|
prev = root.val;
|
||||||
|
|
||||||
|
// 访问右子树
|
||||||
|
root = root.right;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
55
top-interview-leetcode150/binary-search/153寻找旋转排序数组中的最小值.js
Normal file
55
top-interview-leetcode150/binary-search/153寻找旋转排序数组中的最小值.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const findMin = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
数组在某个位置旋转之后会变成两段连续的序列,并且第一段的所有值都比第二段大,我们直接使用二分查找,
|
||||||
|
如果mid落在第一段,则表明我们需要往右边继续找,使left = mid+1,如果落在第二段则将right=mid,如果
|
||||||
|
left===right,返回left所在位置的数即可,就是最小的数。
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
let left = 0;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
// 如果nums[right] > nums[left] 表明在第0个位置旋转,或者说没有旋转,直接返回nums[left]
|
||||||
|
if (nums[right] > nums[left]) return nums[left];
|
||||||
|
while (left < right) {
|
||||||
|
const mid = left + ((right - left) >> 1);
|
||||||
|
// 如果nums[mid]比nums[0]大则表明它落在第一段
|
||||||
|
if (nums[mid] >= nums[0]) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nums[left];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
方法1通过判断nums[mid]是否大于nums[0],如果大于则表明落在第一段,但是[2,1]这个测试用例通不过,nums[0]就是nums[mid]
|
||||||
|
自己,所以还要加上等于的情况,也就是说,如果nums[mid] >= nums[0]的话表明落在了第一段,反之落在了第二段,这样又会产生
|
||||||
|
新的问题,就是如果旋转的位置是0,也就是说整个数组没有发生旋转,那么最后会找到整个数组中最大的数,所以还要用卫语句处理,
|
||||||
|
这种方式不太好。
|
||||||
|
方法2通过判断nums[mid] > nums[right],如果大于则表明落在第一段,反之落在第二段,而且还能通过上面的测试用例,这种方式
|
||||||
|
更加的好,更符合夹逼思想。
|
||||||
|
*/
|
||||||
|
function f2(nums) {
|
||||||
|
let left = 0; let
|
||||||
|
right = nums.length - 1;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
const mid = left + ((right - left) >> 1);
|
||||||
|
|
||||||
|
// 与 nums[right] 比较,更直观,逻辑更接近二分标准模型
|
||||||
|
if (nums[mid] > nums[right]) {
|
||||||
|
left = mid + 1; // 最小值在右边
|
||||||
|
} else {
|
||||||
|
right = mid; // 最小值在左边(可能是 mid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nums[left];
|
||||||
|
}
|
46
top-interview-leetcode150/binary-search/33搜索旋转排序数组.js
Normal file
46
top-interview-leetcode150/binary-search/33搜索旋转排序数组.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const search = function (nums, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:按照题目描述,数组旋转之后会变成两段有序的序列,而且第一段有序序列一定是大于第二段有序序列的
|
||||||
|
可以直接利用二分查找,但是在查找的时候要留意mid落在那一段序列上,如果落在第一段序列上 nums[mid] >= nums[left],
|
||||||
|
反之第二段,知道mid落在那一段序列之后,我们还需判断target在那一段序列上,如果mid在第一段,target也在第一段并且
|
||||||
|
小于nums[mid]收缩右边界为mid-1,如果在第二段,收缩左边界为mid+1
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(nums, target) {
|
||||||
|
let left = 0;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = left + ((right - left) >> 1);
|
||||||
|
if (nums[mid] === target) {
|
||||||
|
return mid; // 找到目标值,直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果mid落在第一段,那么nums[mid] >= nums[0], 否则落在第二段
|
||||||
|
if (nums[mid] >= nums[0]) {
|
||||||
|
// 如果target在第一段,并且target < nums[mid]
|
||||||
|
if (target > -nums[0] && target < nums[mid]) {
|
||||||
|
right = mid - 1;
|
||||||
|
} else {
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果target落在第二段,并且target > nums[mid]
|
||||||
|
// eslint-disable-next-line no-lonely-if
|
||||||
|
if (target <= nums[nums.length - 1] && target > nums[mid]) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // 没有找到目标值
|
||||||
|
}
|
40
top-interview-leetcode150/binary-search/34在排序数组中查找元素出现的范围.js
Normal file
40
top-interview-leetcode150/binary-search/34在排序数组中查找元素出现的范围.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const searchRange = function (nums, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function searchRange(nums, target) {
|
||||||
|
let left = 0, right = nums.length - 1;
|
||||||
|
let start = -1, end = -1;
|
||||||
|
|
||||||
|
// Find the first occurrence
|
||||||
|
while (left <= right) {
|
||||||
|
let mid = Math.floor((left + right) / 2);
|
||||||
|
if (nums[mid] >= target) {
|
||||||
|
right = mid - 1;
|
||||||
|
} else {
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = nums[left] === target ? left : -1;
|
||||||
|
|
||||||
|
// Find the last occurrence
|
||||||
|
left = 0;
|
||||||
|
right = nums.length - 1;
|
||||||
|
while (left <= right) {
|
||||||
|
let mid = Math.floor((left + right) / 2);
|
||||||
|
if (nums[mid] <= target) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end = nums[right] === target ? right : -1;
|
||||||
|
|
||||||
|
return [start, end];
|
||||||
|
}
|
32
top-interview-leetcode150/binary-search/35返回插入的位置.js
Normal file
32
top-interview-leetcode150/binary-search/35返回插入的位置.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const searchInsert = function (nums, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接利用二分查找
|
||||||
|
*/
|
||||||
|
let pos = nums.length;
|
||||||
|
let left = 0;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
|
||||||
|
// 二分查找知道left>right结束查找
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = Math.floor((left + right) / 2);
|
||||||
|
// 如果target<nums[mid] 则更新pos
|
||||||
|
if (target === nums[mid]) return mid;
|
||||||
|
|
||||||
|
if (target < nums[mid]) {
|
||||||
|
pos = mid;
|
||||||
|
right = mid - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
67
top-interview-leetcode150/binary-search/74搜索二维矩阵.js
Normal file
67
top-interview-leetcode150/binary-search/74搜索二维矩阵.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[][]} matrix
|
||||||
|
* @param {number} target
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const searchMatrix = function (matrix, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
将二维矩阵转换成一维数组,然后利用二分查找来查找目标值。
|
||||||
|
*/
|
||||||
|
function f1(matrix, target) {
|
||||||
|
const nums = matrix.flat(); // 将二维矩阵转换为一维数组
|
||||||
|
let left = 0;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = left + Math.floor(right - left) / 2;
|
||||||
|
if (nums[mid] === target) {
|
||||||
|
return true; // 找到目标值
|
||||||
|
} if (nums[mid] < target) {
|
||||||
|
left = mid + 1; // 目标值在右半部分
|
||||||
|
} else {
|
||||||
|
right = mid - 1; // 目标值在左半部分
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // 矩阵中没有这个值
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
两次二分查找,第一次找第一列中小于等于target的那个值,第二次找这一个值是否在这一行存在,存在返回true,不存在返回false
|
||||||
|
*/
|
||||||
|
function f2(matrix, target) {
|
||||||
|
// 二分查找第一列,这里使用upper_bound的方式
|
||||||
|
let left = 0;
|
||||||
|
let right = matrix.length;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
const mid = left + Math.floor((right - left) / 2); // 使用靠右的计算防止死循环
|
||||||
|
if (matrix[mid][0] <= target) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const row = left - 1; // 右边界减1就是我们要找的值
|
||||||
|
|
||||||
|
// 如果右边界小于0表示要找的数不存在矩阵中,直接返回false
|
||||||
|
if (row < 0) return false;
|
||||||
|
|
||||||
|
// 二分查找这一行
|
||||||
|
left = 0;
|
||||||
|
right = matrix[row].length;
|
||||||
|
while (left <= right) {
|
||||||
|
const mid = left + Math.floor((right - left) / 2);
|
||||||
|
if (matrix[row][mid] === target) {
|
||||||
|
return true; // 找到目标值
|
||||||
|
} if (matrix[row][mid] < target) {
|
||||||
|
left = mid + 1; // 目标值在右半部分
|
||||||
|
} else {
|
||||||
|
right = mid - 1; // 目标值在左半部分
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // 矩阵中没有这个值
|
||||||
|
}
|
74
top-interview-leetcode150/binary-tree/100相同的数.js
Normal file
74
top-interview-leetcode150/binary-tree/100相同的数.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/same-tree/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {TreeNode} p
|
||||||
|
* @param {TreeNode} q
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isSameTree = function (p, q) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
如果一个树的字节的和另一个树的子节点相等,只要保证当前节点相等的话,
|
||||||
|
那么整棵树也就相等了
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(p, q) {
|
||||||
|
if (p === null && q === null) return true;
|
||||||
|
if (p && q && p.val === q.val) {
|
||||||
|
return f1(p.left, q.left) && f1(p.right, q.right);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
优化判断分支
|
||||||
|
*/
|
||||||
|
function f2(p, q) {
|
||||||
|
// 如果两个节点都为空,返回 true,表示当前节点相等
|
||||||
|
if (!p && !q) return true;
|
||||||
|
|
||||||
|
// 如果其中一个节点为空,或者节点值不相等,返回 false
|
||||||
|
if (!p || !q || p.val !== q.val) return false;
|
||||||
|
|
||||||
|
// 递归检查左右子树
|
||||||
|
return f1(p.left, q.left) && f1(p.right, q.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
通过BFS来判断两个数,每一层对应节点的值是否相等
|
||||||
|
*/
|
||||||
|
function f3(p, q) {
|
||||||
|
// 初步判断,如果两个树的根节点有一个为空,另一个不为空,则返回 false
|
||||||
|
if (!p && !q) return true;
|
||||||
|
if (!p || !q) return false;
|
||||||
|
// 上面这两个判断可有可无
|
||||||
|
|
||||||
|
const queue = [p, q]; // 队列存储两个树的根节点
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const node1 = queue.shift();
|
||||||
|
const node2 = queue.shift();
|
||||||
|
|
||||||
|
if (!node1 && !node2) continue; // 如果两个节点都为空
|
||||||
|
|
||||||
|
// 如果两个节点值不同,或者其中一个为空,直接返回 false
|
||||||
|
if (!node1 || !node2 || node1.val !== node2.val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将左子节点和右子节点加入队列
|
||||||
|
queue.push(node1.left, node2.left);
|
||||||
|
queue.push(node1.right, node2.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
101
top-interview-leetcode150/binary-tree/101对称二叉树.js
Normal file
101
top-interview-leetcode150/binary-tree/101对称二叉树.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* 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}
|
||||||
|
*/
|
||||||
|
// const isSymmetric = function (root) {
|
||||||
|
// return f1(root.left, root.right);
|
||||||
|
// };
|
||||||
|
|
||||||
|
/*
|
||||||
|
递归:1.基础条件:如果两个树的根节点为空,则它们对称;如果一个为空而另一个不为空,则不对称;如果两个节点的值不相等,则不对称。
|
||||||
|
2.递归检查左右子树的对称性:左子树的左子树与右子树的右子树对称,左子树的右子树与右子树的左子树对称。
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(left, right) {
|
||||||
|
// 如果左右两颗树的根节点都为空,则对称
|
||||||
|
if (!left && !right) return true;
|
||||||
|
// 上面判断排除了都为空的情况,所以至少有一颗树不为空,如果有的话,表明不对称
|
||||||
|
if (!left || !right) return false;
|
||||||
|
// 如果两个节点不为空,两个节点的值不相等返回false
|
||||||
|
if (left.val !== right.val) return false;
|
||||||
|
|
||||||
|
// 递归的对称检查,left.left和right.right, left.right和righ.left
|
||||||
|
|
||||||
|
return f1(left.left, right.right) && f1(left.right, right.left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用bfs,使用两个队列,left和right,分别表示镜像每一层的左右节点,然后检查left从左到右存节点
|
||||||
|
right从右到左存节点,然后一一比较,如果都相当则表明这一层是对称的,继续检查下一层,如果每一层
|
||||||
|
都是对称的就表明整颗树都是对称的
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(root) {
|
||||||
|
const queueL = [root.left]; // 遍历镜像左边的节点
|
||||||
|
const queueR = [root.right]; // 遍历镜像右边的节点
|
||||||
|
|
||||||
|
// 每一层的节点个数
|
||||||
|
let leaveSize = 1;
|
||||||
|
while (queueL.length > 0) {
|
||||||
|
console.log('lq: ', queueL);
|
||||||
|
console.log('rq: ', queueR);
|
||||||
|
|
||||||
|
// 判断左右节点
|
||||||
|
for (let i = 0; i < leaveSize; i++) {
|
||||||
|
// 取出对应节点比较
|
||||||
|
const leftSideNode = queueL.shift();
|
||||||
|
const rightSideNode = queueR.shift();
|
||||||
|
// 如果两个节点都为空,直接跳过本次循环,根本就没有子节点,无需做后面的操作
|
||||||
|
if (!leftSideNode && !rightSideNode) continue;
|
||||||
|
// 如果两个节点一个为空,直接返回false
|
||||||
|
if (!leftSideNode || !rightSideNode) return false;
|
||||||
|
// 左右两个节点值不相等
|
||||||
|
if (leftSideNode.val !== rightSideNode.val) return false;
|
||||||
|
// 将左侧和右侧的节点按照顺序压入栈中
|
||||||
|
queueL.push(leftSideNode.left);
|
||||||
|
queueL.push(leftSideNode.right);
|
||||||
|
queueR.push(rightSideNode.right);
|
||||||
|
queueR.push(rightSideNode.left);
|
||||||
|
}
|
||||||
|
leaveSize = queueL.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
dfs,只需一个循环依此检查即可,
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f3(root) {
|
||||||
|
const stackL = [root.left]; // 遍历镜像左边的节点
|
||||||
|
const stackR = [root.right]; // 遍历镜像右边的节点
|
||||||
|
|
||||||
|
while (stackL.length > 0) {
|
||||||
|
// 拿出镜像左右两边的节点比较
|
||||||
|
const leftSideNode = stackL.pop();
|
||||||
|
const rightSideNode = stackR.pop();
|
||||||
|
|
||||||
|
// 如果两个值都为空就跳过
|
||||||
|
if (!leftSideNode && !rightSideNode) continue;
|
||||||
|
// 如果两个节点中有一个为null就返回false
|
||||||
|
if (!leftSideNode || !rightSideNode) return false;
|
||||||
|
// 如果两个节点都存在,但是值不相等返回false
|
||||||
|
if (leftSideNode.val !== rightSideNode.val) return false;
|
||||||
|
|
||||||
|
// 将左右节点压入栈中,leftSideNode.right->leftSideNode.left, rightSideNode.left->rightSideNode.right
|
||||||
|
stackL.push(leftSideNode.right);
|
||||||
|
stackL.push(leftSideNode.left);
|
||||||
|
stackR.push(rightSideNode.left);
|
||||||
|
stackR.push(rightSideNode.right);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
74
top-interview-leetcode150/binary-tree/102二叉树的层序遍历.js
Normal file
74
top-interview-leetcode150/binary-tree/102二叉树的层序遍历.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number[][]}
|
||||||
|
*/
|
||||||
|
const levelOrder = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
层序遍历,无需多言
|
||||||
|
*/
|
||||||
|
function f1(root) {
|
||||||
|
if (!root) return []; // 如果树为空,返回空数组
|
||||||
|
|
||||||
|
const result = []; // 用于存储每一层的节点值
|
||||||
|
const queue = [root]; // 初始化队列,开始时只有根节点
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const levelSize = queue.length; // 当前层的节点数
|
||||||
|
const levelValues = []; // 存储当前层节点的值
|
||||||
|
|
||||||
|
// 遍历当前层的所有节点
|
||||||
|
for (let i = 0; i < levelSize; i++) {
|
||||||
|
const node = queue.shift(); // 弹出队列中的第一个节点
|
||||||
|
levelValues.push(node.val); // 将节点值加入当前层的结果
|
||||||
|
|
||||||
|
// 如果节点有左子节点,加入队列
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
// 如果节点有右子节点,加入队列
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将当前层的节点值加入最终结果
|
||||||
|
result.push(levelValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
二叉树的层序遍历老生常谈了,通常使用一个队列来处理,但是js中的数组shift的操作复杂度为O(1),这里我们使用一个head指针来优化,表示
|
||||||
|
shift要弹出的元素
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(root) {
|
||||||
|
const ans = []; // 返回的遍历结果
|
||||||
|
|
||||||
|
const queue = []; // 层序遍历使用的队列
|
||||||
|
if (root) queue.push(root);
|
||||||
|
let head = 0; // 队列需要弹出的元素的下标
|
||||||
|
while (head < queue.length) {
|
||||||
|
const n = queue.length - head; // 每一层的元素个数
|
||||||
|
|
||||||
|
// 遍历这一层元素,将值存入levelDatas中
|
||||||
|
const levelDatas = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const node = queue[head++];
|
||||||
|
levelDatas.push(node.val);
|
||||||
|
// 将左右节点加入下一层
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans;
|
||||||
|
}
|
51
top-interview-leetcode150/binary-tree/103二叉树的锯齿形层次bianli.js
Normal file
51
top-interview-leetcode150/binary-tree/103二叉树的锯齿形层次bianli.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number[][]}
|
||||||
|
* https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const zigzagLevelOrder = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目就是102的变种,只需记录一个遍历,order,如果order为true就从左到右,否则从右到左,这里直接复制102的代码
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
if (!root) return []; // 如果树为空,返回空数组
|
||||||
|
|
||||||
|
const result = []; // 用于存储每一层的节点值
|
||||||
|
const queue = [root]; // 初始化队列,开始时只有根节点
|
||||||
|
|
||||||
|
let order = true; // 判断这一层数据是从左到右还是从右到左
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const levelSize = queue.length; // 当前层的节点数
|
||||||
|
const levelValues = []; // 存储当前层节点的值
|
||||||
|
|
||||||
|
// 遍历当前层的所有节点
|
||||||
|
for (let i = 0; i < levelSize; i++) {
|
||||||
|
const node = queue.shift(); // 弹出队列中的第一个节点
|
||||||
|
levelValues.push(node.val); // 将节点值加入当前层的结果
|
||||||
|
|
||||||
|
// 如果节点有左子节点,加入队列
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
// 如果节点有右子节点,加入队列
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将当前层的节点值加入最终结果
|
||||||
|
result.push(order ? levelValues : levelSize.reverse());
|
||||||
|
order = !order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
49
top-interview-leetcode150/binary-tree/104二叉树的最大深度.js
Normal file
49
top-interview-leetcode150/binary-tree/104二叉树的最大深度.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxDepth = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这非常符合递归操作,如需直到这颗数的最大深度,只需计算左右子树的深度中较大的,加上自己的这一层
|
||||||
|
不就是整颗数的深度吗?
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
if (!root) return 0;
|
||||||
|
return Math.max(f1(root.left), f1(root.right)) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用BFS来查找每一层的所有节点,然后遍历所有每一层的所有节点,把查找是否有下一层节点,然后
|
||||||
|
把计数加1,直到这一层没有任何节点
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(root) {
|
||||||
|
if (!root) return 0;
|
||||||
|
|
||||||
|
const queue = [root]; // 初始化队列,将根节点加入队列
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const levelSize = queue.length; // 当前层的节点个数
|
||||||
|
for (let i = 0; i < levelSize; i++) {
|
||||||
|
const node = queue.shift(); // 从队列中取出一个节点
|
||||||
|
if (node.left) queue.push(node.left); // 如果左子节点存在,加入队列
|
||||||
|
if (node.right) queue.push(node.right); // 如果右子节点存在,加入队列
|
||||||
|
}
|
||||||
|
depth++; // 每完成一次层级遍历,深度加一
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
148
top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js
Normal file
148
top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} preorder
|
||||||
|
* @param {number[]} inorder
|
||||||
|
* @return {TreeNode}
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
二叉树的问题都能通过递归抽象解决,利用前序遍历的结果和中序遍历的结果也可以通过抽象解决。
|
||||||
|
抽象问题:以最简单的二叉树[1,2,3]来举例,他的中序遍历结果为[2,1,3],前序遍历的第一个结果为
|
||||||
|
根节点,而在中序遍历中,根节点恰好把遍历结果分成了左右两部分,左侧为根节点左子树的遍历结果
|
||||||
|
右侧为根节点右子树的遍历结果,如果我们能找到根节点左子树的前序遍历结果,和右子树的前序遍历结果
|
||||||
|
就可以递归调用,恢复整棵二叉树,这一点我们能从根节点的二叉树,获取。
|
||||||
|
|
||||||
|
过程分析:
|
||||||
|
以[1,2,4,5,3,6,7]这个二叉树为例,根据上面的思路,我们可以找到二叉树的根节点值1,然后利用中序
|
||||||
|
遍历结果[4,2,5,1,6,3,7],将二叉树左右子树的中序遍历结果获取到,分别为[4,2,5],[6,3,7],仔细观察
|
||||||
|
前序遍历的结果[1,2,4,5,3,6,7]也能获取左子树的前序遍历,那就是从根节点后面的左子树个数个数,也就是
|
||||||
|
根节点后面的三个数,右子树的前序遍历同理,之后递归调用即可
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TreeNode from './tool';
|
||||||
|
|
||||||
|
const buildTree = function (preorder, inorder) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function f1(preorder, inorder) {
|
||||||
|
if (preorder.length === 0) return null; // 如果子树没有节点,直接返回null
|
||||||
|
|
||||||
|
// 根据preorder的第一个值,在inorder中定位根节点,并把遍历结果分成左右子树两部分
|
||||||
|
const rootIndex = inorder.indexOf(preorder[0]);
|
||||||
|
const leftInOrder = inorder.slice(0, rootIndex);
|
||||||
|
const rightInOrder = inorder.slice(rootIndex + 1);
|
||||||
|
// 根据左子树和右子树的节点个数获取前序遍历的结果
|
||||||
|
const leftPreOrder = preorder.slice(1, 1 + rootIndex);
|
||||||
|
const rightPreOrder = preorder.slice(1 + rootIndex);
|
||||||
|
|
||||||
|
// 通过子树的前中序遍历结果递归构建左右子树
|
||||||
|
// const left = f1(leftPreOrder, leftInOrder);
|
||||||
|
// const right = f1(rightPreOrder, rightInOrder);
|
||||||
|
// 创建根节点
|
||||||
|
// const root = new TreeNode(preorder[0], left, right);
|
||||||
|
// return root;
|
||||||
|
|
||||||
|
// 一次性返回上面的过程
|
||||||
|
return new TreeNode(preorder[0], f1(leftPreOrder, leftInOrder), f1(rightPreOrder, rightInOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
f1的做法思路非常清晰,但是分割数组,会消耗大量内存,实际做法应该使用分割左右子树,这样,能节省
|
||||||
|
大量的空间,而且免去了分割数组的复杂度
|
||||||
|
|
||||||
|
思考:只需传递三个下标,第一个指向前序的root,第二个和第三个指向中序的结果左右边界
|
||||||
|
hard: 右子树的左右边界比较难计算,多思考几下
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
|
||||||
|
if (inOrderLeft > inOrderRight) return null;
|
||||||
|
|
||||||
|
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
|
||||||
|
const rootIndex = inorder.indexOf(preorder[preOrderIndex]);
|
||||||
|
const left = f2(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const right = f2(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
|
||||||
|
return new TreeNode(preorder[preOrderIndex], left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
上面代码已经十分简洁了,但是还有一个可以优化的地方,就是indexOf这个找过程,题目中所给的测试
|
||||||
|
用例值是没有重复的,所以我们可以建立一个hashMap,前序遍历的值作为下标,前序遍历的下标作为值
|
||||||
|
到时候查早下标只需O(1)的时间复杂度
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f3(preO, inO) {
|
||||||
|
// 构建map映射
|
||||||
|
const map = new Map();
|
||||||
|
for (let i = 0; i < inO.length; i++) {
|
||||||
|
map.set(inO[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return build(preO, inO, 0, 0, inO.length - 1);
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} preorder 前序遍历结果
|
||||||
|
* @param {*} inorder 中序遍历结果
|
||||||
|
* @param {*} preOrderIndex 前序遍历的根节点值
|
||||||
|
* @param {*} inOrderLeft 中序遍历的左边界
|
||||||
|
* @param {*} inOrderRight 中序遍历的右边界
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function build(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
|
||||||
|
if (inOrderLeft > inOrderRight) return null;
|
||||||
|
|
||||||
|
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
|
||||||
|
const rootIndex = map.get(preOrderIndex[preOrderIndex]);
|
||||||
|
const left = build(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const right = build(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
|
||||||
|
return new TreeNode(preorder[preOrderIndex], left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REVIEW:
|
||||||
|
/*
|
||||||
|
利用迭代:
|
||||||
|
*/
|
||||||
|
function f4(preorder, inorder) {
|
||||||
|
if (!preorder || preorder.length === 0) return null;
|
||||||
|
|
||||||
|
// 根节点是前序遍历的第一个元素
|
||||||
|
const root = new TreeNode(preorder[0]);
|
||||||
|
|
||||||
|
// 栈用于存储节点
|
||||||
|
const stack = [root];
|
||||||
|
let inorderIndex = 0;
|
||||||
|
|
||||||
|
// 从前序遍历数组的第二个元素开始遍历
|
||||||
|
for (let i = 1; i < preorder.length; i++) {
|
||||||
|
const preorderVal = preorder[i];
|
||||||
|
let node = stack[stack.length - 1];
|
||||||
|
|
||||||
|
// 如果栈顶元素的值不等于当前中序遍历的值,说明是左子树的节点
|
||||||
|
if (node.val !== inorder[inorderIndex]) {
|
||||||
|
node.left = new TreeNode(preorderVal);
|
||||||
|
stack.push(node.left);
|
||||||
|
} else {
|
||||||
|
// 否则说明当前节点是右子树的节点
|
||||||
|
while (stack.length > 0 && stack[stack.length - 1].val === inorder[inorderIndex]) {
|
||||||
|
node = stack.pop(); // 回溯
|
||||||
|
inorderIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建右子树节点并将其压入栈
|
||||||
|
node.right = new TreeNode(preorderVal);
|
||||||
|
stack.push(node.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
65
top-interview-leetcode150/binary-tree/112路径总和.js
Normal file
65
top-interview-leetcode150/binary-tree/112路径总和.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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} targetSum
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const hasPathSum = function (root, targetSum) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:利用递归抽象问题,如果左节点或者右节点满足 targetSum - root.val,那么整个树就满足
|
||||||
|
*/
|
||||||
|
function f1(root, targetSum) {
|
||||||
|
if (!root) return false; // 如果当前节点为空,返回 false
|
||||||
|
|
||||||
|
// 如果当前节点是叶子节点,且值等于目标值,返回 true
|
||||||
|
if (!root.left && !root.right) return root.val === targetSum;
|
||||||
|
|
||||||
|
// 递归判断左子树或右子树是否存在满足条件的路径
|
||||||
|
const remainingSum = targetSum - root.val;
|
||||||
|
return hasPathSum(root.left, remainingSum) || hasPathSum(root.right, remainingSum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:利用层序遍历,获得从根节点到当前节点的所有和,只需准备两个队列,一个队列用于层序遍历,一个节点用于保存层序遍历到的
|
||||||
|
每一个值得路径综合,如果这个节点是一个叶子节点,就检查总和是不是和targetSum一致,如果不一致,继续检查其他得叶子节点
|
||||||
|
*/
|
||||||
|
function f2(root, targetSum) {
|
||||||
|
if (!root) return false; // 如果根节点为空直接返回false
|
||||||
|
|
||||||
|
const nodeQue = [root]; // 层序遍历的队列
|
||||||
|
const sumQue = [root.val]; // 遍历nodeQue的节点的路径总和,和nodeQue是同步的
|
||||||
|
|
||||||
|
while (nodeQue.length > 0) {
|
||||||
|
const node = nodeQue.shift();
|
||||||
|
const sum = sumQue.shift();
|
||||||
|
|
||||||
|
// 如果当前节点为叶子节点检查sum是否和targetSum相等
|
||||||
|
if (!node.left && !node.right && sum === targetSum) return true;
|
||||||
|
|
||||||
|
// 如果左右节点存在将左右节点存入队列,对应的路径和存入sum队列
|
||||||
|
if (node.left) {
|
||||||
|
nodeQue.push(node.left);
|
||||||
|
sumQue.push(sum + node.left.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.right) {
|
||||||
|
nodeQue.push(node.right);
|
||||||
|
sumQue.push(sum + node.right.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路三:和f1是一样的,只不过利用dfs来代替递归,//TODO
|
||||||
|
*/
|
102
top-interview-leetcode150/binary-tree/114二叉树展开为列表.js
Normal file
102
top-interview-leetcode150/binary-tree/114二叉树展开为列表.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* 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 {void} Do not return anything, modify root in-place instead.
|
||||||
|
*/
|
||||||
|
import TreeNode from './tool';
|
||||||
|
|
||||||
|
const flatten = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:直接先序遍历整个二叉树,将整个节点按照顺序存进数组最后遍历它们,把它们连接起来
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
// 如果为空返回
|
||||||
|
if (!root) return root;
|
||||||
|
|
||||||
|
const preNodes = [];// 储存先序遍历的数组
|
||||||
|
const dummyHead = new TreeNode(1); // 哑节点,用于构建链表
|
||||||
|
|
||||||
|
// 先序遍历
|
||||||
|
const stack = [root]; // 先序遍历需要用到的栈
|
||||||
|
|
||||||
|
while (stack.length) {
|
||||||
|
const node = stack.pop(); // 取出根节点
|
||||||
|
preNodes.push(node);
|
||||||
|
if (node.right) stack.push(node.right);
|
||||||
|
if (node.left) stack.push(node.left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将数组中所有的节点通过right链接到dummyHead
|
||||||
|
let preNode = dummyHead;
|
||||||
|
for (let i = 0; i < preNodes.length; i++) {
|
||||||
|
preNodes[i].left = null; // 左节点滞空
|
||||||
|
preNode.right = preNodes[i];
|
||||||
|
preNode = preNodes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dummyHead.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用栈来前序遍历,思路和上面迭代一致
|
||||||
|
*/
|
||||||
|
function f2(root) {
|
||||||
|
const preorderTraversal = (node, nodeList) => {
|
||||||
|
if (node) {
|
||||||
|
nodeList.push(node);
|
||||||
|
preorderTraversal(node.left, nodeList);
|
||||||
|
preorderTraversal(node.right, nodeList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!root) return root;
|
||||||
|
|
||||||
|
const list = [];
|
||||||
|
// 递归遍历二叉树,把节点放进list中
|
||||||
|
preorderTraversal(root, list);
|
||||||
|
// 将所有节点链接起来
|
||||||
|
const dummyHead = new TreeNode(-1); // 利用哑节点来处理头节点问题
|
||||||
|
let cur = dummyHead;
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
cur.right = list[i];
|
||||||
|
cur.left = null;
|
||||||
|
cur = list[i];
|
||||||
|
}
|
||||||
|
return dummyHead.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
在前序遍历的同时设置节点,根据前序遍历的顺序,会先处理左子树,再处理右子树,所以只要先把右子树
|
||||||
|
压入栈中,再把左子树压入栈中,这样当所有的左子树处理完毕,会继续处理右子树
|
||||||
|
*/
|
||||||
|
function f3(root) {
|
||||||
|
if (!root) return root;
|
||||||
|
|
||||||
|
const stack = [root];
|
||||||
|
|
||||||
|
// 要链接到的节点,初始为空,表示字节的的前面一个节点
|
||||||
|
let preNode = null;
|
||||||
|
while (stack.length) {
|
||||||
|
const node = stack.pop();
|
||||||
|
// 将当前节点链接到preNode.right
|
||||||
|
if (preNode) {
|
||||||
|
preNode.right = node;
|
||||||
|
preNode.left = null;
|
||||||
|
}
|
||||||
|
// 将右子树压入栈中
|
||||||
|
if (node.right) stack.push(node.right);
|
||||||
|
// 将左子树压入栈中
|
||||||
|
if (node.left) stack.push(node.left);
|
||||||
|
preNode = node;
|
||||||
|
}
|
||||||
|
}
|
133
top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js
Normal file
133
top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* // Definition for a _Node.
|
||||||
|
* function _Node(val, left, right, next) {
|
||||||
|
* this.val = val === undefined ? null : val;
|
||||||
|
* this.left = left === undefined ? null : left;
|
||||||
|
* this.right = right === undefined ? null : right;
|
||||||
|
* this.next = next === undefined ? null : next;
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {_Node} root
|
||||||
|
* @return {_Node}
|
||||||
|
*/
|
||||||
|
const connect = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
按照题目的意思,很容易想到层序遍历,利用BFS拿到每一层的所有元素,定义一个last变量表示需要
|
||||||
|
设置next的节点,默认为当前层的第一个,之后遍历后面的所有元素,last.next = node,即可,node.next
|
||||||
|
默认为null,所以不要考虑为最后一个节点设置next
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
if (!root) return root;
|
||||||
|
|
||||||
|
const queue = [root]; // 定义队列,用于层序遍历
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
// 获取当前层的节点个数,用于遍历设置next
|
||||||
|
let n = queue.length;
|
||||||
|
// 定义last用于设置next
|
||||||
|
let last = null;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const node = queue.shift();
|
||||||
|
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
|
||||||
|
// 如果不是第一个node
|
||||||
|
if (i !== 0) {
|
||||||
|
last.next = node;
|
||||||
|
}
|
||||||
|
last = node;
|
||||||
|
}
|
||||||
|
n = queue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用栈来储存每一层的节点再获取下一层的节点有点浪费空间,因为原本就已经有一个next
|
||||||
|
来指向下一层的节点了,我们可以利用这个特点来遍历每一层的节点,并且为它下一层的节点
|
||||||
|
建立next指针,直到下一层没有节点,这样所有的节点就都构建好next指针
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(root) {
|
||||||
|
if (!root) return root; // 如果根节点为空,直接返回
|
||||||
|
|
||||||
|
let nextStart = root; // 初始时为根节点
|
||||||
|
|
||||||
|
// 1. 每一层的遍历,直到没有更多节点
|
||||||
|
while (nextStart) {
|
||||||
|
let last = null; // 记录每一层的最后一个节点,用来连接
|
||||||
|
let cur = nextStart; // 当前层的遍历指针
|
||||||
|
nextStart = null; // 下一层的起始节点初始化为空
|
||||||
|
|
||||||
|
// 2. 遍历当前层的节点
|
||||||
|
while (cur) {
|
||||||
|
// 3. 如果当前节点有左子节点
|
||||||
|
if (cur.left) {
|
||||||
|
// 如果last不为空,说明上一个节点已连接,需要更新next
|
||||||
|
if (last) last.next = cur.left;
|
||||||
|
last = cur.left; // 更新last为当前左子节点
|
||||||
|
// 如果nextStart为空,表示当前左子节点是下一层的第一个节点
|
||||||
|
if (!nextStart) nextStart = cur.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前节点有右子节点
|
||||||
|
if (cur.right) {
|
||||||
|
if (last) last.next = cur.right;
|
||||||
|
last = cur.right; // 更新last为当前右子节点
|
||||||
|
// 如果nextStart为空,表示当前右子节点是下一层的第一个节点
|
||||||
|
if (!nextStart) nextStart = cur.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动到当前节点的下一个节点
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回根节点,确保修改后的树结构被返回
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路和f2一致,但是通过递归处理
|
||||||
|
*/
|
||||||
|
function f3(node) {
|
||||||
|
if (!node) return; // 如果节点为空,直接返回
|
||||||
|
|
||||||
|
let nextStart = null; // 用来记录下一层的第一个节点
|
||||||
|
let last = null; // 用来记录当前层最后一个节点
|
||||||
|
|
||||||
|
// 遍历当前层的所有节点
|
||||||
|
while (node) {
|
||||||
|
// 连接左子节点
|
||||||
|
if (node.left) {
|
||||||
|
if (last) {
|
||||||
|
last.next = node.left; // 连接当前节点的上一个节点的next
|
||||||
|
}
|
||||||
|
last = node.left; // 更新last为当前左子节点
|
||||||
|
if (!nextStart) nextStart = node.left; // 如果nextStart为空,设置为当前左子节点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接右子节点
|
||||||
|
if (node.right) {
|
||||||
|
if (last) {
|
||||||
|
last.next = node.right; // 连接当前节点的上一个节点的next
|
||||||
|
}
|
||||||
|
last = node.right; // 更新last为当前右子节点
|
||||||
|
if (!nextStart) nextStart = node.right; // 如果nextStart为空,设置为当前右子节点
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.next; // 移动到当前节点的下一个节点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理下一层
|
||||||
|
f3(nextStart);
|
||||||
|
return node;
|
||||||
|
}
|
64
top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js
Normal file
64
top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number}
|
||||||
|
* https://leetcode.cn/problems/binary-tree-maximum-path-sum/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const maxPathSum = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算二叉树中的最大路径和。路径和的定义是:
|
||||||
|
* 从任意节点出发,沿着父子节点的路径进行移动,且路径和的值为路径上所有节点的值之和。
|
||||||
|
* 一个路径的最大和可能穿过根节点,也可能不穿过根节点。
|
||||||
|
*
|
||||||
|
* 1. 初始化变量 `maxSum` 为负无穷,表示初始时的最大路径和。
|
||||||
|
* 2. 使用一个递归函数 `maxGain(node)`,计算以当前节点为起点的路径和贡献值。
|
||||||
|
* - 如果当前节点为空,返回 0(没有路径贡献)。
|
||||||
|
* - 对于每个节点,我们分别计算其左子树和右子树的最大贡献值。
|
||||||
|
* - `leftGain` 和 `rightGain` 代表左右子树的最大路径贡献,若为负数,则选择为 0,避免影响结果。
|
||||||
|
* - 计算该节点的最大路径和 `priceNewPath`,这是通过当前节点的值加上左右子树的最大贡献值得到的。
|
||||||
|
* - 更新 `maxSum` 为当前路径和与已知最大路径和的较大者。
|
||||||
|
* - 返回该节点的最大贡献值,它等于当前节点的值加上左右子树贡献中的较大者。
|
||||||
|
*
|
||||||
|
* 3. 在 `maxPathSum` 中,调用 `maxGain(root)` 来递归计算最大路径和。
|
||||||
|
* 4. 最终,`maxSum` 将包含二叉树中的最大路径和,返回这个值作为结果。
|
||||||
|
*
|
||||||
|
* 需要注意:
|
||||||
|
* - 每个节点的最大贡献值是考虑到该节点本身以及它的左右子节点的最大贡献。
|
||||||
|
* - 我们只选择左右子树中贡献值大于零的部分,因为负值的路径会减少总和。
|
||||||
|
* - `maxSum` 记录的是整个树中的最大路径和,它可能并不经过根节点。
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
let maxSum = -Infinity; // 初始化为最小的整数
|
||||||
|
|
||||||
|
// 内部的递归函数,返回以当前节点为起点的最大路径贡献值
|
||||||
|
function maxGain(node) {
|
||||||
|
if (!node) return 0;
|
||||||
|
|
||||||
|
// 递归计算左右子节点的最大贡献值,只有在大于0时才会选择该节点
|
||||||
|
const leftGain = Math.max(maxGain(node.left), 0);
|
||||||
|
const rightGain = Math.max(maxGain(node.right), 0);
|
||||||
|
|
||||||
|
// 当前节点的最大路径和为该节点值加上左右子树的最大贡献值
|
||||||
|
const priceNewPath = node.val + leftGain + rightGain;
|
||||||
|
|
||||||
|
// 更新答案
|
||||||
|
maxSum = Math.max(maxSum, priceNewPath);
|
||||||
|
|
||||||
|
// 返回该节点的最大贡献值
|
||||||
|
return node.val + Math.max(leftGain, rightGain);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxGain(root); // 开始递归计算
|
||||||
|
return maxSum; // 返回结果
|
||||||
|
}
|
110
top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js
Normal file
110
top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const sumNumbers = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:利用DFS来遍历每一个节点,直到遍历到叶子节点,把从root节点到叶子节点代表的数字求出了,之后求和,处理完所有的叶子节点
|
||||||
|
那么sum就是我们要返回的最终结果,叶子节点要想知道从root节点到父节点所代表的数字,就必须用一个numStack的栈储存。
|
||||||
|
*/
|
||||||
|
function f1(root) {
|
||||||
|
let sum = 0; // 根节点到叶子节点代表的数字之和
|
||||||
|
|
||||||
|
const nodeStack = [root]; // 遍历每一个节点
|
||||||
|
const numStack = [root.val]; // 每一个节点所表示的数字
|
||||||
|
|
||||||
|
// DFS遍历所有节点
|
||||||
|
while (nodeStack.length > 0) {
|
||||||
|
// 弹出要处理的节点,和root节点到这个节点表示的数字
|
||||||
|
const node = nodeStack.pop();
|
||||||
|
const nodeNum = numStack.pop();
|
||||||
|
|
||||||
|
// 如果当前节点是叶子节点将他与sum求和
|
||||||
|
if (!node.left && !node.right) {
|
||||||
|
sum += nodeNum;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前节点的左右子节点存在,将它和它表示的num压入栈中,后续处理
|
||||||
|
if (node.right) {
|
||||||
|
nodeStack.push(node.right);
|
||||||
|
numStack.push(nodeNum * 10 + node.right.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.left) {
|
||||||
|
nodeStack.push(node.left);
|
||||||
|
numStack.push(nodeNum * 10 + node.left.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目利用BFS也能解决,只需改一下f1就行,把栈改成队列
|
||||||
|
*/
|
||||||
|
function f2(root) {
|
||||||
|
let sum = 0; // 根节点到叶子节点代表的数字之和
|
||||||
|
|
||||||
|
const nodeQue = [root]; // 遍历每一个节点
|
||||||
|
const numQue = [root.val]; // 每一个节点所表示的数字
|
||||||
|
|
||||||
|
// DFS遍历所有节点
|
||||||
|
while (nodeQue.length > 0) {
|
||||||
|
// 弹出要处理的节点,和root节点到这个节点表示的数字
|
||||||
|
const node = nodeQue.shift();
|
||||||
|
const nodeNum = numQue.shift();
|
||||||
|
|
||||||
|
// 如果当前节点是叶子节点将他与sum求和
|
||||||
|
if (!node.left && !node.right) {
|
||||||
|
sum += nodeNum;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前节点的左右子节点存在,将它和它表示的num压入栈中,后续处理
|
||||||
|
if (node.right) {
|
||||||
|
nodeQue.push(node.right);
|
||||||
|
numQue.push(nodeNum * 10 + node.right.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.left) {
|
||||||
|
nodeQue.push(node.left);
|
||||||
|
numQue.push(nodeNum * 10 + node.left.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用dfs递归,递归函数接收两个参数一个参数为node,表示当前的节点,一个参数为prevNum,表示根节点到父节点表示的数字,返回值
|
||||||
|
表示的是这个根节点到这个节点下面任意字节的表示的数字的总和
|
||||||
|
*/
|
||||||
|
const dfs = (root, prevNum) => {
|
||||||
|
if (root === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const sum = prevNum * 10 + root.val;
|
||||||
|
if (root.left == null && root.right == null) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
// 将所有叶子节点的值求和
|
||||||
|
return dfs(root.left, sum) + dfs(root.right, sum);
|
||||||
|
};
|
||||||
|
const f3 = function (root) {
|
||||||
|
return dfs(root, 0);
|
||||||
|
};
|
||||||
|
// 上面的递归函数就做了两件事情,找叶子节点,找到,将他与父节点表示的数字组合成新的数字,最后求和
|
77
top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js
Normal file
77
top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
思路:根据题意,在调用next()的时候我们需要按照二叉搜索树的中序遍历顺序返回结果,所以可以先对BST进行中序遍历,然后把遍历的结果
|
||||||
|
储存到this.nodeVals中,再维护一个this.idx 来表示next 需要返回值得下标
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
const BSTIterator = function (root) {
|
||||||
|
this.arr = []; // 二叉搜索树中序遍历结果
|
||||||
|
this.idx = 0; // 调用next返回得值
|
||||||
|
BSTIterator.inOrderTravers(root, this.arr); // 把中序遍历结果存入this.arr
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
BSTIterator.prototype.next = function () {
|
||||||
|
return this.arr[this.idx++];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
BSTIterator.prototype.hasNext = function () {
|
||||||
|
// 如果this.idx 小于this.arr.lengthz则表示,调用next能获取下一个元素
|
||||||
|
return this.idx < this.arr.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
BSTIterator.inOrderTravers = function (root, arr) {
|
||||||
|
if (!root) return;
|
||||||
|
// 处理左子树
|
||||||
|
BSTIterator.inOrderTravers(root.left, arr);
|
||||||
|
// 将值存入数组中
|
||||||
|
arr.push(root.val);
|
||||||
|
// 处理右子树
|
||||||
|
BSTIterator.inOrderTravers(root.right, arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Your BSTIterator object will be instantiated and called as such:
|
||||||
|
* var obj = new BSTIterator(root)
|
||||||
|
* var param_1 = obj.next()
|
||||||
|
* var param_2 = obj.hasNext()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用中序遍历,迭代类维护一个栈和调用next应该打印得值
|
||||||
|
*/
|
||||||
|
|
||||||
|
// var BSTIterator = function(root) {
|
||||||
|
// this.cur = root;
|
||||||
|
// this.stack = [];
|
||||||
|
// };
|
||||||
|
|
||||||
|
// BSTIterator.prototype.next = function() {
|
||||||
|
// while (this.cur) {
|
||||||
|
// this.stack.push(this.cur);
|
||||||
|
// this.cur = this.cur.left;
|
||||||
|
// }
|
||||||
|
// this.cur = this.stack.pop();
|
||||||
|
// const ret = this.cur.val;
|
||||||
|
// this.cur = this.cur.right;
|
||||||
|
// return ret;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// BSTIterator.prototype.hasNext = function() {
|
||||||
|
// return this.cur !== null || this.stack.length;
|
||||||
|
// };
|
53
top-interview-leetcode150/binary-tree/199二叉树的右视图.js
Normal file
53
top-interview-leetcode150/binary-tree/199二叉树的右视图.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number[]}
|
||||||
|
* https://leetcode.cn/problems/binary-tree-right-side-view/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const rightSideView = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
按照题目要求,很容易想到利用层序遍历,将每一层的最后一个结果保存进 ans这个数组,最后返回ans就是我们所需要的答案
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
const ans = []; // 储存结果的数组
|
||||||
|
const queue = []; // 层序遍历需要使用的队列
|
||||||
|
if (root) queue.push(root);
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
// 获取每一层的最后一个元素存入 ans
|
||||||
|
const n = queue.length;
|
||||||
|
ans.push(queue[n - 1].val);
|
||||||
|
|
||||||
|
// 将下一层元素压入队列
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const node = queue.shift();
|
||||||
|
if (node.left) {
|
||||||
|
queue.push(node.left);
|
||||||
|
}
|
||||||
|
if (node.right) {
|
||||||
|
queue.push(node.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路2: 使用dfs优先遍右子树,将每一层遇到的第一个节点储存到一个map中,当遍历完所有元素,按照层数返回map中的值即可
|
||||||
|
过程:右子树DFS,如果当前节点不为空,查看这一层是否已经有元素了,如果没有那么这个值就是这一层的第一个元素,将他
|
||||||
|
加入映射,
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO:
|
98
top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js
Normal file
98
top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const countNodes = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:以任何方式遍历一遍,统计个数
|
||||||
|
*/
|
||||||
|
function f1(root) {
|
||||||
|
let count = 0;
|
||||||
|
// 中序遍历递归函数
|
||||||
|
const inTarverse = (node) => {
|
||||||
|
// 如果root为空直接返回
|
||||||
|
if (!node) return;
|
||||||
|
// 调用左子树
|
||||||
|
inTarverse(node.left);
|
||||||
|
// 统计当前节点
|
||||||
|
count++;
|
||||||
|
// 调用右子树
|
||||||
|
inTarverse(node.right);
|
||||||
|
};
|
||||||
|
|
||||||
|
inTarverse(root);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
除了遍历所有节点统计个数,还可以利用完全二叉树个数与位置的关系通过二分查找来判断节点的个数,分为两步,
|
||||||
|
第一步:首先获得这棵完全二叉树的高度,通过高度我们可以判断一颗完全二叉树的节点数量范围[2^n, 2^n - 1]个节点
|
||||||
|
第二步:节点个数的二进制和位置右某种关系,[1,2,3],来看这个例子,首先根节点,也就是第一个节点,那么它的二进制就是
|
||||||
|
1,再来看第二个,2,二进制位10,3,位11,这还不够明显再加四个数[1,2,3,4,5,6,7],4->100,5->101,6->110,7->111,这个规律就是
|
||||||
|
二进制位决定了这个节点从根节点到这个位置的路径,7->111,第一个1表示的是根节点,第二个1表示这个节点在根节点的右子树,第三个1
|
||||||
|
表示的是在更节点的右节点的右节点。
|
||||||
|
过程,编写一个exists 判断指定位置的节点是否在root中存在,接收三个参数,根节点root,树的高度,指定要判断的节点
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} root 根节点
|
||||||
|
* @param {*} h 完全二叉树的高度
|
||||||
|
* @param {*} k 最后一层的某一个节点 [2^n, 2^n - 1]
|
||||||
|
*/
|
||||||
|
const exists = (root, h, k) => {
|
||||||
|
// 初始bits位 1 << (h-1),假设h是3,那么默认值位100,如果我们要查找第12个数是否存在就可以通过 1100 & 0100来
|
||||||
|
// 判断是在右子树还是在左子树
|
||||||
|
let bits = 1 << (h - 1);
|
||||||
|
let node = root;
|
||||||
|
while (bits) {
|
||||||
|
if (bits & k) { // bits&k不等于0表示这个节点应该往右子树查找
|
||||||
|
node = root.right;
|
||||||
|
} else { // 反之在左子树
|
||||||
|
node = root.left;
|
||||||
|
}
|
||||||
|
bits >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断node是否等于空
|
||||||
|
return node === null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function f2(root) {
|
||||||
|
if (!root) return 0;
|
||||||
|
// 查找树的深度
|
||||||
|
let h = 0;
|
||||||
|
let node = root.left;
|
||||||
|
while (node) {
|
||||||
|
h++;
|
||||||
|
node = node.left;
|
||||||
|
}
|
||||||
|
// 通过树的深度计数出数量的范围 low和high
|
||||||
|
let low = 2 ** h;
|
||||||
|
let high = 2 ** (h + 1) - 1;
|
||||||
|
|
||||||
|
// 二分查找,知道low===high结束查找, 以h=3来思考[8,15]
|
||||||
|
while (low < high) {
|
||||||
|
// 计数出中间位置
|
||||||
|
const mid = low + Math.floor((high - low) / 2);
|
||||||
|
if (exists(root, h, mid)) {
|
||||||
|
// 如果这个树存在,那么节点个数大于等于这个数,将左边界移动到mid
|
||||||
|
low = mid;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 此时low和high会相等,这个位置就是二叉树的个数
|
||||||
|
return low;
|
||||||
|
}
|
84
top-interview-leetcode150/binary-tree/226翻转二叉树.js
Normal file
84
top-interview-leetcode150/binary-tree/226翻转二叉树.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/invert-binary-tree/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {TreeNode}
|
||||||
|
*/
|
||||||
|
const invertTree = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用递归来抽象问题,交换一颗树的左右子树不就可以做到翻转了,函数f1只做单一的功能,就是反转
|
||||||
|
一颗二叉树,递归调用就可以翻转从根节点开始的整棵树
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(root) {
|
||||||
|
if (!root) return; // 如果当前节点为空,直接返回
|
||||||
|
|
||||||
|
const left = root.left;
|
||||||
|
const right = root.right;
|
||||||
|
|
||||||
|
// 交换左右两棵树的位置
|
||||||
|
root.left = right;
|
||||||
|
root.right = left;
|
||||||
|
|
||||||
|
// 递归调用
|
||||||
|
f1(root.left);
|
||||||
|
f1(root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用bfs来翻转一颗树
|
||||||
|
*/
|
||||||
|
function f2(root) {
|
||||||
|
if (!root) return root;
|
||||||
|
|
||||||
|
const queue = [root]; // bfs所需要的队列
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
// 从队列中取出节点,实际顺序为从上到下,从左到右(BFS)
|
||||||
|
const node = queue.shift();
|
||||||
|
// 交换左右两个节点
|
||||||
|
const tmp = node.left;
|
||||||
|
node.left = node.right;
|
||||||
|
node.right = tmp;
|
||||||
|
|
||||||
|
// 如果左子节点存在,加入队列
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
// 如果右子节点存在,加入队列
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用DFS来翻转一颗树
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f3(root) {
|
||||||
|
if (!root) return root;
|
||||||
|
const stack = [root]; // dfs所需要的栈
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
// 从栈中取出节点,DFS
|
||||||
|
const node = stack.pop();
|
||||||
|
|
||||||
|
// 交换左右两棵子树
|
||||||
|
const tmp = node.left;
|
||||||
|
node.left = node.right;
|
||||||
|
node.right = tmp;
|
||||||
|
|
||||||
|
// 如果字节的不为空压入栈继续dfs,
|
||||||
|
if (node.left) stack.push(node.left);
|
||||||
|
if (node.right) stack.push(node.right);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
90
top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js
Normal file
90
top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val) {
|
||||||
|
* this.val = val;
|
||||||
|
* this.left = this.right = null;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @param {TreeNode} p
|
||||||
|
* @param {TreeNode} q
|
||||||
|
* @return {TreeNode}
|
||||||
|
* https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const lowestCommonAncestor = function (root, p, q) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用dfs来遍历每一节点,根据定义,如果节点x是p和q的最近的公共祖先无外乎就下面几种情况
|
||||||
|
1. p在x的左子树,q在x的右子树,反之一样 lson && rson
|
||||||
|
2. p就是x,那么q一定存在 lson 或 rson, (x===p||x===q) && (lson || rson)
|
||||||
|
在遍历的时候只要满足上面条件的节点就是要寻找的最近公共祖先
|
||||||
|
*/
|
||||||
|
function f1(root, p, q) {
|
||||||
|
let ans;
|
||||||
|
/*
|
||||||
|
dfs遍历只干一件事,就是判断root表示的这个数是否有p或者q
|
||||||
|
*/
|
||||||
|
const dfs = (root, p, q) => {
|
||||||
|
// 如果当前节点为空返回false
|
||||||
|
if (!root) return false;
|
||||||
|
|
||||||
|
// 查找左右子树是否包含q或q
|
||||||
|
const lson = dfs(root.left, p, q);
|
||||||
|
const rson = dfs(root.right, p, q);
|
||||||
|
|
||||||
|
// 判断当前节点是否是最近公共祖先
|
||||||
|
if ((lson && rson) || ((root === p || root === q) && (lson || rson))) {
|
||||||
|
ans = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回函数判断
|
||||||
|
return lson || rson || root === p || root === q;
|
||||||
|
};
|
||||||
|
dfs(root, p, q);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
遍历所有的节点,给他们设置对应的父节点,之后不停的回溯q的父节点,并且把它们存入到一个set中,之后再回溯p节点的父节点,如果p节点的父节点
|
||||||
|
在set中,那么这个节点就是最近公共祖先
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(root, p, q) {
|
||||||
|
const parentMap = new Map(); // 储存父节点映射
|
||||||
|
const visited = new Set(); // 记录已访问的节点
|
||||||
|
|
||||||
|
// 深度优先搜索(DFS)来构建父节点映射
|
||||||
|
const dfs = (node) => {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
if (node.left) {
|
||||||
|
parentMap.set(node.left, node); // 记录左节点的父节点
|
||||||
|
dfs(node.left); // 继续递归
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.right) {
|
||||||
|
parentMap.set(node.right, node); // 记录右节点的父节点
|
||||||
|
dfs(node.right); // 继续递归
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行DFS来构建父节点映射
|
||||||
|
dfs(root);
|
||||||
|
|
||||||
|
// 追溯p的路径并标记
|
||||||
|
while (p) {
|
||||||
|
visited.add(p); // 标记p节点
|
||||||
|
p = parentMap.get(p); // 跳到p的父节点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追溯q的路径并查找是否有交点
|
||||||
|
while (q) {
|
||||||
|
if (visited.has(q)) return q; // 找到交点,返回q
|
||||||
|
q = parentMap.get(q); // 跳到q的父节点
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 如果没有交点,返回null
|
||||||
|
}
|
48
top-interview-leetcode150/binary-tree/637二叉树的层平均值.js
Normal file
48
top-interview-leetcode150/binary-tree/637二叉树的层平均值.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {TreeNode} root
|
||||||
|
* @return {number[]}
|
||||||
|
* https://leetcode.cn/problems/average-of-levels-in-binary-tree/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const averageOfLevels = function (root) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接层序遍历,之后对每一层的数据求和,再求平均数(保留五位小数)
|
||||||
|
*/
|
||||||
|
function f1(root) {
|
||||||
|
const queue = [root];
|
||||||
|
const ans = [];
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const n = queue.length;
|
||||||
|
let sum = 0; // 每一层数据的和
|
||||||
|
|
||||||
|
// 遍历每一层的所有数据,对它们求和
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const node = queue.shift();
|
||||||
|
sum += node.val;
|
||||||
|
// 将左右子节点存入下一层
|
||||||
|
if (node.left) queue.push(node.left);
|
||||||
|
if (node.right) queue.push(node.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 求平均数,保留五位小数,并存入结果集
|
||||||
|
ans.push(Number((sum / n).toFixed(5)));
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用DFS来遍历,利用一个数组levelData保存如下数据,数组元素的下标表示每一层的信息,元素为一个对象{sum: 这层元素的和, count: 这层元素个数}
|
||||||
|
dfs的时候如果当前层数level小于leveData.length,表示这是新层的第一个数据,就levelData.push({sum: curNode.val, count:1})
|
||||||
|
*/
|
||||||
|
// TODO:
|
5
top-interview-leetcode150/binary-tree/tool.js
Normal file
5
top-interview-leetcode150/binary-tree/tool.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function TreeNode(val, left, right) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.left = (left === undefined ? null : left);
|
||||||
|
this.right = (right === undefined ? null : right);
|
||||||
|
}
|
25
top-interview-leetcode150/bitwise-operations/190颠倒二进制数.js
Normal file
25
top-interview-leetcode150/bitwise-operations/190颠倒二进制数.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} n - a positive integer
|
||||||
|
* @return {number} - a positive integer
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {number} n - a positive integer
|
||||||
|
* @return {number} - reversed bits result
|
||||||
|
*/
|
||||||
|
const reverseBits = function (n) {
|
||||||
|
let res = 0;
|
||||||
|
|
||||||
|
// 一共处理32位
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
// 提取最低位(0或1)
|
||||||
|
const bit = n & 1;
|
||||||
|
|
||||||
|
// 把结果左移一位,为新加入的bit腾位置
|
||||||
|
res = (res << 1) | bit;
|
||||||
|
|
||||||
|
// 原数字右移一位(无符号),处理下一位
|
||||||
|
n >>>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res >>> 0; // 确保返回的是无符号数(防止变负)
|
||||||
|
};
|
63
top-interview-leetcode150/bitwise-operations/67二进制求和.js
Normal file
63
top-interview-leetcode150/bitwise-operations/67二进制求和.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
const addBinary = function (a, b) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接利用位运算,只算进位加法,和只算不进位加法,得到得结果求和就是要求得结果,投机
|
||||||
|
取巧使用数字快速运算,被大数卡住了,f1只作为思路扩张
|
||||||
|
*/
|
||||||
|
function f1(a, b) {
|
||||||
|
// 将二进制字符串转换为十进制整数
|
||||||
|
let x = parseInt(a, 2);
|
||||||
|
let y = parseInt(b, 2);
|
||||||
|
|
||||||
|
// 使用位运算求和
|
||||||
|
while (y !== 0) {
|
||||||
|
const sum = x ^ y; // 不带进位的加法
|
||||||
|
const carry = (x & y) << 1; // 进位
|
||||||
|
x = sum;
|
||||||
|
y = carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把结果转换成二进制字符串
|
||||||
|
return x.toString(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
模拟加法操作,从个位开始加,由于各位没有人给我进位,所以初始进位carry位0,每一位相加再加上它得进位,
|
||||||
|
计算本位和进位,知道没有进位和ab所有位处理完毕
|
||||||
|
*/
|
||||||
|
function f2(a, b) {
|
||||||
|
let i = a.length - 1; // 指针 i 指向 a 的最后一位(最低位)
|
||||||
|
let j = b.length - 1; // 指针 j 指向 b 的最后一位(最低位)
|
||||||
|
let carry = 0; // 初始化进位为 0
|
||||||
|
const res = []; // 用于存储结果的数组(从低位到高位)
|
||||||
|
|
||||||
|
// 循环条件:只要还有位未处理,或还有进位,就继续
|
||||||
|
while (i >= 0 || j >= 0 || carry !== 0) {
|
||||||
|
// 取当前位的值,如果越界则视为 0
|
||||||
|
const bitA = i >= 0 ? +a[i] : 0;
|
||||||
|
const bitB = j >= 0 ? +b[j] : 0;
|
||||||
|
|
||||||
|
// 本位加法:两个二进制位 + 上一轮进位
|
||||||
|
const sum = bitA + bitB + carry;
|
||||||
|
|
||||||
|
// sum % 2 得到当前位的结果(因为二进制满 2 进 1)
|
||||||
|
res.push(sum % 2);
|
||||||
|
|
||||||
|
// 计算下一轮的进位(如果 sum >= 2 则进位为 1)
|
||||||
|
carry = Math.floor(sum / 2);
|
||||||
|
|
||||||
|
// 移动指针,处理更高位
|
||||||
|
i--;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后结果数组是反着存的,需要翻转后拼接成字符串
|
||||||
|
return res.reverse().join('');
|
||||||
|
}
|
38
top-interview-leetcode150/divide/108将有序数组转换为二叉树搜索树.js
Normal file
38
top-interview-leetcode150/divide/108将有序数组转换为二叉树搜索树.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Definition for a binary tree node.
|
||||||
|
* function TreeNode(val, left, right) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.left = (left===undefined ? null : left)
|
||||||
|
* this.right = (right===undefined ? null : right)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {TreeNode}
|
||||||
|
*/
|
||||||
|
const sortedArrayToBST = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function TreeNode(val, left, right) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.left = (left === undefined ? null : left);
|
||||||
|
this.right = (right === undefined ? null : right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
用数组的中间值作为根节点,然后递归的将左半部分和右半部分处理成一颗二叉搜索树,并设置成根节点的左右子树
|
||||||
|
*/
|
||||||
|
function f1(left, right, nums) {
|
||||||
|
// 如果left > right, 说明这个区间没有元素,直接返回null
|
||||||
|
if (left > right) return null;
|
||||||
|
// 计算中间节点的索引
|
||||||
|
const mid = left + Math.floor((right - left) / 2);
|
||||||
|
|
||||||
|
// 创建根节点,或子树的根节点
|
||||||
|
const root = new TreeNode(nums[mid]);
|
||||||
|
// 递归处理左半部分和右半部分
|
||||||
|
root.left = f1(left, mid - 1, nums);
|
||||||
|
root.right = f1(mid + 1, right, nums);
|
||||||
|
return root;
|
||||||
|
}
|
166
top-interview-leetcode150/divide/148排序列表.js
Normal file
166
top-interview-leetcode150/divide/148排序列表.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Definition for singly-linked list.
|
||||||
|
* function ListNode(val, next) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.next = (next===undefined ? null : next)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
function ListNode(val, next) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.next = (next === undefined ? null : next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ListNode} head
|
||||||
|
* @return {ListNode}
|
||||||
|
*/
|
||||||
|
const sortList = function (head) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用归并排序的思想,将链表分成两半,分别对两半进行排序,然后合并两个已排序的链表。
|
||||||
|
*/
|
||||||
|
function f1(head) {
|
||||||
|
if (!head || !head.next) return head;
|
||||||
|
let tail = head;
|
||||||
|
while (tail.next) {
|
||||||
|
tail = tail.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merge(head, tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
将一个链表归并成一个有序列表,具体过程可以参考归并排序,不好描述,但不是很难
|
||||||
|
*/
|
||||||
|
function merge(head, tail) {
|
||||||
|
if (head === tail) return head;
|
||||||
|
|
||||||
|
// 快慢指针计算中间节点
|
||||||
|
let low = head;
|
||||||
|
let fast = head;
|
||||||
|
|
||||||
|
while (fast.next && fast.next.next) {
|
||||||
|
low = low.next;
|
||||||
|
fast = fast.next.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mid = low;
|
||||||
|
// 将链表从中间截断
|
||||||
|
const head2 = mid.next;
|
||||||
|
mid.next = null;
|
||||||
|
|
||||||
|
// 将截断后的两个链表继续进行merge操作,之后将其合并成一个有序的链表返回
|
||||||
|
return mergeList(merge(head, mid), merge(head2, tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
合并两个有序链表
|
||||||
|
*/
|
||||||
|
function mergeList(l1, l2) {
|
||||||
|
// 至少一个为空的情况
|
||||||
|
if (!l1 || !l2) {
|
||||||
|
// 返回其中任意一个不为空的情况
|
||||||
|
return l1 || l2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两个链表都不为空,将两个链表中的所有节点按大小拼接到dummy上,最后返回dummy.next
|
||||||
|
const dummy = new ListNode(0);
|
||||||
|
let cur = dummy;
|
||||||
|
while (l1 && l2) {
|
||||||
|
if (l1.val <= l2.val) {
|
||||||
|
cur.next = l1;
|
||||||
|
l1 = l1.next;
|
||||||
|
} else {
|
||||||
|
cur.next = l2;
|
||||||
|
l2 = l2.next;
|
||||||
|
}
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
// 当一个链表处理完毕,还会右一个列表存在节点,将它拼接到cur.next上
|
||||||
|
cur.next = l1 === null ? l2 : l1;
|
||||||
|
return dummy.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
优化上面的写法,思路一致,但是只传入一个参数head,表示一个链表的头节点
|
||||||
|
*/
|
||||||
|
function f2(head) {
|
||||||
|
// 如果链表为空,或者只有一个节点,直接返回head
|
||||||
|
if (head === null || head.next === null) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过快慢指针寻找中间节点,将链表分成左右两部分
|
||||||
|
let slow = head;
|
||||||
|
let fast = head;
|
||||||
|
let prev = null;
|
||||||
|
while (fast && fast.next) {
|
||||||
|
prev = slow;
|
||||||
|
slow = slow.next;
|
||||||
|
fast = fast.next.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把链表从中间断开
|
||||||
|
prev.next = null;
|
||||||
|
|
||||||
|
const left = f2(head);
|
||||||
|
const right = f2(slow);
|
||||||
|
return mergeList(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用快速排序的思想,将链表分成三部分,left, pivot, right,left表示小于pivot的部分,right表示大于pivot的部分,最后将它们拼接起来。
|
||||||
|
*/
|
||||||
|
function quickSortList(head) {
|
||||||
|
if (!head || !head.next) return head;
|
||||||
|
|
||||||
|
const pivot = head;
|
||||||
|
const leftDummy = new ListNode(0);
|
||||||
|
const rightDummy = new ListNode(0);
|
||||||
|
let leftCur = leftDummy;
|
||||||
|
let rightCur = rightDummy;
|
||||||
|
let cur = head.next;
|
||||||
|
|
||||||
|
// 分组:< pivot 到 left,≥ pivot 到 right
|
||||||
|
while (cur) {
|
||||||
|
if (cur.val < pivot.val) {
|
||||||
|
leftCur.next = cur;
|
||||||
|
leftCur = leftCur.next;
|
||||||
|
} else {
|
||||||
|
rightCur.next = cur;
|
||||||
|
rightCur = rightCur.next;
|
||||||
|
}
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 截断,避免串联成环
|
||||||
|
leftCur.next = null;
|
||||||
|
rightCur.next = null;
|
||||||
|
pivot.next = null; // 断开 pivot 和旧链表的联系
|
||||||
|
|
||||||
|
const leftSorted = quickSortList(leftDummy.next);
|
||||||
|
const rightSorted = quickSortList(rightDummy.next);
|
||||||
|
|
||||||
|
// 拼接三段:leftSorted + pivot + rightSorted
|
||||||
|
return concat(leftSorted, pivot, rightSorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接:将 left + pivot + right 接成一个链表并返回头结点
|
||||||
|
function concat(left, pivot, right) {
|
||||||
|
let head = pivot;
|
||||||
|
|
||||||
|
if (left) {
|
||||||
|
head = left;
|
||||||
|
let tail = left;
|
||||||
|
while (tail.next) {
|
||||||
|
tail = tail.next;
|
||||||
|
}
|
||||||
|
tail.next = pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot.next = right;
|
||||||
|
return head;
|
||||||
|
}
|
112
top-interview-leetcode150/divide/23合并k个升序列表.js
Normal file
112
top-interview-leetcode150/divide/23合并k个升序列表.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Definition for singly-linked list.
|
||||||
|
* function ListNode(val, next) {
|
||||||
|
* this.val = (val===undefined ? 0 : val)
|
||||||
|
* this.next = (next===undefined ? null : next)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {ListNode[]} lists
|
||||||
|
* @return {ListNode}
|
||||||
|
*/
|
||||||
|
const mergeKLists = function (lists) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function ListNode(val, next) {
|
||||||
|
this.val = (val === undefined ? 0 : val);
|
||||||
|
this.next = (next === undefined ? null : next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义一个minIndex变量来记录当前最小头节点的索引。
|
||||||
|
在每次循环中,遍历lists数组,找到当前最小头节点的索引minIndex。
|
||||||
|
如果找到的minIndex为-1,说明没有更多的节点可以合并,跳出循环。
|
||||||
|
如果找到的minIndex不为-1,将当前最小头节点添加到合并后的链表中。
|
||||||
|
然后将lists[minIndex]指向下一个节点,继续下一轮循环。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并k个升序链表
|
||||||
|
* @param {ListNode[]} lists
|
||||||
|
* @return {ListNode}
|
||||||
|
*/
|
||||||
|
function f1(lists) {
|
||||||
|
const dummy = new ListNode(0);
|
||||||
|
let current = dummy;
|
||||||
|
|
||||||
|
// 遍历lists,找到最小的头节点
|
||||||
|
while (true) {
|
||||||
|
let minIndex = -1;
|
||||||
|
|
||||||
|
for (let i = 0; i < lists.length; i++) {
|
||||||
|
if (lists[i] && (minIndex === -1 || lists[i].val < lists[minIndex].val)) {
|
||||||
|
minIndex = i; // 更新最小头节点的索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minIndex === -1) break; // 没有找到最小的头节点
|
||||||
|
|
||||||
|
current.next = lists[minIndex]; // 拼接到dummy节点上
|
||||||
|
current = current.next; // 移动到下一个节点
|
||||||
|
lists[minIndex] = lists[minIndex].next; // 移动到下一个头节点
|
||||||
|
}
|
||||||
|
return dummy.next; // 返回合并后的链表
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义一个ans表示要返回的链表,然后将lists中的链表挨个和它合并,最终返回ans
|
||||||
|
*/
|
||||||
|
function f2(lists) {
|
||||||
|
let ans = null;
|
||||||
|
for (let i = 0; i < lists.length; i++) {
|
||||||
|
ans = mergeLists(ans, lists[i]);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用分治法
|
||||||
|
*/
|
||||||
|
function f3(lists) {
|
||||||
|
return merge(lists, 0, lists.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ListNode[]} lists 链表数组
|
||||||
|
* @param {number} l 要进行合并的左边界
|
||||||
|
* @param {number} r 要进行合并的右边界
|
||||||
|
* merge函数会将l到r范围的链表合并成一个新链表
|
||||||
|
*/
|
||||||
|
function merge(lists, l, r) {
|
||||||
|
// 如果l和r相等,表明这个范围只有一个链表,直接返回
|
||||||
|
if (l === r) return lists[l];
|
||||||
|
if (l > r) return null;
|
||||||
|
// 将l到r的范围拆分成左右两部分,等这两部分merge之后再合并它们
|
||||||
|
const mid = Math.floor((l + r) / 2);
|
||||||
|
|
||||||
|
return mergeLists(merge(lists, l, mid), merge(lists, mid + 1, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeLists(l1, l2) {
|
||||||
|
if (l1 === null || l2 === null) {
|
||||||
|
return l1 === null ? l2 : l1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dummy = new ListNode(0);
|
||||||
|
let cur = dummy;
|
||||||
|
|
||||||
|
while (l1 && l2) {
|
||||||
|
if (l1.val <= l2.val) {
|
||||||
|
cur.next = l1;
|
||||||
|
l1 = l1.next;
|
||||||
|
} else {
|
||||||
|
cur.next = l2;
|
||||||
|
l2 = l2.next;
|
||||||
|
}
|
||||||
|
cur = cur.next; // 移动到下一个节点
|
||||||
|
}
|
||||||
|
|
||||||
|
cur.next = l1 === null ? l2 : l1; // 将剩余的节点拼接到链表后面
|
||||||
|
return dummy.next; // 返回合并后的链表
|
||||||
|
}
|
93
top-interview-leetcode150/divide/427构建四叉树.js
Normal file
93
top-interview-leetcode150/divide/427构建四叉树.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
function _Node(val, isLeaf, topLeft, topRight, bottomLeft, bottomRight) {
|
||||||
|
this.val = val;
|
||||||
|
this.isLeaf = isLeaf;
|
||||||
|
this.topLeft = topLeft;
|
||||||
|
this.topRight = topRight;
|
||||||
|
this.bottomLeft = bottomLeft;
|
||||||
|
this.bottomRight = bottomRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[][]} grid
|
||||||
|
* @return {_Node}
|
||||||
|
*/
|
||||||
|
const construct = function (grid) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用分治算法,我们很容易发现当啊n=1时grid一定可以构建成一个叶子节点,val=当前这个位置表示的值,当n=2时也即是由四个n=1的grid组成的,
|
||||||
|
我们把它们划分好,然后查看这左上,有伤,左下,右下的这四个四叉树节点的值是否都是叶子节点,如果都是再判断它们的值是否相等,如果是叶子节点
|
||||||
|
并且它们的值也相等,就把他们合成一个更大的叶子节点,否则返回这个有叶子节点的四叉树
|
||||||
|
*/
|
||||||
|
function f1(grid) {
|
||||||
|
const n = grid.length;
|
||||||
|
return buildNode(grid, 0, n - 1, 0, n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} grid
|
||||||
|
* @param {*} start
|
||||||
|
* @param {*} end
|
||||||
|
*/
|
||||||
|
function buildNode(grid, rStart, rEnd, cStart, cEnd) {
|
||||||
|
// 如果划分的grid只有一个值,那么这个值就是四叉树的叶子节点
|
||||||
|
if (rStart === rEnd) {
|
||||||
|
return new _Node(grid[rStart][cStart], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 划分四个区域构建子节点
|
||||||
|
const topLeft = buildNode(grid, rStart, rStart + Math.floor((rEnd - rStart) / 2), cStart, cStart + Math.floor((cEnd - cStart) / 2));
|
||||||
|
const topRight = buildNode(grid, rStart, rStart + Math.floor((rEnd - rStart) / 2), cStart + Math.floor((cEnd - cStart) / 2) + 1, cEnd);
|
||||||
|
const bottomLeft = buildNode(grid, rStart + Math.floor((rEnd - rStart) / 2) + 1, rEnd, cStart, cStart + Math.floor((cEnd - cStart) / 2));
|
||||||
|
const bottomRight = buildNode(grid, rStart + Math.floor((rEnd - rStart) / 2) + 1, rEnd, cStart + Math.floor((cEnd - cStart) / 2) + 1, cEnd);
|
||||||
|
|
||||||
|
// 如果四个节点都是叶子节点,并且值相同将它们合并成一个更大的叶子节点
|
||||||
|
if (topLeft.isLeaf && topRight.isLeaf && bottomLeft.isLeaf && bottomRight.isLeaf && topLeft.val === topRight.val && topRight.val === bottomLeft.val && bottomLeft.val === bottomRight.val) {
|
||||||
|
return new _Node(topLeft.val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建一个包含四个节点的非叶子节点
|
||||||
|
return new _Node(1, 0, topLeft, topRight, bottomLeft, bottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
优化分治思路
|
||||||
|
*/
|
||||||
|
function buildNodeBest(grid) {
|
||||||
|
const n = grid.length;
|
||||||
|
|
||||||
|
function dfs(r0, c0, size) {
|
||||||
|
// 判断是否整个区域都是同一个值
|
||||||
|
const val = grid[r0][c0];
|
||||||
|
let same = true;
|
||||||
|
for (let i = r0; i < r0 + size; i++) {
|
||||||
|
for (let j = c0; j < c0 + size; j++) {
|
||||||
|
if (grid[i][j] !== val) {
|
||||||
|
same = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!same) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (same) {
|
||||||
|
return new Node(val === 1, true, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const half = size / 2;
|
||||||
|
return new Node(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
dfs(r0, c0, half), // topLeft
|
||||||
|
dfs(r0, c0 + half, half), // topRight
|
||||||
|
dfs(r0 + half, c0, half), // bottomLeft
|
||||||
|
dfs(r0 + half, c0 + half, half), // bottomRight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dfs(0, 0, n);
|
||||||
|
}
|
79
top-interview-leetcode150/double-point/11盛水最多的容器.js
Normal file
79
top-interview-leetcode150/double-point/11盛水最多的容器.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/container-with-most-water/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} height
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxArea = function (height) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
暴力解法,组合所有的可能,得出最大的面积,会超时
|
||||||
|
*/
|
||||||
|
function f1(height) {
|
||||||
|
const n = height.length;
|
||||||
|
let mArea = -Infinity;
|
||||||
|
let cArea;// 计算的面积
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = i + 1; j < n; j++) {
|
||||||
|
cArea = Math.min(height[i], height[j]) * (j - i);
|
||||||
|
if (cArea > mArea) mArea = cArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用双指针,定义一个left和一个right,首先判断height[left] 和 height[right]两者直接谁底,因为盛水量是底的那一端决定的,
|
||||||
|
计算当前值并且使用mArea保存起来这个值,之后继续移动地的那一侧,假设height[right]比height[left]底,那么就right--,然后继续
|
||||||
|
比较,反之left++,循环条件为left < right
|
||||||
|
*/
|
||||||
|
function f2(height) {
|
||||||
|
let mArea = -Infinity; // 能盛水的最大面积
|
||||||
|
let cArea; // 缓存计算的面积,避免重复计算
|
||||||
|
let left = 0;
|
||||||
|
let right = height.length - 1;
|
||||||
|
while (left < right) {
|
||||||
|
cArea = Math.min(height[left], height[right]) * [right - left];
|
||||||
|
if (cArea > mArea) mArea = cArea;
|
||||||
|
if (height[right] > height[left]) { // 如果右端比左端高,移动底的那一段
|
||||||
|
left++;
|
||||||
|
} else {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
优化:假设当前右边比左边高,那么我们应该移动左边,面积不仅仅是由两端高度较低的那一端决定的,同时也由right - left这个底部决定,
|
||||||
|
在移动的过程中,这个底部一定是不断变小的,所以当这个决定性的最短边小于之前的最短边,那就没必要计算了,100%小于之前的结果,所以
|
||||||
|
应该定义一个记录有效边高度的变量,用来跳过不必要的判断,以此提高小效率
|
||||||
|
*/
|
||||||
|
function f2(height) {
|
||||||
|
let mArea = -Infinity; // 能盛水的最大面积
|
||||||
|
let cArea; // 缓存计算的面积,避免重复计算
|
||||||
|
let left = 0;
|
||||||
|
let right = height.length - 1;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
cArea = Math.min(height[left], height[right]) * (right - left);
|
||||||
|
if (cArea > mArea) mArea = cArea;
|
||||||
|
|
||||||
|
if (height[left] < height[right]) {
|
||||||
|
let prevLeft = height[left];
|
||||||
|
// 增加跳过无效left的逻辑
|
||||||
|
while (left < right && height[left] <= prevLeft) {
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let prevRight = height[right];
|
||||||
|
// 增加跳过无效right的逻辑
|
||||||
|
while (left < right && height[right] <= prevRight) {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mArea;
|
||||||
|
}
|
||||||
|
|
60
top-interview-leetcode150/double-point/125验证回文字符串.js
Normal file
60
top-interview-leetcode150/double-point/125验证回文字符串.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/valid-palindrome/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} s
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isPalindrome = function (s) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
把符合要求的字符存入数组中,之后判断反转的数组和原数组是否一致,如果一致就是回文
|
||||||
|
*/
|
||||||
|
function f1(s) {
|
||||||
|
// 创建一个新的数组,用来存储有效字符(字母和数字)
|
||||||
|
const filteredChars = [];
|
||||||
|
|
||||||
|
// 遍历原字符串,将符合要求的字符存入新数组并转为小写
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
const char = s[i];
|
||||||
|
if (/[a-zA-Z0-9]/.test(char)) {
|
||||||
|
filteredChars.push(char.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将新数组转换为字符串,并与反转后的字符串比较
|
||||||
|
const reversedStr = filteredChars.slice().reverse().join('');
|
||||||
|
return filteredChars.join('') === reversedStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用双指针来判断是否符合要求,首先创建左右两个指针,判断当前字符是不是有效字符,如果不是就跳过,然后比较s[left] 和 s[right]
|
||||||
|
如果不相等直接返回false,如果循环遍历完毕都没问题,那么就是一个回文字符串,
|
||||||
|
思考:奇偶会影响判断吗?
|
||||||
|
*/
|
||||||
|
function f2(s) {
|
||||||
|
let left = 0;
|
||||||
|
let right = s.length - 1;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
// 跳过非字母数字字符
|
||||||
|
if (!(/[a-zA-Z0-9]/.test(s[left]))) {
|
||||||
|
left++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(/[a-zA-Z0-9]/.test(s[right]))) {
|
||||||
|
right--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较字符是否相等,忽略大小写
|
||||||
|
if (s[left].toLowerCase() !== s[right].toLowerCase()) {
|
||||||
|
return false; // 如果不相等,返回false
|
||||||
|
}
|
||||||
|
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 如果完全匹配,返回true
|
||||||
|
}
|
85
top-interview-leetcode150/double-point/15三数之和.js
Normal file
85
top-interview-leetcode150/double-point/15三数之和.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/3sum/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number[][]}
|
||||||
|
*/
|
||||||
|
const threeSum = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
先对数组排序,然后遍历数组,如果遍历的当前元素大于零则表示后面的元素不可能组合在一起和为0,如果当前元素小于零那么就对剩余的所有元素
|
||||||
|
使用两数之和的方法寻找target=当前元素的相反数,如果找到就返回这几个元素,然后遍历下一个元素
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
nums.sort((a, b) => a - b); // 先对数组排序
|
||||||
|
const result = []; // 结果数组
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
// 如果当前元素大于零直接结束循环,后面的查找没意义
|
||||||
|
if (nums[i] > 0) break;
|
||||||
|
// 如果当前元素等于前面的元素也跳过
|
||||||
|
if (i > 0 && nums[i] === nums[i - 1]) continue;
|
||||||
|
// 按照两数之和的方法寻找nums[i] 的相反数,也就是target == -nums[i]
|
||||||
|
let left = i + 1;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
while (left < right) {
|
||||||
|
const target = nums[left] + nums[right];
|
||||||
|
if (target === -nums[i]) {
|
||||||
|
result.push([nums[i], nums[left], nums[right]]); // 把查到的结果放入结果集中
|
||||||
|
// 继续寻找往下找
|
||||||
|
// 跳过重复元素
|
||||||
|
while (left < right && nums[left] === nums[left + 1]) left++;
|
||||||
|
while (left < right && nums[right] === nums[right - 1]) right--;
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (target < -nums[i]) {
|
||||||
|
left++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (target > -nums[i]) {
|
||||||
|
right--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用ifelse 减少判断逻辑
|
||||||
|
*/
|
||||||
|
function f2(nums) {
|
||||||
|
nums.sort((a, b) => a - b); // 先对数组排序
|
||||||
|
const result = []; // 结果数组
|
||||||
|
|
||||||
|
for (let i = 0; i < nums.length - 2; i++) {
|
||||||
|
// 如果当前元素大于零直接结束循环,后面的查找没意义
|
||||||
|
if (nums[i] > 0) break;
|
||||||
|
|
||||||
|
// 如果当前元素等于前面的元素也跳过
|
||||||
|
if (i > 0 && nums[i] === nums[i - 1]) continue;
|
||||||
|
|
||||||
|
let left = i + 1;
|
||||||
|
let right = nums.length - 1;
|
||||||
|
while (left < right) {
|
||||||
|
const sum = nums[i] + nums[left] + nums[right];
|
||||||
|
|
||||||
|
if (sum === 0) {
|
||||||
|
result.push([nums[i], nums[left], nums[right]]);
|
||||||
|
// 跳过重复元素
|
||||||
|
while (left < right && nums[left] === nums[left + 1]) left++;
|
||||||
|
while (left < right && nums[right] === nums[right - 1]) right--;
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
} else if (sum < 0) {
|
||||||
|
left++;
|
||||||
|
} else {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
27
top-interview-leetcode150/double-point/167两数之和II.js
Normal file
27
top-interview-leetcode150/double-point/167两数之和II.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} numbers
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const twoSum = function (numbers, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用数组有序的特性,使用首位指针,不断的缩小范围找到可能的值
|
||||||
|
*/
|
||||||
|
function f1(numbers, target) {
|
||||||
|
let left = 0;
|
||||||
|
let right = numbers.length - 1;
|
||||||
|
const temp = Infinity;
|
||||||
|
while (left < right) {
|
||||||
|
const temp = numbers[left] + numbers[right];
|
||||||
|
if (temp == target) {
|
||||||
|
return [left + 1, right + 1];
|
||||||
|
} if (temp > target) {
|
||||||
|
right--;
|
||||||
|
} else { left++; }
|
||||||
|
}
|
||||||
|
return []; // 没有找到,不过这个题目测试用例保证一定有解
|
||||||
|
}
|
39
top-interview-leetcode150/double-point/392判断子序列.js
Normal file
39
top-interview-leetcode150/double-point/392判断子序列.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/valid-palindrome/description/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string} t
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isSubsequence = function (s, t) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接遍历,直接遍历t 检查当前s的第一个字符是否在遍历的过程中找到了,如果找到了,就指向s的第二个字符,继续找,找到全部找到 返回true
|
||||||
|
*/
|
||||||
|
function f1(s, t) {
|
||||||
|
if (s === t) return true;
|
||||||
|
let i = 0; // 指向s中要查找的字符
|
||||||
|
for (const char of t) {
|
||||||
|
if (char === s[i]) i++;
|
||||||
|
if (i === s.length) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用传统for提高效率
|
||||||
|
*/
|
||||||
|
function f2(s, t) {
|
||||||
|
if (s === t) return true;
|
||||||
|
let i = 0; // 指向s中要查找的字符
|
||||||
|
let j = 0; // 指向t中当前遍历的字符
|
||||||
|
for(let j = 0;j<t.length,t++) {
|
||||||
|
|
||||||
|
}
|
||||||
|
for (const char of t) {
|
||||||
|
if (char === s[i]) i++;
|
||||||
|
if (i === s.length) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
74
top-interview-leetcode150/dynamic-planning/120三角形最小路径和.js
Normal file
74
top-interview-leetcode150/dynamic-planning/120三角形最小路径和.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[][]} triangle
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const minimumTotal = function (triangle) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:
|
||||||
|
这是一个典型的「从上到下」的动态规划问题,三角形的每个位置只能由上一层的相邻两个位置转移而来。
|
||||||
|
|
||||||
|
定义状态:
|
||||||
|
- dp[i][j] 表示从顶点走到位置 (i, j) 的最小路径和
|
||||||
|
|
||||||
|
状态转移方程:
|
||||||
|
- 左边界:dp[i][0] = dp[i-1][0] + triangle[i][0]
|
||||||
|
- 中间:dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
|
||||||
|
- 右边界:dp[i][i] = dp[i-1][i-1] + triangle[i][i]
|
||||||
|
|
||||||
|
初始化:
|
||||||
|
- dp[0][0] = triangle[0][0]
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
- 最后一层中的最小值,即 Math.min(...dp[n - 1])
|
||||||
|
*/
|
||||||
|
function f1(triangle) {
|
||||||
|
const n = triangle.length;
|
||||||
|
const dp = Array.from({ length: n }, (_, i) => Array(i + 1).fill(0));
|
||||||
|
dp[0][0] = triangle[0][0];
|
||||||
|
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
const len = triangle[i].length;
|
||||||
|
|
||||||
|
// 左边界,只能从上一行第一个元素来
|
||||||
|
dp[i][0] = dp[i - 1][0] + triangle[i][0];
|
||||||
|
|
||||||
|
// 中间部分,来自上一行的[j - 1] 和 [j]
|
||||||
|
for (let j = 1; j < len - 1; j++) {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右边界,只能从上一行最后一个元素来
|
||||||
|
dp[i][len - 1] = dp[i - 1][len - 2] + triangle[i][len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(...dp[n - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
不重复计算len - 1
|
||||||
|
*/
|
||||||
|
function f2(triangle) {
|
||||||
|
const n = triangle.length;
|
||||||
|
const dp = Array.from({ length: n }, (_, i) => Array(i + 1).fill(0));
|
||||||
|
dp[0][0] = triangle[0][0];
|
||||||
|
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
const len = triangle[i].length;
|
||||||
|
const last = len - 1;
|
||||||
|
// 左边界,只能从上一行第一个元素来
|
||||||
|
dp[i][0] = dp[i - 1][0] + triangle[i][0];
|
||||||
|
|
||||||
|
// 中间部分,来自上一行的[j - 1] 和 [j]
|
||||||
|
for (let j = 1; j < last; j++) {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右边界,只能从上一行最后一个元素来
|
||||||
|
dp[i][last] = dp[i - 1][last - 1] + triangle[i][last];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(...dp[n - 1]);
|
||||||
|
}
|
95
top-interview-leetcode150/dynamic-planning/139单词拆分.js
Normal file
95
top-interview-leetcode150/dynamic-planning/139单词拆分.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string[]} wordDict
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const wordBreak = function (s, wordDict) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用回溯法,不停的尝试wordict中的单词是否可以构造s
|
||||||
|
*/
|
||||||
|
function f1(s, wordDict) {
|
||||||
|
let result = false;
|
||||||
|
const n = s.length;
|
||||||
|
|
||||||
|
const backtrack = (start) => {
|
||||||
|
if (start === n) {
|
||||||
|
result = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) return; // 剪枝:一旦找到了就不再继续递归
|
||||||
|
|
||||||
|
for (const word of wordDict) {
|
||||||
|
const len = word.length;
|
||||||
|
if (start + len > n) continue;
|
||||||
|
|
||||||
|
let matched = true;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
if (s[start + i] !== word[i]) {
|
||||||
|
matched = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
backtrack(start + len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backtrack(0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用动态规划,定义dp[i]表示s中的前i个字符是否能由wordDict组成,dp[0]=true,前i个字符是否可以由
|
||||||
|
wordDict组成,可以从第i个字符开始往前数,如果和wordDict中的某一个字符匹配,并且 dp[i-word.length]==true
|
||||||
|
那么 dp[i]===true,由dp[i]的定义可知dp[s.length]表示s的前n个字符(整个字符串)是否可以由wordDict中
|
||||||
|
的字符组成
|
||||||
|
*/
|
||||||
|
function f2(s, wordDict) {
|
||||||
|
// 定义dp表,dp[i] 表示s中的前i个字符是否能由wordDict组成
|
||||||
|
const dp = Array(s.length + 1).fill(false);
|
||||||
|
dp[0] = true;
|
||||||
|
for (let i = 1; i < dp.length; i++) {
|
||||||
|
// 检测末尾是否匹配
|
||||||
|
let cur = i - 1;
|
||||||
|
let flag = true;
|
||||||
|
for (const word of wordDict) {
|
||||||
|
for (let j = word.length - 1; j >= 0; j--, cur--) {
|
||||||
|
if (s[cur] !== s[j] || cur < 0) {
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i >= word.length && flag && dp[i - word.length]) {
|
||||||
|
dp[i] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
f2优化写法
|
||||||
|
*/
|
||||||
|
function f3(s, wordDict) {
|
||||||
|
const dp = Array(s.length + 1).fill(false);
|
||||||
|
dp[0] = true;
|
||||||
|
|
||||||
|
for (let i = 1; i <= s.length; i++) {
|
||||||
|
for (const word of wordDict) {
|
||||||
|
const len = word.length;
|
||||||
|
if (i >= len && dp[i - len] && s.slice(i - len, i) === word) {
|
||||||
|
dp[i] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s.length];
|
||||||
|
}
|
38
top-interview-leetcode150/dynamic-planning/198打家劫舍.js
Normal file
38
top-interview-leetcode150/dynamic-planning/198打家劫舍.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const rob = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
设dp[i]为偷取第i家时获取的最大利润,那么dp[i]可以由两种情况得来
|
||||||
|
1.偷第i家,如果偷第i家,那么最大利润为 dp[i] = dp[i-2] + 第i家拥有的金额
|
||||||
|
2.不偷第i家,如果不偷第i家,那么最大利润为 dp[i-1]的最大利润
|
||||||
|
那么只需比较上面两种情况谁大,就是dp[i]的值,即dp[i] = Max(dp[i-1], dp[i-2] + nums[i])
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
const dp = Array(n);
|
||||||
|
dp[0] = nums[0];
|
||||||
|
dp[1] = Math.max(nums[0], nums[1]);
|
||||||
|
for (let i = 2; i < dp.length; i++) {
|
||||||
|
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
|
||||||
|
}
|
||||||
|
return dp[n - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用双指针优化
|
||||||
|
*/
|
||||||
|
function f2(nums) {
|
||||||
|
let prev = 0;
|
||||||
|
let curr = 0;
|
||||||
|
for (const num of nums) {
|
||||||
|
const temp = Math.max(curr, prev + num);
|
||||||
|
prev = curr;
|
||||||
|
curr = temp;
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
30
top-interview-leetcode150/dynamic-planning/300最长递增子序列.js
Normal file
30
top-interview-leetcode150/dynamic-planning/300最长递增子序列.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const lengthOfLIS = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
设dp[i]为以nums[i]结尾的递增子序列的最长长度,那么dp[i],nums[i]可以接在nums[0-i)中比它小的元素
|
||||||
|
组成子序列,求那个结果最长的就是dp[i],即dp[i] = Max(nums[0-i)) +1
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
const dp = Array(n).fill(1);
|
||||||
|
dp[0] = 1;
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
for (let j = 0; j < i; j++) {
|
||||||
|
if (nums[j] < nums[i]) {
|
||||||
|
// 更新dp[i]为最长子序列
|
||||||
|
dp[i] = Math.max(dp[j] + 1, dp[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(dp);
|
||||||
|
|
||||||
|
return dp[n - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
f1([1, 3, 6, 7, 9, 4, 10, 5, 6]);
|
51
top-interview-leetcode150/dynamic-planning/332零钱兑换.js
Normal file
51
top-interview-leetcode150/dynamic-planning/332零钱兑换.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} coins
|
||||||
|
* @param {number} amount
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const coinChange = function (coins, amount) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个问题可以抽象成一个完全背包问题,物品的价值就是硬币的面值,物品的重量也是硬币的面值,定义dp[i][j]表示0-i类型的硬币
|
||||||
|
中,组合成j所需硬币的数量
|
||||||
|
*/
|
||||||
|
function f1(coins, amount) {
|
||||||
|
const m = coins.length;
|
||||||
|
const n = amount;
|
||||||
|
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(Infinity));
|
||||||
|
|
||||||
|
// 初始化:金额为0,硬币数为0
|
||||||
|
for (let i = 0; i <= m; i++) {
|
||||||
|
dp[i][0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
const coin = coins[i - 1];
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
if (j < coin) {
|
||||||
|
dp[i][j] = dp[i - 1][j]; // 当前硬币无法选,用上一行的结果
|
||||||
|
} else {
|
||||||
|
// 可以选当前硬币(完全背包,不是 i-1)
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coin] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m][n] === Infinity ? -1 : dp[m][n];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上面的代码可以使用一维dp来实现,dp[i]表示找i元需要最少的硬币
|
||||||
|
function coinChange(coins, amount) {
|
||||||
|
const dp = Array(amount + 1).fill(Infinity);
|
||||||
|
dp[0] = 0; // 金额为 0 时需要 0 个硬币
|
||||||
|
|
||||||
|
for (let coin of coins) {
|
||||||
|
for (let j = coin; j <= amount; j++) {
|
||||||
|
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[amount] === Infinity ? -1 : dp[amount];
|
||||||
|
}
|
41
top-interview-leetcode150/dynamic-planning/70爬楼梯.js
Normal file
41
top-interview-leetcode150/dynamic-planning/70爬楼梯.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const climbStairs = function (n) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目就是求斐波那契数列的现实抽象,我们的最终目的是跳到第n个台阶,而每一次要么跳一步,要么跳两步,
|
||||||
|
所以智能由n-1个台阶跳一步上来,或者由n-2个台阶跳两步上来,设dp[i]为跳转到这个台阶可能的方法,那么
|
||||||
|
dp[i] = dp[i-1] + dp[i-2]
|
||||||
|
*/
|
||||||
|
function f1(n) {
|
||||||
|
// 定义dp数组
|
||||||
|
const dp = Array(n + 1);
|
||||||
|
dp[1] = 1;
|
||||||
|
dp[2] = 2;
|
||||||
|
for (let i = 3; i <= n; i++) {
|
||||||
|
dp[i] = dp[i - 1] + dp[i - 2];
|
||||||
|
}
|
||||||
|
return dp[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
通过观察上面的代码可以知道,dp[i]的结果只由它的前一个位置和前前个位置有关,所以定义两个变量first和second来
|
||||||
|
保存这两个值
|
||||||
|
*/
|
||||||
|
function f2(n) {
|
||||||
|
if (n < 3) return n;
|
||||||
|
// 定义跳到前两个台阶可能的步数
|
||||||
|
let first = 1;
|
||||||
|
let second = 2;
|
||||||
|
let result = 1;
|
||||||
|
for (let i = 3; i <= n; i++) {
|
||||||
|
result = first + second;
|
||||||
|
first = second;
|
||||||
|
second = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
123
top-interview-leetcode150/graph/130被围绕的区域.js
Normal file
123
top-interview-leetcode150/graph/130被围绕的区域.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @param {character[][]} board
|
||||||
|
* @return {void} Do not return anything, modify board in-place instead.
|
||||||
|
*/
|
||||||
|
const solve = function (board) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
题目要求我们把被X围绕的O区域修改成X,所以只需要找出所有没有被X围绕的O区域,把这一部分区域修改成A,最后遍历整个矩阵,把所有为O的区域
|
||||||
|
修改成X,所有为A的区域修改成O,所有在边上的O,及其相邻的O一定是没有被包围的,所以我们只需要遍历四条边上的O把它们修改成A即可,这里使用
|
||||||
|
dfs
|
||||||
|
*/
|
||||||
|
function f1(board) {
|
||||||
|
// 如果矩阵为空(虽然测试用例保证 1<=m,n)
|
||||||
|
if (board.length === 0 || board[0].length === 0) return;
|
||||||
|
|
||||||
|
// 获取矩阵的行m和列n,用于后续遍历
|
||||||
|
const m = board.length;
|
||||||
|
const n = board[0].length;
|
||||||
|
|
||||||
|
// 遍历矩阵的第一列和最后一列
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
dfs(i, 0);
|
||||||
|
dfs(i, n - 1);
|
||||||
|
}
|
||||||
|
// 遍历矩阵的第一行和最后一行
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
dfs(1, j);
|
||||||
|
dfs(m - 1, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有矩阵,将O修改成X,将A修改成O
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
if (board[i][j] === 'O') board[i][j] = 'X';
|
||||||
|
if (board[i][j] === 'A') board[i][j] = 'O';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
dfs遍历,将O修改为A
|
||||||
|
注意:board直接找外部作用域,无需传递
|
||||||
|
*/
|
||||||
|
let dfs = (x, y) => {
|
||||||
|
// 如果越界,或者board[x][y] !== 'O'直接return
|
||||||
|
if (x < 0 || y < 0 || x === m || y === n || board[x][y] !== 'O') return;
|
||||||
|
board[x][y] = 'A'; // 将O修改成A
|
||||||
|
// 递归处理四周的O
|
||||||
|
dfs(board, x - 1, y);
|
||||||
|
dfs(board, x, y - 1);
|
||||||
|
dfs(board, x + 1, y);
|
||||||
|
dfs(board, x, y + 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路和上面一致,使用广度优先遍历BFS
|
||||||
|
*/
|
||||||
|
function f2(board) {
|
||||||
|
// 定义查找偏移量
|
||||||
|
const dx = [1, -1, 0, 0];
|
||||||
|
const dy = [0, 0, 1, -1];
|
||||||
|
|
||||||
|
// 如果矩阵为空(虽然测试用例保证 1<=m,n)
|
||||||
|
if (board.length === 0 || board[0].length === 0) return;
|
||||||
|
|
||||||
|
// 获取矩阵的行m和列n,用于后续遍历
|
||||||
|
const m = board.length;
|
||||||
|
const n = board[0].length;
|
||||||
|
|
||||||
|
const queue = []; // 广度遍历的队列,是一个二维数组,储存下标[row, col] 表示几行几列
|
||||||
|
|
||||||
|
// 遍历第一行和最后一行,如果发现O,就将他修改成A并且将它四周的位置压入队列,继续处理
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (board[0][i] === 'O') {
|
||||||
|
board[0][i] = 'A';
|
||||||
|
queue.push([0, i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (board[m - 1][i] === 'O') {
|
||||||
|
board[m - 1][i] = 'A';
|
||||||
|
queue.push([m - 1, i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理第一列和最后一列,这里需要注意四个角落的元素可以不处理,为了统一,这一我就不做处理,对结果不影响
|
||||||
|
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
if (board[i][0] === 'O') {
|
||||||
|
board[i][0] = 'A';
|
||||||
|
queue.push([i, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (board[i][n - 1] === 'O') {
|
||||||
|
board[i][n - 1] = 'A';
|
||||||
|
queue.push([i, n - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始广度遍历
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const [x, y] = queue.pop(); // 获取位置坐标
|
||||||
|
|
||||||
|
// 通过偏移量寻找四周为O的位置,将其修改为A,然后将其压入栈中,继续先四周寻找
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const mx = x + dx[i];
|
||||||
|
const my = y + dy[i];
|
||||||
|
// 如果越界 或 不等于O直接跳过
|
||||||
|
if (mx < 0 || mx >= m || y < 0 || my >= n || board[mx][my] !== 'O') continue;
|
||||||
|
board[mx][my] = 'A';
|
||||||
|
queue.push([mx, my]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有矩阵,将O修改成X,将A修改成O
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
if (board[i][j] === 'O') board[i][j] = 'X';
|
||||||
|
if (board[i][j] === 'A') board[i][j] = 'O';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
top-interview-leetcode150/graph/133克隆图.js
Normal file
85
top-interview-leetcode150/graph/133克隆图.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* // Definition for a _Node.
|
||||||
|
* function _Node(val, neighbors) {
|
||||||
|
* this.val = val === undefined ? 0 : val;
|
||||||
|
* this.neighbors = neighbors === undefined ? [] : neighbors;
|
||||||
|
* };
|
||||||
|
* https://leetcode.cn/problems/clone-graph/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {_Node} node
|
||||||
|
* @return {_Node}
|
||||||
|
*/
|
||||||
|
const cloneGraph = function (node) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-underscore-dangle, no-unused-vars
|
||||||
|
function _Node(val, neighbors) {
|
||||||
|
this.val = val === undefined ? 0 : val;
|
||||||
|
this.neighbors = neighbors === undefined ? [] : neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
只给了我们一个连通图的一个节点,叫我们克隆整个图,根据两图图的定义,从这个节点出发我们能到达途中的任意一个节点(顶点),
|
||||||
|
所以我们只需要遍历所有的neighbor,然后顺着邻居继续遍历邻居即可遍历整个图,但是这里有一个问题,就是如果通过邻居到达了
|
||||||
|
自己,或者邻居的邻居,是自己的邻居,就会发生循环,这里就遇到使用一种数据结果来缓存已经遍历过的邻居,当再次遇到它时跳过就行。
|
||||||
|
|
||||||
|
抽象过程:你需要统计小王家人,你只认识小王,于是题通过小王认识了它的爸爸,妈妈,把他们加入到统计表,然后小王的爸爸向你介绍了
|
||||||
|
它的妹妹,同时小王向你介绍了它的姑姑,但是你发现这两个人是同一个人,所以你只统计了一次,最后你就获得了所有和小王有关的人。
|
||||||
|
*/
|
||||||
|
function f1(node) {
|
||||||
|
const visited = new Map(); // 储存原节点,和克隆节点的映射
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} node 需要克隆的节点
|
||||||
|
* @returns 返回克隆的节点
|
||||||
|
*/
|
||||||
|
function clone(node) {
|
||||||
|
if (!node) return node;
|
||||||
|
|
||||||
|
// 如果这个节点之前克隆过,直接返回其克隆节点
|
||||||
|
if (visited.has(node)) return visited.get(node);
|
||||||
|
|
||||||
|
// 创建这个节点的克隆节点
|
||||||
|
const cloneNode = new _Node(node.val, []);
|
||||||
|
|
||||||
|
// node与cloneNode建立映射
|
||||||
|
visited.set(node, cloneNode);
|
||||||
|
|
||||||
|
// 为克隆节点克隆邻居节点
|
||||||
|
for (const neighbor of node.neighbors) {
|
||||||
|
cloneNode.neighbors.push(clone(neighbor));
|
||||||
|
}
|
||||||
|
return cloneNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路和上面一致但是使用广度优先遍历
|
||||||
|
*/
|
||||||
|
function f2(node) {
|
||||||
|
if (!node) return node;
|
||||||
|
|
||||||
|
const visited = new Map();
|
||||||
|
const queue = [node]; // 用于广度遍历
|
||||||
|
const cNode = new _Node(node.val, []);
|
||||||
|
visited.set(node, cNode); // 克隆第一个节点,将其存入visited
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const n = queue.shift();
|
||||||
|
// 遍历这个节点的所有邻居,如果没有访问过将其clone,并加入visited
|
||||||
|
for (const neighbor of n.neighbors) {
|
||||||
|
if (!visited.has(neighbor)) {
|
||||||
|
visited.set(neighbor, new _Node(neighbor.val, []));
|
||||||
|
queue.push(neighbor);
|
||||||
|
}
|
||||||
|
visited.get(n).neighbors.push(visited.get(neighbor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cNode;
|
||||||
|
}
|
116
top-interview-leetcode150/graph/200岛屿的数量.js
Normal file
116
top-interview-leetcode150/graph/200岛屿的数量.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* @param {character[][]} grid
|
||||||
|
* @return {number}
|
||||||
|
* https://leetcode.cn/problems/number-of-islands/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
*/
|
||||||
|
const numIslands = function (grid) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用深度优先遍历,在character[][]这个二维数组中,我们认为相邻的‘1’之间都有一条边,首先遍历这个二维数组,从遇到的第一个为1的位置,递归
|
||||||
|
的开始向四周查找其他位置是否也为1,如果是的话就继续寻找,并且把找过的位置设置为0,计数加一即可,先当与图中有几个连通分量
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(grid) {
|
||||||
|
/*
|
||||||
|
递归函数向grid[r][c]四周继续递归寻找为1的位置,并把它设置成0,当整个为1位置都为0,表示整个岛屿的小时
|
||||||
|
*/
|
||||||
|
const dfs = (grid, r, c) => {
|
||||||
|
// 如果r,c超出边界,或者grid[r][c] === 0 表示岛屿的边界结束
|
||||||
|
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] === '0') return;
|
||||||
|
// 将当前位置设置为0,表示已经访问过
|
||||||
|
grid[r][c] = '0';
|
||||||
|
// 递归的查找四周
|
||||||
|
dfs(grid, r - 1, c); // 上
|
||||||
|
dfs(grid, r, c + 1); // 右
|
||||||
|
dfs(grid, r + 1, c); // 下
|
||||||
|
dfs(grid, r, c - 1); // 左
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果邻接矩阵为空,或者没有任何数据,直接返回0
|
||||||
|
if (!grid || grid.length === 0) return 0;
|
||||||
|
let nr = grid.length; // 获取行的个数
|
||||||
|
let nc = grid[0].length; // 获取列的个数
|
||||||
|
let lands = 0; // 岛屿数量
|
||||||
|
|
||||||
|
for (let r = 0; r < nr; ++r) {
|
||||||
|
for (let c = 0; c < nc; ++c) {
|
||||||
|
if (grid[r][c] === '1') { // 发现陆地,必定有一座岛,把相邻的陆地全部清空
|
||||||
|
lands++;
|
||||||
|
dfs(grid, r, c); // 从当前点开始DFS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用BFS,思路和DFS一致
|
||||||
|
*/
|
||||||
|
function f2(grid) {
|
||||||
|
// 获取网格的行数
|
||||||
|
const nr = grid.length;
|
||||||
|
if (nr === 0) return 0; // 如果网格为空,返回0岛屿
|
||||||
|
// 获取网格的列数
|
||||||
|
const nc = grid[0].length;
|
||||||
|
|
||||||
|
// 记录岛屿的数量
|
||||||
|
let num_islands = 0;
|
||||||
|
|
||||||
|
// 遍历每个格子
|
||||||
|
for (let r = 0; r < nr; r++) {
|
||||||
|
for (let c = 0; c < nc; c++) {
|
||||||
|
// 如果当前格子是陆地('1'),说明找到了一个新的岛屿
|
||||||
|
if (grid[r][c] === '1') {
|
||||||
|
// 增加岛屿数量
|
||||||
|
num_islands++;
|
||||||
|
// 将当前格子标记为已访问,防止再次访问
|
||||||
|
grid[r][c] = '0';
|
||||||
|
|
||||||
|
// 创建一个队列来进行BFS
|
||||||
|
const neighbors = [];
|
||||||
|
neighbors.push([r, c]); // 将当前岛屿的起始点入队列
|
||||||
|
|
||||||
|
// 进行BFS遍历岛屿中的所有陆地
|
||||||
|
while (neighbors.length > 0) {
|
||||||
|
// 获取队列的第一个元素
|
||||||
|
const [row, col] = neighbors.shift();
|
||||||
|
|
||||||
|
// 检查上下左右四个方向的相邻格子
|
||||||
|
// 上
|
||||||
|
if (row - 1 >= 0 && grid[row - 1][col] === '1') {
|
||||||
|
neighbors.push([row - 1, col]);
|
||||||
|
grid[row - 1][col] = '0'; // 将相邻陆地标记为水
|
||||||
|
}
|
||||||
|
// 下
|
||||||
|
if (row + 1 < nr && grid[row + 1][col] === '1') {
|
||||||
|
neighbors.push([row + 1, col]);
|
||||||
|
grid[row + 1][col] = '0'; // 将相邻陆地标记为水
|
||||||
|
}
|
||||||
|
// 左
|
||||||
|
if (col - 1 >= 0 && grid[row][col - 1] === '1') {
|
||||||
|
neighbors.push([row, col - 1]);
|
||||||
|
grid[row][col - 1] = '0'; // 将相邻陆地标记为水
|
||||||
|
}
|
||||||
|
// 右
|
||||||
|
if (col + 1 < nc && grid[row][col + 1] === '1') {
|
||||||
|
neighbors.push([row, col + 1]);
|
||||||
|
grid[row][col + 1] = '0'; // 将相邻陆地标记为水
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回岛屿的数量
|
||||||
|
return num_islands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用并查集,矩阵里面所有的‘1’都认为是一个小岛屿,用一个一维数组parent表示并查集,以 grid[i][j] === '1' 这个位置为例,那么这个
|
||||||
|
位置在并查集中的下标就是parent[i*col+j] col表示矩阵的列数,而parent[i * col + j]表示的是它的父节点,默认指向自己,定义一个遍历count
|
||||||
|
记录岛屿的数量,
|
||||||
|
*/
|
||||||
|
// TODO:
|
42
top-interview-leetcode150/graph/207课程表.js
Normal file
42
top-interview-leetcode150/graph/207课程表.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* @param {number} numCourses
|
||||||
|
* @param {number[][]} prerequisites
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const canFinish = function (numCourses, prerequisites) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
这个题目是一个典型的拓扑排序问题,只需要判断拓扑排序最终能否输出所有元素,如果可以则表明是一个有向无环图,符合
|
||||||
|
题目要求,否则不符合
|
||||||
|
*/
|
||||||
|
function f1(numCourses, prerequisites) {
|
||||||
|
const graph = Array.from({ length: numCourses }, () => []);
|
||||||
|
const inDegree = Array(numCourses).fill(0);
|
||||||
|
|
||||||
|
// 构建图和入度表
|
||||||
|
for (const [a, b] of prerequisites) {
|
||||||
|
graph[b].push(a);
|
||||||
|
inDegree[a]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找出入度为零的顶点,将其加入队列
|
||||||
|
const queue = [];
|
||||||
|
for (let i = 0; i < numCourses; i++) {
|
||||||
|
if (inDegree[i] === 0) queue.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从队列中输出所有入度为零的顶点,如果输出的顶点数量和课程数量一致,表明可以拓扑排序,否则有环
|
||||||
|
let count = 0;
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const node = queue.shift();
|
||||||
|
count++;
|
||||||
|
for (const neighbor of graph[node]) {
|
||||||
|
inDegree[neighbor]--;
|
||||||
|
if (inDegree[neighbor] === 0) queue.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count === numCourses;
|
||||||
|
}
|
94
top-interview-leetcode150/graph/210课程表.js
Normal file
94
top-interview-leetcode150/graph/210课程表.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* 课程安排 II - 拓扑排序(DFS)
|
||||||
|
* @param {number} numCourses - 课程总数
|
||||||
|
* @param {number[][]} prerequisites - 每一对 [a, b] 表示上课程 a 需要先完成课程 b
|
||||||
|
* @return {number[]} - 返回一个可行的课程学习顺序,如果无法完成所有课程则返回 []
|
||||||
|
*/
|
||||||
|
const findOrder = function (numCourses, prerequisites) {
|
||||||
|
return dfsTopoSort(numCourses, prerequisites);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 DFS 实现拓扑排序,并判断是否存在环
|
||||||
|
* @param {number} numCourses
|
||||||
|
* @param {number[][]} prerequisites
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
function dfsTopoSort(numCourses, prerequisites) {
|
||||||
|
const graph = Array.from({ length: numCourses }, () => []); // 邻接表表示图
|
||||||
|
const visited = Array(numCourses).fill(0); // 节点状态:0=未访问,1=访问中,2=已完成
|
||||||
|
const result = []; // 用于存放拓扑排序结果(逆序)
|
||||||
|
let hasCycle = false; // 用于标记图中是否存在环
|
||||||
|
|
||||||
|
// 构建图:b -> a 表示先学 b 才能学 a
|
||||||
|
for (const [a, b] of prerequisites) {
|
||||||
|
graph[b].push(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度优先搜索当前节点
|
||||||
|
* @param {number} node
|
||||||
|
*/
|
||||||
|
function dfs(node) {
|
||||||
|
if (visited[node] === 1) {
|
||||||
|
// 当前节点正在访问中,说明存在环
|
||||||
|
hasCycle = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (visited[node] === 2 || hasCycle) {
|
||||||
|
// 节点已处理,或已发现环,直接返回
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visited[node] = 1; // 标记为访问中
|
||||||
|
for (const neighbor of graph[node]) {
|
||||||
|
dfs(neighbor);
|
||||||
|
}
|
||||||
|
visited[node] = 2; // 标记为已完成
|
||||||
|
result.push(node); // 所有邻居处理完后再加入结果,确保当前节点排在后面
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有节点,防止图不连通
|
||||||
|
for (let i = 0; i < numCourses; i++) {
|
||||||
|
dfs(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果存在环,则无法完成所有课程
|
||||||
|
return hasCycle ? [] : result.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用kahn算法来实现,一个顶点能从其他顶点过来表明有一个入度,有多少个顶点能到达这个顶点就有多少个入度,这里直接copy207
|
||||||
|
修改一下即可
|
||||||
|
*/
|
||||||
|
function f2(numCourses, prerequisites) {
|
||||||
|
const graph = Array.from({ length: numCourses }, () => []);
|
||||||
|
const inDegree = Array(numCourses).fill(0);
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
// 构建图和入度表
|
||||||
|
for (const [a, b] of prerequisites) {
|
||||||
|
graph[b].push(a);
|
||||||
|
inDegree[a]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找出入度为零的顶点,将其加入队列
|
||||||
|
const queue = [];
|
||||||
|
for (let i = 0; i < numCourses; i++) {
|
||||||
|
if (inDegree[i] === 0) queue.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从队列中输出所有入度为零的顶点,如果输出的顶点数量和课程数量一致,表明可以拓扑排序,否则有环
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const node = queue.shift();
|
||||||
|
// 将入度为零的顶点存入结果集合
|
||||||
|
result.push(node);
|
||||||
|
for (const neighbor of graph[node]) {
|
||||||
|
inDegree[neighbor]--;
|
||||||
|
if (inDegree[neighbor] === 0) queue.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果结果集中的顶点的数量小于numCourses那么表明有环,放回[],否则返回结果集
|
||||||
|
return result.length < numCourses ? [] : result;
|
||||||
|
}
|
96
top-interview-leetcode150/graph/399除法求值.js
Normal file
96
top-interview-leetcode150/graph/399除法求值.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* @param {string[][]} equations
|
||||||
|
* @param {number[]} values
|
||||||
|
* @param {string[][]} queries
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const calcEquation = function (equations, values, queries) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:通过equations和values建立一张有权图edges:[[[1, 2],...], [[2, 3],...]],在这里使用邻接表的方式表示,edges表示的是0号顶点可以到一号顶点
|
||||||
|
并且权值为2,1顶点到2号顶点的权值是3,如果0号顶点表示a,1号顶点表示b,2号顶点表示c,那么这个图在这个题目里面的意思就是,a/b=2,b/c=2,如果要我们,
|
||||||
|
求a/c的值不就是把a->b->c这个路径里面的权值相乘吗?所以这个题目可以分为下面几步:
|
||||||
|
步骤:
|
||||||
|
1.通过equations,values建立有权图edges
|
||||||
|
2.遍历queries,比如要我们求a/e,那么我们就从a为起点出发,开始广度优先遍历,知道找到a->e的路径,之后加权(相乘),如果没找到,就是-1
|
||||||
|
*/
|
||||||
|
function f1(equations, values, queries) {
|
||||||
|
// Step 1: 给每个变量编号,变量名映射到编号
|
||||||
|
let nvars = 0; // 变量的数量
|
||||||
|
const variables = new Map(); // 存储变量名到编号的映射
|
||||||
|
|
||||||
|
// 处理 equations,给每个变量一个唯一的编号
|
||||||
|
const n = equations.length;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
// 如果变量没有编号,就给它一个编号
|
||||||
|
if (!variables.has(equations[i][0])) {
|
||||||
|
variables.set(equations[i][0], nvars++);
|
||||||
|
}
|
||||||
|
if (!variables.has(equations[i][1])) {
|
||||||
|
variables.set(equations[i][1], nvars++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: 创建图结构,存储每个节点与其他节点的比值
|
||||||
|
const edges = new Array(nvars).fill(0); // 创建一个数组来存储每个节点的邻接点和比值
|
||||||
|
for (let i = 0; i < nvars; i++) {
|
||||||
|
edges[i] = []; // 初始化每个节点的邻接表
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历 equations,构建图,每条边的权值是它们之间的比值
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const va = variables.get(equations[i][0]); // 获取变量 a 的编号
|
||||||
|
const vb = variables.get(equations[i][1]); // 获取变量 b 的编号
|
||||||
|
edges[va].push([vb, values[i]]); // a → b 的比值
|
||||||
|
edges[vb].push([va, 1.0 / values[i]]); // b → a 的比值是 1 / (a → b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: 处理查询,计算每个查询的结果
|
||||||
|
const queriesCount = queries.length; // 查询的数量
|
||||||
|
const ret = []; // 存储每个查询的结果
|
||||||
|
|
||||||
|
for (let i = 0; i < queriesCount; i++) {
|
||||||
|
const query = queries[i]; // 当前查询
|
||||||
|
let result = -1.0; // 默认结果为 -1.0,表示无法计算
|
||||||
|
|
||||||
|
// 只有当查询中的两个变量都存在时,才进行计算
|
||||||
|
if (variables.has(query[0]) && variables.has(query[1])) {
|
||||||
|
const ia = variables.get(query[0]); // 获取查询中第一个变量的编号
|
||||||
|
const ib = variables.get(query[1]); // 获取查询中第二个变量的编号
|
||||||
|
|
||||||
|
// 如果两个变量是同一个,直接返回 1.0
|
||||||
|
if (ia === ib) {
|
||||||
|
result = 1.0;
|
||||||
|
} else {
|
||||||
|
// Step 4: 使用 DFS 或 BFS 遍历图,查找从 ia 到 ib 的比值
|
||||||
|
const points = []; // 存储当前正在遍历的节点
|
||||||
|
points.push(ia); // 从 ia 开始遍历
|
||||||
|
|
||||||
|
const ratios = new Array(nvars).fill(-1.0); // 存储每个节点的比值,初始值为 -1.0,表示不可达
|
||||||
|
ratios[ia] = 1.0; // 起点 ia 到 ia 的比值是 1.0
|
||||||
|
|
||||||
|
// 使用 DFS 方式遍历图
|
||||||
|
while (points.length && ratios[ib] < 0) {
|
||||||
|
const x = points.pop(); // 从栈中取出一个节点
|
||||||
|
// 遍历当前节点的所有邻接点
|
||||||
|
for (const [y, val] of edges[x]) {
|
||||||
|
// 如果邻接点 y 尚未访问过(ratios[y] < 0)
|
||||||
|
if (ratios[y] < 0) {
|
||||||
|
ratios[y] = ratios[x] * val; // 更新 y 的比值
|
||||||
|
points.push(y); // 将 y 加入栈中继续遍历
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = ratios[ib]; // 查询的结果是 ib 到达的比值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将当前查询的结果存入结果数组
|
||||||
|
ret[i] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回所有查询的结果
|
||||||
|
return ret;
|
||||||
|
}
|
129
top-interview-leetcode150/graph/909蛇梯棋子.js
Normal file
129
top-interview-leetcode150/graph/909蛇梯棋子.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[][]} board
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const snakesAndLadders = function (board) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
/*
|
||||||
|
把棋盘的每一个位置看成图的一个顶点,每一个顶点都有六个有向边指向后面的六个位置,也就是后面的六个顶点,有的顶点比较特殊,
|
||||||
|
它不指向后面的六个顶点,而是指向其他的顶点,我们要求的就是到达最后一个位置的顶点所需的最少步数,到这里我们很容易发现,这
|
||||||
|
是一个图的BFS题目,我们从最初的位置一层一层的往外扩,需要用几层就是几步,
|
||||||
|
*/
|
||||||
|
function f1(board) {
|
||||||
|
// 定义一个长度为n*n+1的一维数组,将board压缩,减少计数难度
|
||||||
|
const n = board.length;
|
||||||
|
const size = n * n;
|
||||||
|
const arr = new Array(size + 1); // 0位置不需要,这样就能和board一一对应
|
||||||
|
let idx = 1; // 棋盘初始位置
|
||||||
|
let leftToRight = true; // s行遍历board
|
||||||
|
|
||||||
|
for (let i = n - 1; i >= 0; i--) {
|
||||||
|
if (leftToRight) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
arr[idx++] = board[i][j];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
arr[idx++] = board[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leftToRight = !leftToRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bfs队列,从第一个位置开始,[1,0]表示,到达第一个位置最少只需要一步
|
||||||
|
const queue = [[1, 0]];
|
||||||
|
const visited = new Array(size + 1).fill(false); // 防止每一层的元素重复加入到下一层
|
||||||
|
visited[1] = true;
|
||||||
|
while (queue.length) {
|
||||||
|
const [cur, step] = queue.shift();
|
||||||
|
|
||||||
|
// 查看邻接的六个顶点
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
let next = cur + i; // 下一个顶点位置
|
||||||
|
|
||||||
|
// 越界,忽略这个顶点
|
||||||
|
if (next > size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果遇到蛇和梯子立即调整指定位置
|
||||||
|
if (arr[next] !== -1) {
|
||||||
|
next = arr[next];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果邻接顶点就是目标位置,直接放回到达当前顶点的步数在加1
|
||||||
|
if (next === size) {
|
||||||
|
return step + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果元素没有被访问将其加入队列,扩展下一层
|
||||||
|
if (!visited[next]) {
|
||||||
|
visited[next] = true;
|
||||||
|
queue.push([next, step + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // 不可达
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
将二维数组变成一维数组需要log(n*n)的时间复杂度,n为棋牌大小,相等于一开始就遍历了整个棋盘一遍,如果能实时计算当前位置,到其他位置
|
||||||
|
在board中的位置可以大大减少时间复杂度。
|
||||||
|
思考:假设我们有一个大小为n的棋牌,计数位置place(1 ~ n*n)在board中的坐标,首先计算行,行非常好计算,r = (place - 1) / n,那么列
|
||||||
|
还是 (place-1) % n吗?在这个题目不是的因为棋牌是按照s行走的,偶数行从左往右,奇数行从右往左,偶数行显然是(place-1)%n,那么技术行只需要将n
|
||||||
|
减去它即可
|
||||||
|
*/
|
||||||
|
|
||||||
|
const id2rc = (id, n) => {
|
||||||
|
const r = Math.floor((id - 1) / n);
|
||||||
|
let c = (id - 1) % n;
|
||||||
|
if (r % 2 === 1) {
|
||||||
|
c = n - 1 - c;
|
||||||
|
}
|
||||||
|
return [n - 1 - r, c];
|
||||||
|
};
|
||||||
|
|
||||||
|
function f2(board) {
|
||||||
|
const n = board.length;
|
||||||
|
const size = n * n;
|
||||||
|
|
||||||
|
// BFS
|
||||||
|
const queue = [[1, 0]];
|
||||||
|
const visited = new Array(size + 1).fill(false); // 防止每一层的元素重复加入到下一层
|
||||||
|
visited[1] = true;
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
const [cur, step] = queue.shift();
|
||||||
|
|
||||||
|
// 查看邻接的六个顶点
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
let next = cur + i; // 下一个顶点位置
|
||||||
|
|
||||||
|
// 越界,忽略这个顶点
|
||||||
|
if (next > size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [r, c] = id2rc(next, n);
|
||||||
|
// 如果遇到蛇和梯子立即调整指定位置
|
||||||
|
if (board[r][c] !== -1) {
|
||||||
|
next = board[r][c];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果邻接顶点就是目标位置,直接放回到达当前顶点的步数在加1
|
||||||
|
if (next === size) {
|
||||||
|
return step + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果元素没有被访问将其加入队列,扩展下一层
|
||||||
|
if (!visited[next]) {
|
||||||
|
visited[next] = true;
|
||||||
|
queue.push([next, step + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // 不可达
|
||||||
|
}
|
58
top-interview-leetcode150/hash-table/128数组中连续序列的最长长度.js
Normal file
58
top-interview-leetcode150/hash-table/128数组中连续序列的最长长度.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/longest-consecutive-sequence/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const longestConsecutive = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:题目所给的数组是无序的,所以直接对数组排序,依次遍历整个数组,如果当前元素大于前一个元素,并且等于前一个元素加1,就
|
||||||
|
把统计的长度加1,如果不满足就比较是否比最大长度还大,是的话就把这个长度作为为最大长度
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f1(nums) {
|
||||||
|
nums.sort((a, b) => a - b);
|
||||||
|
if (nums.length === 0) return 0;
|
||||||
|
if (nums.length === 1) return 1;
|
||||||
|
let maxLen = 1; // 最大长度
|
||||||
|
let curLen = 1; // 统计长度
|
||||||
|
for (let i = 1; i < nums.length; i++) {
|
||||||
|
const preNum = nums[i - 1];
|
||||||
|
if (nums[i] === preNum) {
|
||||||
|
continue;
|
||||||
|
} else if (preNum + 1 === nums[i]) {
|
||||||
|
curLen++;
|
||||||
|
} else {
|
||||||
|
maxLen = Math.max(maxLen, curLen);
|
||||||
|
curLen = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.max(curLen, maxLen); // 如果后面一个很长的连续序列
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
题目要求我们在O(n)的时间复杂度完成,上面的做法使用了一次排序,nlogn,不符合要求,正确的做法可以先用set对整个数组去重,
|
||||||
|
整个过程的时间复杂度为O(n),之后遍历整个Set,如果当前的num-1在set中不存在,就表明,这个num是数组中某个序列的开头,开始统计
|
||||||
|
num+1在set中存在吗?如果存在就计数加1,如果不存在继续遍历
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f2(nums) {
|
||||||
|
if (nums.length === 0) return 0;
|
||||||
|
const set = new Set(nums);
|
||||||
|
let maxLen = 1;
|
||||||
|
let curLen = 1;
|
||||||
|
for (const num of set) {
|
||||||
|
if (set.has(num - 1)) continue;
|
||||||
|
// 表明num是某个连续序列的开头,直接开始统计
|
||||||
|
let next = num + 1;
|
||||||
|
while (set.has(next)) {
|
||||||
|
curLen++;
|
||||||
|
next++;
|
||||||
|
}
|
||||||
|
maxLen = Math.max(maxLen, curLen);
|
||||||
|
curLen = 1;
|
||||||
|
}
|
||||||
|
return maxLen;
|
||||||
|
}
|
21
top-interview-leetcode150/hash-table/1两数之和.js
Normal file
21
top-interview-leetcode150/hash-table/1两数之和.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
*https://leetcode.cn/problems/two-sum/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const twoSum = function (nums, target) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
创建一个map,map的key为target - num, value为下标,遍历nums,如果当前num在map中存在就返回[map.get(num), i],i表示当前
|
||||||
|
下标
|
||||||
|
*/
|
||||||
|
function f1(nums, target) {
|
||||||
|
const map = new Map();
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
if (map.has(nums[i])) return [map.get(nums[i]), i];
|
||||||
|
map.set(target - nums[i], i);
|
||||||
|
}
|
||||||
|
}
|
71
top-interview-leetcode150/hash-table/202快乐数.js
Normal file
71
top-interview-leetcode150/hash-table/202快乐数.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
*https://leetcode.cn/problems/happy-number/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number} n
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isHappy = function (n) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
根据快乐数的定义我们只需一直计数,把计数的结果放到Set中,如果在后续的计数中,出现了这个结果说明它不是快乐数(不能是1,所以要先
|
||||||
|
检测它是否是1,如果是的话就返回)
|
||||||
|
*/
|
||||||
|
function f1(n) {
|
||||||
|
// 使用set来记录出现过的数字
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
// 循环直到满足条件
|
||||||
|
while (n !== 1) {
|
||||||
|
// 如果当前数字之前的某次计算出过,说明进入循环
|
||||||
|
if (seen.has(n)) return false;
|
||||||
|
|
||||||
|
// 将当前数字加入set
|
||||||
|
seen.add(n);
|
||||||
|
|
||||||
|
// 计算当前数字的每个位置的平方和
|
||||||
|
// n = n.toString().split('').reduce((sum, digit) => sum + Number(digit) ** 2, 0);
|
||||||
|
// 优化:通过取余和除法运算计算每一位的平方和
|
||||||
|
let sum = 0;
|
||||||
|
while (n > 0) {
|
||||||
|
const digit = n % 10;
|
||||||
|
sum += digit * digit;
|
||||||
|
n = Math.floor(n / 10);
|
||||||
|
}
|
||||||
|
n = sum;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用弗洛伊德环形检测算法
|
||||||
|
*/
|
||||||
|
function f2(n) {
|
||||||
|
let slow = n; // 慢指针
|
||||||
|
let fast = n; // 快指针
|
||||||
|
|
||||||
|
// 快慢指针循环
|
||||||
|
while (fast !== 1 && getSumOfSquares(fast) !== 1) { // getSumOfSquares(fast) !== 1直接提前判定慢指针
|
||||||
|
slow = getSumOfSquares(slow);
|
||||||
|
fast = getSumOfSquares(getSumOfSquares(fast));
|
||||||
|
if (slow === fast) { // 如果快慢指针相遇,说明出现了循环
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} num 要计算各位平方和的数
|
||||||
|
* @returns 返回计算平方和的数
|
||||||
|
*/
|
||||||
|
function getSumOfSquares(num) {
|
||||||
|
let sum = 0;
|
||||||
|
while (num > 0) {
|
||||||
|
const digit = num % 10;
|
||||||
|
sum += digit * digit;
|
||||||
|
num = Math.floor(num / 10);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
60
top-interview-leetcode150/hash-table/205同构字符串.js
Normal file
60
top-interview-leetcode150/hash-table/205同构字符串.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/isomorphic-strings/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string} t
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isIsomorphic = function (s, t) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
s和t的长度是一致的,s中的字符会按照某种关系映射到t,比如: a->b, b->c,但是不会出现a->c, b->c这种多个字符映射同一个字符,如果出现
|
||||||
|
这种情况直接返回false,也不会出现a->b, a->c这种一个字符有多个映射的情况,如果有直接返回false。
|
||||||
|
思路: 同时遍历s和t,s[i]作为hashTable的key,t[i]作为hashTable的值,如果s[i] 在hashTable中存在,检查其对应的value是否和
|
||||||
|
t[i]一致,如果不一致,就出现了 a-b, a->c这种一个字符出现多映射的情况,直接返回false,最后遍历一遍hashtable,检查是否存在,多个
|
||||||
|
value相同的情况,如果出现则表明出现了,a-c,b->c这种多个字符映射到同一个字符的情况
|
||||||
|
*/
|
||||||
|
function f1(s, t) {
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
if (map.has(s[i])) {
|
||||||
|
if (map.get(s[i]) !== t[i]) return false;
|
||||||
|
} else {
|
||||||
|
map.set(s[i], t[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 利用set查看map的value是否有重复
|
||||||
|
const set = new Set();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
for (const [_, value] of map) {
|
||||||
|
if (set.has(value)) return false;
|
||||||
|
set.add(value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
我们在处理a->b,a->c这种多映射的情况非常好处理,一次遍历就行,但是在遇到a->c,b->c这种被多个字符映射的情况需要遍历
|
||||||
|
一遍map的所有value,这无疑增加了复杂度,所以可以再增加一个set用来储存哪些字符应该被映射过了,如果出现了一个没有映射
|
||||||
|
的字符,但是它要映射的那个字符在set中已经存在,直接返回false,英文出现了多个字符映射到同一个字符的情况
|
||||||
|
*/
|
||||||
|
function f2(s, t) {
|
||||||
|
// 创建两个map,一个储存字符的映射,一个储存哪些字符已经被映射过了
|
||||||
|
const charMap = new Map();
|
||||||
|
const hasBeenMap = new Set();
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
if (charMap.has(s[i])) {
|
||||||
|
if (charMap.get(s[i]) !== t[i]) return false;
|
||||||
|
} else {
|
||||||
|
// 检查t[i]是否已经被映射
|
||||||
|
if (hasBeenMap.has(t[i])) return false;
|
||||||
|
charMap.set(s[i], t[i]); // 将映射关系加入 charMap
|
||||||
|
hasBeenMap.add(t[i]); // 将 t[i] 标记为已映射
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
67
top-interview-leetcode150/hash-table/219存在重复元素.js
Normal file
67
top-interview-leetcode150/hash-table/219存在重复元素.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/contains-duplicate-ii/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} k
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const containsNearbyDuplicate = function (nums, k) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义两个指针表示abs(i - j) <= k的窗口,i向右移动,查看当前指向的值在set中是否存在,不存在就把这个值存入set,如果i-j >k 表明
|
||||||
|
当前太大了,要缩小,把j指向的元素从set中剔除掉,i++,并将测j指向的值是否在set中存在
|
||||||
|
*/
|
||||||
|
function f1(nums, k) {
|
||||||
|
const set = new Set();
|
||||||
|
|
||||||
|
for (let i = 0, j = 0; i < nums.length; i++) {
|
||||||
|
// 如果 窗口太大,就缩小窗口再检测
|
||||||
|
if (i - j > k) {
|
||||||
|
set.delete(nums[j]);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测 nums[i]是否在set中存在
|
||||||
|
if (set.has(nums[i])) return true;
|
||||||
|
|
||||||
|
// 将当前值存入set
|
||||||
|
set.add(nums[j]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
优化,上面代码中i-j<k的检测是多余的,因为,当i>k时i-j一定大于k,因为后续我们会一个值一个值的检测,例如:当k=3,i=4时,这个时候
|
||||||
|
一定要缩小串口,直接剔除掉i-k-1表示的元素即可,set中剩下的元素还是4个,这样可以少用一个下标,判断也更快
|
||||||
|
*/
|
||||||
|
function f2(nums, k) {
|
||||||
|
const set = new Set();
|
||||||
|
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
// 超出滑动窗口
|
||||||
|
if (i > k) {
|
||||||
|
set.delete(nums[i - k - 1]);
|
||||||
|
}
|
||||||
|
// 检测set中是否含有这个元素
|
||||||
|
if (set.has(nums[i])) return true;
|
||||||
|
set.add(nums[i]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
直接一次遍历,利用map储存之前遍历的所有值和下标,如果当前值在map中存在,并且当前值的下标减去map中对应值的小标,结果小于等于k
|
||||||
|
则表明发生了重复
|
||||||
|
*/
|
||||||
|
|
||||||
|
function f3(nums, k) {
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < nums.length; i++) {
|
||||||
|
const num = nums[i];
|
||||||
|
if (map.has(num) && i - map.get(num) <= k) return true;
|
||||||
|
map.set(num, i);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
51
top-interview-leetcode150/hash-table/242有效的字母异位词.js
Normal file
51
top-interview-leetcode150/hash-table/242有效的字母异位词.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/valid-anagram/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string} t
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const isAnagram = function (s, t) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
判断s是否是t的异位词,异位词就是重新排列字符顺序的词就是异位词,所以异位词的长度和原本单词的长度一致,同一个字符出现的次数也一致
|
||||||
|
题目的输入只包含26个小写字母,所利用数组的下标表示t中出现的小写字母,值为出现的次数,之后遍历s,s中的每个单词会使其对应的计数减少
|
||||||
|
1,如果出现某个计数小于0直接返回false
|
||||||
|
*/
|
||||||
|
function f1(s, t) {
|
||||||
|
if (s.length !== t.length) return false;
|
||||||
|
// 统计t中字母出现的频次
|
||||||
|
const charCounts = new Array(26).fill(0);
|
||||||
|
for (let i = 0; i < t.length; i++) {
|
||||||
|
charCounts[t.charCodeAt(i) - 97]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
const index = s.charCodeAt(i) - 97;
|
||||||
|
charCounts[index]--;
|
||||||
|
if (charCounts[index] < 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
处理unicode字符,js中有一个坑,使用t[i]取unicode字符时,如果这个字符是一个代理对,比如emjoy表情,就会使用两个unicode16表示
|
||||||
|
所以应该使用for of来处理,应该字符串本身就是一个可迭代对象,底层会保证它返回的字符是正确的
|
||||||
|
*/
|
||||||
|
function f2(s, t) {
|
||||||
|
if (s.length !== t.length) return false;
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
for (const ch of t) {
|
||||||
|
map.set(ch, (map.get(ch) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ch of s) {
|
||||||
|
if (!map.has(ch)) return false;
|
||||||
|
map.set(ch, map.get(ch) - 1);
|
||||||
|
if (map.get(ch) < 0) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
40
top-interview-leetcode150/hash-table/290单词规律.js
Normal file
40
top-interview-leetcode150/hash-table/290单词规律.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
*https://leetcode.cn/problems/word-pattern/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} pattern
|
||||||
|
* @param {string} s
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const wordPattern = function (pattern, s) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
和205类似,只不过pattern中的字符映射到s中是一个单词,首先处理s,把s中每一个单词分离出来用数组储存,之后遍历pattern和这个数组,如果这个
|
||||||
|
map中存在这个key,就查看当前key对应的值是否和数组的值相等,如果不相等就返回false, 如果出现多个字符映射同一个字符也返回false,和205一致,
|
||||||
|
这里不过多解释
|
||||||
|
*/
|
||||||
|
function f1(pattern, s) {
|
||||||
|
const map = new Map();
|
||||||
|
const words = s.split(' ');
|
||||||
|
if (pattern.length !== s.length) return false;
|
||||||
|
for (let i = 0; i < pattern.length; i++) {
|
||||||
|
if (map.has(pattern[i])) {
|
||||||
|
if (map.get(pattern[i]) !== words[i]) return false;
|
||||||
|
} else {
|
||||||
|
map.set(pattern[i], words[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测是否出现了多个字符映射同一个字符串
|
||||||
|
const set = new Set();
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
for (const [_, value] of map) {
|
||||||
|
if (set.has(value)) return false;
|
||||||
|
set.add(value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
参考205,优化思路一致,可以使用数组储存对应映射,查找更快,使用set保存已经被映射过的单词,去除最后一次遍历
|
||||||
|
*/
|
68
top-interview-leetcode150/hash-table/383赎金信.js
Normal file
68
top-interview-leetcode150/hash-table/383赎金信.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/ransom-note/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string} ransomNote
|
||||||
|
* @param {string} magazine
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
const canConstruct = function (ransomNote, magazine) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
思路:用hash表统计magazine中的字符出现的数量,之后遍历ransomNote,如果ransomNote中有字符不在这个hash表中,说明magazine不能
|
||||||
|
构成ransomNote,如果这个字符在magazine中存在,就使其计数减1,如果计数为零就从哈希表中删除这个字符
|
||||||
|
*/
|
||||||
|
function f1(ransomNote, magazine) {
|
||||||
|
const map = new Map(); // 利用hash计数
|
||||||
|
for (let i = 0; i < magazine.length; i++) {
|
||||||
|
map.set(magazine[i], (map.get(magazine[i]) || 0) + 1); // 如果map中有这个字符就在原有的基础上加1,没有就设置成1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历ransomNote中的字符,如果字符在map中不存在就返回false
|
||||||
|
|
||||||
|
for (let i = 0; i < ransomNote.length; i++) {
|
||||||
|
const char = ransomNote[i];
|
||||||
|
if (map.has(char)) {
|
||||||
|
map.set(char, map.get(char) - 1);
|
||||||
|
// 如果发现当前字符计数为零,从map中删除这个字符
|
||||||
|
if (map.get(char) === 0) {
|
||||||
|
map.delete(char);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
使用数组来代替hash表,因为题目所给的测试用例只有26个小写的英文字符,可以使用一个长度为26的数组,数组的第一个位置代表a,最后一个位置代表
|
||||||
|
z,每个位置的值代表这个字符在magazine中出现的位置。
|
||||||
|
思考:为什么使用数组会更快,虽然hash查找操作时间复杂度理论上式O(1),但是hash计数,到对应的链表查找数据还是需要花费时间的,在数据量
|
||||||
|
大的时候可以认为是O(1),但是我们只需统计26个英文字符的数量,直接使用数组,数组在内存中是连续的,通过偏移量查找,非常快
|
||||||
|
*/
|
||||||
|
function f2(ransomNote, magazine) {
|
||||||
|
const charsCount = new Array(26).fill(0); // 统计magazine中每一个字符出现的次数
|
||||||
|
|
||||||
|
// 统计magazine中的字符
|
||||||
|
for (let i = 0; i < magazine.length; i++) {
|
||||||
|
const index = magazine.charCodeAt(i) - 97;
|
||||||
|
charsCount[index]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历ransomNote中的字符在charscount中是否存在
|
||||||
|
|
||||||
|
for (let i = 0; i < ransomNote.length; i++) {
|
||||||
|
const index = ransomNote.charCodeAt(i) - 97;
|
||||||
|
|
||||||
|
// if (charsCount[index] > 0) {
|
||||||
|
// charsCount[index]--;
|
||||||
|
// } else {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
if (charsCount[index] === 0) return false;
|
||||||
|
charsCount[index]--;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
103
top-interview-leetcode150/hash-table/49字母异位词分组.js
Normal file
103
top-interview-leetcode150/hash-table/49字母异位词分组.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/group-anagrams/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {string[]} strs
|
||||||
|
* @return {string[][]}
|
||||||
|
*/
|
||||||
|
const groupAnagrams = function (strs) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
题目要求我们对字符串中的异位词分组,首先创建一个map,遍历strs中的所有字符串,查看它是否是map中某一个key的异位词,如果是的话,就
|
||||||
|
把它插入到这个key对应的数组中去,如果不是的话,表明他是一个新的字符串,所以在map中创建一个映射,用来收集它的异位词
|
||||||
|
*/
|
||||||
|
function f1(strs) {
|
||||||
|
const map = new Map();
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < strs.length; i++) {
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
// 查找 map 中是否有对应的异位词
|
||||||
|
for (const [key, value] of map) {
|
||||||
|
if (key.length === strs[i].length && isAnagram(strs[i], key)) {
|
||||||
|
value.push(strs[i]); // 如果是异位词,加入现有的数组
|
||||||
|
found = true; // 标记找到
|
||||||
|
break; // 找到就停止遍历
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到,就创建新的异位词组
|
||||||
|
if (!found) {
|
||||||
|
map.set(strs[i], [strs[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 map 中的所有异位词数组收集到 result
|
||||||
|
for (const [, value] of map) {
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用字符序来判断异位词
|
||||||
|
*/
|
||||||
|
function f2(strs) {
|
||||||
|
const map = new Map();
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
// 遍历所有字符串
|
||||||
|
for (let i = 0; i < strs.length; i++) {
|
||||||
|
// 排序字符串,得到标准化形式
|
||||||
|
const sortedStr = strs[i].split('').sort().join('');
|
||||||
|
|
||||||
|
// 如果 map 中已经有这个排序后的字符串,就将当前字符串加入对应的数组
|
||||||
|
if (map.has(sortedStr)) {
|
||||||
|
map.get(sortedStr).push(strs[i]);
|
||||||
|
} else {
|
||||||
|
// 如果没有,创建新的映射
|
||||||
|
map.set(sortedStr, [strs[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 map 中的所有异位词数组收集到 result
|
||||||
|
for (const [, value] of map) {
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测两个词是否是异位词
|
||||||
|
function isAnagram(s, t) {
|
||||||
|
if (s.length !== t.length) return false;
|
||||||
|
const charCounts = new Array(26).fill(0);
|
||||||
|
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
charCounts[s.charCodeAt(i) - 97]++;
|
||||||
|
charCounts[t.charCodeAt(i) - 97]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCounts.every((count) => count === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
利用一个数组来储存str的字符计数,如果计数相同就判断它们是异位词,这个代码巧妙之处是直接把数组作为key,本质就是调用数组的toString()
|
||||||
|
之后拿这个string作为对象的key,map中不能直接使用,因为map是通过引用来判断的,即使内容一样,也会判为,不同的str,如果非要要用map,可以
|
||||||
|
使用 String(count)作为map的key
|
||||||
|
*/
|
||||||
|
function f3(strs) {
|
||||||
|
// eslint-disable-next-line no-new-object
|
||||||
|
const map = new Object();
|
||||||
|
for (const s of strs) {
|
||||||
|
const count = new Array(26).fill(0);
|
||||||
|
for (const c of s) {
|
||||||
|
count[c.charCodeAt() - 'a'.charCodeAt()]++;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
map[count] ? map[count].push(s) : map[count] = [s];
|
||||||
|
}
|
||||||
|
return Object.values(map);
|
||||||
|
}
|
24
top-interview-leetcode150/kadane/53最大子数组之和.js
Normal file
24
top-interview-leetcode150/kadane/53最大子数组之和.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxSubArray = function (nums) {
|
||||||
|
return f1(nums);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
定义dp[i]表示以nums[i]结尾的最大子数组和,那么dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]),要么是前面最大的子数组
|
||||||
|
加上自己,要么是自己本身
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
let max = nums[0];
|
||||||
|
const dp = new Array(nums.length).fill(0);
|
||||||
|
dp[0] = nums[0];
|
||||||
|
|
||||||
|
for (let i = 1; i < nums.length; i++) {
|
||||||
|
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
|
||||||
|
max = Math.max(max, dp[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
35
top-interview-leetcode150/kadane/918环形子数组最大和.js
Normal file
35
top-interview-leetcode150/kadane/918环形子数组最大和.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const maxSubarraySumCircular = function (nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
最大子数组之和无外乎两种情况,子数组在开头和结尾直接,还有一种情况就是一部分在开头的前缀,一部分在结尾的后缀,
|
||||||
|
所以只需一次遍历统计前缀和的最大值和第一种情况的最大值,之后再倒序遍历统计后缀的最大值,之后比较这两种情况
|
||||||
|
*/
|
||||||
|
function f1(nums) {
|
||||||
|
const n = nums.length;
|
||||||
|
const leftMax = new Array(n).fill(0);
|
||||||
|
// 对坐标为 0 处的元素单独处理,避免考虑子数组为空的情况
|
||||||
|
leftMax[0] = nums[0];
|
||||||
|
let leftSum = nums[0];
|
||||||
|
let pre = nums[0];
|
||||||
|
let res = nums[0];
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
pre = Math.max(pre + nums[i], nums[i]);
|
||||||
|
res = Math.max(res, pre);
|
||||||
|
leftSum += nums[i];
|
||||||
|
leftMax[i] = Math.max(leftMax[i - 1], leftSum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从右到左枚举后缀,固定后缀,选择最大前缀
|
||||||
|
let rightSum = 0;
|
||||||
|
for (let i = n - 1; i > 0; i--) {
|
||||||
|
rightSum += nums[i];
|
||||||
|
res = Math.max(res, rightSum + leftMax[i - 1]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user