括号合法性问题(匹配、删除与生成)

1.有效的括号(20-易)

题目描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:

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

示例

输入:s = "()[]{}"
输出:true

思路本题核心还是栈的应用:

  • 直接栈:遇到左括号,压栈右括号,其他都要出栈比较
  • 映射+栈:代码是压左括号,遇到右括号出栈(映射的key),比较映射值与栈顶值是否相同。

两种思路的共同点是遇到右括号一定出栈比较!

代码

class Solution {

    // 直接使用栈
    public boolean isValid(String s) {
        if (s.length() % 2 == 1) {
            return false;
        }
        Deque<Character> stack = new LinkedList<>();

        for (char c : s.toCharArray()) {
            if (c == '(') {
                stack.push(')');
            } else if (c == '[') {
                stack.push(']');
            } else if (c == '{'){
                stack.push('}');
            } else if (stack.isEmpty() || c != stack.pop()) {
                return false;
            }
        }
        return stack.isEmpty();
    }

    // 栈 + 映射(本质一样)
    private static final Map<Character,Character> map = new HashMap<>(){{
        put(')', '(');
        put(']', '[');
        put('}', '{');
    }};

    public boolean isValid(String s) {
        if (s.length() % 2 == 1) {
            return false;
        }
        Deque<Character> stack = new LinkedList<>();

        for(Character c : s.toCharArray()){
            if (map.containsKey(c)) {
                if (stack.isEmpty() || map.get(c) != stack.pop()) {
                    return false;
                }
            } else {
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }
}

2.括号生成(22-中)

题目描述:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

思路:本题可以使用深度优先遍历(推荐),可以记录当前递归得到的结果,所以不用进行回溯:

  • 产生右分支的时候,还受到左分支的限制,右边剩余可以使用的括号数量一定得在严格大于左边剩余的数量的时候,才可以产生分支,即右边使用的括号大于左边使用的括号就剪枝;
  • 终止条件:左右括号都用完

ps:可以使用括号累加或者减少,代码如下。

代码

// 做加法统计
public List<String> generateParenthesis(int n) {
    List<String> ans = new ArrayList<>();
    if (n == 0) return ans;
    dfs("", 0, 0, n, ans);
    return ans; 
}

public void dfs(String curStr, int left, int right, int n, List<String> ans) {
    if (left == n && right == n) {
        ans.add(curStr);
        return;
    }
    if (left < right) {  // 剪枝
        return;
    } 
    if (left < n) {
        dfs(curStr + "(", left + 1, right, n, ans);
    }
    if (right < n) {
        dfs(curStr + ")", left, right + 1, n, ans);
    }
}

// 做减法统计
public List<String> generateParenthesis(int n) {
    List<String> ans = new ArrayList<>();
    if (n == 0) return ans;
    dfs("", n, n, ans);
    return ans; 
}

public void dfs(String curStr, int left, int right, List<String> ans) {
    if (left == 0 && right == 0) {
        ans.add(curStr);
        return;
    }
    if (left > right) { // 剪枝
        return;
    } 
    if (left > 0) {
        dfs(curStr + "(", left - 1, right, ans);
    }
    if (right > 0) {
        dfs(curStr + ")", left, right - 1, ans);
    }
}

3.括号匹配深度(补充)

题目描述:"","()","()()","((()))"都是合法的括号序列 对于一个合法的括号序列我们又有以下定义它的深度:

  1. 空串""的深度是0

  2. 如果字符串"X"的深度是x,字符串"Y"的深度是y,那么字符串"XY"的深度为max(x,y)

  3. 如果"X"的深度是x,那么字符串"(X)"的深度是x+1

参考:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/dataStructures-algorithms

示例

输入描述:
输入包括一个合法的括号序列s,s长度length(2 ≤ length ≤ 50),序列中只包含'('和')'。

输出描述:
输出一个正整数,即这个序列的深度。
--------------------------------------------------------------------
输入:
(())
输出:
2

思路:遍历字符串,维护一个变量count记录深度。遇到(,则count+1,否则count - 1。

代码

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();
        int count = 0, max = 0;
        for (char ch : s.toCharArray()) {
            if (ch == '(') 
                count++;
            else 
                count--;
            max = Math.max(count, max);
        }
        sc.close();
        System.out.println(max);
    }
}

4.最长有效括号(32-难)

题目描述:给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

思路对于括号匹配问题一般使用栈进行判断,栈中存放左括号的下标,便于计算最大长度:

  • 入栈操作:遇到 ( ,将其下标入栈,等待匹配;注意:为了避免栈为空报错:开始压入-1,过程中用一个右括号下标作为标志。
  • 出栈操作:遇到 ) ,出栈更新最大长度。

另外,官方给出了一种不需要额外空间的解法,维持两个计数器left和right:

  • 遍历字符串,遇到左括号,left增加,否则right增加,当left == right时,计算此时长度。
  • 当right > left时,我们开始,即left和right置0。
  • 注意:但是对于 (() ,左括号一直大于右括号,我们没法得到最大长度,即left == right,其实,我们只需要倒序再遍历一遍即可。

代码

// 辅助栈
public int longestValidParentheses(String s) {
    Deque<Integer> stack = new LinkedList<>();
    if (s == null || s.length() == 0) {
        return 0;
    } 
    int ans = 0;
    stack.push(-1);  // 避免开始即为右括号

    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            stack.push(i);
        } else {
            stack.pop();
            if (stack.isEmpty()) {
                stack.push(i);
            } else {
                ans = Math.max(ans, i - stack.peek());
            }
        }
    }
    return ans;
}

// 遍历计数
public int longestValidParentheses(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    } 
    int ans = 0;
    int left = 0, right = 0;

    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            left++;
        } else {
            right++;
        }
        if (left == right) {
            ans = Math.max(ans, left + right);
        } else if (right > left) {
            left = right = 0;
        }
    }

    left = right = 0;
    for (int i = s.length() - 1; i >= 0; i--) {
        if (s.charAt(i) == '(') {
            left++;
        } else {
            right++;
        }
        if (left == right) {
            ans = Math.max(ans, left + right);
        } else if (left > right) {
            left = right = 0;
        }
    }
    return ans;
}

5.有效的括号字符串(678-中)

题目描述:给定一个只包含三种字符的字符串:*,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:

  • 左右括号必须匹配,左括号在前,右括号在后
  • *可以为单个左右括号或者一个空格
  • 空字符串有效

示例

输入: "(*)"
输出: True

输入: "(*))"
输出: True

思路:基本匹配问题,想到使用栈,关键是如何处理*问题:使用双栈,但是栈中存的是下标,为什么呢?当我们遍历完字符串时,如果两栈中都还有元素,那么我们就可以比较下标判断是否可以构成有效括号,比如 *( 就不可以。

  • 遇到左括号和星号,入栈对应下标
  • 遇到有括号,优先使用left栈中字符,如果没有,使用star栈中星号进行匹配
  • 解决栈中剩余元素下标,最终左括号栈为空返回true(即全部匹配成功)

还有一种类似最长有效括号,使用计数法,正反两遍,判断多余左括号的情况。代码如下。

代码

// 辅助栈
public boolean checkValidString(String s) {
    Deque<Integer> leftStack = new LinkedList<>();
    Deque<Integer> starStack = new LinkedList<>();
    if (s == null || s.length() == 0) {
        return true;
    }
    for (int i = 0; i < s.length(); ++i) {
        if (s.charAt(i) == '(') {
            leftStack.push(i);
        } else if (s.charAt(i) == '*') {
            starStack.push(i);
        } else {
            if (leftStack.isEmpty() && starStack.isEmpty()) {
                return false;
            } else if (!leftStack.isEmpty()) {
                leftStack.pop();
            } else {
                starStack.pop();
            }
        }
    }

    while (!leftStack.isEmpty() && !starStack.isEmpty()) {
        if (leftStack.peek() > starStack.peek()) {
            return false;
        }
        leftStack.pop();
        starStack.pop();
    }
    return leftStack.isEmpty();
}

// 计数法
public boolean checkValidString(String s) {
    int countL = 0, countLS = 0;
    int n = s.length();
    for (int i = 0; i < n; ++i) {
        if (s.charAt(i) == '(') {
            countL++;
        } else if (s.charAt(i) == '*') {
            countLS++;
        } else {
            if (countL > 0) {
                countL--;
            } else if (countLS > 0) {
                countLS--;
            } else {
                return false;
            }
        }
    }

    int countR = 0, countRS = 0;
    for (int i = n - 1; i >= 0; --i) {
        if (s.charAt(i) == ')') {
            countR++;
        } else if (s.charAt(i) == '*') {
            countRS++;
        } else {
            if (countR > 0) {
                countR--;
            } else if (countRS > 0) {
                countRS--;
            } else {
                return false;
            }
        }
    }
    return true;
}

6.删除无效括号(301-中)

题目描述:给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

示例

输入:s = "(a)())()"
输出:["(a())()","(a)()()"]

思路:一般对于括号的有效性,一般使用栈和计数法()。本题使用dfs+计数法:

  • 提前确定dfs计数得分的可能最大值(便于确定边界)
  • 对于枚举括号类型,在不越界的情况下,括号可以选择加或者不加
  • 根据题意,一般类型选择直接加入结果
  • 结果set去重:遍历数组结束,查看得分(score == 0保存这个结果,并更新dfs最大子串的长度),不满足则直接return
  • 最终根据全局变量len,统计最终的结果

代码

class Solution {

    // 记录dfs过程中最大的子串
    private int len = 0;

    public List<String> removeInvalidParentheses(String s) {
        // 对结果进行去重
        Set<String> all = new HashSet<>();
        char[] chs = s.toCharArray();
        int l = 0, r = 0;

        for (char c : chs) {
            if (c == '(') {
                l++;
            } else if (c == ')'){
                r++;
            }
        }
        // 提前dfs可能的最大结果
        int max = Math.min(l, r);

        dfs(chs, 0, 0, max, "", all);
        List<String> ans = new ArrayList<>();
        for (String str : all) {
            // 挑选符合要求的结果
            if (str.length() == len) {
                ans.add(str);
            }
        }
        return ans;
    }

    private void dfs(char[] chs, int i, int score, int max, String cur, Set<String> ans) {
        if (i == chs.length) {
            if (score == 0 && cur.length() >= len) {
                // 找到合法的括号组合,加入结果集
                len = Math.max(len, cur.length());
                ans.add(cur);
            }
            return;
        }

        if (chs[i] == '(') {
            // 通过得分,检查左括号不越界(加或者不加)
            if (score + 1 <= max) {
                dfs(chs, i + 1, score + 1, max, cur + "(", ans);
            }
            dfs(chs, i + 1, score, max, cur, ans);
        } else if (chs[i] == ')') {
            // 通过得分,检查右括号不越界(加或者不加)
            if (score > 0) {
                dfs(chs, i + 1, score - 1, max, cur + ")", ans);
            }
            dfs(chs, i + 1, score, max, cur, ans);
        } else {
            // 一般字符直接加入结果
            dfs(chs, i + 1, score, max, cur + String.valueOf(chs[i]), ans);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容