Stack

Stack & Queue

  • Two Different Data Structure
  • Stack: LIFO (Last in first out)
  • Queue: FIFO (First in first out)

When to use Queue?

  • BFS (Breadth-first Search)
  • Priority Queue (Heap)
  • Multi Task Queue

When to use Stack?

  • Invoke a function
  • Recursion  (This is a special case of the first)
  • DFS (Depth-first Search)

Practice 1

Decode String

Given an encoded string, return its decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; there are no extra white spaces, square brackets are well-formed, etc. Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there will not be input like 3a or 2[4].

The test cases are generated so that the length of the output will never exceed 10^5.

Example 1:

Input: s = "3[a]2[bc]"
Output: "aaabcbc"

 

Example 2:

Input: s = "3[a2[c]]"
Output: "accaccacc"

 

Example 3:

Input: s = "2[abc]3[cd]ef"
Output: "abcabccdcdcdef"

Constraints:

1 <= s.length <= 30


s consists of lowercase English letters, digits, and square brackets '[]'.


s is guaranteed to be a valid input.
All the integers in s are in the range [1, 300].

Decode String

We need to use the stack to store the information:
Integer, String

 

Integer stack can help to store the numbers;

String stack can help to store the letters;

 

Notice the '[' and ']' as conditions 

class Solution:
    def decode_string(self, s: str) -> str:
        if not s:
            return ""
        
        int_stack = []   # stack to store repeat counts
        str_stack = []   # stack to store previous strings
        current_num = 0
        current_str = ""
        
        for char in s:
            if char.isdigit():
                current_num = current_num * 10 + int(char)
            elif char == "[":
                int_stack.append(current_num)
                str_stack.append(current_str)
                current_num = 0
                current_str = ""
            elif char == "]":
                repeat_times = int_stack.pop()
                last_str = str_stack.pop()
                current_str = last_str + current_str * repeat_times
            else:
                current_str += char
        
        return current_str


if __name__ == "__main__":
    solution = Solution()
    s = "3[a]2[bc]"
    print(solution.decode_string(s))  # output: "aaabcbc"

Practice 2

Simplify Path

Given a string path, which is an absolute path (starting with a slash '/') to a file or directory in a Unix-style file system, convert it to the simplified canonical path.

In a Unix-style file system, a period '.' refers to the current directory, a double period '..' refers to the directory up a level, and any multiple consecutive slashes (i.e. '//') are treated as a single slash '/'. For this problem, any other format of periods such as '...' are treated as file/directory names.

The canonical path should have the following format:

The path starts with a single slash '/'.
Any two directories are separated by a single slash '/'.
The path does not end with a trailing '/'.
The path only contains the directories on the path from the root directory to the target file or directory (i.e., no period '.' or double period '..')
Return the simplified canonical path.

Example 1:

Input: path = "/home/"
Output: "/home"
Explanation: Note that there is no trailing slash after the last directory name.

Example 2:

Input: path = "/../"
Output: "/"
Explanation: Going one level up from the root directory is a no-op, as the root level is the highest level you can go.

Example 3:

Input: path = "/home//foo/"
Output: "/home/foo"
Explanation: In the canonical path, multiple consecutive slashes are replaced by a single one.

Consider an example

 

 

Input: path = "/a/./b/../../c/"
Output: "/c"

class Solution:
    def simplifyPath(self, path: str) -> str:
        stack = []
        split_path = path.split("/")

        for split in split_path:
            if stack and split == "..":
                stack.pop()
            elif split and split != "." and split != "..":
                stack.append(split)

        if not stack:
            return "/"

        res = ""
        while stack:
            res = "/" + stack.pop() + res

        return res

# 测试示例
solution = Solution()
path = "/home//foo/"
print(solution.simplifyPath(path))  # 输出:"/home/foo"

Practice 3

Basic Calculator

Given a string s representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation.

Basic Calculator

Example 1:

Input: s = "1 + 1"
Output: 2

 

Example 2:

Input: s = " 2-1 + 2 "
Output: 3

 

Example 3:

Input: s = "(1+(4+5+2)-3)+(6+8)"
Output: 23

 

Example 4:

Input: s = "+48 + -48"
Output: 0
Explanation: Numbers can have multiple digits and start with +/-.

Basic Calculator

Three things to notice:

  1. How to keep the priority of () and +/-
    Use Stack
  2. How to deal with the bad format
    Remove space before processing the expression
  3. How to keep track of the sign?
    Use an integer to track it
class Solution:
    def calculate(self, s: str) -> int:
        result = 0
        expression = s.replace(" ", "")
        sign = -1 if expression[0] == '-' else 1
        cur = 1 if expression[0] == '-' else 0
        stack = []
        
        while cur < len(expression):
            i = cur
            if expression[i].isdigit():
                while i < len(expression) and expression[i].isdigit():
                    i += 1
                value = int(expression[cur:i])
                result += sign * value
                cur = i
            elif expression[i] == '+':
                sign = 1
                cur += 1
            elif expression[i] == '-':
                sign = -1
                cur += 1
            elif expression[i] == '(':
                stack.append(result)
                stack.append(sign)
                result = 0
                sign = 1
                cur += 1
            elif expression[i] == ')':
                result = result * stack.pop() + stack.pop()
                cur += 1
        
        return result
# 测试示例
solution = Solution()
s = "(1+(4+5+2)-3)+(6+8)"
print(solution.calculate(s))  # 输出:23

Practice 4

Remove Duplicate Letters

Remove Duplicate Letters

Given a string which contains only lowercase letters, remove duplicate letters so that every letter appear once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.

Example:
Given "bcabc"
Return "abc"

Given "cbacdcbc"
Return "acdb"

Remove Duplicate Letters

Consider a Stack:

What stack can help us?

 

Record the current shown letter

Pop element from the stack if:

1. this one is larger than the one just comes

2. this one still has some remaining can be used later

Remove Duplicate Letters

class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        freqs = [0] * 256
        for char in s:
            freqs[ord(char)] += 1
        
        visited = [False] * 256
        stack = []
        
        for char in s:
            freqs[ord(char)] -= 1
            if visited[ord(char)]:
                continue

            while stack and stack[-1] > char and freqs[ord(stack[-1])] > 0:
                visited[ord(stack.pop())] = False
            
            stack.append(char)
            visited[ord(char)] = True

        return ''.join(stack)

# 测试示例
solution = Solution()
s = "bcabc"
print(solution.removeDuplicateLetters(s))  # 输出:"abc"

Practice 5

Largest Rectangle in Histogram

Largest Rectangle in Histogram

Similar thought: How to use stack to find the right border?

Example: 2 1 5 6 2 3

Only put rectangle higher than peek into stack

The element before it is the left border

The element pushes it out is the right border

Largest Rectangle in Histogram

Example: 2 1 5 6 2 3

Stack: 2

Stack: 1

Stack: 1 5

Stack: 1 5 6

Stack: 1 2

Stack: 1 2 3

Stack: 1 2

Stack: 1

Stack: Empty

Add 2

Pop 2 Add 1

Add 5

Add 6

Pop 6 5 Add 2

Add 3

Pop 3

Pop 2

Pop 1

 

2*1

 

 

6*1, 5*2

 

3*1

2*4

1*6

Largest Rectangle in Histogram

Example: 2 1 5 6 2 3

How do we know the width?

Instead of directly putting height into stack, we put the index. Anyway height can be accessed by the index easily. And using index, we can know the width

Largest Rectangle in Histogram

Example: 2 1 5 6 2 3

Stack: 0

Stack: 1

Stack: 1 2

Stack: 1 2 3

Stack: 1 4

Stack: 1 4 5

Stack: 1 4

Stack: 1

Stack: empty

Add 2

Pop 2 Add 1

Add 5

Add 6

Pop 6 5 Add 2

Add 3

Pop 3

Pop 2

Pop 1

 

2*1

 

 

6*1, 5*2

 

3*1

2*4

1*6

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        stack = []
        area = 0
        
        for i in range(len(heights)):
            while stack and heights[stack[-1]] > heights[i]:
                start = stack.pop()
                width = i if not stack else i - stack[-1] - 1
                area = max(area, heights[start] * width)
            
            stack.append(i)
        
        n = len(heights)
        while stack:
            start = stack.pop()
            width = n if not stack else n - stack[-1] - 1
            area = max(area, heights[start] * width)
        
        return area

# 测试示例
solution = Solution()
heights = [2, 1, 5, 6, 2, 3]
print(solution.largestRectangleArea(heights))  # 输出:10

Python [GoValley-Jo] Stack 6

By ZhiTongGuiGu

Python [GoValley-Jo] Stack 6

  • 7