栈的经典应用:从「有效括号」到「寻找两个正序数组的中位数」深度解析

目录

[一、有效括号(LeetCode 20・简单)](#一、有效括号(LeetCode 20・简单))

题目描述

解题思路

[Java 代码实现(标准栈版)](#Java 代码实现(标准栈版))

复杂度分析

核心知识点总结

[二、寻找两个正序数组的中位数(LeetCode 4・困难)](#二、寻找两个正序数组的中位数(LeetCode 4・困难))

题目描述

解题思路

核心思想

[Java 代码实现(标准二分版)](#Java 代码实现(标准二分版))

复杂度分析

核心知识点总结

三、算法思想对比与学习路径

学习建议

四、总结


大家好,今天我们来拆解两道经典算法题:一道是栈的入门必刷题有效括号 ,另一道是二分查找的天花板难题寻找两个正序数组的中位数。前者帮你彻底吃透栈的核心思想,后者带你解锁二分查找的高阶应用,非常适合作为算法学习的进阶内容~


一、有效括号(LeetCode 20・简单)

题目描述

给定一个只包括 '('')''{''}''['']' 的字符串 s,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例:

plaintext

复制代码
输入:s = "()[]{}"
输出:true

输入:s = "(]"
输出:false

解题思路

这道题是 ** 栈(Stack)** 的经典入门应用,核心逻辑非常简单:

  1. 栈的特性:后进先出(LIFO),完美匹配括号「先开后闭」的顺序。
  2. 遍历字符串
    • 遇到左括号 ({[,就把对应的右括号压入栈中(方便后续匹配)
    • 遇到右括号,判断栈是否为空(空则说明没有对应左括号,直接返回 false),并弹出栈顶元素,判断是否与当前右括号匹配
  3. 最终校验:遍历结束后,栈必须为空(说明所有左括号都被正确闭合),否则返回 false

Java 代码实现(标准栈版)

java

运行

复制代码
import java.util.Stack;

public class ValidParentheses {
    public boolean isValid(String s) {
        // 奇数长度一定无效
        if (s.length() % 2 != 0) {
            return false;
        }

        Stack<Character> stack = new Stack<>();
        for (char c : s.toCharArray()) {
            // 遇到左括号,压入对应的右括号
            if (c == '(') {
                stack.push(')');
            } else if (c == '{') {
                stack.push('}');
            } else if (c == '[') {
                stack.push(']');
            } else {
                // 遇到右括号,判断栈是否为空,或栈顶不匹配
                if (stack.isEmpty() || stack.pop() != c) {
                    return false;
                }
            }
        }

        // 栈为空说明所有括号都正确闭合
        return stack.isEmpty();
    }
}

复杂度分析

  • 时间复杂度:O(n),只遍历一次字符串,每个字符入栈 / 出栈最多一次。
  • 空间复杂度:O(n),最坏情况(全是左括号)需要存储整个字符串长度的栈空间。

核心知识点总结

  1. 栈的核心应用场景:匹配问题、逆序问题、深度优先搜索(DFS)、表达式求值等。
  2. 括号匹配的关键:左括号入栈、右括号匹配出栈,最终栈空才有效。
  3. 优化技巧:先判断字符串长度是否为奇数,直接排除无效情况,提升效率。

二、寻找两个正序数组的中位数(LeetCode 4・困难)

题目描述

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的中位数

算法的时间复杂度应该为 O(log(m+n))。

示例:

plaintext

复制代码
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

解题思路

这道题的核心难点是时间复杂度要求 O(log(m+n)) ,因此不能用简单的合并数组法(时间复杂度 O(m+n)),必须用二分查找的思路。

核心思想

中位数的本质是:将两个数组的所有元素分成左右两部分,左部分元素个数 = 右部分元素个数(或差 1),且左部分最大值 ≤ 右部分最小值

我们的目标就是找到这个「分割线」:

  1. 为了简化计算,我们始终让 nums1 是长度更短的数组(m ≤ n),保证二分查找的效率。
  2. nums1 进行二分,找到分割点 i,则 nums2 的分割点 j = (m + n + 1) / 2 - i(保证左部分总元素个数 = 右部分总元素个数)。
  3. 定义四个边界值:
    • leftMax1nums1 左部分的最大值(i == 0 时为 -∞
    • rightMin1nums1 右部分的最小值(i == m 时为 +∞
    • leftMax2nums2 左部分的最大值(j == 0 时为 -∞
    • rightMin2nums2 右部分的最小值(j == n 时为 +∞
  4. 当满足 leftMax1 ≤ rightMin2leftMax2 ≤ rightMin1 时,分割线正确,计算中位数:
    • 总长度为奇数:中位数 = max(leftMax1, leftMax2)
    • 总长度为偶数:中位数 = (max(leftMax1, leftMax2) + min(rightMin1, rightMin2)) / 2
  5. 若不满足条件,调整二分边界:
    • leftMax1 > rightMin2:说明 nums1 分割点太靠右,需要左移(right = i - 1
    • leftMax2 > rightMin1:说明 nums1 分割点太靠左,需要右移(left = i + 1

Java 代码实现(标准二分版)

java

运行

复制代码
public class FindMedianSortedArrays {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 保证nums1是长度更短的数组,简化二分
        if (nums1.length > nums2.length) {
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }

        int m = nums1.length;
        int n = nums2.length;
        int left = 0;
        int right = m;
        // 左部分总元素个数 = (m + n + 1) / 2,保证奇数时左部分多1个
        int totalLeft = (m + n + 1) / 2;

        while (left <= right) {
            // nums1的分割点
            int i = left + (right - left) / 2;
            // nums2的分割点
            int j = totalLeft - i;

            // 边界处理:分割点在数组两端时,用无穷大/无穷小代替
            int leftMax1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
            int rightMin1 = i == m ? Integer.MAX_VALUE : nums1[i];
            int leftMax2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
            int rightMin2 = j == n ? Integer.MAX_VALUE : nums2[j];

            // 找到正确的分割线
            if (leftMax1 <= rightMin2 && leftMax2 <= rightMin1) {
                // 总长度为奇数
                if ((m + n) % 2 == 1) {
                    return Math.max(leftMax1, leftMax2);
                } else {
                    // 总长度为偶数
                    return (Math.max(leftMax1, leftMax2) + Math.min(rightMin1, rightMin2)) / 2.0;
                }
            } else if (leftMax1 > rightMin2) {
                // nums1分割点太靠右,左移
                right = i - 1;
            } else {
                // nums1分割点太靠左,右移
                left = i + 1;
            }
        }

        // 理论上不会走到这里
        return 0.0;
    }
}

复杂度分析

  • 时间复杂度:O(logmin(m,n)),只对长度更短的数组进行二分,满足题目要求的 O(log(m+n))。
  • 空间复杂度:O(1),仅使用常数级额外空间。

核心知识点总结

  1. 二分查找的高阶应用:不直接查找元素,而是查找「分割线」,通过分割线的位置计算中位数。
  2. 边界处理:必须处理分割点在数组两端的情况(用无穷大 / 无穷小代替),避免数组越界。
  3. 分割点计算j = (m + n + 1) / 2 - i 是核心公式,保证左右两部分元素个数相等(或差 1)。
  4. 奇偶长度统一处理 :通过 totalLeft = (m + n + 1) / 2,让奇数长度时左部分多 1 个,统一中位数计算逻辑。

三、算法思想对比与学习路径

表格

算法 核心思想 适用场景 时间复杂度
栈(Stack) 后进先出(LIFO) 括号匹配、逆序、DFS、表达式求值 O(n)
二分查找 分治,每次缩小一半范围 有序数组查找、中位数计算 O(logn)

学习建议

  1. 栈的学习:先掌握「有效括号」这道基础题,再进阶到「最长有效括号」「柱状图中最大的矩形」等难题,彻底吃透栈的应用。
  2. 二分查找的学习:先掌握基础有序数组的二分查找,再进阶到旋转数组、中位数等难题,重点理解「分割线」的思想。

四、总结

  • 有效括号:栈的经典入门题,核心是利用栈的后进先出特性匹配括号,是学习栈的必刷题。
  • 寻找两个正序数组的中位数:二分查找的天花板难题,核心是通过二分查找找到分割线,将两个数组分成左右两部分,从而计算中位数,是算法面试的高频难题。
相关推荐
独隅2 小时前
PyTorch 文本生成完整代码模板与深度解析
人工智能·pytorch·python
阿_旭2 小时前
基于YOLO26深度学习的骑行安全检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】
人工智能·python·深度学习·骑行安全检测
xiaotao1312 小时前
阶段零:Python 安装与虚拟环境(venv / Conda)
开发语言·人工智能·python·conda
黑剑客与剑2 小时前
pycdc-studio v0.1.8,支持Pyarmor 解密
python·pycdc·pyarmor·pycdc-studio
dr_yingli2 小时前
fMRI(4-1)统计分析报告生成器说明
开发语言·matlab
岁岁的O泡奶2 小时前
NSSCTF_reverse_[SWPUCTF 2022 新生赛]base64——[HDCTF 2023]easy_re
经验分享·python·逆向
阿Y加油吧2 小时前
二分查找进阶:旋转排序数组的两道经典题深度解析
数据结构·算法
wgzrmlrm742 小时前
Django怎么优雅发送邮件_Python配置SMTP后端实现异步通知
jvm·数据库·python
想带你从多云到转晴2 小时前
05、数据结构与算法---栈与队列
java·数据结构·算法