Post

Stack

Java implementations of Stack:

  • Deque (Stack)
  • StringBuilder
  • Array

Binary Searchable Numbers in an Unsorted Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int binarySearchableNumbers(int[] nums) {
    int n = nums.length;
    // for an element to be found,
    // it should be greater that all elements before it,
    // and smaller than all element after it
    Deque<Integer> st = new ArrayDeque<>();
    int max = Integer.MIN_VALUE;
    for (int num : nums) {
        // monotonically increasing
        // pops stack element if it's greater than num
        // ensures in-stack elements always < later elements
        while (!st.isEmpty() && num < st.peek()) {
            st.pop();
        }

        // num > all previous elements
        if (num > max) {
            st.push(num);
            max = num;
        }
    }
    return st.size();
}

Create Maximum Number

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
    int n1 = nums1.length, n2 = nums2.length;
    int[] result = new int[k];
    // picks i digits from nums1
    // picks (k - i) digits from nums2
    // 0 <= i <= k
    // 0 <= i <= n1
    // 0 <= k - i <= n2 => i >= k - n2
    for (int i = Math.max(0, k - n2); i <= Math.min(k, n1); i++) {
        int[] candidate = merge(maxArray(nums1, i), maxArray(nums2, k - i), k);
        if (isGreaterThanOrEqualTo(candidate, 0, result, 0)) {
            result = candidate;
        }
    }
    return result;
}

private int[] merge(int[] nums1, int[] nums2, int k) {
    int[] result = new int[k];
    int i = 0, j = 0, index = 0;
    while (index < k) {
        result[index++] = isGreaterThanOrEqualTo(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
    }
    return result;
}

// Check if nums1[i:] >= nums2[j:]
private boolean isGreaterThanOrEqualTo(int[] nums1, int i, int[] nums2, int j) {
    int n1 = nums1.length, n2 = nums2.length;
    while (i < n1 && j < n2 && nums1[i] == nums2[j]) {
        i++;
        j++;
    }
    return j == n2 || (i < n1 && nums1[i] >= nums2[j]);
}

/**
 * Creates the maximum number of length k from digits of the array.
 * @param nums number represented as an array
 * @param k length of the result number
 * @return the maximum number represented as an array
 */
private int[] maxArray(int[] nums, int k) {
    int n = nums.length;
    Deque<Integer> st = new ArrayDeque<>();
    for (int i = 0; i < n; i++) {
        // monotonically decreasing stack
        // st.size + n - i > k means if we push all the remaining digits to the stack
        // the stack size will be greater than k
        // so we need to pop the stack to make room
        while (st.size() + n - i > k && !st.isEmpty() && nums[i] > st.peek()) {
            st.pop();
        }
        if (st.size() < k) {
            st.push(nums[i]);
        }
    }

    int[] result = new int[k];
    for (int i = k - 1; i >= 0; i--) {
        result[i] = st.pop();
    }
    return result;
}

Use array as a stack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private int[] maxArray(int[] nums, int k) {
    int n = nums.length;
    // this array is used as a stack, too
    int[] result = new int[k];
    for (int i = 0, j = 0; i < n; i++) {
        while (n - i + j > k && j > 0 && nums[i] > result[j - 1]) {
            j--;
        }

        if (j < k) {
            result[j++] = nums[i];
        }
    }
    return result;
}

Number of Valid Subarrays

1
2
3
4
5
6
7
8
9
10
11
12
public int validSubarrays(int[] nums) {
    int count = 0;
    Deque<Integer> st = new ArrayDeque<>();
    for (int num : nums) {
        while (!st.isEmpty() && num < st.peek()) {
            st.pop();
        }
        st.push(num);
        count += st.size();
    }
    return count;
}

Jump Game V

It’s easy to come up with an O(nd) top-down DP solution.

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
38
39
40
41
42
43
44
// O(n)
public int maxJumps(int[] arr, int d) {
    int n = arr.length;

    // dp[i]: max number of indices we can visit from index i
    int[] dp = new int[n];
    Arrays.fill(dp, 1);

    // stack1 is the main stack
    Deque<Integer> st1 = new ArrayDeque<>(), st2 = new ArrayDeque<>();
    for (int i = 0; i <= n; i++) {
        // stack1 is monotonically decreasing
        // starts popping stack1 when the next element is greater, or it's the end of the array
        while (!st1.isEmpty() && (i == n || arr[i] > arr[st1.peek()])) {
            // top of the stack
            int top = arr[st1.peek()];

            // pops all indices in the stack whose elements equal top
            while (!st1.isEmpty() && top == arr[st1.peek()]) {
                // i -> j is a valid jump
                int j = st1.pop();
                if (i < n && i - j <= d) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }

                // pushes the current index to the stack2
                st2.push(j);
            }

            // stack2 stores all the indices whose elements equal the previous stack1.top
            // pops and processes each
            while (!st2.isEmpty()) {
                int j = st2.pop();
                // current stack1.top -> previous top is a valid jump
                if (!st1.isEmpty() && j - st1.peek() <= d) {
                    dp[st1.peek()] = Math.max(dp[st1.peek()], dp[j] + 1);
                }
            }
        }
        st1.push(i);
    }

    return Arrays.stream(dp).max().getAsInt();
}

Car Fleet II

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
public double[] getCollisionTimes(int[][] cars) {
    int n = cars.length;
    // monotonically decreasing stack wrt the collision time
    Deque<Integer> st = new ArrayDeque<>();
    double[] answer = new double[n];

    // iterates backwards
    for (int i = n - 1; i >= 0; i--) {
        answer[i] = -1;
        // top car doesn't collide with the current car if:
        // 1. speed of i-th car is no greater than the top car, or
        // 2. top car has already collided with another car (e.g. j-th car),
        //    thus i-th car would possibly collide with j-th car instead, and we need to pop the top
        //    iow, if i-th car can't catch (i + 1)-th car, before (i + 1)-th car catches (i + 2)-th car,
        //    then we can think of (i + 2)-th car as the candidate, removing (i + 1)-th car
        while (!st.isEmpty()
               && (cars[i][1] <= cars[st.peek()][1]
               || (computeTime(cars, i, st.peek()) >= answer[st.peek()] && answer[st.peek()] > 0))) {
            st.pop();
        }

        if (!st.isEmpty()) {
            answer[i] = computeTime(cars, i, st.peek());
        }
        st.push(i);
    }
    return answer;
}

// computes the time car i takes to catch car j
// i is faster than j
private double computeTime(int[][] cars, int i, int j) {
    return (double)(cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1]);
}

In the problem below, the monotonicity of the stack is not straightforward:

Maximum Number of Books You Can Take

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
public long maximumBooks(int[] books) {
    Deque<Integer> st = new ArrayDeque<>();
    // pushes a virtual index to makes formula simple
    st.push(-1);

    long max = 0, curr = 0;
    for (int i = 0; i < books.length; i++) {
        // monotonically increasing stack
        // pops a shelf j if the b[j] > b[i] - (i - j)
        // e.g. [3, 5, 4] -> [2, 3, 4]
        // b[1]: 5 > 4 - 1
        // b[0]: 3 > 4 - 2
        while (st.peek() >= 0 && books[i] < books[st.peek()] + i - st.peek()) {
            int top = st.pop();
            curr -= sum(books[top], top - st.peek());
        }

        // uses the current shelf i as the last element of an arithmetic sequence (d = 1)
        // the sequence stops by st.peek()
        curr += sum(books[i], i - st.peek());
        st.push(i);
        max = Math.max(max, curr);
    }
    return max;
}

// sum of {an - n + 1, an - n + 2, ..., an}
private long sum(long an, int n) {
    long a1 = Math.max(0, an - n + 1);
    return (a1 + an) * (an - a1 + 1) / 2;
}

Histogram

The problem Largest Rectangle in Histogram deserves a closer look. The stack approach can be applied to many similar problems, like rectangle count/max area in a matrix.

Maximal Rectangle

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
int maximalRectangle(vector<vector<char>>& matrix) {
    int m = matrix.size(), n = matrix[0].size(), area = 0;
    vector<int> heights(n);
    stack<int> st;
    for (int i = 0; i < m; i++) {
        stack<int>().swap(st);
        st.push(-1);

        for (int j = 0; j <= n; j++) {
            // Histogram height array in each row
            if (j < n) {
                heights[j] = matrix[i][j] == '0' ? 0 : heights[j] + 1;
            }

            // 84. Largest Rectangle in Histogram
            while (st.top() >= 0 && (j == n || heights[j] < heights[st.top()])) {
                int h = heights[st.top()];
                st.pop();
                int w = j - 1 - st.top();
                area = max(area, h * w);
            }
            st.push(j);
        }
    }
    return area;
}

Count Submatrices With All Ones

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
public int numSubmat(int[][] mat) {
    int m = mat.length, n = mat[0].length, count = 0;
    // sums[i]: count of all-one submatrices with i as the right side
    int[] sums = new int[n];
    int[] heights = new int[n];

    for (int i = 0; i < m; i++) {
        Deque<Integer> st = new ArrayDeque<>();
        st.push(-1);
        Arrays.fill(sums, 0);

        for (int j = 0; j < n; j++) {
            // histogram height array in each row
            heights[j] = mat[i][j] == 0 ? 0 : heights[j] + 1;

            // 84. Largest Rectangle in Histogram
            while (st.peek() >= 0 && heights[j] < heights[st.peek()]) {
                st.pop();
            }

            // counts the all-one matrices whose bottom is at current row
            // and right side is on heights[j]
            int prev = st.peek();
            sums[j] = (prev < 0 ? 0 : sums[prev]) + heights[j] * (j - prev);
            st.push(j);
        }

        count += Arrays.stream(sums).sum();
    }

    return count;
}

Stack

Instead of maining an extra array int[] sums, we can use {index, sum} as the compound stack element.

The final result is a culumative sum. The following solution gets rid of the extra array int[] sums and keeps using the single index stack element:

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
public int numSubmat(int[][] mat) {
    int m = mat.length, n = mat[0].length, count = 0;
    int[] heights = new int[n];

    for (int i = 0; i < m; i++) {
        Deque<Integer> st = new ArrayDeque<>();
        st.push(-1);

        // this is sums[j] in the above solution
        int sum = 0;
        for (int j = 0; j < n; j++) {
            // histogram height array in each row
            heights[j] = mat[i][j] == 0 ? 0 : heights[j] + 1;

            // subtracts sums[prev + 1], ..., sums[j - 1] in the above solution
            // that yields to sums[prev]
            while (st.peek() >= 0 && heights[j] < heights[st.peek()]) {
                int top = st.pop();
                sum -= heights[top] * (top - st.peek());
            }

            // rhs is the new count which has j as right side
            sum += heights[j] * (j - st.peek());
            count += sum;
            st.push(j);
        }
    }

    return count;
}

Lookahead

Use an auxiliary array to check the occurrences of certain elements in later inputs.

Remove Duplicate Letters

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 String removeDuplicateLetters(String s) {
    // last index
    int[] last = new int[26];
    for (int i = 0; i < s.length(); i++) {
        last[s.charAt(i) - 'a'] = i;
    }

    // each character appears in the stack once and only once
    Deque<Integer> st = new ArrayDeque<>();
    boolean[] inStack = new boolean[26];
    for (int i = 0; i < s.length(); i++) {
        int c = s.charAt(i) - 'a';
        if (!inStack[c]) {
            // if c is less than stack top, and the top will appear later,
            // pops the top
            while (!st.isEmpty() && c < st.peek() && i < last[st.peek()]) {
                inStack[st.pop()] = false;
            }
            st.push(c);
            inStack[c] = true;
        }
    }

    StringBuilder sb = new StringBuilder();
    for (int i : st) {
        sb.append((char)('a' + i));
    }
    return sb.reverse().toString();
}

Using a Robot to Print the Lexicographically Smallest String

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
public String robotWithString(String s) {
    int[] count = new int[26];
    for (char ch : s.toCharArray()) {
        count[ch - 'a']++;
    }

    Deque<Character> st = new LinkedList<>();
    StringBuilder sb = new StringBuilder();
    int minCharIndex = 0;
    for (char ch : s.toCharArray()) {
        // always pushes the char to stack
        // then decides which operation to take next
        st.push(ch);
        count[ch - 'a']--;

        // finds the smallest unused char
        while (minCharIndex < count.length && count[minCharIndex] == 0) {
            minCharIndex++;
        }

        while (!st.isEmpty() && minCharIndex >= st.peek() - 'a') {
            sb.append(st.pop());
        }
    }
    return sb.toString();
}

Smallest K-Length Subsequence With Occurrences of a Letter

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
38
public String smallestSubsequence(String s, int k, char letter, int repetition) {
    // count of `letter`
    int count = (int)s.chars().filter(ch -> ch == letter).count();
    int n = s.length();

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < n; i++) {
        char c = s.charAt(i);
        // if the current character can improve the lexicographical order, we pop the top if:
        // - there are enough remaining characters to construct a k-size string
        // - if the top is the same as letter, then the number of remaining letters >= `repetition`.
        //   number of remaining letter = count - 1 (-1 is the stack top)
        while (sb.length() > 0 && c < sb.charAt(sb.length() - 1) && n - i > k - sb.length()
               && (sb.charAt(sb.length() - 1) != letter || count > repetition)) {
            // pops
            char top = sb.charAt(sb.length() - 1);
            sb.deleteCharAt(sb.length() - 1);
            if (top == letter) {
                repetition++;
            }
        }

        if (sb.length() < k) {
            if (c == letter) {
                sb.append(c);
                repetition--;
            } else if (k - sb.length() > repetition) {
                // k - st.size() - repetition is the number of non-letter characters that need to be pushed
                sb.append(c);
            }
        }

        if (c == letter) {
            count--;
        }
    }
    return sb.toString();
}

Flatten Nested List Iterator

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
private Deque<NestedInteger> st;

public NestedIterator(List<NestedInteger> nestedList) {
    st = new ArrayDeque<>();
    pushToStack(nestedList);
}

@Override
public Integer next() {
    // caller doesn't necessarily calls hasNext() before next()
    return hasNext() ? st.pop().getInteger() : null;
}

@Override
public boolean hasNext() {
    while (!st.isEmpty()) {
        if (st.peek().isInteger()) {
            return true;
        }
        pushToStack(st.pop().getList());
    }
    return false;
}

private void pushToStack(List<NestedInteger> nestedList) {
    for (int i = nestedList.size() - 1; i >= 0; i--) {
        st.push(nestedList.get(i));
    }
}

Ternary Expression Parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public String parseTernary(String expression) {
    Deque<Character> st = new ArrayDeque<>();
    char op = ' ';
    for (int i = expression.length() - 1; i >= 0; i--) {
        char c = expression.charAt(i);
        if (c == '?' || c == ':') {
            op = c;
        } else {
            if (op == '?') {
                char c1 = st.pop(), c2 = st.pop();
                st.push(c == 'T' ? c1 : c2);
            } else {
                st.push(c);
            }
        }
    }
    return String.valueOf(st.peek());
}

Parsing

Basic Calculator

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 int calculate(String s) {
    // stores operands and signs alternatively
    Deque<Integer> stack = new ArrayDeque<>();
    int result = 0;
    int operand = 0, sign = 1;

    for (char c : s.toCharArray()) {
        if (Character.isDigit(c)) {
            operand = 10 * operand + c - '0';
        } else if (c == '+' || c == '-') {
            result += sign * operand;
            sign = c == '+' ? 1 : -1;
            operand = 0;
        } else if (c == '(') {
            stack.push(result);
            stack.push(sign);
            sign = 1;
            result = 0;
        } else if (c == ')') {
            result += sign * operand;
            result *= stack.pop();  // sign
            result += stack.pop();  // operand
            operand = 0;
        }
    }

    // + sign * operand at '+', '-', ')' or end of s
    return result + sign * operand;
}
This post is licensed under CC BY 4.0 by the author.