跳转至

典型问题——括号相关的问题

以括号为背景的题目在LeetCode出现的频率是很高的,往往只是题目条件做了简单的修改,解题方法会发生很大的变化,涉及的方法从贪心、尺取、递归到动态规划等,所以来做个总结,从中提取最简模型。

括号匹配问题

以LeetCode 20 Valid Parentheses为例,这个题目估计是学栈的时候基本都会遇到的问题,所以这类问题也可以认为是**括号有效性类型**的问题。

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

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

//LeetCode 20 Valid Parentheses
class Solution {
public:
    bool isValid(string s) {
        std::ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        cout.tie(NULL);

        stack<char> store;
        for (auto e : s) {
            switch(e) {
                case '(': case '[': case '{' : 
                    store.push(e); break;
                case ')':
                    if (store.empty() || store.top() != '(') return false;
                    else {store.pop(); break;}
                case ']':
                    if (store.empty() || store.top() != '[') return false;
                    else {store.pop(); break;}
                default:
                    if (store.empty() || store.top() != '{') return false;
                    else store.pop();
            }
        }

        return store.empty();
    }
};

括号生成问题

比如LeetCode 22。给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且**有效的**括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

一般这种需要生成所有结果的,常见的思考方向是递归或者深搜。这道题稍加修改就可以伪装成计数问题,比如问给定n,最多可以生成多少组不同的有效的括号,实际上还是生成问题。

//LeetCode 22
class Solution {
public:
    vector<string> res;
    vector<string> generateParenthesis(int n) {
        std::ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        cout.tie(NULL);

        solve(0, 0, n, "");
        return res;
    }

    void solve(int left, int right, int n, string tmp)
    {
        if (left == n && right == n) {
            res.push_back(tmp);
            return;
        }

        if (left < n) solve(left + 1, right, n, tmp + "(");
        if (right < left) solve(left, right + 1, n, tmp + ")");
    }
};

括号修改问题

**括号修改**问题涉及最多的就是**增加**括号和**删除**括号。比如LeetCode 1249. 移除无效的括号,301. 删除无效的括号,1021. 删除最外层的括号, 921 使括号有效的最少添加(UVA 1626)。

另外还有一种题型,比如给定一串数字和运算符,让添加括号生成不同的结果;或者一个连乘,添加括号让计算次数最小等,虽然是以添加括号为背景,但是很明显,这考察的是区间DP,典型的相似问题就是取石子,所以这种类型的暂时不会归结到这里。

括号计数问题

一般这类问题常见的解法就是贪心或动态规划。典型以LeetCode 32最长有效括号为例(扩充形式为洛谷-P1944)。当然贪心也是很好的解法。

//LeetCode  32
class Solution {
public:
    int longestValidParentheses(string s) {
        std::ios_base::sync_with_stdio(false);
        cin.tie(NULL);
        cout.tie(NULL);

        int n = s.size();
        vector<int> d(n + 1, 0);
        int res = 0;
        for (int i = 1; i < n; ++i) {
            if (s[i] == ')' && i > d[i - 1] && s[i - 1 - d[i - 1]] == '(') {
                d[i] = d[i - 1] + 2 + (i - 2 - d[i - 1] >= 0 ? d[i - 2 - d[i - 1]] : 0);
                res = max(res, d[i]);
            }
        }

        return res;
    }
};

括号树

典型题目:

  • LeetCode 1111Maximum Nesting Depth of Two Valid Parentheses Strings (和括号树是一个背景的题目,贪心求解,类似于32的贪心解法)
  • LeetCode 22 Generate Parentheses(括号生成问题,递归求解)
  • 面试题08.09 括号
  • LeetCode 32 Longest Valid Parentheses(动态规划或尺取法)和洛谷P1944基本一致,提高/省选难度
  • LeetCode 856 Score of Parentheses
  • LeeCode 1096 Brace Expansion II
  • LeetCode 20 Valid Parentheses(栈,括号匹配问题)
  • LeetCode 1249 Minimum Remove to Make Valid Parentheses
  • LeetCode 678 Valid Parenthesis String
  • LeetCode 301 Remove Invalid Parentheses
  • LeetCode 921 Minimum Add to Make Parentheses Valid
  • LeetCode 1021 Remove Outermost Parentheses
  • LeetCode 1190 Reverse Substrings Between Each Pair of Parentheses
  • 一本通-1353 表达式括号匹配
  • 一本通-1572 括号配对
  • 一本通-1987 括号树
  • 洛谷-P5658 括号树
  • 洛谷-P1241 括号序列(括号树背景的题目)
  • 洛谷-P2308 添加括号
  • UVA 1662 Brackets Removal
  • UVA 1626 Brackets Sequence(和LeetCode 921是一个问题)
  • 洛谷-P3215 括号修复/括号序列(和括号树背景类似)
  • 洛谷 P2651 添加括号III
  • 洛谷-P1944 最长括号匹配(括号序列背景)和POJ 2955是一个解法。
  • 洛谷-P1739 表达式括号匹配
  • UVA 673 平衡的括号 Parentheses Balance
  • 洛谷-P3310 括号序列计数
  • POJ 2955 Backets(区间DP)