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]);
    }
}
posted @   lv6laserlotus  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示