AcWing - 150 - 括号画家 = 栈

本文详细解析了一种使用栈实现的括号匹配算法,通过记录字符种类和匹配的最大长度,高效地解决了括号匹配问题。文章提供了完整的代码实现,并解释了算法思路。

https://www.acwing.com/problem/content/152/

思路:开一个pair栈,第一个元素存字符的种类,第二个元素存“这个字符之后曾经匹配过的最大长度”。

为什么是“这个字符之后”,我也很难说清楚,但是感觉这样是对的。某一次遇到右括号弹栈之后,顺便把栈顶的最大长度也update了。

一开始插入一个虚拟节点就不需要判空了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

char ss[100005];
stack<pair<char, int> > s;

int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    int sum = 0;
    scanf("%s", ss);
    int n = strlen(ss);
    s.push({'#', 0});
    for(int i = 0; i < n; ++i) {
        if(ss[i] == ')') {
            if(s.top().first == '(') {
                int tmp = s.top().second;
                s.pop();
                char c2 = s.top().first;
                int tmp2 = s.top().second;
                s.pop();
                s.push({c2, tmp2 + tmp + 2});
                sum = max(sum, tmp2 + tmp + 2);
            } else {
                s.push({ss[i], 0});
            }
        } else if(ss[i] == ']') {
            if(s.top().first == '[') {
                int tmp = s.top().second;
                s.pop();
                char c2 = s.top().first;
                int tmp2 = s.top().second;
                s.pop();
                s.push({c2, tmp2 + tmp + 2});
                sum = max(sum, tmp2 + tmp + 2);
            } else {
                s.push({ss[i], 0});
            }
        } else if(ss[i] == '}') {
            if(s.top().first == '{') {
                int tmp = s.top().second;
                s.pop();
                char c2 = s.top().first;
                int tmp2 = s.top().second;
                s.pop();
                s.push({c2, tmp2 + tmp + 2});
                sum = max(sum, tmp2 + tmp + 2);
            } else {
                s.push({ss[i], 0});
            }
        } else {
            s.push({ss[i], 0});
        }
    }
    printf("%d\n", sum);
}

转载于:https://www.cnblogs.com/Inko/p/11423111.html

<think>嗯,我现在需要帮用户分析他们提供的C语言代码中的问题。这段代码是用来实现括号匹配的,使用结构来处理。首先,我得仔细阅读代码,理解每个部分的功能,然后找出潜在的错误或不足之处。 首先看结构定义部分。用户定义了一个`Stack`结构体,包含`data`和`next`指针,这应该是的节点。然后有一个`Shead`结构体,包含`num`记录中元素数量,以及`bottom`和`top`指针。看起来设计是合理的,用来管理的头部信息。 接下来是`initHead`函数,它分配内存并初始化`Shead`结构,设置`num`为0,`bottom`和`top`为NULL。这部分看起来没问题,正确初始化了的头结构。 然后看`Push`函数,这是关键部分。函数里有一个无限循环`while(1)`,通过`scanf`读取字符`x`。当遇到左括号时,将其压入中,处理正确,更新`top`和`num`。遇到右括号时,检查是否为空,若空则报错。否则,检查顶是否匹配当前右括号,若匹配则弹出顶元素。 这里可能存在几个问题。首先,`ptr`在每次循环开始时被初始化为`phead->bottom`,然后在弹出操作时,`while(ptr->next != phead->top)`这个循环可能有问题。例如,如果中只有一个元素,此时`ptr`指向`bottom`,即底,而`ptr->next`可能为NULL,这时候循环条件会导致访问空指针,或者无法正确找到顶的前一个节点。此外,当弹出元素时,如果中元素数量减少到0,应该将`bottom`和`top`都置为NULL,否则`top`可能仍然指向已释放的内存,导致后续操作出错。 另一个问题是,用户在处理右括号时,没有处理输入中的其他字符,比如非括号字符会被忽略,但代码中并没有跳过这些字符,可能会导致循环无法正确终止或者处理错误。比如,当输入中有字母或数字时,程序会继续等待输入,但用户可能希望只处理括号。 还有,`scanf("%c", &x)`会读取每一个字符,包括换行符和空格。如果用户在输入时用了回车,可能会被当作输入处理,导致意外的行为。比如,输入结束后按回车,可能会被当作一个字符读取,但此时程序可能还在等待下一个字符,导致无法正确结束循环。 另外,代码中的无限循环`while(1)`没有退出条件,只有在遇到右括号不匹配或空时才会`break`,但正常输入结束后如何退出循环呢?例如,当用户输入完所有括号后,程序如何终止循环并检查的状态?目前代码中没有处理EOF或结束输入的机制,会导致程序一直等待输入,无法自动结束。 还有,全局变量`char x`可能有问题,因为多个地方使用同一个全局变量,可能在多线程或复杂逻辑中出现问题,但在这个简单程序中可能影响不大,不过通常建议将变量定义在函数内部,避免全局使用。 再看弹出顶元素的部分。当匹配成功时,代码试图找到顶的前一个节点,将`ptr->next`设为NULL,然后释放顶元素,并将`top`指向`ptr`。但如果中只有一个元素,此时`ptr`初始化为`bottom`,即指向该元素。`ptr->next`是NULL,循环`while(ptr->next != phead->top)`不会执行,直接执行`ptr->next = NULL`,但此时`ptr`就是`phead->top`,所以这个操作可能不正确。例如,当只有一个元素时,`ptr`等于`phead->bottom`,也就是顶元素本身,这时候`ptr->next`是NULL,循环条件不满足,直接执行`ptr->next = NULL`,这实际上不会有任何变化,然后释放`top`,并将`top`设为`ptr`,此时`top`和`bottom`都指向同一个已被释放的内存,导致悬空指针。这会导致后续操作出错,比如再次访问`top->data`时出现未定义行为。 此外,弹出操作后,`phead->num`减1,但如果中元素数量减到0,应该同时将`bottom`设为NULL,否则`bottom`仍然指向已释放的内存,同样导致悬空指针。 还有,当处理右括号时,如果输入的是多个右括号,比如")]}",在第一次弹出后,可能已经为空,但后续的右括号仍会被处理,但由于已空,会导致错误。但代码中在每次处理右括号时都会检查是否为空,所以应该会正确报错。不过需要注意,一旦`break`跳出循环,后面的处理是否正确。 另外,代码中的`while(1)`循环没有处理输入结束的情况,比如遇到文件结束符EOF,或者用户输入结束。例如,当输入流关闭时,`scanf`会返回EOF,但代码中没有检查`scanf`的返回值,导致无限循环。应该处理这种情况,比如当`scanf`返回不等于1时退出循环。 还有,在匹配成功并弹出顶元素后,代码中的`ptr`遍历逻辑可能效率低下,特别是当很大时,每次弹出都需要从`bottom`开始遍历到`top`的前一个节点,时间复杂度为O(n),而的弹出操作应该是O(1)时间。这说明的实现有问题,应该使用双向链表或者保持`top`节点的前驱指针,以便快速找到前一个节点。或者,更好的方法是使用链表实现,其中每个新元素插入在头部,这样顶就是链表的头节点,弹出时只需要将头节点指向下一个节点即可,不需要遍历整个链表。当前的实现可能是将顶作为链表的尾节点,这样每次弹出都需要遍历整个链表,效率很低,尤其是当很大时。 例如,正确的实现应该是每次将新节点插入到链表头部,这样`top`始终指向头节点,`bottom`指向最后一个节点。但通常只需要`top`指针,因为插入和删除都在同一端。但此代码中的实现可能有问题,导致弹出操作需要遍历链表,效率低且容易出错。 总结问题: 1. 的实现方式不正确,导致弹出操作需要遍历链表,效率低,并且存在悬空指针的风险。 2. 处理右括号时,当中只有一个元素,弹出后的指针处理错误,导致`top`和`bottom`指向已释放的内存。 3. 无限循环没有退出条件,无法正常结束输入。 4. 未处理非括号字符,可能导致程序无法正确终止或处理错误。 5. 全局变量`x`可能带来不必要的副作用。 6. 没有处理`scanf`的返回值,导致无法检测输入结束。 7. 弹出元素后,当为空时,未正确设置`bottom`为NULL。 8. 当输入多个右括号导致空后,后续处理可能不正确。</think>### 代码问题分析 #### 1. **结构设计缺陷** 当前实现将新节点添加到链表尾部,导致弹出操作需要遍历整个链表(时间复杂度$O(n)$)。**正确做法**应使用头插法,使顶始终位于链表头部,弹出操作只需操作头节点(时间复杂度$O(1)$)。当前实现中`while(ptr->next!=phead->top)`的遍历逻辑会导致: - 空时访问野指针 - 中只剩一个元素时`ptr`指向已释放内存[^1] #### 2. **悬空指针问题** 当弹出最后一个元素时: ```c free(phead->top); // 释放顶节点 phead->top=ptr; // 此时ptr指向已释放的节点 phead->num--; ``` 未将`phead->bottom`置空,导致`bottom`指针悬空 #### 3. **输入处理缺陷** - 使用全局变量`x`可能引发意外修改 - 未处理换行符(输入中的回车会被当作字符处理) - 缺少输入终止条件(如EOF检测) #### 4. **逻辑漏洞** ```c while(1) { scanf("%c",&x); // 未检查scanf返回值 ... } ``` 未处理以下情况: - 输入流结束(需增加`if(scanf()!=1) break`) -括号字符(如字母/数字导致死循环) ### 改进建议 #### 1. 重构结构 ```c typedef struct node { char data; struct node *next; } Stack; typedef struct { Stack *top; int count; } StackHead; StackHead* create_stack() { StackHead *s = malloc(sizeof(StackHead)); s->top = NULL; s->count = 0; return s; } ``` #### 2. 优化入/出 ```c void push(StackHead *s, char c) { Stack *node = malloc(sizeof(Stack)); node->data = c; node->next = s->top; // 头插法 s->top = node; s->count++; } char pop(StackHead *s) { if(!s->top) return '\0'; Stack *temp = s->top; char data = temp->data; s->top = s->top->next; free(temp); s->count--; return data; } ``` #### 3. 完善输入处理 ```c int check_parentheses() { StackHead *stack = create_stack(); char c; while(scanf("%c", &c) == 1) { // 检测输入有效性 if(c == '\n') break; // 以回车结束输入 if(is_left_bracket(c)) { push(stack, c); } else if(is_right_bracket(c)) { if(stack->count == 0 || !is_matched(pop(stack), c)) { return 0; } } } int result = (stack->count == 0); destroy_stack(stack); return result; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值