Scarlet Serenade

LeetCode Solutions | 1 - 100

1. Two Sum

hashtable

2. Add Two Numbers

use a variable to indicate if carrying

3. Longest Substring Without Repeating Characters

sliding window, instead of manually moving i to right each time, we can use a table to store the index the i will move to

5. Longest Palindromic Substring

dp[i][j] represents whether s[i..=j] can form a palindromic substring, dp[i][j] is true when s[i] equals to s[j] and s[i + 1..=j - 1] is a palindromic substring. When we found a palindrome, check if it's the longest one. Time complexity O(n^2).

for (let i = str.length - 1; i >= 0; i -= 1) {
    for (let j = i; j < str.length; j += 1) {
        // ...
        }
}

Manacher algorithm

15. 3Sum

sort to avoid duplicates; based on two sum

16. 3Sum Closest

sort & fix the left one, use two pointers to find the sum that minimizes the diff on the right part

17. Letter Combinations of a Phone Number

cartesian product + foldl


backtracking

22. Generate Parentheses

backtracking: chose left_paren or right_paren at curr
use left_cnt to cut branches


recursion based on n || dynamic programming

this approach will introduce duplicates

const prev = generateParenthesis(n - 1)
return [...prev.map(p => `()${p}`), ...prev.map(p => `${p}()`), ...prev.map(p => `(${p})`)]

better solution is divide the prev into two parts left and right

for (let i = 1; i <= n; i += 1) {
    for (let pairs_cnt_of_left = 0; pairs_cnt_of_left < i; pairs_cnt_of_left += 1) {
        for (const left_part of dp[pairs_cnt_of_left]) {
            for (const right_part of dp[i - 1 - pairs_cnt_of_left]) {
                dp[i].push(`(${left_part})${right_part}`)
            }
        }
    }
}

return dp[n]

23. Merge k Sorted Lists

merge lists one by one => merge with divide and conquer


heap with size k

25. Reverse Nodes in k-Group

const reverse = node => ... // do reverse in-place
const take = node => ... // take `k` nodes from node

// while `take` do `reverse`

26. Remove Duplicates from Sorted Array

two pointers, where to store, whats the current value, compare current value with last stored

31. Next Permutation

find the right_most peak

36. Valid Sudoku

the same with 348. Design Tic-Tac-Toe

38. count-and-say

just iterate

41. First Missing Positive

make an array, iterate through xs, mark array[x] as true, find the first index at which the value is false


O(1) space: move all elements within range to its right position, find the first index where the value is not right

the same with 448. Find All Numbers Disappeared in an Array

42. Trapping Rain Water

对每一个坑 heights[i],应填充的水量为 cmp::max(0, cmp::min(ph, nh) - h) // cmp::min(ph, nh) 即为其两侧 bound 中较小的
用 DP 找到 max_height_from_left[]max_height_from_right[]


注意到 max_height_from_left[]max_height_from_right[] 中的每一个元素只用一次,尝试压缩

max_height_from_left 很容易,for i in 0..heights.len() 时实时更新即可,而 max_height_from_right[] 是从相反的方向构造的。所以这里我们也将 i 从两个方向逼近

let mut left = 0;
let mut max_before_left = 0;
let mut right = heights.len() - 1;
let mut max_after_right = 0;
while left <= right {
    // 说明 left 的小的那边的 bound 已经确定了
    if max_before_left <= max_after_right {
        if max_before_left > heights[left] {
            ret += max_before_left - heights[left];
        }
        max_before_left = cmp::max(max_before_left, heights[left]);
        left += 1;
    } else {
        if max_after_right > heights[right] {
            ret += max_after_right - heights[right];
        }
        max_after_right = cmp::max(max_after_right, heights[right]);
        right -= 1;
    }
}

monotonic stack: for every heights[i] find next_higher_or_equal[i] and prev_higher[i]

ret += (next_higher_or_equal[i] - prev_higher[i] - 1) * (cmp::min(heights[next_higher_or_equal[i]], heights[prev_higher[i]]) - heights[i])

next_higher_or_equal[] 保证了同一水坑的同一高度的行仅会计算一次


monotonic stack

// monotonic decreasing stack, which means that the current bar is bounded by the previous bar in the stack.
let mut stack = vec![];
for (i, h) in heights.into_iter().enumerate().skip_while(|&(_, h)| h == 0) {
    while let Some(&(ph, pi)) = stack.last() {
        if ph <= h {
            stack.pop();
            // 每次 pop 的时候相当于把区间内的坑填到了和 cmp::min(left_bound, h) 的高度
            if let Some(&(lbh, li)) = stack.last() {
                ret += (cmp::min(lbh, h) - ph) * (i - li - 1) as i32;
            }
        } else {
            break;
        }
    }
    stack.push((h, i));
}

46. Permutations

const curr: number[] = []
const selected: boolean[] = new Array(nums.length).fill(false)
const f = () => {
    if (curr.length == nums.length) {
        ret.push([...curr])
        return
    }

    for (let i = 0; i < nums.length; i += 1) {
        if (selected[i]) continue

        selected[i] = true
        curr.push(nums[i])
        f()
        curr.pop()
        selected[i] = false
    }
}

function<void(int)> backtrack = [&](int from) {
    if (from == nums.size()) {
        ret.push_back(vector<int>(nums));
        return;
    }

    for (int curr = from; curr < nums.size(); curr += 1) {
        swap(nums[from], nums[curr]);
        backtrack(from + 1);
        swap(nums[from], nums[curr]);
    }
};

47. Permutations II

nums.sort((a, b) => a - b)

const curr: number[] = []
const selected: boolean[] = new Array(nums.length).fill(false)
const f = () => {
    if (curr.length == nums.length) {
        ret.push([...curr])
        return
    }

    for (let i = 0; i < nums.length; i += 1) {
        if (selected[i] || (nums[i] == nums[i - 1] && !selected[i - 1])) continue

        selected[i] = true
        curr.push(nums[i])
        f()
        curr.pop()
        selected[i] = false
    }
}

fn backtracking(nums: &mut [i32], pos: usize, ret: &mut Vec<Vec<i32>>) {
    if pos == nums.len() {
        ret.push(Vec::from(nums));
        return;
    }

    let mut selection = pos;
    let mut selected = HashSet::new();
    while selection < nums.len() {
        if selected.contains(&nums[selection]) {
            selection += 1
        } else {
            nums.swap(pos, selection);
            backtracking(nums, pos + 1, ret);
            nums.swap(pos, selection);

            selected.insert(nums[selection]);
            selection += 1;
        }
    }
}

45. Jump Game II

  1. calc min steps from tail to head
  2. bfs, use a curr and prev furthest value to indicating current level

48. Rotate Image

49. Group Anagrams

其实就是按照字符串中每个元素出现的个数分类,可以用一个长度位 26 的数组作为 key,遍历字符串的时候 arr[c] += 1,相同 key 对应的字符串就应该被归类在一起

如果个数只有 0,或者一,26 位的数组就可以用 26bit 的二进制数优化

如果空间有限的话,可以把每个 char 对应到一个素数,key = \prod primes

53. Maximum Subarray

一眼看上去就很像标准的 sliding window 问题,设立 i,j 两个指针表示范围。当范围内的和为正的时候表示这段距离还有价值,保留,增加 j。当范围内的和为负的时候,不如舍弃这一段让 i=j,sum 从零开始

仔细观察发现,其实 i 没有实际意义,可以只用 j,四舍五入就是直接遍历数组就行了,sum = max(0, sum),每次 sum 取 0 就代表舍弃了前面的那段

let ret = -Infinity
let curr = 0
for (let i = 0; i < nums.length; i += 1) {
    curr = Math.max(curr + nums[i], nums[i])
    ret = Math.max(ret, curr)
}

f i j = max left_sum cross_sum right_sum
    where left_sum = f i (m - 1)
          cross_sum = max $ subarray (contains m)
          right_sum = f (m + 1) j

54. Spiral Matrix

simulate the walking path


layer by layer

55. Jump Game

memorized recursion or dp


check from the back, if the final lastpos that can reach the end(or the prev lastpos) equals to zero, then return true

56. Merge Intervals

sort then merge using a stack

57. Insert Interval

use binary search to find when to merge the new interval

59. Spiral Matrix II

simulate the walking path


layer by layer

60. Permutation Sequence

based on next_permutation


对于 selections[],根据 k % factorial(selections.length - 1) 判断当前位应该选择 selections 中第几大的。递归以上步骤

71. Simplify Path

use an array, . makes no change, .. decrease length by one

72. Edit Distance

dp (insertion is equal to deletion

73. Set Matrix Zeroes

use two arrays to indicate elemen ts on which rows and cols should be set 0


instead of using two arrays, use the first element on the row and col

78. Subsets

backtracking a tree-like structure

external link

79. Word Search

check neighbors with a visited hash or making a mark on the board

80. Remove Duplicates from Sorted Array II

two pointers, next position to check and next position to store, compare next value with last 2 stored value

82. Remove Duplicates from Sorted List II

continuing check to following values to determine if its acceptable

83. Remove Duplicates from Sorted List

inplace, check curr with last


another list to store, check last stored value with curr value

84. Largest Rectangle in Histogram

use monotonic stack to calc prev_smaller[] and next_smaller[]

let ret = -Infinity
for (let i = 0; i < heights.length; i += 1) {
    ret = Math.max(ret, heights[i] * (next_smaller[i] - prev_smaller[i] - 1))
}

return ret

85. Maximal Rectangle

calc heights[][] for every points using dp
for every row, its the same with 84. Largest Rectangle in Histogram

86. Partition List

two list

87. Scramble String

memoized recursion || dp

const f = (xi: number, xj: number, yi: number, yj: number): boolean

can be optimized into

const f = (xi: number, yi: number, length: number): boolean

89. Gray Code

trash problem, dont do it remember how to get/set the bit number at specified position is enough

90. Subsets II

use a hash set to get the count of every number, recursion on unique numbers, decide how many are selected


instead of recursion, iter, get result from prev decided expr

93. Restore IP Addresses

just do it

95. Unique Binary Search Trees II

recursion (kinda like brute force

98. Validate Binary Search Tree

99. Recover Binary Search Tree