栈题目:平衡括号字符串的最少插入次数

题目

标题和出处

标题:平衡括号字符串的最少插入次数

出处:1541. 平衡括号字符串的最少插入次数

难度

4 级

题目描述

要求

给你一个括号字符串 s \texttt{s} s,它只包含字符 ‘(’ \texttt{`('} ‘(’ ‘)’ \texttt{`)'} ‘)’。一个括号字符串被称为平衡的当它满足:

  • 任何左括号 ‘(’ \texttt{`('} ‘(’ 必须对应两个连续的右括号 ‘))’ \texttt{`))'} ‘))’
  • 左括号 ‘(’ \texttt{`('} ‘(’ 必须在对应的连续两个右括号 ‘))’ \texttt{`))'} ‘))’ 之前。

例如, "())" \texttt{"())"} "())" "())(())))" \texttt{"())(())))"} "())(())))" "(())())))" \texttt{"(())())))"} "(())())))" 都是平衡的, ")()" \texttt{")()"} ")()" "()))" \texttt{"()))"} "()))" "(()))" \texttt{"(()))"} "(()))" 都是不平衡的。

你可以在任意位置插入字符 ‘(’ \texttt{`('} ‘(’ ‘)’ \texttt{`)'} ‘)’ 使字符串平衡。

请你返回让 s \texttt{s} s 平衡的最少插入次数。

示例

示例 1:

输入: s   =   "(()))" \texttt{s = "(()))"} s = "(()))"
输出: 1 \texttt{1} 1
解释:第二个 ‘(’ \texttt{`('} ‘(’ 有与之匹配的两个 ‘))’ \texttt{`))'} ‘))’,但是第一个 ‘(’ \texttt{`('} ‘(’ 只有一个 ‘)’ \texttt{`)'} ‘)’。我们需要在字符串结尾额外增加一个 ‘)’ \texttt{`)'} ‘)’ 使字符串变成平衡字符串 "(())))" \texttt{"(())))"} "(())))"

示例 2:

输入: s   =   "())" \texttt{s = "())"} s = "())"
输出: 0 \texttt{0} 0
解释:字符串已经平衡了。

示例 3:

输入: s   =   "))())(" \texttt{s = "))())("} s = "))())("
输出: 3 \texttt{3} 3
解释:添加 ‘(’ \texttt{`('} ‘(’ 去匹配最开头的 ‘))’ \texttt{`))'} ‘))’,然后添加 ‘))’ \texttt{`))'} ‘))’ 去匹配最后一个 ‘(’ \texttt{`('} ‘(’

示例 4:

输入: s   =   "((((((" \texttt{s = "(((((("} s = "(((((("
输出: 12 \texttt{12} 12
解释:添加 12 \texttt{12} 12 ‘)’ \texttt{`)'} ‘)’ 得到平衡字符串。

示例 5:

输入: s   =   ")))))))" \texttt{s = ")))))))"} s = ")))))))"
输出: 5 \texttt{5} 5
解释:在字符串开头添加 4 \texttt{4} 4 ‘(’ \texttt{`('} ‘(’ 并在结尾添加 1 \texttt{1} 1 ‘)’ \texttt{`)'} ‘)’,字符串变成平衡字符串 "(((())))))))" \texttt{"(((())))))))"} "(((())))))))"

数据范围

  • 1 ≤ s.length ≤ 10 5 \texttt{1} \le \texttt{s.length} \le \texttt{10}^\texttt{5} 1s.length105
  • s \texttt{s} s 只包含 ‘(’ \texttt{`('} ‘(’ ‘)’ \texttt{`)'} ‘)’

前言

这道题和「使括号有效的最少添加」相似,都是需要插入最少的括号使得括号字符串平衡。区别在于,这道题中的每个左括号和两个右括号匹配。和「使括号有效的最少添加」一样,这道题也可以使用栈和计数的解法,具体做法有所不同。

解法一

思路和算法

平衡括号字符串中,每个左括号的右边都有两个连续的右括号和该左括号匹配。需要插入括号的情况有以下三种:

  • 如果右括号左边没有左括号,则需要插入左括号;

  • 如果左括号右边没有右括号,则需要插入右括号;

  • 如果连续的右括号数量是奇数,则需要插入右括号使连续的右括号数量变成偶数,才可能满足每两个连续的右括号和一个左括号匹配。

可以使用栈存储左括号,从左到右遍历字符串 s s s,计算使字符串 s s s 平衡的最少插入次数,遍历过程中需要记录下标,对于当前下标,进行如下操作:

  • 遇到左括号,则将左括号入栈,并将下标加 1 1 1

  • 遇到右括号,则需要使用两个连续的右括号和一个左括号匹配,依次进行如下操作:

    1. 如果栈不为空,则将栈顶的左括号出栈,表示该左括号被匹配,否则将插入次数加 1 1 1,表示需要插入一个左括号,然后使用插入的左括号匹配;

    2. 如果当前字符的后面一个字符是右括号(注意当前字符已经是右括号),则将下标加 2 2 2,表示使用连续两个右括号和左括号匹配,否则将插入次数加 1 1 1,并将下标加 1 1 1,表示需要插入一个右括号,然后使用连续两个右括号和左括号匹配。

遍历结束之后,如果栈不为空,则栈内的每个左括号都没有右括号匹配,对于栈内的每个左括号都需要插入两个右括号才能匹配,因此将插入次数加上栈内左括号的个数的 2 2 2 倍。

最终得到的插入次数即为使字符串 s s s 平衡的最少插入次数。

注意上述操作中,只计算需要插入的左括号个数和右括号个数,没有考虑插入括号的位置,这是因为每一次都可以在字符串的任意位置插入一个括号,无论是插入左括号还是右括号,都一定能找到合适的插入位置。

代码

class Solution {
    public int minInsertions(String s) {
        int insertions = 0;
        Deque<Character> stack = new ArrayDeque<Character>();
        int length = s.length();
        int index = 0;
        while (index < length) {
            char c = s.charAt(index);
            if (c == '(') {
                stack.push(c);
                index++;
            } else {
                if (!stack.isEmpty()) {
                    stack.pop();
                } else {
                    insertions++;
                }
                if (index < length - 1 && s.charAt(index + 1) == ')') {
                    index += 2;
                } else {
                    insertions++;
                    index++;
                }
            }
        }
        insertions += stack.size() * 2;
        return insertions;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每次入栈和出栈操作的时间都是 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。空间复杂度主要取决于栈空间,栈内的元素个数为 O ( n ) O(n) O(n)

解法二

思路和算法

也可以不用栈,而是使用计数的方式计算使字符串 s s s 平衡的最少插入次数。

使用 insertions \textit{insertions} insertions 记录添加次数,使用 count \textit{count} count 记录未匹配的左括号的数量,初始时 insertions = count = 0 \textit{insertions} = \textit{count} = 0 insertions=count=0

从左到右遍历字符串 s s s,遍历过程中记录下标,对于当前下标,进行如下操作:

  • 遇到左括号,则将 count \textit{count} count 1 1 1,并将下标加 1 1 1

  • 遇到右括号,则需要使用两个连续的右括号和一个左括号匹配,依次进行如下操作:

    1. 如果 count > 0 \textit{count} > 0 count>0,则将 count \textit{count} count 1 1 1,表示一个左括号被匹配,否则将插入次数加 1 1 1,表示需要插入一个左括号,然后使用插入的左括号匹配;

    2. 如果当前字符的后面一个字符是右括号(注意当前字符已经是右括号),则将下标加 2 2 2,表示使用连续两个右括号和左括号匹配,否则将插入次数加 1 1 1,并将下标加 1 1 1,表示需要插入一个右括号,然后使用连续两个右括号和左括号匹配。

遍历结束之后, count \textit{count} count 为尚未匹配的左括号的数量,对于每个尚未匹配的左括号都需要插入两个右括号,因此将 insertions \textit{insertions} insertions count × 2 \textit{count} \times 2 count×2

最终得到的 insertions \textit{insertions} insertions 即为使字符串 s s s 平衡的最少插入次数。

代码

class Solution {
    public int minInsertions(String s) {
        int insertions = 0;
        int count = 0;
        int length = s.length();
        int index = 0;
        while (index < length) {
            char c = s.charAt(index);
            if (c == '(') {
                count++;
                index++;
            } else {
                if (count > 0) {
                    count--;
                } else {
                    insertions++;
                }
                if (index < length - 1 && s.charAt(index + 1) == ')') {
                    index += 2;
                } else {
                    insertions++;
                    index++;
                }
            }
        }
        insertions += count * 2;
        return insertions;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每次更新计数的时间都是 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值