Compare commits
10 Commits
a5e958c203
...
5c1a70a60f
Author | SHA1 | Date | |
---|---|---|---|
5c1a70a60f | |||
dbcfc3f91a | |||
614b10bcd0 | |||
09c5a746f1 | |||
2e1ef51465 | |||
81d4ae749b | |||
21d1a55888 | |||
f53e76dcf0 | |||
0a41e81e58 | |||
7eb11b0136 |
@ -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 内的包
|
||||||
|
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;
|
||||||
|
}
|
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));
|
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;
|
||||||
|
}
|
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;
|
||||||
|
}
|
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
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
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; // 矩阵中没有这个值
|
||||||
|
}
|
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);
|
||||||
|
}
|
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];
|
||||||
|
}
|
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; // 不可达
|
||||||
|
}
|
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;
|
||||||
|
}
|
61
top-interview-leetcode150/sliding-Window/209长度最小的子数组.js
Normal file
61
top-interview-leetcode150/sliding-Window/209长度最小的子数组.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/minimum-size-subarray-sum/?envType=study-plan-v2&envId=top-interview-150
|
||||||
|
* @param {number} target
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const minSubArrayLen = function (target, nums) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
题目要求我们找到最短的子数组,子数组就是原数组中连续的某部分,我们先遍历原数组,之后在遍历到的位置开始往下遍历,并且求和
|
||||||
|
如果结果大于等于target就返回这个长度
|
||||||
|
*/
|
||||||
|
function f1(target, nums) {
|
||||||
|
let minLen = Infinity; // 初始化最小长度
|
||||||
|
for (let left = 0; left < nums.length; left++) {
|
||||||
|
let sum = 0; // 保存当前子数组的和
|
||||||
|
for (let right = left; right < nums.length; right++) {
|
||||||
|
sum += nums[right];
|
||||||
|
if (sum >= target) {
|
||||||
|
minLen = Math.min(minLen, right - left + 1);
|
||||||
|
// 后面匹配的结果都会符合sum>=target但是一定比当前子数组长,所以直接break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLen === Infinity ? 0 : minLen; // 注意清理初始化的值,如果没有符合要求的,返回0,而不是Infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
function f2(target, nums) {
|
||||||
|
let minLen = Infinity; // 初始化最小长度
|
||||||
|
let left = 0; // 滑动窗口的左边界
|
||||||
|
let sum = 0; // 滑动窗口中所有值的和
|
||||||
|
for (let right = 0; right < nums.length; right++) {
|
||||||
|
sum += nums[right];
|
||||||
|
while (sum >= target) {
|
||||||
|
minLen = Math.min(minLen, right - left + 1);
|
||||||
|
// 如果靠近right的数值很大,靠近left的数值很小,会造成数组长度很长所以要从左缩小窗口
|
||||||
|
sum -= nums[left];
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLen === Infinity ? 0 : minLen; // 注意清理初始化的值,如果没有符合要求的,返回0,而不是Infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
function f3(target, nums) {
|
||||||
|
let minLen = Infinity; // 初始化最小长度
|
||||||
|
let left = 0; // 滑动窗口的左边界
|
||||||
|
let sum = 0; // 滑动窗口中所有值的和
|
||||||
|
for (let right = 0; right < nums.length; right++) {
|
||||||
|
sum += nums[right];
|
||||||
|
while (sum >= target) {
|
||||||
|
minLen = Math.min(minLen, right - left + 1);
|
||||||
|
// 如果靠近right的数值很大,靠近left的数值很小,会造成数组长度很长所以要从左缩小窗口
|
||||||
|
sum -= nums[left];
|
||||||
|
left++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLen === Infinity ? 0 : minLen; // 注意清理初始化的值,如果没有符合要求的,返回0,而不是Infinity
|
||||||
|
}
|
112
top-interview-leetcode150/sliding-Window/438找到字符串中所有字母异位词.js
Normal file
112
top-interview-leetcode150/sliding-Window/438找到字符串中所有字母异位词.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* https://leetcode.cn/problems/find-all-anagrams-in-a-string/
|
||||||
|
* @param {string} s
|
||||||
|
* @param {string} p
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
const findAnagrams = function (s, p) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
按照暴力解法的话我们可能会把p的所有异位词都枚举出来,之后遍历遍历所有的异位词,利用indexof查看每一个异位词在s中的位置,如果
|
||||||
|
这个异位词在s中存在,那么就把起始下标放入到结果集中,如果p非常长的话那么所有异位词就是一个长度的全排列!m,加上对s的查找,那就是一个
|
||||||
|
(n+m)*!m, 这显然是不能接受的,所以我们需要换一种思路.
|
||||||
|
思路:异位词和原词在单词数量上是一致的,所以我们可以维护一个在s上,长度位p.length的窗口,只要这个窗口里面所有单词出现的数量和
|
||||||
|
p中所有单词出现的数量一致的话,那么就认为这个窗口表示的单词是p的一个异位词,最后只需把窗口的起始下标放入结果集合即可
|
||||||
|
*/
|
||||||
|
function f1(s, p) {
|
||||||
|
const result = []; // 返回的结果集
|
||||||
|
if (s.length < p.length) return result; // p比s还长,在s中不可能有p的异位词,直接返回
|
||||||
|
const need = new Map(); // 记录p中每一个词出现的次数,用于验证异位词
|
||||||
|
const window = new Map(); // 记录当前窗口中的异位词
|
||||||
|
// 统计p的所有字符出现的次数
|
||||||
|
for (const char of p) {
|
||||||
|
need.set(char, (need.get(char) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = 0; // 滑动窗口的左边界
|
||||||
|
let right = 0; // 滑动窗口的右边界
|
||||||
|
let valid = 0; // 滑动窗口中符合异位字的字符数量;
|
||||||
|
|
||||||
|
while (right < s.length) {
|
||||||
|
const char = s[right];
|
||||||
|
if (need.has(char)) {
|
||||||
|
window.set(char, (window.get(char) || 0) + 1);
|
||||||
|
if (window.get(char) === need.get(char)) {
|
||||||
|
valid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果窗口长度等于异位字符p的长度,那么就检测当前窗口是否是一个异位词
|
||||||
|
if (right - left + 1 === p.length) {
|
||||||
|
if (valid === need.size) {
|
||||||
|
result.push(left); // 把left添加到结果集中
|
||||||
|
}
|
||||||
|
const leftChar = s[left];
|
||||||
|
// 收缩窗口
|
||||||
|
left++;
|
||||||
|
// 判断收缩掉的这个字符是否是need中的某一个字符
|
||||||
|
if (need.has(leftChar)) {
|
||||||
|
if (window.get(leftChar) === need.get(leftChar)) {
|
||||||
|
valid--; // 验证符合异位词字符个数的词减1
|
||||||
|
}
|
||||||
|
window.set(leftChar, window.get(leftChar) - 1); // 使这个字符的数量减1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 扩展窗口
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function f3(s, p) {
|
||||||
|
const result = []; // 返回的结果集
|
||||||
|
if (s.length < p.length) return result; // p比s还长,在s中不可能有p的异位词,直接返回
|
||||||
|
|
||||||
|
const need = new Map(); // 记录p中每一个字符出现的次数,用于验证异位词
|
||||||
|
const window = new Map(); // 记录当前窗口中的异位词
|
||||||
|
|
||||||
|
// 统计p的所有字符出现的次数
|
||||||
|
for (const char of p) {
|
||||||
|
need.set(char, (need.get(char) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = 0; // 滑动窗口的左边界
|
||||||
|
let right = 0; // 滑动窗口的右边界
|
||||||
|
|
||||||
|
while (right < s.length) {
|
||||||
|
const char = s[right];
|
||||||
|
// 扩展窗口
|
||||||
|
window.set(char, (window.get(char) || 0) + 1);
|
||||||
|
|
||||||
|
// 如果当前窗口大小达到p的长度,判断是否为异位词
|
||||||
|
if (right - left + 1 === p.length) {
|
||||||
|
// 比较窗口内字符的频次是否和p中一致
|
||||||
|
let isAnagram = true;
|
||||||
|
for (const [key, value] of need) {
|
||||||
|
if (window.get(key) !== value) {
|
||||||
|
isAnagram = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAnagram) {
|
||||||
|
result.push(left); // 将符合条件的起始索引加入结果
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收缩窗口
|
||||||
|
const leftChar = s[left];
|
||||||
|
window.set(leftChar, window.get(leftChar) - 1); // 减少左边字符的频次
|
||||||
|
if (window.get(leftChar) === 0) {
|
||||||
|
window.delete(leftChar); // 如果频次为0,移除该字符
|
||||||
|
}
|
||||||
|
left++; // 移动左指针
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扩展右指针
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user