题目

标题和出处

标题:反转每对括号间的子串

出处:1190. 反转每对括号间的子串

难度

5 级

题目描述

要求

给出一个字符串 栈题目:反转每对括号间的子串 - 图1(仅含有小写英语字母和括号)。

请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。

注意,结果中不应包含任何括号。

示例

示例 1:

输入:栈题目:反转每对括号间的子串 - 图2%22%7D#card=math&code=%5Ctexttt%7Bs%20%3D%20%22%28abcd%29%22%7D&id=kZazB)
输出:栈题目:反转每对括号间的子串 - 图3

示例 2:

输入:栈题目:反转每对括号间的子串 - 图4i)%22%7D#card=math&code=%5Ctexttt%7Bs%20%3D%20%22%28u%28love%29i%29%22%7D&id=PvVLb)
输出:栈题目:反转每对括号间的子串 - 图5
解释:先反转子字符串 栈题目:反转每对括号间的子串 - 图6,然后反转整个字符串。

示例 3:

输入:栈题目:反转每对括号间的子串 - 图7)el)%22%7D#card=math&code=%5Ctexttt%7Bs%20%3D%20%22%28ed%28et%28oc%29%29el%29%22%7D&id=LOW8J)
输出:栈题目:反转每对括号间的子串 - 图8
解释:先反转子字符串 栈题目:反转每对括号间的子串 - 图9,接着反转 栈题目:反转每对括号间的子串 - 图10,然后反转整个字符串。

示例 4:

输入:栈题目:反转每对括号间的子串 - 图11p)q%22%7D#card=math&code=%5Ctexttt%7Bs%20%3D%20%22a%28bcdefghijkl%28mno%29p%29q%22%7D&id=P3Nm8)
输出:栈题目:反转每对括号间的子串 - 图12

数据范围

  • 栈题目:反转每对括号间的子串 - 图13
  • 栈题目:反转每对括号间的子串 - 图14 中只有小写英语字母和括号
  • 题目测试用例确保所有括号都是成对出现的

解法一

思路和算法

由于给定的字符串 栈题目:反转每对括号间的子串 - 图15 的所有括号都是成对出现的,因此每个右括号都有一个左括号匹配。从左到右遍历字符串 栈题目:反转每对括号间的子串 - 图16,每次遇到的右括号都是最内层的右括号。由于题目要求从最内层开始反转,因此每次遇到右括号时,就找到对应的左括号,反转这对左右括号之间的字符串,然后去掉这对左右括号,遍历结束时即可得到反转后的字符串。

对于每个右括号需要寻找匹配的左括号,可以使用栈实现。

从左到右遍历字符串 栈题目:反转每对括号间的子串 - 图17,模拟反转操作。对于每个字符,如果该字符是字母或左括号,则将该字符入栈,如果该字符是右括号,则将栈内的字符出栈,直到遇到左括号,将左括号出栈之后,将出栈的字符按照出栈顺序依次入栈。在上述操作之后,最内层的左右括号被去掉,且左右括号之间的字符被反转。

遍历结束时,栈内元素按照从栈底到栈顶的顺序得到的字符串即为结果字符串。由于只有栈顶元素可以出栈,因此需要将栈内元素依次出栈拼接到结果字符串,然后将结果字符串反转。

下面是示例 3 的模拟过程。

字符串 栈题目:反转每对括号间的子串 - 图18,长度为 栈题目:反转每对括号间的子串 - 图19

下标 栈题目:反转每对括号间的子串 - 图20栈题目:反转每对括号间的子串 - 图21 的字符都是字母和左括号,因此每个字符都入栈。此时栈为 栈题目:反转每对括号间的子串 - 图22,其中左边为栈底,右边为栈顶。

下标 栈题目:反转每对括号间的子串 - 图23 的字符是右括号,因此将 栈题目:反转每对括号间的子串 - 图24栈题目:反转每对括号间的子串 - 图25栈题目:反转每对括号间的子串 - 图26 出栈,然后将 栈题目:反转每对括号间的子串 - 图27栈题目:反转每对括号间的子串 - 图28 入栈(注意入栈顺序和出栈顺序相同)。此时栈为 栈题目:反转每对括号间的子串 - 图29

下标 栈题目:反转每对括号间的子串 - 图30 的字符是右括号,因此将 栈题目:反转每对括号间的子串 - 图31栈题目:反转每对括号间的子串 - 图32栈题目:反转每对括号间的子串 - 图33栈题目:反转每对括号间的子串 - 图34栈题目:反转每对括号间的子串 - 图35 出栈,然后将 栈题目:反转每对括号间的子串 - 图36栈题目:反转每对括号间的子串 - 图37栈题目:反转每对括号间的子串 - 图38栈题目:反转每对括号间的子串 - 图39 入栈。此时栈为 栈题目:反转每对括号间的子串 - 图40

下标 栈题目:反转每对括号间的子串 - 图41 到下标 栈题目:反转每对括号间的子串 - 图42 的字符都是字母,因此每个字符都入栈。此时栈为 栈题目:反转每对括号间的子串 - 图43

下标 栈题目:反转每对括号间的子串 - 图44 的字符是右括号,由于与当前右括号匹配的左括号位于栈底,因此将栈内元素全部出栈,然后将除了左括号以外的出栈元素按照出栈顺序依次入栈。此时栈为 栈题目:反转每对括号间的子串 - 图45

此时遍历结束,将栈内元素依次出栈拼接到结果字符串,得到 栈题目:反转每对括号间的子串 - 图46,然后将结果字符串反转,得到 栈题目:反转每对括号间的子串 - 图47

代码

  1. class Solution {
  2. public String reverseParentheses(String s) {
  3. Deque<Character> stack = new ArrayDeque<Character>();
  4. int length = s.length();
  5. for (int i = 0; i < length; i++) {
  6. char c = s.charAt(i);
  7. if (c != ')') {
  8. stack.push(c);
  9. } else {
  10. StringBuffer temp = new StringBuffer();
  11. int count = 0;
  12. while (stack.peek() != '(') {
  13. temp.append(stack.pop());
  14. count++;
  15. }
  16. stack.pop();
  17. for (int j = 0; j < count; j++) {
  18. stack.push(temp.charAt(j));
  19. }
  20. }
  21. }
  22. StringBuffer sb = new StringBuffer();
  23. while (!stack.isEmpty()) {
  24. sb.append(stack.pop());
  25. }
  26. sb.reverse();
  27. return sb.toString();
  28. }
  29. }

复杂度分析

  • 时间复杂度:栈题目:反转每对括号间的子串 - 图48#card=math&code=O%28n%5E2%29&id=aE1Dt),其中 栈题目:反转每对括号间的子串 - 图49 是字符串 栈题目:反转每对括号间的子串 - 图50 的长度。需要遍历字符串 栈题目:反转每对括号间的子串 - 图51 一次,每一层括号之间的字符串反转的时间复杂度是 栈题目:反转每对括号间的子串 - 图52#card=math&code=O%28n%29&id=Pafvb),因此总时间复杂度是 栈题目:反转每对括号间的子串 - 图53#card=math&code=O%28n%5E2%29&id=I1yqg)。
  • 空间复杂度:栈题目:反转每对括号间的子串 - 图54#card=math&code=O%28n%29&id=rGPeY),其中 栈题目:反转每对括号间的子串 - 图55 是字符串 栈题目:反转每对括号间的子串 - 图56 的长度。空间复杂度主要取决于栈空间和存储出栈元素的 栈题目:反转每对括号间的子串 - 图57 类型的对象,空间复杂度是 栈题目:反转每对括号间的子串 - 图58#card=math&code=O%28n%29&id=etNDH)。

解法二

思路和算法

解法一模拟反转操作,存在对同一个字符多次反转的情况,因此时间复杂度较高。可以换一个思路,不模拟反转操作,而是直接拼接结果字符串。

在没有括号的情况下,字符应按照从左到右的顺序拼接到结果字符串。如果遇到了括号,则每进入更深一层,字符串的顺序都需要反转。按照反转后的顺序遍历更深一层的字符时,需要首先找到与当前括号匹配的另一个括号,移动到另一个括号,然后从另一个括号开始按照相反顺序遍历字符。

具体而言,可能有以下几种情况:

  • 从左到右遍历,遇到左括号,则移动到匹配的右括号,然后从右括号开始从右到左遍历;
  • 从左到右遍历,遇到右括号,则移动到匹配的左括号,然后从左括号开始从右到左遍历;
  • 从右到左遍历,遇到左括号,则移动到匹配的右括号,然后从右括号开始从左到右遍历;
  • 从右到左遍历,遇到右括号,则移动到匹配的左括号,然后从左括号开始从左到右遍历。

为了知道每个括号匹配的括号,需要使用栈进行预处理括号匹配关系。遍历字符串 栈题目:反转每对括号间的子串 - 图59,遇到左括号则将左括号所在下标入栈,遇到右括号则将栈顶元素出栈,出栈元素即为与当前右括号匹配的左括号下标。使用数组存储匹配的左右括号下标,即可在 栈题目:反转每对括号间的子串 - 图60#card=math&code=O%281%29&id=CJhTv) 的时间内得到每个括号的匹配的括号的下标。

预处理括号匹配关系之后,遍历字符串 栈题目:反转每对括号间的子串 - 图61 拼接结果字符串。具体做法如下。

  1. 初始化下标 栈题目:反转每对括号间的子串 - 图62,遍历方向 栈题目:反转每对括号间的子串 - 图63 表示从左到右遍历。
  2. 根据字符串 栈题目:反转每对括号间的子串 - 图64 在下标 栈题目:反转每对括号间的子串 - 图65 处的字符,进行如下操作:
    • 如果是字母,则将该字母拼接到结果字符串;
    • 如果是括号,则将 栈题目:反转每对括号间的子串 - 图66 更新为与当前括号匹配的括号下标,并将 栈题目:反转每对括号间的子串 - 图67 取相反数表示将遍历方向反向。
  3. 栈题目:反转每对括号间的子串 - 图68,此时 栈题目:反转每对括号间的子串 - 图69 移动到下一个需要遍历的字符。
  4. 重复第 2 步和第 3 步,直到 栈题目:反转每对括号间的子串 - 图70 超出字符串 栈题目:反转每对括号间的子串 - 图71 的下标范围,此时遍历结束,返回结果字符串。

下面是示例 3 的计算过程。

字符串 栈题目:反转每对括号间的子串 - 图72,长度为 栈题目:反转每对括号间的子串 - 图73

预处理括号匹配的数组 栈题目:反转每对括号间的子串 - 图74(其中 栈题目:反转每对括号间的子串 - 图75 表示非括号字符没有匹配的括号)。

遍历过程会依次经过如下字符:

栈题目:反转每对括号间的子串 - 图76
栈题目:反转每对括号间的子串 - 图77
栈题目:反转每对括号间的子串 - 图78
栈题目:反转每对括号间的子串 - 图79
栈题目:反转每对括号间的子串 - 图80
栈题目:反转每对括号间的子串 - 图81
栈题目:反转每对括号间的子串 - 图82
栈题目:反转每对括号间的子串 - 图83
栈题目:反转每对括号间的子串 - 图84
栈题目:反转每对括号间的子串 - 图85
栈题目:反转每对括号间的子串 - 图86
栈题目:反转每对括号间的子串 - 图87
栈题目:反转每对括号间的子串 - 图88
栈题目:反转每对括号间的子串 - 图89

只有字母会拼接到结果字符串中,因此结果字符串是 栈题目:反转每对括号间的子串 - 图90

代码

  1. class Solution {
  2. public String reverseParentheses(String s) {
  3. int length = s.length();
  4. int[] match = new int[length];
  5. Arrays.fill(match, -1);
  6. Deque<Integer> stack = new ArrayDeque<Integer>();
  7. for (int i = 0; i < length; i++) {
  8. char c = s.charAt(i);
  9. if (c == '(') {
  10. stack.push(i);
  11. } else if (c == ')') {
  12. int left = stack.pop();
  13. match[left] = i;
  14. match[i] = left;
  15. }
  16. }
  17. StringBuffer sb = new StringBuffer();
  18. int index = 0, direction = 1;
  19. while (index < length) {
  20. char c = s.charAt(index);
  21. if (Character.isLetter(c)) {
  22. sb.append(c);
  23. } else {
  24. index = match[index];
  25. direction = -direction;
  26. }
  27. index += direction;
  28. }
  29. return sb.toString();
  30. }
  31. }

复杂度分析

  • 时间复杂度:栈题目:反转每对括号间的子串 - 图91#card=math&code=O%28n%29&id=F5SnF),其中 栈题目:反转每对括号间的子串 - 图92 是字符串 栈题目:反转每对括号间的子串 - 图93 的长度。需要遍历字符串 栈题目:反转每对括号间的子串 - 图94 一次预处理括号匹配关系,拼接结果字符串需要遍历字符串 栈题目:反转每对括号间的子串 - 图95,其中每个字母最多遍历 栈题目:反转每对括号间的子串 - 图96 次,每个括号最多遍历 栈题目:反转每对括号间的子串 - 图97 次,因此总时间复杂度是 栈题目:反转每对括号间的子串 - 图98#card=math&code=O%28n%29&id=NKMrH)。
  • 空间复杂度:栈题目:反转每对括号间的子串 - 图99#card=math&code=O%28n%29&id=MPsBr),其中 栈题目:反转每对括号间的子串 - 图100 是字符串 栈题目:反转每对括号间的子串 - 图101 的长度。空间复杂度主要取决于栈空间和存储括号匹配关系的数组,以及存储结果字符串 栈题目:反转每对括号间的子串 - 图102 类型的对象,空间复杂度是 栈题目:反转每对括号间的子串 - 图103#card=math&code=O%28n%29&id=bjjUb)。