给出一个字符串 s
(仅含有小写英文字母和括号)。
请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。
注意,您的结果中 不应 包含任何括号。
示例 1:
**输入:** s = "(abcd)"
**输出:** "dcba"
示例 2:
**输入:** s = "(u(love)i)"
**输出:** "iloveu"
**解释:** 先反转子字符串 "love" ,然后反转整个字符串。
示例 3:
**输入:** s = "(ed(et(oc))el)"
**输出:** "leetcode"
**解释:** 先反转子字符串 "oc" ,接着反转 "etco" ,然后反转整个字符串。
示例 4:
**输入:** s = "a(bcdefghijkl(mno)p)q"
**输出:** "apmnolkjihgfedcbq"
提示:
1 <= s.length <= 2000
s
中只有小写英文字母和括号
题目测试用例确保所有括号都是成对出现的
方法一:栈 思路及算法
本题要求按照从括号内到外的顺序进行处理。如字符串 (u(love)i),首先处理内层括号,变为 (uevoli),然后处理外层括号,变为 iloveu。
对于括号序列相关的题目,通用的解法是使用递归或栈。本题中我们将使用栈解决。
我们从左到右遍历该字符串,使用字符串 str 记录当前层所遍历到的小写英文字母。对于当前遍历的字符:
注意到我们仅在遇到右括号时才进行字符串处理,这样可以保证我们是按照从括号内到外的顺序处理字符串。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : string reverseParentheses (string s) { stack<string> stk; string str; for (auto &ch : s) { if (ch == '(' ) { stk.push (str); str = "" ; } else if (ch == ')' ) { reverse (str.begin (), str.end ()); str = stk.top () + str; stk.pop (); } else { str.push_back (ch); } } return str; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public String reverseParentheses (String s) { Deque<String> stack = new LinkedList <String>(); StringBuffer sb = new StringBuffer (); for (int i = 0 ; i < s.length(); i++) { char ch = s.charAt(i); if (ch == '(' ) { stack.push(sb.toString()); sb.setLength(0 ); } else if (ch == ')' ) { sb.reverse(); sb.insert(0 , stack.pop()); } else { sb.append(ch); } } return sb.toString(); } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Solution { public string ReverseParentheses (string s ) { Stack<string > stack = new Stack<string >(); StringBuilder sb = new StringBuilder(); foreach (char ch in s) { if (ch == '(' ) { stack.Push(sb.ToString()); sb.Length = 0 ; } else if (ch == ')' ) { char [] arr = sb.ToString().ToCharArray(); sb.Length = 0 ; for (int i = arr.Length - 1 ; i >= 0 ; i--) { sb.Append(arr[i]); } sb.Insert(0 , stack.Pop()); } else { sb.Append(ch); } } return sb.ToString(); } }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var reverseParentheses = function (s ) { const stk = []; let str = '' ; for (const ch of s) { if (ch === '(' ) { stk.push (str); str = '' ; } else if (ch === ')' ) { str = str.split ("" ).reverse ().join ("" ); str = stk[stk.length - 1 ] + str; stk.pop (); } else { str += ch; } } return str; };
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func reverseParentheses (s string ) string { stack := [][]byte {} str := []byte {} for i := range s { if s[i] == '(' { stack = append (stack, str) str = []byte {} } else if s[i] == ')' { for j, n := 0 , len (str); j < n/2 ; j++ { str[j], str[n-1 -j] = str[n-1 -j], str[j] } str = append (stack[len (stack)-1 ], str...) stack = stack[:len (stack)-1 ] } else { str = append (str, s[i]) } } return string (str) }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void reverse (char * str, int strSize) { for (int j = 0 ; j < strSize / 2 ; j++) { char tmp = str[j]; str[j] = str[strSize - j - 1 ], str[strSize - j - 1 ] = tmp; } } char * reverseParentheses (char * s) { int n = strlen (s); char * stk[n]; int top = 0 ; char * str = malloc (sizeof (char ) * (n + 1 )); str[0 ] = '\0' ; int strSize = 0 ; for (int i = 0 ; i < n; i++) { if (s[i] == '(' ) { stk[top] = malloc (sizeof (char ) * (strSize + 1 )); memcpy (stk[top], str, sizeof (char ) * (strSize + 1 )); top++; str[0 ] = '\0' ; strSize = 0 ; } else if (s[i] == ')' ) { reverse(str, strSize); int len = strlen (stk[top - 1 ]); for (int j = strSize; j >= 0 ; j--) { str[j + len] = str[j]; } memcpy (str, stk[top - 1 ], sizeof (char ) * len); strSize += len; top--; } else { str[strSize++] = s[i]; str[strSize] = '\0' ; } } return str; }
复杂度分析
方法二:预处理括号 思路及算法
我们可以将括号的反转理解为逆序地遍历括号,如下图:
第一步我们向右移动到左括号,此时我们跳跃到该左括号对应的右括号(进入了更深一层);
第二到第三步我们在括号内部向左移动(完成了更深层的遍历);
第四步我们向左移动到左括号,此时我们跳跃到该左括号对应的右括号(返回到上一层);
第五步我们在括号外向右移动(继续遍历)。
读者们可以自行尝试模拟两层乃至多层括号嵌套的移动方案,规律可以从当前的单层括号中总结出来。
假设我们沿着某个方向移动,此时遇到了括号,那么我们只需要首先跳跃到该括号对应的另一个括号所在处,然后改变我们的移动方向即可。这个方案同时适用于遍历时进入更深一层,以及完成当前层的遍历后返回到上一层的方案。
在实际代码中,我们需要预处理出每一个括号对应的另一个括号所在的位置,这一部分我们可以使用栈解决。当我们预处理完成后,即可在线性时间内完成遍历,遍历的字符串顺序即为反转后的字符串。
代码
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution {public : string reverseParentheses (string s) { int n = s.length (); vector<int > pair (n) ; stack<int > stk; for (int i = 0 ; i < n; i++) { if (s[i] == '(' ) { stk.push (i); } else if (s[i] == ')' ) { int j = stk.top (); stk.pop (); pair[i] = j, pair[j] = i; } } string ret; int index = 0 , step = 1 ; while (index < n) { if (s[index] == '(' || s[index] == ')' ) { index = pair[index]; step = -step; } else { ret.push_back (s[index]); } index += step; } return ret; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public String reverseParentheses (String s) { int n = s.length(); int [] pair = new int [n]; Deque<Integer> stack = new LinkedList <Integer>(); for (int i = 0 ; i < n; i++) { if (s.charAt(i) == '(' ) { stack.push(i); } else if (s.charAt(i) == ')' ) { int j = stack.pop(); pair[i] = j; pair[j] = i; } } StringBuffer sb = new StringBuffer (); int index = 0 , step = 1 ; while (index < n) { if (s.charAt(index) == '(' || s.charAt(index) == ')' ) { index = pair[index]; step = -step; } else { sb.append(s.charAt(index)); } index += step; } return sb.toString(); } }
[sol2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Solution { public string ReverseParentheses (string s ) { int n = s.Length; int [] pair = new int [n]; Stack<int > stack = new Stack<int >(); for (int i = 0 ; i < n; i++) { if (s[i] == '(' ) { stack.Push(i); } else if (s[i] == ')' ) { int j = stack.Pop(); pair[i] = j; pair[j] = i; } } StringBuilder sb = new StringBuilder(); int index = 0 , step = 1 ; while (index < n) { if (s[index] == '(' || s[index] == ')' ) { index = pair[index]; step = -step; } else { sb.Append(s[index]); } index += step; } return sb.ToString(); } }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var reverseParentheses = function (s ) { const n = s.length ; const pair = new Array (n).fill (0 ); const stack = []; for (let i = 0 ; i < n; i++) { if (s[i] === '(' ) { stack.push (i); } else if (s[i] == ')' ) { const j = stack.pop (); pair[i] = j; pair[j] = i; } } const sb = []; let index = 0 , step = 1 ; while (index < n) { if (s[index] === '(' || s[index] === ')' ) { index = pair[index]; step = -step; } else { sb.push (s[index]); } index += step; } return sb.join ('' ); };
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func reverseParentheses (s string ) string { n := len (s) pair := make ([]int , n) stack := []int {} for i, b := range s { if b == '(' { stack = append (stack, i) } else if b == ')' { j := stack[len (stack)-1 ] stack = stack[:len (stack)-1 ] pair[i], pair[j] = j, i } } ans := []byte {} for i, step := 0 , 1 ; i < n; i += step { if s[i] == '(' || s[i] == ')' { i = pair[i] step = -step } else { ans = append (ans, s[i]) } } return string (ans) }
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 char * reverseParentheses (char * s) { int n = strlen (s); int pair [n]; memset (pair , 0 , sizeof (pair )); int stk[n], top = 0 ; for (int i = 0 ; i < n; i++) { if (s[i] == '(' ) { stk[top++] = i; } else if (s[i] == ')' ) { int j = stk[--top]; pair [i] = j, pair [j] = i; } } char * ret = malloc (sizeof (char ) * (n + 1 )); int retSize = 0 ; int index = 0 , step = 1 ; while (index < n) { if (s[index] == '(' || s[index] == ')' ) { index = pair [index]; step = -step; } else { ret[retSize++] = s[index]; } index += step; } ret[retSize] = '\0' ; return ret; }
复杂度分析