题解 | 最长有效括号

题干分析:

题目要求我们针对一个只含有'('和')'字符的字符串中选取最长的可匹配的括号字串,返回子串的长度.

首先关于括号匹配,由于匹配的括号一定为一对,因此我们可以确定这个返回值是个偶数(虽然基本没啥用)

算法思路:

我们有至少两个思路.

方法一便是直接模拟,我们创建一个既能存储整数,又能存储字符的栈(可以使用结构体),我们扫描整个字符串,

遇到'('便压栈,

遇到')':

首先判断栈顶是否为'(',

如果是,则匹配成功,将栈顶元素弹出,创建一个整数2表示已匹配字符串长度,然后持续弹出栈顶为数字的元素并相加,直到栈顶再次为字符,保证整数合并;

如果栈顶不是'('但是是数字,我们弹出该数字暂存,然后判断下一个字符是否为'(',

如果是则匹配成功,暂存数字+2,之后持续合并数字后压栈,

如果不是,暂存数字压栈,字符')'也压栈;

同时其他情况(栈顶为')')也直接压栈.

最后持续弹栈记录栈中最大的整数即为可匹配的最长括号字串.

方法二则更加巧妙:

我们已知符合要求的字串一定是'('的数量=')'的数量,我们假设从左侧开始扫描字符串并持续分别记录两种字符串的数量,当我们一旦发现两种字符数量相等便记录一下最大值.但我们也得注意,从左侧开始扫描只有"()"是合法的匹配方式,换句话说")("是不视为匹配发生的,因此扫描过程中但凡发现')'的数量多于'('的数量,证明无法匹配,两者均归零,此时开始就是下一个能够匹配的字串长度了.

同时我们还得注意,此方法到此为止并不能判断'('的数量多于')'时的情况,我们将上述从左到右扫描的方式反过来,再次进行扫描记录便能解决这个问题,虽然这样做会做些许重复工作,但最终结果是正确的.

实现代码:

struct CI {
        int i; // 整数表示已匹配成功的括号组
        char c;
        bool isInt;

        explicit CI(const int _i) : i(_i), c(0), isInt(true) {
        }

        explicit CI(const char _c) : i(0), c(_c), isInt(false) {
        }
    };

int longestValidParentheses(string s) {
        stack<CI> st;
        for (auto c: s) {
            // '('直接压栈
            if (c == '(') st.emplace(c);
            else {
                // ')'且栈顶为'('
                if (!st.empty() && st.top().c == '(') {
                    st.pop();
                    CI tmp(2);
                    // 持续合并整数
                    while (!st.empty() && st.top().isInt) {
                        tmp.i += st.top().i;
                        st.pop();
                    }
                    // 压入整数
                    st.push(tmp);
                } else if (!st.empty() && st.top().isInt) {
                    // ')'且栈顶为整数
                    CI tmp = st.top();
                    st.pop();
                    // 栈顶元素为'('
                    if (!st.empty() && st.top().c == '(') {
                        st.pop();
                        tmp.i += 2;
                        // 持续合并整数
                        while (!st.empty() && st.top().isInt) {
                            tmp.i += st.top().i;
                            st.pop();
                        }
                        st.push(tmp);
                    } else {
                        st.push(tmp);
                        st.emplace(c);
                    }
                } else st.emplace(c);
            }
        }
        int _max = 0;
        while (!st.empty()) {
            if (st.top().isInt) _max = max(_max, st.top().i);
            st.pop();
        }
        return _max;
    }
int longestValidParentheses(string s) {
        int maxLen = 0, left = 0, right = 0;
        const int n = s.length();

        // 从左到右:统计左右括号数量
        for (int i = 0; i < n; ++i) {
            if (s[i] == '(') left++;
            else right++;

            if (left == right) maxLen = max(maxLen, 2 * left);
            else if (right > left) left = right = 0; // 无效,重置
        }

        // 从右到左:处理左括号多余情况
        left = right = 0;
        for (int i = n - 1; i >= 0; --i) {
            if (s[i] == '(') left++;
            else right++;

            if (left == right) maxLen = max(maxLen, 2 * left);
            else if (left > right) left = right = 0; // 无效,重置
        }

        return maxLen;
    }

复杂度分析:

针对方法一:

每个元素最多进出栈一次,辅助判断的整数消耗的也是常量级别的时间与空间,因此时间复杂度与空间复杂度均为O(n).

针对方法二:

需要对字符串进行两次扫描,对每个字符进行操作只需要常数的时间,因此时间复杂度为O(n).

全过程只使用到了常数的额外空间,因此空间复杂度为O(1).

全部评论

相关推荐

昨天 16:45
莆田学院 Java
那天早上,我提前了四十分钟到公司楼下,手里拿着入职通知反复核对楼层,像在确认一场重要约会的密码。走进部门时,工位已经布置好:一台崭新的电脑,一本厚重的员工手册,还有行政同事留下的欢迎便签。一切都标准、周到,却也带着一种等待被书写的空白。我坐下,按下主机按钮,风扇启动的轻微嗡鸣,成了我职业生涯的第一个背景音。最考验人的环节是“自我介绍”。当经理将我带到团队中间,十几道友善但审视的目光投来时,我提前准备好的那套“学校-项目-兴趣”的标准话术突然卡壳。最后只干涩地说出名字和岗位,末尾加了一句“请大家多指教”,声音比预想中轻。好在同事们的掌声及时响起,一位前辈紧接着玩笑说:“指教不敢当,以后代码写错了记得请奶茶就行。”&nbsp;空气里的那层薄冰,就在这样的笑声中悄然裂开。真正让我感到“成为其中一员”的瞬间,发生在傍晚。导师发给我一个真实的、但边界清晰的小任务,并附上了详细的内部文档链接。就在我沉浸于配置本地环境时,身后的灯光渐次亮起——原来早已过了下班时间,而隔壁工位的几位同事仍专注地对着屏幕。那一刻没有人在宣扬加班文化,但我却奇异地感到安心:这里不是一个到点就清空的地方,它允许你按照自己的节奏,去进入、去探索。关电脑时,我保存好了未完成的环境配置,心里知道,明天还会回到这里,继续。
入职第一天
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务