Skip to content

Commit 1ba3cf5

Browse files
committed
Add new stack & queue problems
1 parent fa13157 commit 1ba3cf5

20 files changed

+1123
-94
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# [1021. Remove Outermost Parentheses](https://leetcode.com/problems/remove-outermost-parentheses/description/)
2+
3+
## Counting
4+
We maintain a left parenthsis count, it will be the outermost when:
5+
1. It's the first left parenthsis. it's the left parenthsis that makes the count to 1.
6+
2. It's the last left parenthsis, it's the right parenthsis that makes the count to 0.
7+
8+
If it's not the outermost, we append it to the result.
9+
```kotlin
10+
fun removeOuterParentheses(s: String): String {
11+
var leftCount = 0
12+
val result = StringBuilder()
13+
for (c in s) {
14+
if (c == '(') {
15+
if (leftCount != 0) result.append(c.toString())
16+
leftCount++
17+
} else {
18+
leftCount--
19+
if (leftCount != 0) result.append(c.toString())
20+
}
21+
}
22+
return result.toString()
23+
}
24+
```
25+
* **Time Complexity:** `O(n)`, where `n` is the length of the string.
26+
* **Space Complexity:** `O(1)`, where we don't count the space for the result.
27+
28+
## Stack
29+
> Might skip this solution, it's overcomplicated.
30+
```kotlin
31+
fun removeOuterParentheses(s: String): String {
32+
val ans = StringBuilder()
33+
val stack = Stack<String>()
34+
for (c in s) {
35+
if (c == '(') {
36+
stack.push(c.toString())
37+
} else {
38+
val str = LinkedList<String>()
39+
while (stack.isNotEmpty() && stack.peek() != "(") {
40+
str.addFirst(stack.pop())
41+
}
42+
stack.pop() // Pop (
43+
if (stack.isNotEmpty()) {
44+
str.addLast(")")
45+
str.addFirst("(")
46+
stack.push(str.joinToString(""))
47+
} else { // It's outermost, add current string to answer
48+
ans.append(str.joinToString(""))
49+
}
50+
}
51+
}
52+
return ans.toString()
53+
}
54+
```
Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
## [1047. Remove All Adjacent Duplicates In String](https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string/)
2-
3-
## Clarification Questions
4-
* No, it's clear from problem description.
5-
6-
## Test Cases
7-
### Normal Cases
8-
```
9-
Input:
10-
Output:
11-
```
12-
### Edge / Corner Cases
13-
*
14-
```
15-
Input:
16-
Output:
17-
```
1+
# [1047. Remove All Adjacent Duplicates In String](https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string/)
182

193
## Stack
4+
We can use stack to remove adjacent duplicates. If the top of the stack is the same as the current character, we pop the top of the stack. Otherwise, we push the current character to the stack.
5+
206
```kotlin
217
fun removeDuplicates(s: String): String {
228
val stack = Stack<Char>()
@@ -27,11 +13,61 @@ fun removeDuplicates(s: String): String {
2713
stack.push(s[i])
2814
}
2915
}
30-
val result = StringBuilder()
31-
while (!stack.isEmpty()) {
32-
result.append(stack.pop())
16+
return stack.joinToString("")
17+
}
18+
```
19+
20+
* **Time Complexity:** `O(n)`, where n is the length of the string.
21+
* **Space Complexity:** `O(n)`.
22+
23+
## Two Pointers
24+
We have read and write pointers. The read pointer reads the characters from the string. The write pointer is the next index to write the character. If the current character is the same as the previous character, we decrease the write pointer. Otherwise, we write the current character to the write pointer.
25+
26+
For example, the read point is at index 2, `s[read] == s[write - 1]` which is adjacent duplicate, so we decrease the write pointer.
27+
28+
```js
29+
0 1 2 3 4 5
30+
a b b a c a
31+
r
32+
w
33+
34+
// After
35+
0 1 2 3 4 5
36+
a b b a c a
37+
r
38+
w
39+
40+
// At the end of iteration
41+
0 1 2 3 4 5
42+
c a b a c a // We have overwritten the string at index 0 and 1.
43+
r
44+
w
45+
46+
// Return the substring from 0 to write pointer
47+
0 1 2 3 4 5
48+
c a
49+
r
50+
w
51+
```
52+
53+
```kotlin
54+
fun removeDuplicates(s: String): String {
55+
val c = s.toCharArray()
56+
val n = s.length
57+
var read = 1
58+
var write = 1
59+
while (read < n) {
60+
if (write - 1 >= 0 && c[read] == c[write - 1]) {
61+
write--
62+
} else {
63+
c[write] = c[read]
64+
write++
65+
}
66+
read++
3367
}
34-
result.reverse()
35-
return result.toString()
68+
return c.concatToString(0, write)
3669
}
37-
```
70+
```
71+
72+
* **Time Complexity:** `O(n)`, where n is the length of the string.
73+
* **Space Complexity:** `O(n)` for converting the string to char array.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# [1190. Reverse Substrings Between Each Pair of Parentheses](https://leetcode.com/problems/reverse-substrings-between-each-pair-of-parentheses/)
2+
3+
## Stack
4+
We use stack for the nested structure and reverse the string between each pair of parentheses.
5+
```js
6+
s = "((ab)z)i"
7+
*
8+
stack = (, (, a, b
9+
10+
// We pop b, a to form "ba" and push back to stack
11+
stack = (, ba
12+
13+
s = "((ab)z)i"
14+
*
15+
stack = (, ba, z
16+
17+
// We pop z, ba to form "zab" and push back to stack
18+
stack = zab
19+
20+
s = "((ab)z)i"
21+
*
22+
stack = zab, i
23+
24+
// Then we join the stack to form the result
25+
return "zabi"
26+
```
27+
28+
```kotlin
29+
fun reverseParentheses(s: String): String {
30+
val stack = Stack<String>()
31+
for (c in s) {
32+
if (c == '(') {
33+
stack.push(c.toString())
34+
} else if (c == ')') {
35+
val builder = StringBuilder()
36+
while (stack.isNotEmpty() && stack.peek() != "(") {
37+
builder.append(stack.pop().reversed())
38+
}
39+
stack.pop() // Pop (
40+
stack.push(builder.toString())
41+
} else {
42+
stack.push(c.toString())
43+
}
44+
}
45+
return stack.joinToString("")
46+
}
47+
```
48+
* **Time Complexity:** `O(n^2)`, we iterate through the string and reverse the string between each pair of parentheses when poping from the stack.
49+
* **Space Complexity:** `O(n)`
50+
51+
> TODO: There is another solution that traverse the string based on the nested structure:
52+
>
53+
> ```js
54+
> (ABCDE(FIJKL)MNOPQ)
55+
> 1 --> 3 <-- 2 --> 4
56+
> ABCDE, LKJIHF, MNOPQ

leetcode/1249.minimum-remove-to-make-valid-parentheses.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Output:
1717
```
1818

1919
## Stack
20+
We use stack to match the parentheses, and we store the index when any unmatched parentheses are found.
2021
```kotlin
2122
fun minRemoveToMakeValid(s: String): String {
2223
if (s.isEmpty()) return ""
@@ -51,7 +52,7 @@ fun minRemoveToMakeValid(s: String): String {
5152
* **Time Complexity**: `O(n)`, to iterate the string for scanning and building the result.
5253
* **Space Complexity**: `O(n)` for hash set and stack.
5354

54-
Another solution, we can iterate the string from left to right, to remove invalid left parentheses, then iterate from right to left to remove the invalid right parentheses.
55+
Another solution, we can iterate the string from left to right, to remove umatched left parentheses, then iterate from right to left to remove the unmatched right parentheses.
5556

5657
```kotlin
5758
fun minRemoveToMakeValid(s: String): String {
@@ -77,4 +78,47 @@ fun minRemoveToMakeValid(s: String): String {
7778
}
7879
return str.toString()
7980
}
80-
```
81+
```
82+
83+
## Greedy
84+
We can use the similar idea from [921. Minimum Add to Make Parentheses Valid](https://leetcode.com/problems/minimum-add-to-make-parentheses-valid/), we can use a count variable to store the number of umatched left parentheses when iterating from left to right, to remove the unmatched **right** parentheses. Then we iterate from right to left with the count variable to store the number of unmatched right parentheses to remove the unmatched **left** parentheses.
85+
86+
```kotlin
87+
fun minRemoveToMakeValid(s: String): String {
88+
val toRemove = HashSet<Int>()
89+
var leftCount = 0
90+
for (i in 0 until s.length) {
91+
val c = s[i]
92+
if (c == '(') leftCount++
93+
else if (c == ')') leftCount--
94+
95+
// More right parentheses that can't match
96+
if (leftCount < 0) {
97+
toRemove.add(i)
98+
leftCount = 0
99+
}
100+
}
101+
var rightCount = 0
102+
for (i in s.length - 1 downTo 0) {
103+
if (toRemove.contains(i)) continue // Remember to skip the invalid left parentheses
104+
val c = s[i]
105+
if (c == ')') rightCount++
106+
else if (c == '(') rightCount--
107+
108+
// More left parentheses that can't match
109+
if (rightCount < 0) {
110+
toRemove.add(i)
111+
rightCount = 0
112+
}
113+
}
114+
val str = StringBuilder()
115+
for (i in 0 until s.length) {
116+
if (toRemove.contains(i)) continue
117+
str.append(s[i])
118+
}
119+
return str.toString()
120+
}
121+
```
122+
123+
* **Time Complexity**: `O(n)`, to iterate the string for scanning and building the result.
124+
* **Space Complexity**: `O(n)` for hash set.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# [1541. Minimum Insertions to Balance a Parentheses String](https://leetcode.com/problems/minimum-insertions-to-balance-a-parentheses-string/)
2+
3+
## Stack (Greedy or Counting)
4+
It's a variant of [921. Minimum Add to Make Parentheses Valid](../leetcode/921.minimum-add-to-make-parentheses-valid.md). The difference is that we need to match one left parenthesis with two right parentheses. We can use the similar approach from it:
5+
1. For left parenthesis, we increase the count.
6+
2. For right parenthesis, we check if we have two adjacent right parentheses:
7+
* There are two right parentheses, we can match one left parenthesis with two right parentheses, we decrease the count.
8+
* There is only one right parenthesis, we need to add one right parenthesis, and decrease the count because we can match after adding one right parenthesis.
9+
3. At the end of iteration, we need to add the right parenthesis for the remaining left parentheses, we add the `count * 2` to the result, we need to add two right parentheses for each left parenthesis.
10+
11+
```kotlin
12+
fun minInsertions(s: String): Int {
13+
var leftCount = 0
14+
var insertions = 0
15+
var i = 0
16+
while (i < s.length) {
17+
val c = s[i]
18+
if (c == '(') {
19+
leftCount++
20+
} else {
21+
// We check if we have two adjacent right parentheses
22+
if (i + 1 < s.length && s[i + 1] == ')') {
23+
leftCount-- // We can match one left parenthesis with two right parentheses
24+
i++ // We have checked i, and i + 1, so we need to skip i + 1
25+
} else { // We only have one right parenthesis
26+
insertions++ // We need to add one left parenthesis
27+
leftCount-- // We match after adding one left parenthesis
28+
}
29+
}
30+
31+
// We check if right parenthesis is more than left parenthesis
32+
if (leftCount < 0) {
33+
insertions++ // We need to add one left parenthesis
34+
leftCount = 0 // We match after adding one left parenthesis
35+
}
36+
i++
37+
}
38+
return insertions + leftCount * 2 // We need to add two right parentheses for each left parenthesis
39+
}
40+
```
41+
42+
Or we can count the needed right parentheses directly:
43+
```kotlin
44+
fun minInsertions(s: String): Int {
45+
var rightNeeded = 0 // How many right parentheses we need to match
46+
var insertions = 0
47+
for (c in s) {
48+
if (c == '(') {
49+
rightNeeded += 2
50+
51+
// For the balanced parentheses, we always need the even number of
52+
// right parentheses. For odd number, we have to add one right parenthesis.
53+
if (rightNeeded % 2 != 0) {
54+
insertions++
55+
// Because we add one right parenthesis, so the needed right parentheses - 1
56+
rightNeeded--
57+
}
58+
} else {
59+
// We have one right parenthesis, so we decrease the needed right parentheses
60+
rightNeeded--
61+
62+
// For negative number of right parentheses, which means left > right
63+
// We need to add one left parenthesis to match
64+
if (rightNeeded < 0) {
65+
insertions++
66+
rightNeeded += 2 // After adding one left, we need to add two right parentheses to match.
67+
}
68+
}
69+
}
70+
return insertions + rightNeeded
71+
}
72+
```
73+
74+
## References
75+
* https://www.youtube.com/watch?v=MipkQzEkhBM
76+
* The second approach: https://leetcode.cn/problems/minimum-insertions-to-balance-a-parentheses-string/solutions/391236/ping-heng-gua-hao-zi-fu-chuan-de-zui-shao-cha-ru-2/comments/1558355
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# [1614. Maximum Nesting Depth of the Parentheses](https://leetcode.com/problems/maximum-nesting-depth-of-the-parentheses/description/)
2+
3+
## Stack
4+
We can use stack to trace the depth of the parentheses. When we meet a `(`, we push it into the stack. When we meet a `)`, we pop the stack and update the maximum depth. To optimizee the space complexity, we can use a variable to store the count of unmatched left parentheses and update the maximum depth.
5+
6+
```kotlin
7+
fun maxDepth(s: String): Int {
8+
var maxDepth = 0
9+
var leftCount = 0
10+
for (c in s) {
11+
if (c == '(') {
12+
leftCount++
13+
maxDepth = maxOf(maxDepth, depth)
14+
} else if (c == ')') {
15+
leftCount--
16+
}
17+
}
18+
return maxDepth
19+
}
20+
```
21+
22+
* **Time Complexity:** `O(n)`.
23+
* **Space Complexity:** `O(1)`.

0 commit comments

Comments
 (0)