LeetCode - 10. 单调栈、拓扑排序、最小生成树
刷题顺序来自:代码随想录
单调栈
739. 每日温度
function dailyTemperatures(temperatures) {
let res = Array(temperatures.length).fill(0);
let stack = []; // 单调栈
// 倒序遍历, 记录当日之后的最高[温度, 索引]
for (let i = temperatures.length - 1; i >= 0; i--) {
let temp = temperatures[i]; // 当日温度
while (stack.length && stack[stack.length - 1][0] <= temp) {
stack.pop();
}
if (stack.length) {
// 栈顶元素 => 右边第一个温度比当前高的日期
res[i] = stack[stack.length - 1][1] - i;
}
stack.push([temp, i]); // 当前元素加入单调栈
}
return res;
};
496. 下一个更大元素 I
function nextGreaterElement(nums1, nums2) {
let stack = [], map = new Map();
for (let i = nums2.length - 1; i >= 0; i--) {
// 维护单调栈
while (stack.length && stack[0] <= nums2[i]) {
stack.shift();
}
// 如果当前节点右边没有更大的节点, 则不记录
if (stack.length) {
map.set(nums2[i], stack[0]);
}
// 记录当前节点
stack.unshift(nums2[i]);
}
let res = Array(nums1.length).fill(-1);
for (let i = 0; i < nums1.length; i++) {
if (map.has(nums1[i])) {
res[i] = map.get(nums1[i]);
}
}
return res;
}
503. 下一个更大元素 II
function nextGreaterElements(nums = []) {
let stack = []; // 单调栈
// 此操作相当于将nums的前 len - 1 项复制并拼接在nums尾部
for (let i = nums.length - 2; i >= 0; i--) {
while (stack.length && stack[0] <= nums[i]) {
stack.shift();
}
stack.unshift(nums[i]);
}
let res = Array(nums.length).fill(-1);
for (let i = nums.length - 1; i >= 0; i--) {
while (stack.length && stack[0] <= nums[i]) {
stack.shift();
}
if (stack.length) {
res[i] = stack[0];
}
stack.unshift(nums[i]);
}
return res;
}
42. 接雨水
注意:
- 两个单调栈:一个找比自己高的第一个位置,另一个找不低于自己高度的第一个位置
function trap(height = []) {
// 记录当前数字的 左/右边 第一个更大的数字
let left = Array(height.length).fill(-1);
let right = Array(height.length).fill(-1);
// 记录left
let stack = []; // 单调栈
for (let i = 0; i < height.length; i++) {
let h = height[i]; // 当前高度
while (stack.length && stack[0][0] <= h) {
stack.shift();
}
if (stack.length) {
left[i] = stack[0].slice();
}
stack.unshift([h, i]);
}
stack = []; // 单调栈
for (let i = height.length - 1; i >= 0; i--) {
let h = height[i]; // 当前高度
while (stack.length && stack[0][0] < h) {
stack.shift();
}
if (stack.length) {
right[i] = stack[0].slice();
}
stack.unshift([h, i]);
}
let res = 0; // 雨水
for (let i = 0; i < height.length; i++) {
if (Array.isArray(left[i]) && Array.isArray(right[i])) {
let h = Math.min(left[i][0], right[i][0]) - height[i]; // 雨水高度
let w = right[i][1] - left[i][1] - 1; // 雨水宽度
res += h * w;
}
}
return res;
}
- 不需要单调栈的做法:
function trap(height = []) {
let left = Array(height.length).fill(0);
let right = Array(height.length).fill(0);
// 从左往右遍历, 记录每个位置的左边最高高度 (包括自身)
let maxHeight = 0;
for (let i = 0; i < height.length; i++) {
maxHeight = Math.max(maxHeight, height[i]);
left[i] = maxHeight;
}
// 从右往左遍历, 记录每个位置的右边最高高度 (包括自身)
maxHeight = 0;
for (let i = height.length - 1; i >= 0; i--) {
maxHeight = Math.max(maxHeight, height[i]);
right[i] = maxHeight;
}
// 每个位置对答案的贡献: min(左边最高, 右边最高) - 当前高度
let res = 0;
for (let i = 0; i < height.length; i++) {
res += Math.min(left[i], right[i]) - height[i];
}
return res;
}
拓扑排序
207. 课程表
function canFinish(numCourses, prerequisites) {
let inDeg = Array(numCourses).fill(0); // 入度
let outNodes = Array(numCourses).fill(0); // 当前节点指向节点 (出度)
outNodes = outNodes.map(() => []);
// 记录每个节点的入度和指向的节点
for (let pre of prerequisites) {
inDeg[pre[0]]++;
outNodes[pre[1]].push(pre[0]);
}
// queue保存入度为0的节点, count统计访问过节点的数量
let queue = [], count = 0;
// 初始化queue, 保存所有入度为0的节点
for (let i = 0; i < numCourses; i++) {
if (inDeg[i] === 0) {
queue.push(i);
count++;
}
}
while (queue.length) {
let node = queue.shift(); // 弹出队首节点
// 将弹出节点node指向的每一个节点的入度-1
for (let i = 0; i < outNodes[node].length; i++) {
inDeg[outNodes[node][i]]--; // 入度-1
// 如果减到0, 则进入队列
if (inDeg[outNodes[node][i]] === 0) {
queue.push(outNodes[node][i]);
count++;
}
}
}
return count === numCourses;
};
210. 课程表 II
function findOrder(numCourses, prerequisites) {
let inDeg = Array(numCourses).fill(0); // 入度
let outNodes = Array(numCourses).fill(0); // 节点指向的节点 (数组长度为出度)
outNodes = outNodes.map(() => []);
// 根据前置关系统计每个节点的入度、出度
for (let i = 0; i < prerequisites.length; i++) {
let pre = prerequisites[i];
inDeg[pre[0]]++;
outNodes[pre[1]].push(pre[0]);
}
// 将当前入度为0的节点加入queue
let queue = [];
let res = []; // 遍历结果
for (let i = 0; i < inDeg.length; i++) {
if (inDeg[i] === 0) {
queue.push(i);
}
}
while (queue.length) {
let index = queue.shift(); // 队首的入度为0的节点
let backwards = outNodes[index]; // 该节点的后置节点
// 每个后置节点的入度-1, 出现入度为0则加入queue
for (let i = 0; i < backwards.length; i++) {
inDeg[backwards[i]]--;
if (inDeg[backwards[i]] === 0) {
queue.push(backwards[i]);
}
}
res.push(index); // 记录遍历顺序
}
// 如果没有全部遍历到, 返回空
if (res.length !== numCourses) {
return [];
}
return res;
}
最小生成树
1584. 连接所有点的最小费用
function minCostConnectPoints(points = []) {
let cost = []; // 以[distance, p1, p2]的方式记录每条边
for (let i = 0; i < points.length - 1; i++) {
for (let j = i + 1; j < points.length; j++) {
cost.push([distance(points[i], points[j]), i, j]);
}
}
cost.sort((a, b) => a[0] - b[0]); // 根据distance从小到大排序
let father = Array(points.length).fill(0);
father.forEach((_, i) => {father[i] = i;}); // 初始化, 每个节点的父亲是自己
let count = points.length - 1, res = 0; // 当操作n-1次后, 最小生成树构建完成
for (let edge of cost) {
// 当p1和p2的父亲不同, 意味着不在同一颗树下, 进行合并
if (find(edge[1]) !== find(edge[2])) {
count--;
res += edge[0];
father[find(edge[1])] = find(edge[2]); // 合并到同一颗树
}
if (!count) {
break;
}
}
return res;
// 查找节点p1的父亲
function find(p1) {
if (father[p1] !== p1) {
father[p1] = find(father[p1]); // 递归查找, 顺便进行路径压缩
}
return father[p1];
}
// 计算2个节点之间的曼哈顿距离
function distance(p1, p2) {
return Math.abs(p1[0] - p2[0]) + Math.abs(p1[1] - p2[1]);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构