惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
SecWiki News
SecWiki News
Martin Fowler
Martin Fowler
T
Tor Project blog
N
Netflix TechBlog - Medium
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
V
Visual Studio Blog
GbyAI
GbyAI
PCI Perspectives
PCI Perspectives
D
DataBreaches.Net
Jina AI
Jina AI
H
Heimdal Security Blog
云风的 BLOG
云风的 BLOG
P
Privacy International News Feed
A
About on SuperTechFans
J
Java Code Geeks
美团技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News | PayPal Newsroom
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
博客园 - 司徒正美
C
Check Point Blog
T
Threat Research - Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
The Last Watchdog
The Last Watchdog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
I
InfoQ
阮一峰的网络日志
阮一峰的网络日志
Cisco Talos Blog
Cisco Talos Blog
W
WeLiveSecurity
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
博客园 - Franky
Security Archives - TechRepublic
Security Archives - TechRepublic

Kivinsae's Nest

2025年年度总结 2024年年度总结 2023年年度总结 让我们粗鄙一点 写好项目技术文档 - CH02. 文档标准设计 窜访游记:红山森林动物园 写好项目技术文档 - CH01. Repository 设计 NuGet 应用实践:手搓 Vitual Studio Choco 安装包 关于 初码先生 某推中典型诡辩论部分的拆解 关于说话:不要叠加态,不要反问句 杂谈:聊聊高尔夫球杆 TCC 数据库:开发者的噩梦,攻击者的狂欢 加密货币杂谈(三):虚妄和繁荣 | Illusion and Prosperity 如何合法申请一张欧盟 N26 银行信用卡用于 ChatGPT 加密货币杂谈(二):黄金矿工 加密货币杂谈(一):创生与加密 2022年年度总结 LeetCode Weekly Contest 301 自我解题思路 聊聊排版和『盘古之白』
LeetCode 括号问题详解
Kivinsae Fang · 2022-06-18 · via Kivinsae's Nest

如果经常刷LeetCode的话,其实应该会对有一道题目印象非常深刻。那就是括号问题 [20] Valid Parentheses 。第一次看到这道题的话多少都会楞一下,因为乍一看题目内容非常的简单,无非是判断括号闭合的合法与否,但是仔细想想其实也有不少坑。尽管这道题是LeetCode中序号最小的堆栈问题,但是第一次做不一定能反应过来是堆栈。

这类题在LeetCode中一共有三道。分别是:

但是很离谱的是,我个人做下来的感觉 [20] Valid Parentheses 才是Medium的难度,剩下两道毫无疑问是Easy难度的, 甚至它们因为太简单可能做的时候不一定能直观感受到堆栈的思想。当然,后两题如果追求极限的低复杂度(比如O(1))的话,还是很有挑战的。

还是来聊聊这类题目的基本思路吧,这里先以 [20] Valid Parentheses 为例。其实基本思路不复杂,如果你理解堆栈的概念,其实一瞬间就能想明白是怎么一回事情。

+────+────+────+────+────+────+
| {  | [  | (  | (  | [  | (  |  <-- ')' Accept
+────+────+────+────+────+────+
|   +────+────+────+────+────+────+
--> | {  | [  | (  | (  | [  | X  |  <-- '('&')' Clear
    +────+────+────+────+────+────+
+────+────+────+────+────+────+
| {  | [  | (  | (  | [  | (  |  <-- ']' Dismatch
+────+────+────+────+────+────+

从上图可以看到对于每一个左括号,我们都按照顺序(先进后出,后进先出)放入堆栈中。但是对于右括号,我们要做的是两个核心步骤:

  1. 将其和堆栈最上层的那个左括号进行对比,如果成对,则安全匹配
  2. 完成匹配后,将这个最上层的左括号消掉。然后如此循环往复,如果能够消除干净,则字符串Syntax合法。

从上面两个步骤又可以分别写出两个对应的逻辑,对于步骤一,所谓的对比就是对右括号本身取对应的左括号

char left(char c) {
    if (c == 41) {return 40;}
    else if (c == 93) {return 91;}
    else {return 123;}
}

对于步骤二,只需要对整个输入的字符串进行逐字对比,并不停调用步骤一的left(char c)

for (int i = 0; i < len; ++i) {
    if (s[i] == 40 || s[i] == 91 || s[i] == 123) {++idx; status[idx] = s[i];}
    else {
        if (left(s[i]) == status[idx]) {--idx;}
        else {return false;}
    }
}

将两个函数进行拼合,并对边界情况进行处理之后,便是 [20] Valid Parentheses 的答案了(个人习惯用数字替代ascii码的对比):

char left(char c) {
    if (c == 41) {return 40;}
    else if (c == 93) {return 91;}
    else {return 123;}
}

bool isValid(char * s){
    int len=strlen(s);
    if (len == 1) {return false;}
    int *status = (int *)malloc(sizeof(int) * 10001);
    memset(status, 0, 10001);
    int idx = 0;

    for (int i = 0; i < len; ++i) {
        if (s[i] == 40 || s[i] == 91 || s[i] == 123) {++idx; status[idx] = s[i];}
        else {
            if (left(s[i]) == status[idx]) {--idx;}
            else {return false;}
        }
    }
    bool res;
    return res = (idx == 0) ? true : false;
}

其实这类括号题目,最重要就是第一时间要意识到是堆栈的思路,其次是需要对括号字符串有取左或者取右的直觉。这样其实就可以超快速解题了。

下面来看看另外两道标注了Medium,实则更简单的括号类问题。

先以 [921] Minimum Add to Make Parentheses Valid 为例,这道题真的不知道是怎么被评定为Medium的,其实这里本质就两种情况:入栈的是左括号 & 入栈的是右括号。对于入栈的是左括号的情况,用一个状态变量status记录一下还欠缺右括号的数量。对于入栈的是右括号,如果左括号还有结余,也就是status > 0,直接进行堆栈抵消;对于左括号没有结余的情况,就必须让左括号进行增补,也就是++res;。事实上这已经是一个一维堆栈了,而 [20] Valid Parentheses 至少还是一个二维堆栈。

int minAddToMakeValid(char * s){
    int len = strlen(s), status = 0, res = 0;
    for (int i = 0; i < len; ++i)
    {
        if (s[i] == 40) {++status;}
        else {
            if (status > 0) {--status;}
            else {++res;}
        }
    } 
    return res + status;
}

接下来我们再来聊聊 [1541] Minimum Insertions to Balance a Parentheses String。这道题完完全全就是 [921] Minimum Add to Make Parentheses Valid 的轻度升级版。唯一区别是把 ( & ) 的括号对异化为了 ( & )) 。这就意味着我们需要多做一件事情:把字符串先还原为Q921中的字符串,然后执行Q921的解法即可。这真的称不上是Medium难度。

需要新增的格式化步骤如下,主要是增加了对于单个右括号的增补处理,需要注意单个右括号在字符串最末尾的特殊情况

for (int i=0,j=0; i < len; ++i, ++j) {
    if (s[i] == 40) {new[j] = s[i];}
    else {
        if (i != len - 1 && s[i + 1] == 41) {new[j] = ')'; ++i;} 
        else {new[j] = ')'; ++res;}
    }
}

然后把格式化步骤和 [921] 的答案结合一下,即为 [1541] 的正确解法:

int minInsertions(char * s){
    int res = 0,len = strlen(s);
    char *new = (char *)malloc(sizeof(char) * 3 * len);
    memset(new, '\0', 3*len);

    for (int i=0,j=0; i < len; ++i, ++j) {
        if (s[i] == 40) {new[j] = s[i];}
        else {
            if (i != len - 1 && s[i + 1] == 41) {new[j] = ')'; ++i;} 
            else {new[j] = ')'; ++res;}
        }
    }
    int i = 0, status = 0;
    while (new[i] != '\0')
    {
        if (new[i] == 40) {++status;}
        else {
            if (status > 0) {--status;}
            else {++res;}
        }
        ++i;
    }
    
    return res + status*2;
}

总结: 这类括号问题的关键是什么相信也比较明了了,其实就是堆栈思路。无论是 [20] 的二维堆栈,还是后面两道题目的一维堆栈,其实本质是一样的。而难度本身也至多围绕其他方面展开,比如增加字符串预处理之类的。不过还是有点期待会不会在未来出现三维的堆栈。