0. 743. Network Delay Time.java Level: Medium Tags: [BFS, DFS, Graph, Heap, PQ]
quesiton: sorting by travel delay/time will find better answer earlier?
Dijkstras algorithm
is based on repeatedly making the candidate move that has the least distance travelled.- PQ: pick close node to vist, and add siblings back to PQ
- avoid visited
- time: O(nLogn), visit n nodes, each time insert to heap takes O(logn) time
- space: O(n)
-
- build graph map, 2) traverse map, 3) prioritize short delay nodes first
- use a map
<node, timeElapsed>
globally track dealy to nodes; compare all at the end
0. Sort List.java Level: Medium Tags: [Divide and Conquer, Linked List, Merge Sort, Sort]
-
- find middle. 快慢指针
-
- Sort: 切开两半,先sort前半, 如果先sort了mid.next~end, sort后,中间点mid.next == null,再sort前半段
-
- Merge: 假设given list A, B 已经是sorted, 然后按照大小,混合。
- 要recursively call sortList() on partial list.
- 想做可以看讲义:http://www.jiuzhang.com/solutions/sort-list/
- 但是quick sort不建议用在list上面。
- 排列list, merge sort可能更可行和合理。原因分析在下面, 以及: http://www.geeksforgeeks.org/why-quick-sort-preferred-for-arrays-and-merge-sort-for-linked-lists/
1. [tool]. MergeSort.java Level: Medium Tags: [Lint, Merge Sort, Sort]
- Divide and conquer, recursively
- 先从中间分段, merge sort 左边 (dfs), merge sort 右边
- 最后merge起来
- merge的时候因为是做int[], 所以没办法必须要O(n) space
- Time O(nlogn), Space O(n)
2. 327. Count of Range Sum.java Level: Hard Tags: [BIT, Divide and Conquer, Merge Sort, PreSum, Segment Tree]
TODO: Write the code + merge function
- https://leetcode.com/problems/count-of-range-sum/discuss/77990/Share-my-solution
-
- build preSum[n+1]: then sum range [i,j]= preSum[j+1] - preSum[i]
-
- Divide and Conquer:
- 先考虑[start, mid] range里的 ran sum result
- 再考虑[mid, end] range里面的结果
- 最后考虑[low, high]总体的结果
- NOTE: should write merge() function, but that is minor, just use
Arrays.sort(nums, start, end)
, OJ passed - Every mergeSort() has a for loop => O(n log n)
- 如何 count range?
- 这里比较特别的一个做法: 找一个 [low, mid]里面的i, mid 之后的preSum作比较 (解释源自: https://blog.csdn.net/qq508618087/article/details/51435944)
- 即在右边数组找到两个边界, 设为
m, n
, - 其中m是在右边数组中第一个使得
sum[m] - sum[i] >= lower
的位置, - n是第一个使得
sum[n] - sum[i] > upper
的位置, - 这样
n-m
就是与左边元素i所构成的位于[lower, upper]
范围的区间个数.
- 边界[lower, higher] 在 sorted array 好作比较, 一旦过界, 就可以停止计算, 减少不必要计算.
- 上面这个n,m的做法可行的前提: preSum[]里面前后两个 range[low, mid], [mid, high]已经sorted了
- 也就是说, 在recursively mergeSort()的时候, 真的需要merge sorted 2 partitions
- 也许会问: 能不能sort呢, sort不久打乱了顺序? 对,打乱的是preSum[]的顺序.
- 但是不要紧: 很巧妙的, 分治的时候, 前半段/后半段 都在原顺序保留的情况下 分开process完了, 最后才merge
- 在做m,n 的range的时候, 原理如下, 比如preSum被分成这么两段:
[A,B,C]
,[D,E,F]
- 每一个preSum value
A
在跟 preSum[i] 作比较的时候A - preSum < lower
, 都是单一作比较, 不牵扯到 B, C - 因此,
[A, B, C]
是否保留一开始 preSum的顺序在此时不重要
- 每一个preSum value
- 此时最重要的是,
[A,B,C]
以及排序好, 那么在于lower
boundary 作比较的时候, 一旦过界, 就可以停止计算(减少不必要的计算)
- TODO?
- This segment tree approach(https://leetcode.com/problems/count-of-range-sum/discuss/77987/Java-SegmentTree-Solution-36ms)
- does not build segment tree based on given nums index
- it is built on sorted preSum array.
- regular segment tree based on nums array does not work:
- segment tree based on input array is good for: search/query by index
- is NOT good at: given range sum/value, find indexes
- why? segment tree is built based on index division, not by range value division.
3. 493. Reverse Pairs.java Level: Medium Tags: [BST, Binary Indexed Tree, Divide and Conquer, Merge Sort, Segment Tree]
给一串数字, count total reverse pair nums[i] > 2*nums[j]
, i < j
This problem can be solved with Merge sort concept, BST, Segment Tree and Binary Indexed Tree. Good for learning/review.
- Using merge sort concept (NOT merge sort impl).
- One very simply desire: if we want to know # elements between [i, j] such that
nums[i] > 2*nums[j]
, it would be so great if array is sorted!- If sorted, fix index i, keep j++ for all
nums[i]/2.0 > nums[j]
- We CANNOT just sort entire array. WHY? Because it distrupts the value of curr index i, and the restriction is:
find matching elements on right side of curr index i
- BUT, what about just sort
right side of i
, and make sure the subproblem (i+1, end) is solved first?
- If sorted, fix index i, keep j++ for all
- 灵感: use merge sort concept.divide and conquer [i ~ n] into 2 sections:
-
- solve subProblem(start,mid) & subProblem(mid+1, end). sort the sub array so that it can be used recursively at parent level.
-
- solve the curr pblem: for all [i, mid], check against [mid+1, end].
-
- Question1: does it cover all use cases?
- First, subProblem(start,mid) & subProblem(mid+1, end) recursively solves its own range
- Last, the only range is the current level problem check
[i, mid]
against its entire right side range:[mid+1, end]
. DONE. all covered.
- Question2: what it is okay for
subProblem(start,mid) & subProblem(mid+1, end)
partially sort the array?- that is the goal: 1) we want the right side range to be sorted; 2) left range is sorted but it does not matter since we treat [start, mid] as 1 group
- use classic while loop
while(j<=e && nums[i]/2.0 > nums[j])
to count pairs
- TODO
- split the array into index-based segment tree, where each element is at leaf
- store min of range: use min to determine if certain range is needed for further query
- query for each element right side range (i + 1, end), where it recursively query&aggregate sub-range if meeting requirement
nums[i] > 2*nums[j]
- only when target > subRange.min * 2: there are possible candidates, query further
- worst case O(n^2) when all tailing elements are meeting requirement.
- TODO
- Build the BST based on node value. It will be not applicable if we search after entire tree is built (our goal is right range), so we need to build right elements, and search/count right after the elements is added
- Worst case is still O(n^2), if all added nodes are meeting requirement
- search(tree, curr / 2.0)
- check each one of them
4. 23. Merge k Sorted Lists.java Level: Medium Tags: [Divide and Conquer, Heap, Linked List, Merge Sort, PriorityQueue]
给一个array of ListNode, 把所有node按照大小连成一条.
- By Definition, merge sort: divide the list into 2 parts
- recursively merge them together.
- time complexity: O(nlogk) divide by log(k) times, each recursive call can work on n nodes.
- space: O(logk) stacks
- Iterative, PQ来排列所有list的leading node.
- Note: k lists need to be sorted (luckily, already given)
- 时间:n*O(logk), where n = total node number, and PriorityQueue: logk,
- Note:
-
- 不要忘记customized priority需要一个customized new Comparator()
-
- Given list 里面也可能有null node, 不要忘记查.
-
- 如果k很大,一个机器上放不下所有的k list怎么办?
- 如果Merge起来的很长,一个机器上放不下怎么办?
0. Strobogrammatic Number II.java Level: Medium Tags: [DFS, Enumeration, Math, Sequence DFS]
TODO:
- use list, iterative? keep candidates and populating
- clean up the dfs code, a bit messy
- edge case of "0001000" is invalid, right?
- A bit like BFS solution: find inner list, and then combine with outter left/right sides.
- find all solutions, DFS will be easier to write than iterative/BFS
- when n = 1, there can be list of candidates at bottom of the tree, so bottom->up is better
- bottom->up, dfs till leaf level, and return candidates.
- each level, pair with all the candidates
- 其实就是剥皮,一层一层,是一个central-depth-first的,钻到底时候,return n=1,或者n=2的case,然后开始backtracking。
- 难的case先不handle.到底之后来一次overall scan.
- every level have 5 choices of digital pairs to add on sides. Need to do for n-2 times.
- Time complexity: O(5^n)
1. 22. Generate Parentheses.java Level: Medium Tags: [Backtracking, DFS, Sequence DFS, String]
- start with empty string, need to go top->bottom
- 取或者不取
(
,)
- rule: open parentheses >= close parentheses
- Note: 在DFS时 pass a reference (StringBuffer) and maintain, instead of passing object (String) and re-create every time
- time: O(2^n), pick/not pick, the decision repat for all nodes at every level
- time: T(n) = 2 * T(n - 1) + O(1) = O(2^n)
- space: < than 2^n results = O(2^n)
- figure out n=1, n=2 => build n=3, and n=4
- dfs(n-1) return a list of candidates
- add a pair of
()
to the candidates: either in front, at end, or contain the candidates
0. 146. LRU Cache.java Level: Medium Tags: [Design, Doubly Linked List, Hash Table, Linked List]
- 用了一个特别的双向的ListNode,有了head和tail,这样就大大加快了速度
- 主要加快的就是那个‘更新排位’的过程,找到item hashmap O(1), 做减法换位也都是O(1)
- Overall O(1)
- 巧妙点
-
- head和tail特别巧妙:除掉头和尾,和加上头和尾,都O(1)
-
- remove node: 把node.pre和node.next 连起来, node就自然而然的断开不要了
-
- 一旦知道怎么解决了,就不是很特别,并不是难写的算法
- moveToHead()
- insertHead()
- remove()
- Instead of building
Double Linked List
, utilize JavaDeque<E> queue = new LinkedList<>()
- works but problem:
queue.remove(E)
is O(n) - time: O(1) on average but much slower
1. 432. All One Data Structure.java Level: Hard Tags: [Design, Doubly Linked List]
- IMPORTANT: the problem aims to put keys of same frequency in same node! This affects the design of node
- Main a class
Node {keySet, count, last/next pointers}
- Each operation:
-
- finds target node and extract the key
-
- calculate: count +/- 1
-
- find new spot to store the key (prior positions or later positions)
-
- Be careful when handling the cases in inc() and dec()
2. 716. Max Stack.java Level: Medium Tags: [Design, Doubly Linked List, Stack, TreeMap]
- one to keep regular elements
- one to repat the max at current stack level
- time: O(n) for popMax() and O(1) for the rest operations
- space: O(n)
- Reference: https://leetcode.com/problems/max-stack/solution/
- Use TreeMap to store <Int, List of Nodes>, which gives: O(logN) insert, delete and find MAX
- Key reason to use
DoubleLinkedList
is to perform O(1) removal forpopMax()
- The problem becomes finding the target value & remove from DoubleLinkedList
- time: O(1) for popMax() and O(logN) for the rest
- space: O(n)
0. Interval Minimum Number.java Level: Medium Tags: [Binary Search, Divide and Conquer, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的最小值.
- SegtmentTree, methods: Build, Query. 这题是在SegmentTreeNode里面存min.
- 类似的有存:max, sum, min
1. Count of Smaller Number.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字, array size = n. 给一串query: 每个query是一个数, 目的找 count# items smaller than query element.
- 和平时的segment tree问题不同。 [0 ~ n] 代表实际数字: based on real value的segment tree.
- Modify时,把array里面的value带进去,找到特定的位子, 然后count + 1.
- 最终在SegmentTree leaf上面全是array里面实际的数字。
- node.count: 在node range里面的有多少个数字
- build() 只是 empty segment tree, 没有property
- modify() 需要: 1. 找到left, update count+=1; 2. aggregate all parent when after returning
- 所以每一个modify 都是在整个path上所有的node上 + count
- 在query前,给进去的start和end是: 0 ~ value-1.
value-1
: 找比自己所在range小1的range(那么自然而然地就不包括自己了),这样就找到了smaller number.
- [那么其他做过的SegmentTree是怎么样呢?]
- 那些构成好的SegmentTree(找min,max,sum)也有一个Array。但是构成Tree时候,随Array的index而构架。
- 也就是说,假如有Array[x,y,....]:在leaf,会有[0,0] with value = x. [1,1] with value = y.
- [但是这题]
- 构成时,是用actual value.也就是比如Array[x,y,....]会产生leaf:[x,x]with value = ..; [y,y]with value =...
- 其实很容易看穿:
- 若给出一个固定的array构成 SegmentTree,那估计很简单:按照index从0~array.lengh,leaf上就是[0,0] with value = x.
- 若题目让构造一个空心SegmentTree,
based on value 0 ~ n-1 (n <= 10000)
, 然后把一个Array的value modify 进去。 - 这样八成是另外一种咯。
2. [tool] Quick Select - Median.java Level: Easy Tags: [Array, Lint, Quick Select, Quick Sort, Two Pointers]
给一串无序数组, 找到median(sort之后 位置在中间的数字).
- 跟
kth largest element in an Array
的 template一样. - quickSelect 可以找到 kth 最小的元素
- 利用这个原理, 找这个kth最小值, 然后如果 == target index, 就找到了我们的median
- 主要步骤:
-
- partition
-
- check end state
pivot index ?= target index
- check end state
-
- recursive call one part of the array
-
- time: 与quickSort不同在于, 每次只要在一半list里面recurring, 所以把O(logn)的时间复杂度降到O(n)
- n + n/2 + n/4 + n/8 + ....+ 1 = O(2n) = O(n)
- space: O(logn), based on recursive stacks
3. Segment Tree Modify.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给一个segmentTree, node里面存max. 写一个modify function: modify(node, index, value).
- Recursively 在segment tree里面找index, update it with value.
- 每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下
- 最后轮回到头顶,头顶一下包括头顶,就全部都是max了
4. Segment Tree Query II.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
- 和 Segment Tree Query I 以及其他Segment Tree类似: 这个SegmentTreeNode return count of elements in range
- 这个题目考了validate input source:input 的start,end可能超出root[start,end]。
- 那么第一步就要先clear一下: 1. 完全不在range就return 0. 2. 有range重合就规整到root的range.
5. Interval Sum.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的sum.
- 其实是segment tree 每个node上面加个sum
- 记得Segment Tree methods: Build, Query
- Note: 存在SegmentTreeNode里面的是sum. 其他题目可能是min,max,count ... or something else.
6. Interval Sum II.java Level: Hard Tags: [Binary Search, Lint, Segment Tree]
SegmentTree大集合. Methods: build, query, modify
. 不难。只是要都记得不犯错.
- build: recursively build children based on index-mid and link to curr node
- query: recursively try to find
node.start == targetStart && node.end == targetEnd
. Compare with node.mid - modify: recursively try to find
node.start == targetStart && node.end == targetEnd
; modify and recursively assign upper interval with updated interval property.
7. [lint]. Segment Tree Build II.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个array, 建造segment tree structure,
每个treeNode 里面存这个range里的 max value, return root node.
-
给的是Array. 注意找区间内的max, assign给区间. 其余和普通的segment tree build一样
-
注意, segment tree是根据array index range 排位: 根据index in [0, array.length - 1]割开区间, break到底
-
最终start==end做结尾
-
这道题要trackmax, 那么在leaf node assign max=A[start] or A[end]
-
往上,parent一层的max:就是比较左右孩子,其实都是在两个sub-tree里面比较sub-tree的max。
-
Devide and Conquer
-
先分,找到left/right,比较max,在create current node,再append到当前node上面。
-
实际上是depth-first, 自底向上建立起的。
8. [lint]. Nth to Last Node in List.java Level: Easy Tags: [Linked List, Lint]
- 先找到nth node
- 然后head开始跑
- node 到底,而head ~ node刚好是 n 距离。所以head就是要找的last nth
9. [lint]. Product of Array Exclude Itself.java Level: Medium Tags: [Array, Lint]
10. [lint]. Compare Strings.java Level: Easy Tags: [Lint, String]
看StringA是不是包括所有 StringB的字符. Anagram
- 比较一下大小, null.
- 然后用int[]来count chars from A, count[x]++. 再对照chars in B, count[x]--
- 如果 count[c] < 0, 就 false.
- O(n)
11. [lint]. Segment Tree Query.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给了segment Tree, node里面有Max value, 找[start,end]里面的max
- 根据[start,end]跟 mid of (root.start, root.end) 做比较:
-
- [start,end] on LEFT of mid
-
- [start, end] on RIGHT of mid
-
- [start, end] includes mid: break into 2 queries
- query [leftNode, start, node.left.end]
- query [rightNode, node.right.start, end]
-
12. [lint]. HashHeap.java Level: Hard Tags: [HashHeap, Heap, Lint]
非题.是从九章找来的HashHeap implementation.
- An efficient implementation of a priority queue.
- The linear hash function monotonically maps keys to buckets, and each bucket is a heap
- https://xlinux.nist.gov/dads/HTML/hashheap.html
13. [lint]. Longest Words.java Level: Easy Tags: [Hash Table, Lint, String]
给一串String, 找到最长的长度, 把最长的String全都return
- <Integer,List>
- 存最长值, 最后map.get(max)
14. [lint]. Anagrams.java Level: Medium Tags: [Array, Hash Table, Lint]
把anagram找到并output
- 存在int[26], Arrays.toString(arr) 就是 string key: character frequency map
- anagram都有一样的key, 存进hashmap<string, list of anagrams>
- output anagrams
- HashMap 的做法. sort每个string, 存进HashMap, 重复的就是anagrams,最后输出。
- toCharArray
- Arrays.sort
- Stirng.valueOf(char[])
- 时间nLO(logL),L是最长string的长度。
- Arrays.toString(arr)的做法。arr是int[26], assuming only have 26 lowercase letters.
- Count occurrance, 然后convert to String,作为map的key.
- Time complexity: nO(L)
- 另一种做法:http://www.jiuzhang.com/solutions/anagrams/
-
- take each string, count the occurrance of the 26 letters. save in int[]count.
-
- hash the int[] count and output a unique hash value; hash = hash * a + num; a = a * b.
-
- save to hashmap in the same way as we do.
- 这一步把for s: strs 里面的时间复杂度降到了O(L). L = s.length().
- Need to work on the getHash() function.
- 时间变成n*O(L). Better.
15. [lint]. 3 Sum Closest.java Level: Medium Tags: [Array, Lint, Two Pointers]
3Sum 的一种简单形式, 并且都没有找index, value, 而只是找个sum罢了.
double for loop。 2Sum只能用土办法 left/right 2 pointers。 O(n^2)
注意:check closest时候用long, 以免int不够用
16. [lint]. Unique Characters.java Level: Easy Tags: [Array, Lint, String]
determine if characters are unique in string
- space O(n), time O(n)
- space O(n), time O(nlogn)
- double for loop: O(n^2)
17. [lint]. Lowest Common Ancestor II.java Level: Easy Tags: [Hash Table, Lint, Tree]
给一个Binary Tree root, 以及两个node A, B. 特点: node里面存了parent pointer. 找 lowest common ancestor
- 这个题有个奇葩的地方, 每个node还有一个parent, 所以可以自底向上.
- save visited in hashset. 第一个duplicate, 就是A B 的 lowest common ancestor
- 自底向上。利用parent往root方向返回
- 把所有parent存下来, 然后在两个list里面找最后一个 common node
- 无法从root去直接搜target node 而做成两个list. 因为根本不是Binary Search Tree!
18. [lint]. Heapify.java Level: Medium Tags: [HashHeap, Heap, Lint, MinHeap]
Turn unsorted array into a min-heap array, where for each A[i],
A[i * 2 + 1] is the left child of A[i] and A[i * 2 + 2] is the right child of A[i].
- Heap用的不多. 得用一下, 才好理解. 通常default 的PriorityQueue就是给了一个现成的min-heap:
- 所有后面的对应element都比curr element 小。
- Heapify里面的siftdown的部分:
- 只能从for(i = n/2-1 ~ 0), 而不能从for(i = 0 ~ n/2 -1): 必须中间开花,向上跑的时候才能确保脚下是符合heap规则的
- 确保在heap datastructure里面curr node下面的两个孩子,以及下面所有的node都遵循一个规律
- 比如在这里,若是min-heap,就是后面的两孩子都要比自己大。若不是,就要swap。
- for each element A[i], we will get A[i * 2 + 1] >= A[i] and A[i * 2 + 2] >= A[i].
- siftdown时:在curr node和两个child里面小的比较。如果的确curr < child, 搞定,break while.
- 但若curr 并不比child小,那么就要换位子,而且继续从child的位子往下面盘查。
19. [lint]. Subarray Sum.java Level: Easy Tags: [Array, Hash Table, Lint, PreSum, Subarray]
给一串数字, 找其中的一个subarray的 [start, end] index, 条件: subarary sum == 0.
subarray sum equals k
的简单版: k = 0- 求preSum, 然后不断check
map.containsKey(preSum - k)
. - 如果
priorSum = preSum - k == 0
, 说明 [priorSum.index + 1, curr index] 就是我们要找的这一段
- 求preSum, 然后不断check
- 分析出,如果sum[0
a]=x, 然后sum[0b]=x, 说明sum[a+1 ~ b] == 0 - 用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组.
20. [lint]. Recover Rotated Sorted Array.java Level: Easy Tags: [Array, Lint]
rotate的意思,是有个点断开,把一边的array节选出来放在另外一边。 Rotate三步: rotate前半 rotate后半 rotate全部
注意先找到断点。
21. [lint]. 2 Sum II.java Level: Medium Tags: [Array, Binary Search, Lint, Two Pointers]
与 2sum II - input array is sorted类似. 都是sort array, 然后two pointer.
LintCode的题. 注意找的是greater/bigger than target。
由于给定条件允许O(nLogn):
sort
two pointer
while里面two pointer移动。每次如果num[left]+num[right] > target,那么其中所有num[left++]的加上num[right]都>target.
也就是,num[right]不动,计算加入挪动left能有多少组,那就是: right-left这么多。 全部加到count上去。
然后right--.换个right去和前面的left部分作比较。
22. [lint]. Segment Tree Build.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个区间[startIndex, endIndex], 建造segment tree structure, return root node.
- Usage
- which of these intervals contain a given point
- which of these points are in a given interval
- Recursively build the binary tree
- 左孩子:(A.left, (A.left+A.rigth)/2)
- 右孩子:((A.left+A.rigth)/2+1, A.right)
23. [tool]. MergeSort.java Level: Medium Tags: [Lint, Merge Sort, Sort]
- Divide and conquer, recursively
- 先从中间分段, merge sort 左边 (dfs), merge sort 右边
- 最后merge起来
- merge的时候因为是做int[], 所以没办法必须要O(n) space
- Time O(nlogn), Space O(n)
24. [tool]. Hash Function.java Level: Easy Tags: [Hash Table, Lint]
In general, there is no universal recipe to stick to when it comes to implementing hashCode(). https://www.baeldung.com/java-hashcode
-
解释Hash怎么做.
-
Hash function例子:
-
hashcode("abcd") = (ascii(a) * 33^3 + ascii(b) * 33^2 + ascii(c) *33^1 + ascii(d)*33^0) % HASH_SIZE
-
用到的参数比如: magic number 33, HASH_SIZE.
-
Hash的意义是:给一个string key, 转换成数字,从而把size变得更小。
-
真实的implementation还要处理collision, 可能需要design hash function 等等。
- hashRst = hashRst * 33 + (int)(key[i]);
- hashRst = hashRst % HASH_SIZE;
- 原因是,hashRst会变得太大,所以不能算完和 再 %...
25. [tool]. UnionFind.java Level: Medium Tags: [Lint, Union Find]
- union(), find()
- Path Compresion: store skip father after found, which makes find O(1)
26. [tool]. Topological Sorting.java Level: Medium Tags: [BFS, DFS, Lint, Topological Sort]
- indegree tracking: Track all neighbors/childrens. 把所有的children都存在 inDegree<label, indegree count>里面
- Process with a queue: 先把所有的root加一遍(indegree == 0),可能多个root。并且全部加到queue里面。
- BFS with Queue:
- Only when map.get(label) == 0, add into queue && rst. (indegree剪完了, 就是root啦)
- inDegree在这里就 count down indegree, 确保在后面出现的node, 一定最后process.
- 几个graph的condition:
-
- 可能有多个root
-
- directed node, 可以direct backwards.
TODO:
- build
Map<DirectedGraphNode, Integer> inDegree = new HashMap<>();
and include the root itself - that is more traditional indegree building
0. Restore IP Addresses.java Level: Medium Tags: [Backtracking, DFS, String]
给一串数字, 检查是否是valid IP, 如果合理, 给出所有valid 的IP组合方式.
- 递归的终点:list.zie() == 3, 解决最后一段
- 递归在一个index上面, pass s.toCharArray()
- validate string要注意leading '0'
- 注意: 递归的时候可以用一个start/level/index来跑路
- 但是尽量不要去改变Input source, 会变得非常confusing.
- note: code有点messy, 因为要考虑IP的valid情况
- 那个'remainValid', 其实是一个对于remain substring的判断优化, 不成立的就不dfs了
1. Reverse String.java Level: Easy Tags: [String, Two Pointers]
Similar to Reverse Integer. 可以用StringBuffer, 也可以two pointer reverse head/tail
2. Binary Representation.java Level: Hard Tags: [Bit Manipulation, String]
- 首先要分两半解决,断点是'.': str.split("\.");
- Integer那一半好弄,whie loop里: num%2, num/2. 做一个
parseInteger()
function - Decimal那边复杂点. 做一个
parseDecimal()
function: - bit == 1的数学条件: 当下num * 2 >= 1。 更新: num = num * 2 - 1;
- bit == 0的数学条件: num * 2 < 1. 更新: num = num * 2
- num是 double, 小数在
num = num * 2 - 1
的公式下可能无限循环 - 因此check: num重复性,以及binary code < 32 bit.
- 所以题目也才有了32BIT的要求!
3. Palindromic Substrings.java Level: Medium Tags: [DP, String]
根据题意, count # of palindromic substring. (不同index截取出来的substring算不同的情况)
- build boolean[][] to check isPalin[i][j] with DP concept
- �check all candidates isPalin[][]
- O(n^2)
4. Count and Say.java Level: Easy Tags: [Basic Implementation, String]
介绍一种count数字的方法, 然后每一行读出上一行的结果, 一行一行推算. 问nth行是啥样?
- 主要是题意很难理解, 非常misleading, 等到看明白题目, 其实没有什么算法要求.
- Count duplicates and print
5. Letter Combinations of a Phone Number.java Level: Medium Tags: [Backtracking, String]
方法1: Iterative with BFS using queue.
方法2: Recursively adding chars per digit
6. Orderly Queue.java Level: Hard Tags: [Math, String]
7. Scramble String.java Level: Hard Tags: [DP, Interval DP, String]
- 给两个string S, T. 检验他们是不是scramble string.
- scramble string 定义: string可以被分拆成binary tree的形式, 也就是切割成substring;
- 旋转了不是leaf的node之后, 形成新的substring, 这就是原来string的 scramble.
- 降维打击, 分割, 按照长度来dp.
- dp[i][j][k]: 数组S从index i 开始, T从index j 开始, 长度为k的子串, 是否为scramble string
- 一切两半以后, 看两种情况: , 或者不rotate这两半. 对于这些substring, 各自验证他们是否scramble.
- 不rotate分割的两半: S[part1] 对应 T[part1] && S[part2] 对应 T[part2].
- rotate分割的两半: S[part1] 对应 T[part2] && S[part2] 对应 T[part1].
- len == 1的时候, 其实无法旋转, 也就是看S,T的相对应的index是否字符相等.
- initialization非常非常重要. 很神奇, 这个initailization 打好了DP的基础, 后面一蹴而就, 用数学表达式就算出了结果.
- input s1, s2 在整个题目的主要内容里面, 几乎没有用到, 只是用在initialization时候.
- More details, 看解答
8. Compare Version Numbers.java Level: Medium Tags: [String]
给两串version number, 由数字和'.' 组成. 比较先后顺序.
If version1 > version2 return 1, if version1 < version2 return -1, otherwise return 0.
- 用 str.split("\.") 分割string
- Convert成integer, 逐个击破
- '1.0' 和 '0' 是相等的
- 如果可以假设version integer都是valid, 直接Integer.parseInt()就可以了
- 不然的话, 可以compare string
9. Longest Common Substring.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP, String]
- 两个string, 找最值: longest common string length
- 序列型, 并且是双序列, 找两个序列 (两维的某种性质)
- dp[i][j]: 对于 A 的前i个字母, 对于 B 的前j个字母, 找最长公共substring的长度
- dp = new int[m + 1][n + 1]
- dp[i][j] = dp[i - 1][j - 1] + 1; only if A.charAt(i - 1) == B.charAt(j - 1)
- 注意track max, 最后return
- space O(n^2), time(n^2)
- 空间优化, [i] 只有和 [i - 1] 相关, 空间优化成 O(n)
- 找所有A的substring, 然后B.contains()
- track max substring length
- O(n^2) time
10. Shortest Palindrome.java Level: Hard Tags: [KMP, String]
- check (mid, mid+1), or (mid-1, mid+1).
- If the two position matches, that is a palindrome candidate
- 比较front string 是否是 end string 的substring
- O(n^2)
- timeout on last case: ["aaaaaa....aaaacdaaa...aaaaaa"]
- TODO
11. Space Replacement.java Level: Medium Tags: [String]
12. Palindrome Pairs.java Level: Hard Tags: [Hash Table, String, Trie]
Obvious的做法是全部试一遍, 判断, 变成 O(n^2) * O(m) = O(mn^2). O(m): isPalindrome() time.
当然不行了, 那就看是O(nlogN), 还是O(n)?
O(mn)
- 每一个word, 都可以拆分成 front + mid + end. 如果这个word + 其他word可以组成palindrome,那就是说
- 砍掉 (mid+end), front.reverse() 应该存在 words[] 里面.
- 砍掉 (front+mid), end.reverse() 应该存在 words[] 里面.
- 我们用HashMap存所有的<word, index>, 然后reverse, 找配对就好.
- 如果有 empty string "", 那么它跟任何一个palindrome word, 都可以配对, 并且根据位置前后变换, 凑成2份 distinct indexes.
- 这样就有了那个
if (reverseEnd.equals("")) {...}
的logic. - 注意: 虽然在处理砍头/砍尾的两个 for loop里面都在根据 empty string 重复记录, 但因为 "" 自己本身不能作为起点, 所以overall只会在被其他palindrome配对时记录一次.
还要做一下那.
13. Longest Substring Without Repeating Characters.java Level: Medium Tags: [Hash Table, String, Two Pointers]
方法1: Two Pointers 双指针: 从start开始遍历, 但是第一步是while loop来推进end; 直到推不动end, 然后start++ 巧妙点: 因为end是外围variable, 在start的loop上, end不会重置;[star ~ end] 中间不需要重复计算. 最终可以O(n);
Previous verison of two pointers:
用两个pointer, head和i.
注意:head很可能被退回到很早的地方,比如abbbbbba,当遇到第二个a,head竟然变成了 head = 0+1 = 1.
当然这是不对的,所以head要确保一直增长,不回溯
方法2: HashMap<Char, Integer>: <character, last occurance index> 一旦有重复, rest map. 没有重复时候, 不断map.put(), 然后求max值
问题: 每次reset map之后就开始从新从一个最早的index计算, 最坏情况是O(n^2): 'abcdef....xyza'
14. Reverse Words in a String II.java Level: Medium Tags: [String]
- reverse用两回. 全局reverse。局部:遇到空格reverse
- 注意ending index:
i == str.length - 1
, 结尾点即使没有' '也要给reverse一下最后一个词
15. One Edit Distance.java Level: Medium Tags: [String]
如果S, T只用一个operation就能变成相等, return true.
- 换完之后,理论上换成的String 就应该全等
- for loop, 一旦找到不一样的char, 就判断那三种可能性: insert/delete/replace
- insert/delete 对于2个string来说, 效果是类似的
- O(n)
16. Distinct Subsequences.java Level: Hard Tags: [DP, String]
Double Sequence DP: 0. DP size (n+1): 找前nth的结果, 那么dp array就需要开n+1, 因为结尾要return dp[n][m]
- 在for loop 里面initialize dp[0][j] dp[i][0]
- Rolling array 优化成O(N): 如果dp[i][j]在for loop里面, 就很好替换 curr/prev
17. Encode and Decode Strings.java Level: Medium Tags: [String]
如题.
- 'word.length()#word' 这样encode, 可以避免遇到#
- 基于我们自己定的规律, 在decode的里面不需要过多地去check error input, assume所有input都是规范的.
- decode就是找"#",然后用"#"前的数字截取后面的string.
18. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
19. Expression Add Operators.java Level: Hard Tags: [Backtracking, DFS, Divide and Conquer, String]
给一个数字String, 数字来自0-9
, 给3个操作符 +
,-
,*
, 看如何拼凑, 可以做出结果target.
output 所有 expression
- 跟string相关, 写起来可能稍微繁琐一点
- 数字有 dfs([1,2,3...]) 组合方法
- operator有[
+
,-
,*
] 3种组合方法
- 注意1: 乘号要特殊处理, pass along 连乘的数字, 计算下一步乘积的时候, 要 sum - preProduct + product
- 注意2: '01' 这种数字要skip
- 注意3: 第一个选中数字不需要加操作符, 直接加进去
- Time: O(4^n), Space: O(4^n)
- T(n) = 3 * T(n-1) + 3 * T(n-2) + 3 * T(n-3) + ... + 3 *T(1);
- T(n-1) = 3 * T(n-2) + 3 * T(n-3) + ... 3 * T(1);
- Thus T(n) = 4T(n-1) = 4^2 * T(n - 1) = .... O(4^n)
- 逻辑一样, 代码更短, 只不过不做list, 直接pass
buffer + "+" + curr
- 因为每次都创建新string, 所以速度稍微慢一点. Time complexity 一样
20. Longest Valid Parentheses.java Level: Hard Tags: [Coordinate DP, Stack, String]
给一串string, 里面只有(
, )
. 找最长valid parentheses 的长度.
- use dp[i] track local max, maintain global max
- int[] dp. dp[i]: longest valid string that ends on i.
- 结尾是 ')', 2种情况: 1. 刚好s[i-1]是'('; 2. s[i]的')'更前面的一个起始'(' 对应
- 注意, 结尾如果是'('属于不合理情况, 忽略.
- init: dp[0] = 0, 单个char不可能成型.
- 计算顺序: 从左到右, 找local max, maintain global max
- O(n) space, O(n) runtime
- Stack 里面存所有的open/close parentheses.
- 如果遇到stack.top()刚好开合结掉, 就stack.pop().
- 剩下的都是不合理的elements.
- 有点像negatively找 solution:
endIndex - 最后一个failedIndex(stack.pop()) - 1
, 应该就是最后一个succeeded string的长度 - 每次更新 endIndex 为stack.top(), 然后从stack继续找下一个failedIndex
- 所有的length作比较, 就可以找出最长length
- O(n) stack space, O(n) runtime. 应该比dp慢一点, 因为做了2遍O(n)
21. Interleaving String.java Level: Hard Tags: [DP, String]
双序列DP, 从最后点考虑. 拆分问题的末尾, 考虑和s1, s2 subsequence之间的关联.
求存在性, boolean
22. Sort Letters by Case.java Level: Medium Tags: [Partition, Sort, String, Two Pointers]
给一串字符(ASCII 大写, 小写字母), 要求sort 小写字母, 在大写字母前面.
字母间的前后顺序无所谓, 也不需要preserve original order .
跟sort color分成相似.
- 其实就是quick sort里面的partition function的简化版
- Two pointers, 找一个 pivot 'a' 来区分大写小写字母
- ASCII code 里面 大写字母在小写字母前面, 数字更小
- 然后 while, move start++, end--,
- 每一轮都swap
- 直接用两个 pointer left/right 标记开头结尾
- 每次遇到
>= 'a'
就是小写字母, swap(chars, i, left); - 每次遇到
< 'a'
就是大写字母, swap(chars, i, right); - 注意: 每次处理完left swap, 任由for loop i++, 因为确定 [0 left] 都是准确的
- 每次处理完 right swap, 我们不确定从 right index 换过来的是不是正确的, 所以 i--, 跟for loop 的 i++抵消.
- 写 while loop 的 solution看起来更容易理解.
23. [HackerRank]. Change to Anagram.java Level: Easy Tags: [String]
HackerRank里面的random 题目: 给一个string, 一切成两半, 看两半要变化多少个字符, 能变成anagram.
-
切两半成两个String A,B. 分别在int count[26]里面++, --.
-
记录 26个小写字母的频率. 如果全部抵消, 就是anagram.
-
注意: 最后count出来要除以2:字母不同,会在不同的字母位上加减count,那么就是刚好重复计算了一遍。所以除以二
-
Note: HackerRank里要注意自己写: Scanner, import java.util, non-static method ...etc.
24. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start
25. Next Closest Time.java Level: Medium Tags: [Basic Implementation, Enumeration, String]
给一个时间string"12:09", 用里面的4个integer组合成其他时间string, 目标找最小的next time.
如果组合出的time string 在input time之前, 默认 + 24 hours.
- enumerate all candidates and filter to keep the correct ones
- String.compareTo(string) -> gives lexicographical comparision
26. Group Shifted Strings.java Level: Medium Tags: [Hash Table, String]
- shit by offset.
int offset = s.charAt(0) - 'a';
- increase if less than 'a':
if (newChar < 'a') newChar += 26;
- 相同shift规则的string, 能被推算到同一个零起始点,就是共同减去一个char,最后就相等。以此作为key,用HashMap。一目了然。
- 记得根据题目意思,一开始要String[] sort一下。
27. [lint]. Compare Strings.java Level: Easy Tags: [Lint, String]
看StringA是不是包括所有 StringB的字符. Anagram
- 比较一下大小, null.
- 然后用int[]来count chars from A, count[x]++. 再对照chars in B, count[x]--
- 如果 count[c] < 0, 就 false.
- O(n)
28. [lint]. Longest Words.java Level: Easy Tags: [Hash Table, Lint, String]
给一串String, 找到最长的长度, 把最长的String全都return
- <Integer,List>
- 存最长值, 最后map.get(max)
29. [lint]. Unique Characters.java Level: Easy Tags: [Array, Lint, String]
determine if characters are unique in string
- space O(n), time O(n)
- space O(n), time O(nlogn)
- double for loop: O(n^2)
30. 788. Rotated Digits.java Level: Easy Tags: [Basic Implementation, String]
- [3,4,7] -> cannot rotate, failures. Must NOT have. set1
- 2,5,6,9 -> good candidates. Must have 1. set2
- [0,1,8] -> goes back to itself. can have
- loop over [1, N], count=int[10] appearance.
- set1 meet 0
- set2 meet at least 1
31. 22. Generate Parentheses.java Level: Medium Tags: [Backtracking, DFS, Sequence DFS, String]
- start with empty string, need to go top->bottom
- 取或者不取
(
,)
- rule: open parentheses >= close parentheses
- Note: 在DFS时 pass a reference (StringBuffer) and maintain, instead of passing object (String) and re-create every time
- time: O(2^n), pick/not pick, the decision repat for all nodes at every level
- time: T(n) = 2 * T(n - 1) + O(1) = O(2^n)
- space: < than 2^n results = O(2^n)
- figure out n=1, n=2 => build n=3, and n=4
- dfs(n-1) return a list of candidates
- add a pair of
()
to the candidates: either in front, at end, or contain the candidates
32. 408. Valid Word Abbreviation.java Level: Easy Tags: [Basic Implementation, String]
tricky: find integer within a string edge case: leading '0' should not be allow in such abbr.
33. 415. Add Strings.java Level: Easy Tags: [Basic Implementation, Math, String]
- Use i, j to process from end of 2 strings
- handle edge case for i, j
- if i < 0, its num = 0 (since we are doing sum, blindly setting 0 is okay)
- Note:
sb.insert(0, x)
is much slower than doing a finalsb.reverse()
- when converting to int[], remember to reverse string.
- when converting to int[], remember to reserve extra space for carry
34. 1108. Defanging an IP Address.java Level: Easy Tags: [Basic Implementation, String]
35. 383. Ransom Note.java Level: Easy Tags: [Basic Implementation, String]
count chars in int[256]
36. 76. Minimum Window Substring.java Level: Hard Tags: [Hash Table, Sliding Window, String, Two Pointers]
基本思想:
- 用个char[]存string的frequency.
- 2 pointer:
- move
end
to find a valid window; - once valid inwindow found: now move
start
to narrow down to minimum window. - once window invalid, continue moving
end
and repeat last 2 steps
- move
- HashMap的做法比char[]写起来要复杂一点, 但是更generic
- Almost identical approach as in
438. Find All Anagrams in a String
- use sliding window template:
-
- extend right pointer and reduce char count
-
- process when count == 0
-
- contract/shrink left side
-
- special on the
3) step
:- there is no hard length limit in this problem: in fact, the goal is to find the shortest length
3) step
now apperas in thewhile(counter == 0)
loop- shrink the left side of the window as long as counter == 0, until we break the
counter==0
balance.
- time: O(n) one pass
- space: O(1), freq[256] can be ignored.
- Two Pointers, use 1 char freq map + counter to determine valid state
- Inspired by: https://leetcode.com/problems/minimum-window-substring/discuss/26808/Here-is-a-10-line-template-that-can-solve-most-'substring'-problems
- Idea: use freqMap and counter to maintain a valid substring range, use two pointers to iterate; reduce to
counter==0
which is the valid substring state. - Steps:
-
- build valid freq count map based on target string
-
- use end index [0~n) to find valid char and reduce counter to find valid range
-
- count==0 gives valid range: process; then
map[s.charAt(start++)]++ == 0
to break the peace
- count==0 gives valid range: process; then
-
- Explain
if (map[s.charAt(start++)]++ == 0) counter++
:- when
count != 0
,map[s.charAt(end++)]--
reduces freq regardless of what char it visits (it can be ANY char, rather than T characters) - when
count == 0
,map[s.charAt(start++)]++
increases freq regardless of what char that is.- if
map[s.charAt(start)] == 0
: it is a T character being reduced to 0 previously (so we can break the balance on this char) - YES, map has other index that has 0 freq: however,
start
ONLY covers indexes thatend
has stepped through :)
- if
- when
- time: O(n)
- space: O(1)
- much faster than method2: skip the O(256*n) comparison logic.
- Note: from the concept, it is the reversed thinking of method2.
- Use 2 char freq maps: source/target.
- target map: fixed freq map, used for comparision
- source map: attempt to build a valid freq map on the fly
- two pointers:
- use index
start=[0, n)
as start index of source candidate - have a end pointer that will attempt to as far as possible to find 1st valid sequence
- use index
- time: have double while loop, but still O(n), why?
- end pointer will at most reach full length n, only once
- start pointer iterate source strichtly once O(n)
- overall, it will be O(n)
- space: O(1), only used a constant char[256]
- Option2: use map, a bit more generic
37. 293. Flip Game.java Level: Easy Tags: [String]
- 可以用 sb.replace(i, j, "replacement string")
- 简单按 window=2 来扫描
- 原来只需要从'++'转到'--'的情况
- O(n)
38. 686. Repeated String Match.java Level: Easy Tags: [Basic Implementation, Edge Case, String]
Track: 纸上分析edge case. Validation helps speed it up.
39. 1216. Valid Palindrome III.java Level: Hard Tags: [DFS, DP, Memoization, String]
- Transform the problem:
removing at most k items to make it a palindrome
- that is: find the longest palindrome subsequence with length x, such that
n - x <= k
516. Longest Palindromic Subsequence
utilizes Interval DP to find LPS length x- at the end, perform n - x <= k?
- time: O(n^2) to find LPS
- space: O(n^2) for dp
- Either times out or too much space used
- time: O(n^2)
- space: O(n^2) or O(k*n^2)
40. 5. Longest Palindromic Substring.java Level: Medium Tags: [DP, String]
给一个string, 找到最长的palindrome substring.
Related: Longest Palindromic Subsequence, Palindrome Partioning II
O(n^2) is not too hard to think of. How about O(n)?
- Very similar to
216. Longest Palindromic Subsequence
, but this problem requires solid substring(i+1, j-1) to be palindromic - Similarly: process i = n-1, from end so [i + 1, j] is always ready to consume
- boolean dp[i][j] to mark range (i, j) as palindrome or not.
- 在计算 dp[i][j]的时候, isPalin[i+1][j-1]应该已经计算过了.
- time: O(n^2) dp
- space: O(n^2)
- 从中间劈开, 遍历i: 从n个不同的点劈开: 每次劈开都看是否可以从劈开出作为palindromic的中点延伸
- palindrome两种情况: odd, even palindrome
- Worst case: 整个string都是相同字符,time complexity变成: 1 + 2 +3 + ... +n = O(n^2)
41. 58. Length of Last Word.java Level: Easy Tags: [String]
给一个String, 里面有lower case character 和 ' '. 找最后一个单个word的长度
- 从末尾找' ', 找到了计算长度
- 记得要s.trim(), 把首尾的space去掉
42. 824. Goat Latin.java Level: Easy Tags: [Basic Implementation, String]
43. 151. Reverse Words in a String.java Level: Medium Tags: [String]
- Option1: With
s.split(" ")
: No brain, and super fast - Option2: With
s.split("\\s+")
, it skips space, but slow. Use sb.insert(0, xxx) - trim() output
- Time, Space: O(n)
- flip entire string, then flip each individual string
- Time, Space: O(n)
44. 67. Add Binary.java Level: Easy Tags: [Math, String, Two Pointers]
- 注意加法结果的位置.
- Use two pointers i, j to track the 2 strings
- Add when i and j are applicable. While (i >= 0 || j >= 0)
StringBuffer.insert(0, x);
- handle carry
- Reverse string -> Convert to Integer List, add up -> Convert back to string
- pointer 从前向后, 所以只需要 1个pointer.
- 操作复杂, 如上, 证明可以解决. 没必要reverse.
把binary换成数字作加法. 如果input很大,那么很可能int,long都hold不住。不保险。
45. 557. Reverse Words in a String III.java Level: Easy Tags: [String]
给一个String, 里面的Word被single space split开来, 目的是reverse里面所有的Word, 但preserve Word 和 space order.
- Reverse Words in a String II 的降级版, 去掉第一个overall reverse就好了
46. 1249. Minimum Remove to Make Valid Parentheses.java Level: Medium Tags: [Stack, String]
- Goal: remove extra '(' or ')' so it is valid.
- Forward thinking: use stack to track '(' and ')', then keep appending partial string to output
- Backward thinking: use stack to filter out false indexes, and remove them in the end
47. 443. String Compression.java Level: Easy Tags: [Basic Implementation, String]
48. 727. Minimum Window Subsequence.java Level: Hard Tags: [DP, Hash Table, Sliding Window, String, Two Pointers]
- DIFFERENT from sliding window for substring (
76. Minimum Window Substring
)- because this problem rquries keeping the order of characters from the target string
- Use a
backtrack mechanism
based on target matching to find closest left starting point to right
- Simple two pointers:
-
- move sIndex and tIndex: find all T chars in S, in order.
-
- backtrack tIndex to 0; backtrack sIndex to initial char match
-
- record potential min result
-
- Be VERY careful about pointer and index.
- time: O(n^2), backtrack n steps
- Since it requires order of substring,
freqMap+counter+twoPointers
approach is NOT applicable
- TODO
49. 158. Read N Characters Given Read4 II - Call multiple times.java Level: Hard Tags: [Enumeration, String]
Read N Character using Read4(char[] buf)
的加强版: 可以不断读 read(buf, n)
- 注意String的index handle, 慢慢写edge case
- 理解题目意思:
read4(char[] buf)
这样的populate input object
的function稍微少一点. - 遇到时候, 仔细理解function用法, 不要慌乱. 其实思考方式很简单, 仔细handle string 还有 edge case就好了.
- alaternatively: use queue to hold so we do not need to worry about size
50. 43. Multiply Strings.java Level: Medium Tags: [Math, String]
给两个integer String, 求乘积
- let num1 = multipier, num2 = base. mutiply and save into int[m + n].
- Loop over num1, each row num1[x] * num2, save to correct index (i + j + 1)
- Note: skip leading '0' during output, but do not delete string "0"
- time,space O(mn)
- Option1: Calculate carry on the fly
- index
curr = i + j + 1
, left indexleft = curr - 1
, since we start calculation from end of the array. - we only touch right side of the array once, so we can move the carry off from it, and carry to left index
- code is concise
- index
- Option2: save product first without calculating carry
- save product in each int index
- calculate carry on rst[] and
sb.insert(0, c)
(since we start from end of rst) - this is actaully faster than Option1, somehow.
-
- 数字‘123’, 在数组里面, index == 0 是 ‘1’。 但是我们平时习惯从最小位数开始乘积,就是末尾的'3'开始。
- 所以!翻转两个数字先!我去。这个是个大坑。
-
- 乘积product,和移动Carrier都很普通。
-
- !!最后不能忘了再翻转。
-
- 最后一个看坑。要是乘积是0,就返回‘0’。 但是这个其实可以在开头catch到没必要做到结尾catch。
- 用到几个StringBuffer的好东西: reverse(), sb.deleteCharAt(i)
- 找数字,或者26个字母,都可以: s.charAt(i) - '0'; s.charAt(i) - 'a';
51. 680. Valid Palindrome II.java Level: Easy Tags: [String]
- delete an index: 有两种情况
- 用一个boolean parameter来表现state. 如果有其他status, state可以变成 String/enum
52. 387. First Unique Character in a String.java Level: Easy Tags: [Hash Table, String]
- 按照题意, 找到第一个 first index == last index的字母.
- 用hashmap存字母的index, 有些重复字母的index就会是个list.
- 找到单一index, 结合成list, sort, return list.get(0)
- slow due
53. 345. Reverse Vowels of a String.java Level: Easy Tags: [String, Two Pointers]
vowels: 元音字母. 要求reverse所有元音字母.
- 前后两个指针, 在while loop里面跑.
- 注意 i<j, 一旦相遇, 就break.
- 找到合适的, 就做swap.
- StringBuffer可以 sb.setCharAt()记得用.
- O(n)
拿出所有vowels, 反过来放进去. O(n)
54. 10. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
IMPORTANT: '*' 需要有一个 prefix element [elm], so it becomes [elm]*
. There 2 possible cases:
- [elm] repeats 0 times: move p, j + 2
- [elm] repeats 1 or more times: need s[i] == p[i], then move s, i+1
- DFS on remaining of s and p. Analyze the different cases when next char == '*'
- End case: both i,j reached end true; or one of them reached end.
- The two different cases when given any index j on p, the p[j+1]=='*'
- TRUE:
- ignore p[j, j+1], continue from p[j+2]
- check if s[i]==p[j] or p[j]='.'; continue from s[i+1] and p
- FALSE: check i,j, and move forward with s[i+1], p[j+1]
- TRUE:
- If next p char != '*', check curr s[i] ?= p[i]
- Improvement with memo with 2D Booelan[][] memo: much faster
- memo[i][j] records result the remaining strings: s.substring(i) compare with p.substring(j)
- use
Boolean
: when memo[i][j] != null, return something!
- Two sequence, DP, find if possible to match.
- The '*' takes effect of preceding/prior element, so we can start matching from end.
- DP[i][j]: is it possible to match s[0 ~ i - 1] and p[0 ~ j - 1].
- Check last index of s and p, there can be a few possibilities:
-
- s[i-1]==p[j-1] and they are normal characters => && dp[i - 1][j - 1];
-
- p[j-1] == '.', match => dp[i - 1][j - 1]
-
- p[j-1] == '*':
- a. ignore a* => |= dp[i][j - 2];
- b. use a* => |= dp[i - 1][j];
-
- init: dp[0][j] and dp[i][0] will all be false since there cannot be any match.
55. 28. Implement strStr().java Level: Easy Tags: [String, Two Pointers]
给两个string A, B, 找一个 B 在 A 种的起始位置.
- 找到B在A中的起始位置, 然后看一下从这个点开始的substring是否等于B就可以了
- 还挺多坑的, 这些可以帮助优化:
-
- 当B是“”的时候,也就是能在A的其实位置找到B....index = 0.
-
- edge condition: 如果 haystack.length() < needle.length() 的话, 必须错, return -1
-
- 如果在某个index, A后面剩下的长度, 比B的长度短, 也是误解, return -1
56. 1106. Parsing A Boolean Expression.java Level: Hard Tags: [DFS, Stack, String]
- Analyze the pattern: 1) single char, 2) with !, 3) with &, |
- Identify sub problem
- Use stack to parse the data in "()", which is a sub problem to solve with recursive call
- Handle &, | case: need to parse multiple
- Be comfortable with string parsing
- Slight improve:
- If see obvious result, directly return evaluation w/o further parsing
- use memo to store evaluated exp
- Use '(' and ')' to mark inner exp
- Evaluate the inner exp and save result back to Stack: the result will be 'f' or 't'
- This is slightly slow because:
- It requires all stack items on top to be processed before reaching the operator
- There is no room to optimize even there is simplification for specific operator
57. 12. Integer to Roman.java Level: Medium Tags: [Basic Implementation, Math, String]
- Parse each digit based on rules
-
- parse: analyze the situations
58. 14. Longest Common Prefix.java Level: Easy Tags: [String]
找一串String里面最长的公共prefix.
- Sort O(nlogn)
- first and last string should share common prefix
- 这里假设题目要求的是所有string的公共 prefix, 而不是部分strings
- Nested loop, 每一次比较所有string 同位是否相等
- 相等,append string. 不等,return.
- O(mn)
59. 20. Valid Parentheses.java Level: Easy Tags: [Stack, String]
- 剥皮过程。解铃还须系铃人
- 左边的外皮'{['在stack底部
- 右边的外皮应该和stack顶上的左外皮一一对应
60. 893. Groups of Special-Equivalent Strings.java Level: Easy Tags: [Basic Implementation, String]
Mark # of characters can be useful to print string signature
61. 91. Decode Ways.java Level: Medium Tags: [DP, Partition DP, String]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
- 加法原理: 根据题意, 有 range = 1 的 [1, 9], range = 2 的 [10~26] 来作为partition.
- there can be 2 final states at dp[i]: single digit or double digit.
- validate if
single digit
, dp[i] += dp[i - 1]. Last 1 digit can be decoded. - validate if
double digit
, dp[i] += dp[i - 2]. Last 2 digits can be decoded.
- validate if
- note:
- get number from char:
c - '0'
- validatae single digit != '0', 因为'0' 不在条件之中(A-Z)
- get number from char:
- Option1: dp[i] as # ways to decode at index i, including letter i
- The validation is done on: 1) single digit i, or 2) double digit [i-1, i]
- Option2: Partition DP, dp[i] as # ways to decode for first i letters (excluding letter i)
- 定义
dp[i] = 前i个digits最多有多少种decode的方法
: new dp[n + 1]. - dp[i] += dp[i - x], where x = 1, 2
- The validation is done on: 1) single digit [i-1], or 2) double digit [i-2, i-1]
- Option2 is better in terms of clean coding. We assume
dp[0]=1
as 1 way to decode 0 digits.- No need to specially handle length == 1, because it is covered later
- No need to manualy init the first 2-digit case as in Option1
- Return of
dp[n]
is clean
- 定义
- 引申: 这里只有两种partition的情况 range=1, range =2. 如果有更多partition的种类, 就可能多一层for loop做循环
- if single-digit is working, sum += dfs(s, i+1);
- if double-digit is working, sum += dfs(s, i+2);
- end case: i >= n, return 0; i == n - 1; i == n - 2
- especially when i = n - 2, handle 2-digt edge case carefully:
-
- check if two-digit range [i, i+1] is valid
-
- check if single-digit [i] is valid; if so, += dfs(s, i + 1)
-
- especially when i = n - 2, handle 2-digt edge case carefully:
- memo[i]: # ways to decode from [i, n). init with
memo[i]=-1
62. 68. Text Justification.java Level: Hard Tags: [Enumeration, String]
按照规则 adjust text. 就是Word里面: 有一行太长, adjust word 中间的space, 然后保证每一行的total width 顶格.
还有一些细节规则, 看原题
- greedy approach: line up as many words as possible; once exceed the MaxLength, justify the list of words
- Steps
-
- Split & group
-
- Juststify a row of words
-
- clean up last row
-
- Calcualte bounded row length =
width + (list.size() - 1)
.list.size()-1
= Minimum amount of slot/space used. - Calculate max ave spaces ot insert into each slot =
totalExtraSpace/slot
Juststify a row of words
:-
- take a list of words and assume minimum 1 space in-between words
-
- distribute the remaining spaces evenly to each slot
-
- Overall runtime: O(n) to go over all space
- Overall space O(maxWidth) for maxWidth amount of strings
63. 340. Longest Substring with At Most K Distinct Characters.java Level: Hard Tags: [Hash Table, LinkedHashMap, Sliding Window, String, Two Pointers]
- Method1 and Method2 are identical to
159. Longest Substring with At Most Two Distinct Characters
. - However, time complexity for Method2 in increases to O(nk). https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- we want to do better than that (Method3)
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == k, process and record max len
-
- if map.size() > k, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(k)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(nk) to find the left-most element
- space: O(k)
- https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- as mentioned above, Method2 uses O(nk), because it takes O(k) to find head item that was inserted first
- meanwhile, we still need the hash map feature to get/put/remove last occurance of a char with O(1)
- Solution: use a LinkedHashMap:
map.entrySet().iterator()
maintains the insertion order!
- Special handling:
- since we want the
lastOccurMap
to preserve laset insertion order - we need to
remove
the char every time before put.
- since we want the
- time: O(n)
- space: O(k)
64. 796. Rotate String.java Level: Easy Tags: [String]
给两个String, 看A rotate之后 能不能变成B
- Basics
- StringBuffer.deleteCharAt(xx), StringBuffer.append(xx)
- O(n)
- Different problem: 给一个char[], 要rotate offset times.
- 三步rotate
- 有个坑:offset可能很长,那么要%length,才能得到真正需要rotate的部分。
- Note: rotate 一个 full length之后,是string 不变
65. 1041. Robot Bounded In Circle.java Level: Easy Tags: [String]
简单的character checking. 各个方向, 加加减减.
66. 157. Read N Characters Given Read4.java Level: Easy Tags: [Enumeration, String]
Read4 题目. 理解题目: 是有个input object buff, 会被populated with data.
- 理解题目: 其实就是track
可以read多少bytes by read4() response
- 另外一个有用的function
System.arraycopy(src, srcIndex, dest, destIndex, length)
- Edge Case:
- When there is not enough space to the result buffer,
i + 3 > n
, then only copy what we can:Math.min(n - i, count)
count < 4
meaning there is not enough content to read, break
- When there is not enough space to the result buffer,
67. 273. Integer to English Words.java Level: Hard Tags: [Enumeration, Math, String]
给一个小于 Integer.MAX_VALUE (2^31 - 1) 的数字, 转换成英语. (不需要加 'and')
- 基本implementation
分类讨论
: thounsand, million, billion.3个数字一格
.- 用array枚举 token
- 运用 % 和 / 来找到每个分段的英语翻译
- 3-digit 的部分, 可以用一个helper funtion来找到结果, 每段的处理方法都是一样的
- Note:
- StringBuffer 更有效率!
sb.insert(0, xxx)
append在sb前面 - 注意加 " " 的时候, 如果多余, 要
trim()
- 注意,
小于20的数字, 有自己的特殊写法, 需要额外handle
- 这道题目就是要细致
耐心
, 几乎么有什么算法, 就是想要写的efficient并且正确, 需要很小心
- StringBuffer 更有效率!
- Thinking process:
1 ~ 19
: [one, two ... nine, ten, eleven, ...., ninteen]20 ~ x0
: [twenty, thirty, fourty, ... ninety]x00
: hundred: 100- thousand: 10^3
- million: 10^6
- billion: 10^9
- trillian: 10^12 way over 2^31, not needed
- plan:
- parse 3 digits at a time
- convert the 3 digit to [xx hundred xx-ty x]
- come up with a string[]
- insert the thousands/million/billion to the string[]
68. 125. Valid Palindrome.java Level: Easy Tags: [String, Two Pointers]
验证string是不是 palindrome. 只考虑 alphanumeric, 其他字符可以忽略
- Time O(n), Space O(1).
- 普通方法, 两边check, 速度相比较regular expression更快. leetcode 4ms.
- Use helper functions.
- 前后两个指针, 往中间移动, 查看是否字母重合
- 可以用 ASCII code 来手动过滤, 只要 '0' ~ '9', 'a' ~ 'z', 'A' - 'Z' 之间的
- 也可以用 regular expression: match 所有这些字母, 是 [a-zA-Z0-9]
- 那凡是不是这些字母的 match, 就是取反: "[^a-zA-Z0-9]". 测试: https://regex101.com/
69. 65. Valid Number.java Level: Hard Tags: [Enumeration, Math, String]
分析edge case, 和各种情况, 然后判别是否是valid number
- 遇到
.
,e
,+/-
,int
的几种不同情况 - 分别遇到的顺序不同时候, 结果也不同.
- 这道题更多是分析情况, 然后把edge case enumerate出来, 算法的意义比较少.
70. 49. Group Anagrams.java Level: Medium Tags: [Hash Table, String]
给一串string, return list of list, 把anagram 放在一起.
- 存anagram
- 用 character frequency 来做unique key
- 用固定长度的char[26] arr 存每个字母的frequency; 然后再 new string(arr).
- 因为每个位子上的frequency的变化,就能构建一个unique的string
- O(nk), k = max word length
- 和check anagram 想法一样:转化并sort char array,用来作为key。
- 把所有anagram 存在一起。注意结尾Collections.sort().
- O(NKlog(K)), N = string[] length, k = longest word length
71. 159. Longest Substring with At Most Two Distinct Characters.java Level: Medium Tags: [Hash Table, Sliding Window, String, Two Pointers]
如题.
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == 2, process and record max len
-
- if map.size() > 2, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(1)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(n)
- space: O(1)
72. 1170. Compare Strings by Frequency of the Smallest Character.java Level: Easy Tags: [Array, String]
- Goal: find word count that fits into
f(queries[i]) < f(W)
- What if: we can store the f(W) as preSum, then goal:
rst[i] = preSum[end] - preSum[queryWordCount]
- count(W) and store in count[i]
- calc preSum
- processs queries array
- kinda bucket sort:
-
- we know the boundary of the word length, so we can create
bucket
- we know the boundary of the word length, so we can create
-
function count(w)
can produce a value that sort a word into a specific bucket slot
- extend: the bucket can store keys that links back to the word (if there are follow up questions)
-
- time: O(m + n)
- space: O(m + n)
- time:
- O(nm) to count all words, O(nlogn) to sort the wordCount
- O(nm) to to count all queries
- O(n^2) to perform the match
- space: O(n)
73. 8. String to Integer (atoi).java Level: Medium Tags: [Math, String]
- Handling use cases
- Parse steps:
-
- trim space
- 1 parse operator
- 2 trim leading zero
-
- get number string
-
- Validation:
-
- max length over max integer length
-
- exceed min/max value
-
- Alternatively, regular expression, but not applicable in interview: if (!str.matches("[+-]?(?:\d+(?:\.\d*)?|\.\d+)")). 猛了一点
74. 767. Reorganize String.java Level: Medium Tags: [Greedy, Hash Table, Heap, Sort, String]
We want to exhaust largest population and merge like merging k list. Problem: largest population may result in them being adjacent. How to resolve?
- process and check at the end, or, 2) sanitize first and process assume correct input
- Aggregate map<char, count>, and sort the entry with priority queue.(Optionally, can use object
Letter {char c, int count}
) - Naturally: we want to prioritize the largest population and exhaust it first, so we want to keep it in the a buffer queue
- it is a queue, first in first out
- monitor queue size k = 2, so that it holds off the just last-processed letter for 1 unit of time
- the buffer then sends the last-process item to the main priority queue (pq will sort it again)
- Error handling: largest population may have extra letter
- the main PQ has already exhausted
- but the largest-population-letter will end up stuck in the buffer queue
- it will never be picked up again so the final result sb will be shorter than orignal string: that is the error case
- Option0. Similar to
621. Task Scheduler
:- use a buffer to hold potential letter to add back, but NOT ADD BACK YET, until k slots have been filled.
- time: O(m), m = # of unique letters
- space: O(nmLogm), n = length, pq sorting requires mlogm, we will visit all n nodes.
- put all in map<char, count>
- Sanitize the input: if certain popular char count is over (n + 1)/2, then it should fail right away, just return empty map.
- Once the input is sanitized, when building results, we can be greedy and consume most popular char and then the rest
- Int[2] can be used store char and count
- PriorityQueue can sort int[]. Okay to not specific length of int[] when defining pq.
- Alternatively, can use a Letter {char c, int count} to represent
75. 71. Simplify Path.java Level: Medium Tags: [Stack, String]
给一个path, simplify成最简单形式. 注意考虑edge case
- 理解unix path:
-
.
代表current directory, 可以忽略.
-
../
表示previous level.
-
- double slash 可以忽略.
-
- empty string 要output
/
- empty string 要output
-
- parse by '/', and go over using stack
- put [folder] in stack
- ".." pop() 1 element of the stack, if anything
- "." stays the same
- output stack reversely: connect with '/', skip tail
76. 13. Roman to Integer.java Level: Easy Tags: [Math, String]
- 熟悉罗马字母规则
-
- 'I V X L C D M' 分别代表的数字
-
- 列举combo的情况,需要从原sum里面减掉多加的部分: 'IV, IX'减2, 'XL, XC'减20, 'CD, CM'减200.
- Leading
I(1*2)
,X(10*2)
,C(100*2)
causes double counting
https://en.wikipedia.org/wiki/Roman_numerals
77. 72. Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, String]
两个字符串, A要变成B, 可以 insert/delete/replace, 找最小变化operation count
- 考虑两个字符串的末尾index� s[i], t[j]: 如果需要让这两个字符一样, 可能使用题目给出的三种operation: insert/delete/replace?
- 先calculate最坏的情况, 3种operation count + 1; 然后在比较match的情况.
- 注意, 在i或者j为0的时候, 变成另外一个数字的steps只能是全变.
- 第一步, 空间时间都是O(MN), O(MN)
- 滚动数组优化, 空间O(N)
- insert: assume insert on s, �
#ofOperation = (s[0 ~ i] to t[0 ~ j-1]) + 1;
- delete: assume delete on t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j]) + 1;
- replace: replace both s and t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j - 1]) + 1;
- dp[i][j]�代表了两个 sequence 互相之间的性质: �s[0 ~ i] �转换成 s[0~j] 所需要的最少 operation count
- init: 当i==0, dp[0][j] = j; �每次都要 + j 个character; 同理, 当j==0, dp[i][0] = i;
- 而dp[i][j]有两种情况处理:
s[i] == t[j]
ors[i] != t[j]
- 这种判断取决于经验: 如果知道initialization可以再 double for loop 里面一起做, 那么可以留着那么做
- 这样属于
需要什么, initialize什么
- 事后在做space optimization的时候, 可以轻易在 1st dimension 上做rolling array
- 可以做, 但是不建议:这道题需要找 min count, 而不是search/find all solutions, 所以search会写的比较复杂, 牛刀杀鸡.
0. Jump Game II.java Level: Hard Tags: [Array, Coordinate DP, DP, Greedy]
给一串数字 是可以跳的距离. goal: 跳到最后的index 所可能用的最少次数.
- maintain the
farest can go
, and use it the target for i increse towards. Why?-
- when I know the
farest can go
, in fact it is just currently 1 step away.
- when I know the
-
- why to iterate from curr
i to farset
? In range [i, farest], we will calc the newmaxRange
- why to iterate from curr
-
- once
i
reachesfarset
, updatefarest = maxRange
- once
-
- greedy concept: once we know the farest we can reach at the moment, it is just 1 step away :)
- On average should be jumpping through the array without looking back
- time: average O(n)
- Impl:
- 图解 http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html
- track the farest point
- whenver curr index reachest the farest point, that means we are making a nother move, so count++
- there seems to have one assumption: must have a solution. Otherwise, count will be wrong number.
- 其实跟第一个greedy的思维模式是一模一样的.
- DP[i]: 在i点记录,走到i点上的最少jump次数
- dp[i] = Math.min(dp[i], dp[j] + 1);
- condition (j + nums[j] >= i)
- 注意使用 dp[i] = Integer.MAX_VALUE做起始值, 来找min
- time: O(n^2), slow, and timesout
1. Backpack VI.java Level: Medium Tags: [Backpack DP, DP]
给一个数组nums, 全正数, 无重复数字; 找: # of 拼出m的方法.
nums 里的数字, 可以重复使用. 不同的order可以算作不同的拼法.
- dp[i] 表示: # of ways to fill weight i
- 1维: dp[w]: fill weigth w 有多少种方法. 前面有多少种可能性, 就sum多少个:
- dp[w] = sum{dp[w - nums[i]]}, i = 0~n
- 拼背包时, 可以有重复item, 所以考虑'最后被放入的哪个unique item' 就没有意义了.
- 背包问题, 永远和weight分不开关系.
- 这里很像coin chagne: 考虑最后被放入的东西的value/weigth, 而不考虑是哪个.
2. House Robber III.java Level: Medium Tags: [DFS, DP, Status DP, Tree]
Houses被arrange成了binary tree, 规则还是一样, 连续相连的房子不能同时抄.
求Binary Tree neighbor max 能抄多少.
- 判断当下的node是否被采用,用一个boolean来表示.
- 如果curr node被采用,那么下面的child一定不能被采用.
- 如果curr node不被采用,那么下面的children有可能被采用,但也可能略过,所以这里用Math.max() 比较一下两种可能有的dfs结果。
- dfs重复计算:每个root都有4种dive in的可能性, 假设level高度是h, 那么时间O(4^(h)), where h = logN, 也就是O(n^2)
- 并不是单纯的DP, 是在发现DFS很费劲后, 想能不能代替一些重复计算?
- 基本思想是dfs解法一致: 取root找最大值, 或者不取root找最大值
- 在root上DFS, 不在dfs进入前分叉; 每一个level按照状态来存相应的值: dp[0] root not picked, dp[1] root picked.
- Optimization: DP里面, 一口气找leftDP[]会dfs到最底层, 然后自下向上做计算
- 这个过程里面, 因为没有在外面给dfs()分叉, 计算就不会重叠, 再也不用回去visit most-left-leaf了, 算过一遍就完事.
- 然而, 普通没有dp的dfs, 在算完visited的情况下的dfs, 还要重新dfs一遍!visited的情况.
- Space O(h), time O(n), 或者说是O(2^h), where h = log(n)
- 不为状态而分叉dfs
- 把不同状态model成dp
- 每一个dfs都return一个based on status的 dp array.
- 等于一次性dfs计算到底, 然后back track, 计算顶部的每一层.
- DP 并不一定要是以n为base的. 也可以是局部的去memorize状态->value.
3. Backpack V.java Level: Medium Tags: [Backpack DP, DP]
- 与背包1不同: 这里不是check可能性(OR)或者最多能装的size是多少; 而是计算有多少种正好fill的可能性.
- dp[i][w]: 用前i本书, 正好fill到 w weight的可能性.
- 对于末尾, 还是两种情况:
-
- i-1位置没有加bag
-
- i-1位置加了bag
- 两种情况可以fill满w的情况加起来, 就是我们要的结果.
- 如常: dp[n + 1][w + 1]
- 重点: dp[0][0] 表示0本书装满weight=0的包, 这里我们必须 dp[0][0] = 1, 给后面的 dp function 做base
- Space, time: O(MN)
- Rolling array, 空间优化, 滚动数组. Space: O(M)
- 分析row(i-1)的规律, 发现所有row(i)的值, 都跟row(i-1)的左边element相关, 而右边element是没用的.
- 所以可以被override.
- Space: O(M), 真*一维啊!
- Time: O(MN)
4. Unique Binary Search Tree.java Level: Medium Tags: [BST, DP, Tree]
Not quite clear. 根据左右分割而总结出了原理, 每次分割, 左右两边都会有一定数量的permutation, 总体上的情况数量当然是相乘. 然后每一个不同的分割点都加一遍: f(n) = f(0)*f(n-1) + f(1)*f(n-2) + ... + f(n-2)*f(1) + f(n-1)*f(0)
然后把数学公式转换成DP的方程, 有点玄学的意思啊! 不好想.
5. Triangles.java Level: Medium Tags: [Array, Coordinate DP, DFS, DP, Memoization]
给一个list<list> triangle, 细节原题. 找 min path sum from root.
- 其实跟给一个2D matrix没有什么区别, 可以做dfs, memoization.
- initialize memo: pathSum[i][j] = MAX_VALUE; 计算过的path省略
- Bottom-top: 先dfs到最深的path, 然后逐步网上返回
OR 原理: min(pathA, pathB) + currNode
- 浪费一点空间, pathSum[n][n]. space: O(n^2), where n = triangle height
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- 跟dfs的原理很像,
OR 原理: min(pathA, pathB) + currNode
- init dp[n-1][j] = node values
- build from bottom -> top: dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
- 跟传统的coordinate dp有所不同, inner for loop 是需要计算 j <= i, 原因是triangle的性质.
- 空间: dp[n][n]. space: O(n^2)
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- Based on the DP solution: the calculation always depend on
next row
for col atj
andj + 1
- 既然只depend on next row, 可以用rolling array来处理: reduce to O(n) space.
- Further: 可以降维, 把第一维彻底去掉, 变成 dp[n]
- 同样是double for loop, 但是只在乎column changes:
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
6. Frog Jump.java Level: Hard Tags: [DP, Hash Table]
Frog jump 的题目稍微需要理解: 每个格子可以 jump k-1, k, k+1 steps, 而k取决于上一步所跳的步数. 默认 0->1 一定是跳了1步.
注意: int[] stones 里面是stone所在的unit (不是可以跳的步数, 不要理解错).
- 原本想按照corrdiante dp 来做, 但是发现很多问题, 需要track 不同的 possible previous starting spot.
- 根据jiuzhang答案: 按照定义, 用一个 map of <stone, Set<possible # steps to reach stone>>
- 每次在处理一个stone的时候, 都根据他自己的 set of , 来走下三步: k-1, k, or k+1 steps.
- 每次走一步, 查看 stone + step 是否存在; 如果存在, 就加进 next position:
stone+step
的 hash set 里面
dp.put(stone, new HashSet<>())
mark 每个stone的存在dp.get(0).add(0)
init condition, 用来做 dp.put(1, 1)
- 最终做下来思考模式, 更像是BFS的模式: starting from (0,0), add all possible ways
- 然后again, try next stone with all possible future ways ... etc
7. Stone Game.java Level: Medium Tags: [DP]
这个DP有点诡异. 需要斟酌。 NOT DONE YET
8. Longest Increasing Continuous subsequence II.java Level: Medium Tags: [Array, Coordinate DP, DP, Memoization]
- due to access permission, not test
- dp[i][j]: longest continuous subsequence length at coordinate (i, j)
- dp[i][j] should come from (i-1,j) and (i, j-1).
- dp[0][0] = 1
- condition: from up/left, must be increasing
- return dp[m-1][n-1]
- O(mn) space for dp and flag.
- O(mn) runtime because each spot will be marked once visited.
- 这个题目的简单版本一个array的例子:从简单题目开始想DP会简单一点。每个位置,都是从其他位置(上下左右)来的dpValue + 1. 如果啥也没有的时候,init state 其实都是1, 就一个数字,不增不减嘛。
9. Paint Fence.java Level: Easy Tags: [DP, Sequence DP]
- 最多2个fence 颜色相同
- 假设i是和 i-1不同,那么结果就是 (k-1)*dp[i - 1]
- 假设i是何 i-1相同,那么根据条件,i-1和i-2肯定不同。那么所有的结果就是(k-1)*dp[i-2]
- dp[i]: count # of ways to paint 前i个 fence
- 加法原理
- time, space: O(n)
- rolling array: space O(1)
- 这题目很有意思. 一开始分析的太复杂, 最后按照这个哥们的想法(http://yuanhsh.iteye.com/blog/2219891) 的来做,反而简单了许多。
- 设定T(n)的做法,最后题目化简以后就跟Fibonacci number一样一样的。详细分析如下。
- 做完,还是觉得如有神。本来是个Easy题,想不到,就是搞不出。
10. Minimum Subarray.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最小值
- 看到 min value, 至少考虑dp:
- Consider last num: min sum will be (preMinSum + curr, or curr)
- Use preMinSum to cache previouly calcualted min sum, also compare with +curr.
- Have a global min to track: because the preMinSum can be dis-continuous.
- 也可以写成 dp[i] 但是没什么必要
11. Flip Game II.java Level: Medium Tags: [Backtracking, DFS, DP]
String 只包含 + , - 两个符号. 两个人轮流把consecutive连续的++
, 翻转成 --
.
如果其中一个人再无法翻转了, 另一个人就赢. 求: 给出string, 先手是否能赢.
- curr player 每走一步, 就生成一种新的局面, dfs on this
- 等到dfs结束, 不论成功与否, 都要backtracking
- curr level: 把"++" 改成 "--"; backtrack的时候, 改回 '--'
- 换成boolean[] 比 string/stringBuilder要快很多, 因为不需要重新生成string.
- ++ 可以走 (n - 1)个位置:
- T(N) = (N - 2) * T(N - 2) = (N - 4) * (N - 2) * T(N - 4) ... = O(N!)
- 做一个String s的 replica: string or stringBuilder
- 每次dfs后, 然后更替里面的字符 "+" => "-"
- 目的只是Mark已经用过的index
- 真正的dfs 还是在 original input string s 身上展开
- 每次都重新生成substring, 并不是很efficient
- 保证p1能胜利,就必须保持所有p2的move都不能赢
- 或者说, 在知道棋的所有情况时, 只要p2有一种路子会输, p1就一定能走对路能赢.
- 同时,p1只要在可走的Move里面,有一个move可以赢就足够了。
- p1: player1, p2: player2
- 需要Game Theory的功底, Nim game. https://www.jiuzhang.com/qa/941/
- http://www.1point3acres.com/bbs/thread-137953-1-1.html
- TODO: https://leetcode.com/problems/flip-game-ii/discuss/73954/Theory-matters-from-Backtracking(128ms)-to-DP-(0ms)
12. Palindromic Substrings.java Level: Medium Tags: [DP, String]
根据题意, count # of palindromic substring. (不同index截取出来的substring算不同的情况)
- build boolean[][] to check isPalin[i][j] with DP concept
- �check all candidates isPalin[][]
- O(n^2)
13. Perfect Squares.java Level: Medium Tags: [BFS, DP, Math, Partition DP]
给一个数字n, 找到这个数字 最少能用多少个 平方数组成.
平方数比如: 1, 4, 9, 16 ... etc
- 遇到最值, 想到DP.
- 看到分割字眼, 想到分割型 DP.
- 思考, 如果 j * j = 9, 那么 j = 3 就是最少的一步; 但是如果是10呢? 就会分割成1 + 9 = 1 + j * j
- 考虑最后的数字: 要是12割个1出来, 剩下11怎么考虑? 割个4出来,剩下8怎么考虑?
- partion的方式: 在考虑dp[i - x]的时候, x 不是1, 而是 x = j*j.
- 就变成了dp = Min{dp[i - j^2] + 1}
- 乍一看是O(n*sqrt(n)). 实际也是. 但如何推导?
- 考虑上限: 把小的数字变成大的 推导上限; 考虑下限: 把数字整合归小, 找到下限.
- 考虑sqrt(1) + sqrt(2) + ....sqrt(n):找这个的upper bound and lower bound.
- 最后发现它的两边是 Ansqrt(n) <= actual time complexity <= Bnsqrt(n)
- 那么就是O(n*sqrt(n))啦
- minus all possible (i*i) and calculate the remain
- if the remain is new, add to queue (use a hashset to mark calculated item)
- find shortest path / lowest level number
- 一开始没clue.看了一下提示
- 1. 第一步想到了,从数学角度,可能是从最大的perfect square number开始算起。
- 2. 然后想法到了dp, 假设最后一步用了最大的maxSqrNum, 那么就在剩下的 dp[i - maxSqrNum^2] +1 不就好了?
- 3. 做了,发现有个问题...最后一步选不选maxSqrNum? 比如12就是个例子。
- 然后就根据提示,想到BFS。顺的。 把1~maxSqrNum 都试一试。找个最小的。
- 看我把12拆分的那个example. 那很形象的就是BFS了。
- 面试时候,如果拆分到这个阶段不确定,那跟面试官陶瓷一下,说不定也就提示BFS了。
14. Backpack II.java Level: Medium Tags: [Backpack DP, DP]
给i本书, 每本书有自己的重量 int[] A, 每本书有value int[] V
背包有自己的大小M, 看最多能放多少value的书?
- 做了Backpack I, 这个就如出一辙, 只不过: dp存的不是max weight, 而是 value的最大值.
- 想法还是,选了A[i - 1] 或者没选A[i - 1]时候不同的value值.
- 时间空间O(mn)
- Rolling Array, 空间O(m)
- 如果无法达到的w, 应该mark as impossible. 一种简单做法是mark as -1 in dp.
- 如果有负数value, 就不能这样, 而是要开一个can[i][w]数组, 也就是backpack I 的原型.
- 这样做似乎要多一些代码, 好像并不是非常需要
15. House Robber II.java Level: Medium Tags: [DP, Sequence DP, Status DP]
和House Robber I 类似, 搜刮房子, 相邻不能动. 特点是: 现在nums排成了圈, 首尾相连.
- dp[i][status]: 在 status=[0,1] 情况下, 前i个 房子拿到的 max rob gain. status=0, 1st house robbed; status=1, 1st house skipped
- 根据dp[i-1]是否被rob来讨论dp[i]: dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i - 1]);
- 特别的是,末尾的last house 和 first house相连. 这里就需要分别讨论两种情况: 第一个房子被搜刮, 或者第一个房子没被搜刮
- be careful with edge case nums = [0], only with 1 element.
- Time,space: O(n)
- 是否搜刮了第一个房子, 分出两个branch, 可以看做两种状态.
- 可以考虑用两个DP array; 也可以加一dp维度, 补充这个状态.
- 连个维度表示的是2种状态(1st house being robbed or not); 这两种状态是平行世界的两种状态, 互不相关.
- 与House Robber I一样, 可以用%2 来操作rolling array, space reduced to O(1)
16. Backpack.java Level: Medium Tags: [Backpack DP, DP]
给i本书, 每本书有自己的重量 int[] A, 背包有自己的大小M, 看最多能放多少重量的书?
- 简单直白的思考 dp[i][m]: 前i本书, 背包大小为M的时候, 最多能装多种的书?
- 注意: 背包问题, 重量weight一定要是一维.
- dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
- 每一步都track 最大值
- 最后return dp[n][m]
- 时间空间 O(mn)
- Rolling array, 空间O(m)
- true/false求解, 稍微曲线救国: 重点是, 最后, 按照weight从大到小遍历, 第一个遇到true的, index就是最大值.
- 考虑: 用i个item (可跳过地取), 是否能装到weight w?
- 需要从'可能性'的角度考虑, 不要搞成单一的最大值问题.
-
- 背包可装的物品大小和总承重有关.
-
- 不要去找dp[i]前i个物品的最大总重, 找的不是这个. dp[i]及时找到可放的最大sum, 但是i+1可能有更好的值, 把dp[i+1]变得更大更合适.
- boolean[][] dp[i][j]表示: 有前i个item, 用他们可否组成size为j的背包? true/false.
- (反过来考虑了,不是想是否超过size j, 而是考虑是否能拼出exact size == j)
- 注意: 虽然dp里面一直存在i的位置, 实际上考虑的是在i位置的时候, 看前i-1个item.
-
- picked A[i-1]: 就是A[i-1]被用过, weight j 应该减去A[i-1]. 那么dp[i][j]就取决于dp[i-1][j-A[i-1]]的结果.
-
- did not pick A[i-1]: 那就是说, 没用过A[i-1], 那么dp[i][j]就取决于上一行d[i-1][j]
- dp[i][j] = dp[i - 1][j] || dp[i - 1][j - A[i - 1]]
- 跑一遍dp 最下面一个row. 从末尾开始找, 最末尾的一个j (能让dp[i][j] == true)的, 就是最多能装的大小 :)
- 时间,空间都是:O(mn)
17. Longest Common Subsequence.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP]
给两个string, A, B. 找这两个string里面的LCS: 最长公共字符长度 (不需要是continuous substring)
- 设定dp长度为(n+1), 因为dp[i]要用来表示前i个(ith)时候的状态, 所以长度需要时i+1才可以在i位置, hold住i.
- 双序列: 两个sequence之间的关系, 都是从末尾字符看起, 分析2种情况:
-
- A最后字符不在common sequence 或者 B最后字符不在common sequence.
-
- A/B最后字符都在common sequence. 总体count + 1.
18. Maximal Rectangle.java Level: Hard Tags: [Array, DP, Hash Table, Stack]
分解开来, 其实是'Largest Rectangle in Histogram', 只不过这里要自己model heights. 一个2D array里面的rectangle, 最终也是用height * width做出来的. 巧妙在于, 把每一行当做底边, 算出这个底边, 到顶部的height:
- 如果底边上的一个value==0, 那么算作没有height(以这个底边做rectangle, value==0的位置是空中楼阁, 不能用)
- 如果底边上的value==1, 那么就把上面的height加下来, 做成histogram
如果看具体实例, 有些row似乎是白算的, 但是没有办法, 这是一个搜索的过程, 最终会比较出最优解.
Coordinate DP?
19. Scramble String.java Level: Hard Tags: [DP, Interval DP, String]
- 给两个string S, T. 检验他们是不是scramble string.
- scramble string 定义: string可以被分拆成binary tree的形式, 也就是切割成substring;
- 旋转了不是leaf的node之后, 形成新的substring, 这就是原来string的 scramble.
- 降维打击, 分割, 按照长度来dp.
- dp[i][j][k]: 数组S从index i 开始, T从index j 开始, 长度为k的子串, 是否为scramble string
- 一切两半以后, 看两种情况: , 或者不rotate这两半. 对于这些substring, 各自验证他们是否scramble.
- 不rotate分割的两半: S[part1] 对应 T[part1] && S[part2] 对应 T[part2].
- rotate分割的两半: S[part1] 对应 T[part2] && S[part2] 对应 T[part1].
- len == 1的时候, 其实无法旋转, 也就是看S,T的相对应的index是否字符相等.
- initialization非常非常重要. 很神奇, 这个initailization 打好了DP的基础, 后面一蹴而就, 用数学表达式就算出了结果.
- input s1, s2 在整个题目的主要内容里面, 几乎没有用到, 只是用在initialization时候.
- More details, 看解答
20. Longest Increasing Continuous subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP]
https://leetcode.com/problems/longest-continuous-increasing-subsequence/description/
O(n)跑2遍for. O(1)是用了两个int来存:每次到i点时,i点满足条件或不满足条件所有的longestIncreasingContinuousSubsequence. 特点:返跑一回,ans还是继续和left轮的ans作比较;求的所有情况的最大值嘛。
21. K Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, Trie]
给一串String, target string, int k. 找string array里面所有的candidate: 变化K次, 能变成target.
TODO
- Edit Distance的follow up.
- 其实就是改一下 minEditDistance的function, 带入K作比较罢了.
- 写起来跟Edit Distance 的主要逻辑是一模一样的.
- 但是LintCode 86% test case 时候timeout.
- Time O(mnh), where h = words.length, 如果 n ~ m, Time 就几乎是 O(n^2), 太慢.
22. Russian Doll Envelopes.java Level: Hard Tags: [Binary Search, Coordinate DP, DP]
俄罗斯套娃, 这里用envelope来表现. 给一串array, 每一个[x, y] 是envelope 长宽. [[5,4],[6,4],[6,7],[2,3]].
看用这些套娃, 可以最多套几个.
- envelopes没有顺序, 先排序 (主要根据第一个index排序)
- 然后观察: 排序过后, 就变成了1D的坐标动态规划.
- max number 取决于上一个成功Russian doll的 max value + 1
- 上一个index不知道, 所以遍历找上一个index.
- 当下index i 的状态, 取决于前面index j 的状态, 所以遍历两个index.
- O(n^2)的DP, n = envelopes.length;
- 这个方法是自己想出来的, 但是时间复杂度太大, timeout
- 把envelop标记在2D grid上面, 然后像走机器人一样, 求到最右下角的最大 count max.
- count 当下能存在多少Russian doll
- 两种情况: 当下coordinate 没有target, 当下coordinate有target
- 当下coordinate 没有target: 如同机器人走法, Math.max(dp[i - 1][j], dp[i][j - 1])
- 当下coordinate 有target: dp[i - 1][j - 1] + dp[i][j]
- timeout: O(n^2), n = largest coordinate.
23. Longest Common Substring.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP, String]
- 两个string, 找最值: longest common string length
- 序列型, 并且是双序列, 找两个序列 (两维的某种性质)
- dp[i][j]: 对于 A 的前i个字母, 对于 B 的前j个字母, 找最长公共substring的长度
- dp = new int[m + 1][n + 1]
- dp[i][j] = dp[i - 1][j - 1] + 1; only if A.charAt(i - 1) == B.charAt(j - 1)
- 注意track max, 最后return
- space O(n^2), time(n^2)
- 空间优化, [i] 只有和 [i - 1] 相关, 空间优化成 O(n)
- 找所有A的substring, 然后B.contains()
- track max substring length
- O(n^2) time
24. Backpack III.java Level: Hard Tags: [Backpack DP, DP]
给n种不同的物品, int[] A weight, int[] V value, 每种物品可以用无限次
问最大多少value可以装进size是 m 的包?
- 可以无限使用物品, 就失去了last i, last unique item的意义: 因为可以重复使用.
- 所以可以转换一个角度:
-
- 用i 种 物品, 拼出w, 并且满足题目条件(max value). 这里因为item i可以无限次使用, 所以考虑使用了多少次K.
-
- K虽然可以无限, 但是也被 k*A[i]所限制: 最大不能超过背包大小.
- dp[i][w]: 前i种物品, fill weight w 的背包, 最大价值是多少.
- dp[i][w] = max {dp[i - 1][w - k*A[i-1]] + kV[i-1]}, k >= 0
- Time O(nmk)
- 如果k = 0 或者 1, 其实就是 Backpack II: 拿或者不拿
- 优化时间复杂度, 画图发现:
- 所计算的 (dp[i - 1][j - k*A[i - 1]] + k * V[i - 1])
- 其实跟同一行的 dp[i][j-A[i-1]] 那个格子, 就多出了 V[i-1]
- 所以没必要每次都 loop over k times
- 简化: dp[i][j] 其中一个可能就是: dp[i][j - A[i - 1]] + V[i - 1]
- Time O(mn)
- 根据上一个优化的情况, 画出 2 rows 网格
- 发现 dp[i][j] 取决于: 1. dp[i - 1][j], 2. dp[i][j - A[i - 1]]
- 其中: dp[i - 1][j] 是上一轮 (i-1) 的结算结果, 一定是已经算好, ready to be used 的
- 然而, 当我们 i++,j++ 之后, 在之前 row = i - 1, col < j的格子, 全部不需要.
- 降维简化: 只需要留着 weigth 这个 dimension, 而i这个dimension 可以省略:
- (i - 1) row 不过是需要用到之前算出的旧value: 每一轮, j = [0 ~ m], 那么dp[j]本身就有记录旧值的功能.
- 变成1个一位数组
- 降维优化的重点: 看双行的左右计算方向
- Time(mn). Space(m)
25. Combination Sum IV.java Level: Medium Tags: [Array, Backpack DP, DP]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
- 计数问题, 可以想到DP. 其实就是Backpack VI.
- 从x个数字里面找candidate(可以重复用同一个数字), 来sum up to target. 找: # of ways to form the sequence.
- Backpack VI: 给一个数组nums, 全正数, 无重复数字; 找: # of 拼出m的方法
- dp[i]: # of ways to build up to target i
- consider last step: 如果上一步取的是 candidate A, 那么就该加到dp[i]:
- dp[i] += dp[i - A]
- 要找overall dp[i], 就做一个for loop: dp[i] = sum{dp[i - num]}, where for (num: nums)
- Time: O(mn). m = size of nums, n = target
- If we optimize dp for loop, 需要Sort nums. O(mlogm). will efficient 如果m是constant或者relatively small. Overall: O(n)
- 尽管思考方式是对的, 但是 times out
- 可以重复使用数字的时候, 比如用1 来拼出 999, 这里用1就可以走999 dfs level, 不efficient
26. Number of Longest Increasing Subsequence.java Level: Medium Tags: [Coordinate DP, DP]
给一串 unsorted sequence, 找到长 increasing subsequence 的个数!
- 需要能够判断综合题, 分清楚情况和套路: combination of
longest subsequence
andways to do
, as well as global variable. - len[i] (我们平时的dp[i]): 在前i个元素中, 最长的 increasing subsequence length;
- count[i]: 在前i个元素中, 并且以 len[i]这个长度为准的 subsequence的 count. 或者: 在前i个元素中, ways to reach longest increasing subsequence.
len[i] == len[j] + 1
: same length, but different sequence, so add allcount[i] += count[j]
len[i] < len[j] + 1
: 这就是更长的情况找到了, 那么有多少次 count[j] 有多少, count[i] 就有多少. 仔细想sequence: 长度增长了, 但是ways to reach i 没有增长.- 同样的判断需要用在 maxLen 和 maxFreq上:
- 如果没有增长 maxLen 不变, maxFreq上面需要 +=count[i] (同一种长度, 多了更多的做法)
- 如果maxLen 变长, maxFreq 也就是采用了 count[i] = count[j]
- TODO: Is rolling array possible?
- 都是 Coordiate DP, DP的鼻祖家族:
- Longest Increasing Subsequence (跟这道题的一部分一模一样)
- Longest Continuous Increasing Subsequence (连续, 只check dp[i - 1])
- Longest Increasing Continuous Subsequence I, II (Lintcode, II 是matrix)
27. Longest Increasing Subsequence.java Level: Medium Tags: [Binary Search, Coordinate DP, DP, Memoization]
无序数组, 找最长的上升(不需要连续)数组 的长度. 先做O(n^2), 然后可否O(nLogN)?
- 找subsequence: 不需要continous, 可以skip candidate
- 考虑nums[i]结尾的时候, 在[0, i), dp[i - 1] 里count有多少小于nums[i]
- dp[i]: 到i为止 (对于所有 j in [0, i], 记录max length of increasing subsequence
- max需要在全局维护: nums是无序的, nums[i]也可能是一个很小的值, 所以末尾dp[i]并不是全局的max, 而只是对于nums[i]的max.
- 正因此, 每个nums[i]都要和每个nums[j] 作比较, j < i.
- dp[i] = Maht.max(dp[i], dp[j] + 1); j = [0 , i - 1]
- 时间复杂度 O(n^2)
- 维持一个list of increasing sequence
- 这个list其实是一个base-line, 记录着最低的increasing sequence.
- 当我们go through all nums的时候, 如果刚好都是上升, 直接append
- 如果不上升, 应该去list里面, 找到最小的那个刚好大于new num的数字, 把它换成num
- 这样就完成了baseline. 举个例子, 比如替换的刚好是在list最后一个element, 等于就是把peak下降了, 那么后面其他的数字就可能继续上升.
- '维护baseline就是一个递增的数列' 的证明, 还没有仔细想.
28. Best Time to Buy and Sell Stock III.java Level: Hard Tags: [Array, DP, Sequence DP]
比stock II 多了一个限制:只有2次卖出机会.
- 只卖2次, 把买卖分割成5个状态模块.
- 在状态index 0, 2, 4: 没有持有股票. 1. 一直在此状态, max profit不变; 2. 刚卖掉, dp[i][前状态] + profit
- 在状态index 1, 3: 持有股票. 1. 一直在此状态, daily profit. 2. 刚刚买进, 状态改变, 但是没有profit yet: dp[i][前状态]
- 把每天的partial profit (diff)加在一起, 最终的overall profit是一样的. 唯一更好的是, 不需要记录中间买入的时间点.
- 什么时候会积累profit呢?
-
- 原本就持有股票的, 如果毫无动作, 那么状态不变, 积累profit diff.
-
- 卖出了股票, 状态改变, 积累profit diff.
- 注意: 只有在状态index: 0, 2, 4, 也就是卖掉股票的时候, 才可以积累profit
- [i] 只有和 [i-1] 打交道, reduce space
- O(1) space, O(n) time
- 找峰头;然后往下再找一个峰头。
- 怎么样在才能Optimize两次巅峰呢?从两边同时开始找Max!(棒棒的想法)
- leftProfit是从左往右,每个i点上的最大Profit。
- rightProfit是从i点开始到结尾,每个点上的最大profit.
- 那么在i点上,就是leftProfit,和右边rightProfit的分割点。在i点,leftProfit+rightProfit相加,找最大值。
- 三个O(n),还是O(n)
29. Distinct Subsequences.java Level: Hard Tags: [DP, String]
Double Sequence DP: 0. DP size (n+1): 找前nth的结果, 那么dp array就需要开n+1, 因为结尾要return dp[n][m]
- 在for loop 里面initialize dp[0][j] dp[i][0]
- Rolling array 优化成O(N): 如果dp[i][j]在for loop里面, 就很好替换 curr/prev
30. Coins in a Line II.java Level: Medium Tags: [Array, DP, Game Theory, Memoization, MiniMax]
给一串coins, 用values[]表示; 每个coin有自己的value. 先手/后手博弈, 每次只能 按照从左到右的顺序, 拿1个或者2个棋子, 最后看谁拿的总值最大.
MiniMax的思考方法很神奇, 最后写出来的表达式很简单
- 跟Coins in a line I 不一样: 每个coin的value不同.
- 用到MiniMax的思想, 这里其实是MaxiMin. Reference: http://www.cnblogs.com/grandyang/p/5864323.html
- Goal: 使得player拿到的coins value 最大化.
- 设定dp[i]: 从index i 到 index n的最大值. 所以dp[0]就是我们先手在[0 ~ n]的最大取值
- 于此同时, 你的对手playerB也想最大化, 而你的选择又不得不被对手的选择所牵制.
- 用MaxiMin的思想, 我们假设一个当下的状态, 假想对手playerB会做什么反应(从对手角度, 如何让我输)
- 在劣势中(对手让我输的目标下)找到最大的coins value sum
- Reference里面详细介绍了表达式如何推到出来, 简而言之:
- 如果我选了i, 那么对手就只能选(i+1), (i+2) 两个位置, 而我在对方掌控时的局面就是min(dp[i+2], dp[i+3])
- 如果我选了i和(i+1), 那么对手就只能选(i+2), (i+3) 两个位置, 而我在对方掌控时的局面就是min(dp[i+3], dp[i+4])
- 大家都是可选1个或者2个coins
- 目标是maximize上面两个最坏情况中的最好结果
- 更加简化一点: 如果我是先手, dp[i]代表我的最大值.
- 取决于我拿了[i], 还是[i] + [i+1], 对手可能是dp[i + 1], 或者是dp[i+2]
- 其实dp[i] = Math.max(sum - dp[i + 1], sum - dp[i + 2]);
- 这里的sum[i] = [i ~ n] 的sum, 减去dp[i+1], 剩下就是dp[i]的值没错了
- 这个做法是从最后往前推的, 注意initialize dp末尾的值.
- dp = new int[n + 1]; dp[n] = 0; // [n ~ n]啥也不选的时候, 为0.
- sum = new int[n + 1]; sum[n] = 0; // 啥也不选的时候, 自然等于0
- 然后记得initialize (n-1), (n-2)
Time O(n) Space O(n): dp[], sum[]
31. Unique Binary Search Tree II.java Level: Medium Tags: [BST, DP, Divide and Conquer, Tree]
给一个数字n, 找到以(1...n)为node的所有unique BST.
- 根据BST规则, divide and conquer
- 取一个value, 然后分两半(start, value - 1), (value + 1, end) 分别dfs
- 然后左右两边的结果cross match
32. Ones and Zeroes.java Level: Hard Tags: [DP]
还是Double Sequence, 但是考虑第三种状态: 给的string array的用量. 所以开了3维数组.
如果用滚动数组优化空间, 需要把要滚动的那个for loop放在最外面, 而不是最里面. 当然, 这个第三位define在 dp[][][]的哪个位置, 问题都不大.
另外, 注意在外面calcualte zeros and ones, 节约时间复杂度.
33. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
34. Best Time to Buy and Sell Stock IV.java Level: Hard Tags: [DP, Sequence DP]
有int[] price of stock, 最多做 k transactions. 求最大profit.
- 根据StockIII, 不难发现StockIV就是把状态划分为2k+1份. 那么同样的代码, 移植.
- 如果k很大, k>n/2, 那么长度为n的数组里面, 最多也只能n/2个transaction
- 那么题目简化为stockII, 给n数组, 无限次transaction.
- 注意, status的数量是 2k+1
- Time O(NK), Space O(2k+1) to store the status
- 最后状态是'没有stock'的都该考虑, 做一个 for 循环比较max.
- 当然, 来一个profit variable, 不断比较, 也是可以的.
- (previous notes, 熟练第一种方法的思考就可以)
- 记得要理解:为什么 i-1天的卖了又买,可以和第 i 天的卖合成一次交易?
- 因为每天交易的price是定的。所以卖了又买,等于没卖!这就是可以合并的原因。要对价格敏感啊少年。
- Inspired from here: http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html
-
local[i][j] = max(global[i – 1][j – 1] + diff, local[i – 1][j] + diff)
-
global[i][j] = max(global[i – 1][j], local[i][j])
-
local[i][j]: 第i天,当天一定进行第j次交易的profit
-
global[i][j]: 第i天,总共进行了j次交易的profit.
-
local[i][j]和global[i][j]的区别是:local[i][j]意味着在第i天一定有交易(卖出)发生。
-
当第i天的价格高于第i-1天(即diff > 0)时,那么可以把这次交易(第i-1天买入第i天卖出)跟第i-1天的交易(卖出)合并为一次交易,即local[i][j]=local[i-1][j]+diff;
-
当第i天的价格不高于第i-1天(即diff<=0)时,那么local[i][j]=global[i-1][j-1]+diff,而由于diff<=0,所以可写成local[i][j]=global[i-1][j-1]。
-
(Note:在我下面这个solution里面没有省去 +diff)
-
global[i][j]就是我们所求的前i天最多进行k次交易的最大收益,可分为两种情况:
-
如果第i天没有交易(卖出),那么global[i][j]=global[i-1][j];
-
如果第i天有交易(卖出),那么global[i][j]=local[i][j]。
35. Ugly Number II.java Level: Medium Tags: [DP, Enumeration, Heap, Math, PriorityQueue]
- curr index is based on previous calculation: the min of all 3 previous factors
- O(n)
- 非常brutle的。
- 每次把dp[i-1]拿出来,不管三七二十一,分别乘以2,3,5. 出来的结果放进priority queue做比较。
- 最后时间是nlog(n3)
- 注意:use long, use HashSet确保没有重复
- O(nlogn)
36. Copy Books.java Level: Hard Tags: [Binary Search, DP, Partition DP]
给一串书pages[i], k个人, pages[i] 代表每本书的页数. k个人从不同的点同时开始抄书.
问, 最快什么时候可以抄完?
- 第一步, 理解题目要求的问题: 前k个人copy完n本书, 找到最少的用时; 也可以翻译成:
n本书, 让k个人来copy, 也就是分割成k段
. - 最后需要求出 dp[n][k]. 开: int[n+1][k+1].
- 原理:
-
- 考虑最后一步: 在[0 ~ n - 1]本书里, 最后一个人可以选择copy 1 本, 2 本....n本, 每一种切割的方法的结果都不一样
-
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
Math.max(dp[j][k - 1], sum)
.
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
-
- 其中:
dp[j][k-1]
是 [k-1]个人读完j本书的结果, 也就是著名的上一步
. 这里循环考虑的是第k个人不同的j种上一步 : )
- 其中:
-
- 循环的结果, 是要存在 dp[i][k] = Math.min(Math.max(dp[j][k - 1], sum[j, i]), loop over i, k, j = [i ~ 0])
- Time: O(kn^2), space O(nk)
- Init: dp[0][0] = 0, 0个人0本书
- Integer.MAX_VALUE的运用:
- 当 i = 1, k = 1, 表达式: dp[i][k] = Math.min(dp[i][k], Math.max(dp[j][k - 1], sum));
- 唯一可行的情况就只有一种: i=0, k=0, 刚好 0 个人 copy 0 本书, dp[0][0] = 0.
- 其他情况, i = 1, k = 0, 0 个人读 1本书, 不可能发生: 所以用Integer.MAX_VALUE来冲破 Math.max, 维持荒谬值.
- 当 i=0, k=0 的情况被讨论时候, 上面的方程式才会按照实际情况计算出 dp[i][k]
- 这道题的init是非常重要而tricky的
- k个人, 需要一个for loop;
- k个人, 从copy1本书开始, 2, 3, ... n-1,所以 i=[1, n], 需要第二个for loop
- 在每一个i上, 切割的方式可以有[0 ~ i] 中, 我们要计算每一种的worst time
- [k] 只有和 [k - 1] 相关
- Space: O(n)
- 根据: 每个人花的多少时间(time)来做binary search: 每个人花多久时间, 可以在K个人之内, 用最少的时间完成?
- time variable的范围不是index, 也不是page大小. 而是[minPage, pageSum]
- validation 的时候注意3种情况: 人够用 k>=0, 人不够所以结尾减成k<0, 还有一种是time(每个人最多花的时间)小于当下的页面, return -1
- O(nLogM). n = pages.length; m = sum of pages.
37. Longest Increasing Path in a Matrix.java Level: Hard Tags: [Coordinate DP, DFS, DP, Memoization, Topological Sort]
m x n 的matrix, 找最长增序的序列长度. 这里默认连续的序列.
- 接成圈是不行的, 所以visit过得 (x,y)就不能再去了.
- 斜角方向不能走, 只能走上下左右
- 无法按照坐标DP来做, 因为计算顺序4个方向都可以走.
- 最终要visit所有node, 所以用DFS搜索比较合适.
- 简单版: longest path, only allow right/down direction:
dp[x][y] = Math.max(dp[prevUpX][prevUpY], or dp[prevUpX][prevUpY] + 1)
; and compare the other direction as well- This problem, just compare the direction from dfs result
- DFS太多重复计算; memoization (dp[][], visited[][]) 省去了重复计算
- initialize dp[x][y] = 1, (x,y) 自己也算path里的一格
- dfs(matrix, x, y): 每次检查(x,y)的4个neighbor (nx, ny), 如果他们到(x,y)是递增, 那么就考虑和比较:
- Maht.max(dp[x][y], dp[nx][ny] + 1); where dp[n][ny] = dfs(matrix, nx, ny)
- top level: O(mn), 尝试从每一个 (x,y) 出发
- O(m * n * k), where k is the longest path
还没有做
38. Interleaving String.java Level: Hard Tags: [DP, String]
双序列DP, 从最后点考虑. 拆分问题的末尾, 考虑和s1, s2 subsequence之间的关联.
求存在性, boolean
39. Counting Bits.java Level: Medium Tags: [Bit Manipulation, Bitwise DP, DP]
给一个数组, 算里面有多少bit 1.
- 对于每一个数字, 其实很简单就能算出来: 每次 >>1, 然后 & 1 就可以count 1s. Time: 一个数字可以 >>1 O(logN) 次
- 现在要对[0 ~ num] 都计算, 也就是N个数字, 时间复杂度: O(nLogN).
- 用DP来优化, 查找过的number的1s count, 存下来在 dp[number]里面.
- 计算你顺序从 0 -> num, count过的数字就可以重复利用.
- Bit题目 用num的数值本身表示DP的状态.
- 这里, dp[i] 并不是和 dp[i-1]有逻辑关系; 而是dp[i] 和dp[i>>1], 从binary representation看出有直接关系.
40. k Sum.java Level: Hard Tags: [DP]
DP. 公式如何想到, 还需要重新理解.
dp[i][j][m]: # of possibilities such that from j elements, pick m elements and sum up to i. i: [0~target]
dp[i][j][m] = dp[i][j-1][m] + dp[i - A[j - 1]][j-1][m-1] (i not included) (i included)
41. Coins in a Line III.java Level: Hard Tags: [Array, DP, Game Theory, Interval DP, Memoization]
LeetCode: Predict the Winner
还是2个人拿n个coin, coin可以有不同的value.
只不过这次选手可以从任意的一头拿, 而不限制从一头拿. 算先手会不会赢?
- 跟Coins in a Line II 一样, MaxiMin的思想: 找到我的劣势中的最大值
dp[i][j] 代表在[i,j]区间上 选手最多能取的value 总和
- 同样, sum[i][j]表示[i] 到 [j]间的value总和
- 对手的最差情况, 也就是先手的最好情况:
- dp[i][j] = sum[i][j] - Math.min(dp[i][j - 1], dp[i + 1][j]);
- 这里需要search, 画出tree可以看明白是如何根据取前后而分段的.
- 因为是看区间[i,j]的情况, 所以可以想到是区间 DP
- 这个方法需要复习, 跟数学表达式的推断相关联: S(x) = - S(y) + m. 参考下面的公式推导.
- dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x).
- 其中一个S(x) = dp[i][j] = a[i] - dp[i + 1][j]
- m 取在开头, m 取在末尾的两种情况:
- dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}
- len = 1, 积分就是values[i]
- 最后判断 dp[0][n] >= 0, 最大数字和之差大于0, 就赢.
- 时间/空间 O(n^2)
- S(x) = X - Y, 找最大数字和之差, 这里X和Y是选手X的总分, 选手Y的总分.
- 对于选手X而言: 如果S(x)最大值大于0, 就是赢了; 如果最大值都小于0, 就一定是输了.
- 选手Y: S(y)来表示 对于Y, 最大数字和之差. S(y) = Y - X
- 根据S(x) 来看, 如果从 数字和X里面, 拿出一个数字 m, 也就是 X = m + Xwithout(m)
- S(x) = m + Xwithout(m) - Y = m + (Xwithout(m) - Y).
- 如果我们从全局里面索性去掉m, 那么 S(y'') = Y - Xwithout(m)
- 那么推算下来: S(x) = m + (Xwithout(m) - Y) = m - (Y - Xwithout(m)) = m - S(y'')
- 在这个问题里面, 我们model X 和 Y的时候, 其实都是 dp[i][j], 而区别在于先手/后手.
- 将公式套用, 某一个S(x) = a[i] - dp[i + 1][j], 也就是m=a[i], 而 S(y'') = dp[i + 1][j]
- 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维.
- 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.
- Trick: 利用差值公式, 推导有点难想到.
- 找出[i, j]区间内的性质: dp[i][j]下标表示区间范围 [i, j]
- 子问题: 砍头, 砍尾, 砍头砍尾
- loop应该基于区间的length
- template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;
42. Burst Balloons.java Level: Hard Tags: [DP, Divide and Conquer, Interval DP, Memoization]
一排球, 每个球有value, 每次扎破一个, 就会积分: 左中间右 的值. 求, 怎么扎, 最大值?
TODO: Need more thoughts on why using dp[n + 2][n + 2] for memoization, but dp[n][n] for interval DP.
- 因为数组规律会变, 所以很难找'第一个burst的球'. 反之, 想哪一个是最后burst?
- 最后burst的那个变成一堵墙: 分开两边, 分开考虑, 加法原理; 最后再把中间的加上.
- dp[i][j] represent max value on range [i, j)
- Need to calculate dp[i][j] incrementally, starting from range size == 3 ---> n
- Use k to divide the range [i, j) and conquer each side.
- 中间劈开
- 砍断首或尾
- Range区间作为iteration的根本
- use pi[i][j] and print recursively.
- Print k, using pi[i][j]: max value taken at k
- 其实会做之后挺好想的一个DP
- dp[i][j] = balloons i~j 之间的 max.
- 然后找哪个点开始burst? 设为x。
- For loop 所有的点作为x, 去burst。
- 每次burst都切成了三份:左边可以recusive 求左边剩下的部分的最大值 + 中间3项相乘 + 右边递归下去求最大值。
- Note: 这个是Memoization, 而不纯是DP
- 因为recursive了,其实还是搜索,但是memorize了求过的值,节省了Processing
43. Best Time to Buy and Sell Stock with Cooldown.java Level: Medium Tags: [DP]
Sequence DP 跟StockIII很像. 分析好HaveStock && NoStock的状态, 然后看最后一步.
44. Palindrome Partitioning II.java Level: Hard Tags: [DP, Partition DP]
给一个String s, 找出最少用多少cut, 使致 切割出的每一个substring, 都是palindrome
- Find minimum cut: 分割型DP
- dp[i]: 最少cut多少刀, 使得前 i 长度的string, 割出来都是palindrome
- 最终要得到 dp[n], 所以 int[n + 1]
- 移动切刀, 看在哪里切, index j in [0 ~ i]
- 考虑[j, i - 1] 是否是回文串, 如果是, 那么: dp[i]= min(dp[i], d[j] + 1).
- note: 估计遍历 j 的时候, 反过来遍历也可以.
- 利用palindrome的性质, 可以算出 boolean palindrome[i, j]的情况.
- 找一个任意mid point:
-
- 假设palindrome是奇数长度, 那么 mid 是单独的字符, 而两边的字符 [mid-1], [mid+1] 应该完全相等.
-
- 假设palindrome是偶数长度, 那么 [mid] 和 [mid + 1] 这样位置的字符应该相等.
- 这样做出来 palindrome[i, j]: 从字符 i 到 字符 j 的 substring 是否是 palindrome
- 这样就给我们的问题合理降维, 目前是time: O(n^2).
- 不然求一次palindrome, 就是n, 会变成O(n^3)
- Double for loop 检查每种substring string (i~j). 若i,j相邻或者同点,那么肯定isPal;否则,i,j之间的(i+1, j-1)一定得isPal。
- 看上去,在检查i,j的时候,中间按的(i+1, j-1)怎么可能先知道? 其实不然..在j慢慢长大的时候,所有的0~j的substring都检查过。所以isPal[i+1][j-1]一定是已经知道结果的。
- okay.那么假如以上任意一种情况成立,也就是说isPal[i][j] == true。那就要判断,切到第一层循环参数j的末尾点时,有多少种切法?
- 想法很顺:我们naturally会想到,把i之前的cut加上i~j之间发生的不就好了。
- 反正现在j不变,现在就看吧i定在哪里,cut[i - 1]是否更小/最小; 再在cut[i-1]基础上+1就完了。
- 当然,如果i==0, 而 i~j又是isPal,那没啥好谈的,不必切,0刀。
- 最终,刷到cut[s.length() - 1] 也就是最后一点。 return的理所应当。
45. Maximum Vacation Days.java Level: Hard Tags: [DP]
46. Best Time to Buy and Sell Stock with Transaction Fee.java Level: Medium Tags: [Array, DP, Greedy, Sequence DP, Status DP]
跟Stock II 一样, 买卖无限, 需先买在卖. 附加条件: 每个sell transaction要加一笔fee.
- 与StockII一样, dp[i]: represents 前i天的最大profit.
- sell 的时候, 才完成了一次transaction, 需要扣fee; 而买入不扣fee.
- model sell on dp[i] day (which depends on dp[i-1]) and each day can be sell/buy => add status to dp[i][status]
- status[0] buy on this day, status[1] sell on this day
- dp[i][0] = Math.max(dp[i-1][0], dp[i - 1][0] - prices[i]);
- dp[i][1] = Math.max(dp[i-1][1], dp[i - 1][1] + prices[i] - fee);
- init: dp[0][0,1] = 0; dp[1][1] = 0; dp[1][0] = - prices;
- return dp[n][1]
47. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
48. Predict the Winner.java Level: Medium Tags: [DP, MiniMax]
Detailed in Coins in a Line III
49. Coins in a Line.java Level: Medium Tags: [DP, Game Theory, Greedy]
拿棋子游戏, 每个人可以拿1个或者2个, 拿走最后一个子儿的输. 问: 根据给的棋子输, 是否能确定先手的输赢?
Game Theory: 如果我要赢, 后手得到的局面一定要'有输的可能'.
- 要赢, 必须保证对手拿到棋盘时, 在所有他可走的情况中, '有可能败', 那就足够.
- 设计dp[i]:表示我面对i个coins的局面时是否能赢, 取决于我拿掉1个,或者2个时, 对手是不是会可能输?
- dp[i] = !dp[i - 1] || !dp[i-2]
- 时间: O(n), 空间O(n)
- 博弈问题, 常从'我的第一步'角度分析, 因为此时局面最简单.
空间优化O(1). Rolling array, %2
50. Number Of Corner Rectangles.java Level: Medium Tags: [DP, Math]
具体看题目: count # of valid rectangles (four corner are 1) in a grid[][].
- Fix two rows and count matching columns
- Calculate number rectangles with
combination
concept: - total number of combinations of pick 2 points randomly: count * (count - 1) / 2
- TODO. HOW?
- O(m^2 * n^2), times out
51. Minimum Swaps To Make Sequences Increasing.java Level: Medium Tags: [Coordinate DP, DP, Status DP]
- 特点: 上一步可能是swaped也可能是fixed
- 考虑A,B之间的现状:
A[i] > A[i - 1] && B[i] > B[i - 1]
或者A[i] > B[i - 1] && B[i] > A[i - 1]
- 问题: 如何把这个状态变成合理的strick-increasing状态?
A[i] > A[i - 1] && B[i] > B[i - 1]
: 1. 已经合理, 也不动. 2. [i], [i-1] 全部都swapA[i] > B[i - 1] && B[i] > A[i - 1]
, 交错开来, 所以调换[i], 或者[i-1]: 1. 换[i-1]. 2. 换[i]- 注意因为求min, 所以init value应该是 Integer.MAX_VALUE;
52. Target Sum.java Level: Medium Tags: [DFS, DP]
// 如何想到从中间initialize
53. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
54. Nim Game.java Level: Easy Tags: [Brainteaser, DP, Game Theory]
- 著名Nim游戏
- 写一些,发现n=4,5,6,7,8...etc之后的情况有规律性: 谁先手拿到4就输了.
- 最终很简单n%4!=0就可以了, time, space O(1)
- 正规地找规律做, 就跟 coins in a line 一样, 按照先手后手来做
- 可以rolling array 优化空间
- Time O(n), 当然啦, 这个题目这样会timeout, 可以使用brainteaser的做法写出结果.
55. 198. House Robber.java Level: Easy Tags: [DP, Sequence DP, Status DP]
搜刮房子, 相邻的不能碰. 每个房子里有value, 求max.
- dp[i]: 前i个房子拿到的max gain
- 看最后结尾状态的前一个或前两个的情况,再综合考虑当下的
- 搞清楚当下[i]的和之前[i-x]的情况的关系: 不可以连着house, 那么就直接考虑 dp[i-2]的情况
- Sequence DP, new dp[n + 1];
- Rolling Array
- [i]'只和前两个位子 [i-1], [i - 2]'相关
- 用%2来标记 [i], [i - 1], [i - 2]三个位置.
- 其他滚动时惯用curr/prev来表示坐标, 这里%2虽然抽象, 但是更加实用.
- dp[i] depends on nums[i-1] or nums[i-2] based on the state at (i-1)
- use dp[n][2] to store dp[i] and stages
- dp[0][0] = 0; dp[0][1] = nums[0]
- calculation
- dp[i][0] = Math.max(dp[i - 1][1], dp[i - 1][0]). The prior house can be either state.
- dp[i][1] = dp[i - 1][0] + nums[i]. The prior house must be
NOT ROBBED
.
- return
Math.max(dp[n - 1][0], dp[n - 1][1])
56. 301. Remove Invalid Parentheses.java Level: Hard Tags: [BFS, DFS, DP]
给一个string, 里面有括号和其他字符. 以最少刀 剪出 valid string, 求所有这样的string.
这个题目有多种解法, 最强就是O(n) space and time
- Goal: identify invalid parentheses and remove (minimum removals)
- Step:
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
if(count<0)
- When invalid occurs:
- chance for correction. Remove the incorrect parentheses, one at a time
- dfs on the rest of the s that has not been tested yet: start index from index i
- Core edge cases:
- Do not correct twice of the same parenthesis by checking [j-1] pos
- Make sure to attempt correction of all possible parenthesis within tested range: because it outputs all results at the same level
- return/finish once correction done
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
- Success case:
- a string s passed test: make sure it passes REVERSED string test!
- Core Concept:
if a parenthese string is valid, the reverse of it should also be valid
- Test s with open='(', close=')' first; reverse s, and test it with open=')', close='('
- Minor details
- only procceed to remove invalid parenthese when
count<0
, and also break && return dfs after the recursive calls. - The above 2 facts eliminates all the redundant results.
- Reverse string before alternating open and close parentheses, so when returning final result, it will return the correct order.
- only procceed to remove invalid parenthese when
- How does it guarantee minimum removals?
- When seeing a chance to correct, it jumps into a for loop of DFS. It
return
after the for loop. This stops additional testing - When invalid occurs, correct it right away: minimum correction
- When seeing a chance to correct, it jumps into a for loop of DFS. It
- Complexity:
- O(nk), k being the # of recursive calls. It takes n calls to finish a full string case.
- Similar to DFS, we wnat to test: 1) test input s valid, 2) remove 1 invalid parenthesis at a time, 3) process substring
- instead of testing all substrings (timeout), we want to establish rules to improve reprocess:
- Test1: skip regular char. No need to test it.
- Test2: if redundant paren, do 1 is enough. skip adjacent ones.
- Test3: if last removed extra paren is '(', the next ')' must be a valid pair. LastRemoved char: pecial handling by using a struct:
class Node {String s, int index, char lastRemoved}
- How to end tests? When there is data in rst, stop adding to queue.
57. 1216. Valid Palindrome III.java Level: Hard Tags: [DFS, DP, Memoization, String]
- Transform the problem:
removing at most k items to make it a palindrome
- that is: find the longest palindrome subsequence with length x, such that
n - x <= k
516. Longest Palindromic Subsequence
utilizes Interval DP to find LPS length x- at the end, perform n - x <= k?
- time: O(n^2) to find LPS
- space: O(n^2) for dp
- Either times out or too much space used
- time: O(n^2)
- space: O(n^2) or O(k*n^2)
58. 5. Longest Palindromic Substring.java Level: Medium Tags: [DP, String]
给一个string, 找到最长的palindrome substring.
Related: Longest Palindromic Subsequence, Palindrome Partioning II
O(n^2) is not too hard to think of. How about O(n)?
- Very similar to
216. Longest Palindromic Subsequence
, but this problem requires solid substring(i+1, j-1) to be palindromic - Similarly: process i = n-1, from end so [i + 1, j] is always ready to consume
- boolean dp[i][j] to mark range (i, j) as palindrome or not.
- 在计算 dp[i][j]的时候, isPalin[i+1][j-1]应该已经计算过了.
- time: O(n^2) dp
- space: O(n^2)
- 从中间劈开, 遍历i: 从n个不同的点劈开: 每次劈开都看是否可以从劈开出作为palindromic的中点延伸
- palindrome两种情况: odd, even palindrome
- Worst case: 整个string都是相同字符,time complexity变成: 1 + 2 +3 + ... +n = O(n^2)
59. 303. Range Sum Query - Immutable.java Level: Easy Tags: [DP, PreSum]
给一串数字, 求sumRange.
- pre sum 的definition
- preSum也是dp[]一种最简易的形式把.
- dp[i], preSum[i]: 前(i-1)个元素的和.
60. 674. Longest Continuous Increasing Subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP, Sliding Window]
找连续的持续上升子序列的长度.
- update the window start index;
left
in sliding window- update when we need to start a new range:
nums[i-1] >= nums[i]
- calculate the max distance
i - widowStart + 1
- O(n) time and O(1) space
- size++ when meeting condition
nums[i] > nums[i - 1]
- otherwise, reset size = 1
- track max all the way
- 1D coordinate, dp 的角标, 就是代表 index i 的状态
- 求最值, dp[i] = 在index i位置的最长子序列
- 如果 nums[i] > nums[i - 1], dp[i] = dp[i - 1] + 1
- 如果没有持续上升, 那么dp[i] = 1, 重头来过
- maintain max
61. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
62. 152. Maximum Product Subarray.java Level: Medium Tags: [Array, DP, PreProduct, Subarray]
从一组数列(正负都有)里面找一串连续的子序列, 而达到乘积product最大值.
- Continuous product can be positive/negative/zero
- If nums[i] > 0, want prior largest product[i-1] * nums[i]
- If nums[i] < 0, want prior smallest product[i-1] * nums[i]
- If nums[i] == 0, product = 0
- `prior product[i-1]: 想到DP
-
- 正负数情况, 需要用两个
PreProduct
array: minProduct[], maxProduct[]
- 正负数情况, 需要用两个
-
- continuous prodct: it has to utilize curr nums[i]
- 是跟nums[x]当下值比较的, 如果当下值更适合, 会舍去之前的continous product, 然后重新开始.
- Use a global variable to hold overall result.
-
- Time/Space O (n)
- Space optimization, rolling array
- maxProduct && minProduct 里面的 index i, 都只能 i - 1相关, 所以可以省去redundant operatoins
- Time: O(n)
- space: O(1)
- same concept as method1, but simplified: given that we always have to use nums[i], so only 1 result can be passed on
- FAST, simple to write and read
- time: O(n)
- space: O(1)
- working solution, BUT Time/Space complexity O(n^2) are too much
63. 518. Coin Change 2.java Level: Medium Tags: [Backpack DP, DP]
给串数字, target amount, 求总共多少种方式可以reach the amount.
- O(MN): M, total target amount; N: size of coins
- 类似于: 网格dp, unique path 里面的2种走法: 从上到下, 从左到右
- 状态: dp[i]: sum of ways that coins can add up to i.
- Function: dp[j] += dp[j - coins[i]];
- Init: dp[0] = 1 for ease of calculation; other dp[i] = 0 by default
- note: 避免重复count, 所以 j = coins[i] as start
- 注意 coins 需要放在for loop 外面, 主导换coin的流程, 每个coin可以用无数次, 所以在每一个sum value上都尝试用一次每个coin
64. 509. Fibonacci Number.java Level: Easy Tags: [DP, Math, Memoization]
- fib[n] = fibonacci(n - 1) + fibonacci(n - 2);
- 滚动数组, 简化DP
- recursively calculate fib(n - 1) + fib(n - 2). 公式没问题, 但是时间太长, timeout.
65. 221. Maximal Square.java Level: Medium Tags: [Coordinate DP, DP]
只能往右边,下面走, 找面积最大的 square. 也就是找到变最长的 square.
- 正方形, 需要每条边都是
一样长度
.- 以右下角为考虑点, 必须满足条件: left/up/diagonal的点都是1
- 并且, 如果三个点分别都衍生向三个方向, 那么最长的 square 边就是他们之中的最短边 (受最短边限制)
- dp[i][j]: max square length when reached at (i, j), from the 3 possible directions
- dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
- init: 每个点都可能是边长1, 如果 matrix[i][j] == '1'
- Space, time O(mn)
- Rolling array: [i] 和 [i - 1] 之间的关系, 想到滚动数组优化 space, O(n) sapce.
66. 1048. Longest String Chain.java Level: Medium Tags: [Bucket Sort, DP, Hash Table, Sort]
- store
Map<Word, Longest Chain Length>
- sort all words, try from short to long: short word will be calculated first to serve later words as candidate
- time: O(nlogn)
- space: O(n)
- store
Bucket: List[17] of words
, given word size limit [0 ~ 16] - time: O(n)
- space: O(n)
67. 62. Unique Path.java Level: Medium Tags: [Array, Coordinate DP, DP]
2D array, 算走到最右下角,有多少种方式.
- 计数DP: 2 ways to reach (i,j): dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- non-overlapping:
dp[i - 1][j]
,dp[i][j - 1]
- covers the only 2 possible way to reach (i,j)
- non-overlapping:
- initialization: dp[i][0] = 1, dp[0][i] = 1
- Of course, row i = 0, or col j = 0, there is only 1 way to reach
- time O(mn), space O(mn)
- [i] 只跟 [i - 1] 有关系, 用 curr/prev 建立滚动数组.
- space O(n) 优化空间
- move from (0,0) towards (m, n)
- use Map<coordinate, steps> as memoization technique
68. 322. Coin Change.java Level: Medium Tags: [Backpack DP, DFS, DP, Memoization]
给一串不同数额的coins, 和total amount to spent. 求 最少 用多少个coin可以组合到这个amount. 每种coins个数不限量.
- define dp[x], 积累到amount x, 最少用多少个coin
- function:
dp[x] = Math.min(dp[x], dp[x - coinValue] + 1)
. two branches based on choosing coinValue or not - initialization
- dp[0], zero amount uses 0 coin. so dp[0] = 0
- Utilize
Integer.MAX_VALUE
as default val for initialize dp[x]: 1) alert error stage; 2) easy comparison
- create subproblem: (coins, amount - pickedCoin)
- memo[i] 依然表示: min # of coints to make amount i
- initialize memo[i] = Integer.MAX_VALUE
- 先选最后一步(遍历coins), 然后dfs做同样的操作
- 记录memo[amount] 如果已经给过value, 不要重复计算, 直接return.
- time: O(n * S), worst case it runs n coins for S(amount) iterations
- space: O(S)
69. 55. Jump Game.java Level: Medium Tags: [Array, DP, Greedy]
给出步数,看能不能jump to end.
- start from index = 0
- Keep track of farest can go
- 一旦 farest >= nums.length - 1, 也就是到了头, 就可以停止, return true.
- 一旦 farest <= i, 也就是说, 在i点上, 已经走过了步数, 不能再往前跳, 于是 return false
- start from index = n - 1
- greedy: start from end, and mark last index
- loop from i = [n - 2 -> 0], where i + nums[i] should always >= last index
- check if last == 0 when returning. It means: can we jump from index=0 to the end?
- time: O(n)
- space: O(1)
- DP[i]: 在i点记录,i点之前的步数是否可以走到i点? True of false.
- 其实j in [0~i)中间只需要一个能到达i 就好了
- Function: DP[i] = DP[j] && (j + A[j] >= i), for all j in [0 ~ i)
- Return: DP[dp.length - 1];
- time: O(n^2)
- space: O(n)
70. 140. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary. (Word Break I
only checks possibility)
利用 memoization: Map<prefix, List<suffix variations>>
- IMPORANT, Memoization:
Map<prefix, List<suffix variations>>
to build substring segments. Reduces repeated calculation if the substring has been tried. - Realize the input s expands into a tree of possible prefixes.
- Find list of candidates from subproblem, and cross-match
- DFS returns List segments of target s: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....'
, anddict=[a,b,c,d,e,f...]
- DFS on string: find a valid prefix, dfs on the suffix, building individual candidate in list till substring exhaust.
- improvement:
- use memo to record failed case (solved the timeout issue explained below)
- use min/max to as boundary for dict check.
- core code is short; helper code is slightly longer
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
71. 741. Cherry Pickup.java Level: Hard Tags: [DFS, DP]
special hint: r1 + c1 = constant t = r2 + c2
, if the two points are moving at same time.
- Similar concept to Minimum Path Sum
- https://leetcode.com/problems/cherry-pickup/solution/
- realize r1 + c1 = r2 + c2. Knowing 3 parameters can uniquely identify the 4th.
- assume there are 2 people starting from origin, and the 2 people can go total 4 directions
- perform DFS based on the 4 directions
- concern: do they visit the same spot? possible. when that happens, make sure we do not double count the grid[i][j]
- when is the end state?
- then anyone, for example, (r1,c1) reaches end (n-1, n-1).
- it means the other person also reaches end
- use memo: memo[r1][c1][r2], it records any given (r1, c1, r2, c2) state
72. 727. Minimum Window Subsequence.java Level: Hard Tags: [DP, Hash Table, Sliding Window, String, Two Pointers]
- DIFFERENT from sliding window for substring (
76. Minimum Window Substring
)- because this problem rquries keeping the order of characters from the target string
- Use a
backtrack mechanism
based on target matching to find closest left starting point to right
- Simple two pointers:
-
- move sIndex and tIndex: find all T chars in S, in order.
-
- backtrack tIndex to 0; backtrack sIndex to initial char match
-
- record potential min result
-
- Be VERY careful about pointer and index.
- time: O(n^2), backtrack n steps
- Since it requires order of substring,
freqMap+counter+twoPointers
approach is NOT applicable
- TODO
73. 70. Climbing Stairs.java Level: Easy Tags: [DP, Memoization, Sequence DP]
每一步可以走1步或者2步, 求总共多少种方法爬完梯子.
- 递归很好写, 但是重复计算, timeout. time: O(2^n)
- O(2^n): each n can spawn 2 dfs child, at next level, it will keep spawn. Total 2^n nodes will spawn.
- 用全局变量int[] memo 帮助减少重复计算
- O(n) time, space
- 加法原理, 最后一步被前两种走法决定: dp[i] = dp[i - 1] + dp[i - 2]
- 基础sequence DP, int[] dp = int[n + 1];
- DP[]存的是以 1-based index的状态
- dp[i]: count # of ways to finish 前i个 台阶
- 需要知道dp[n] 的状态, 但是最大坐标是[n-1], 所以int[n+1]
- dp[0]往往是有特殊状态的. 这里, dp[0]: 1种方式可以原地不动
- dp[1]=1, 1种方式到达index=1,
- 之后的
dp[2] = dp[0]+dp[1]
: 就是dp[0]的走法 or dp[1]的走法
- O(n) space, time
- [i] only associates with [i-2], [i-1].
- %2
- O(1) space
74. 10. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
IMPORTANT: '*' 需要有一个 prefix element [elm], so it becomes [elm]*
. There 2 possible cases:
- [elm] repeats 0 times: move p, j + 2
- [elm] repeats 1 or more times: need s[i] == p[i], then move s, i+1
- DFS on remaining of s and p. Analyze the different cases when next char == '*'
- End case: both i,j reached end true; or one of them reached end.
- The two different cases when given any index j on p, the p[j+1]=='*'
- TRUE:
- ignore p[j, j+1], continue from p[j+2]
- check if s[i]==p[j] or p[j]='.'; continue from s[i+1] and p
- FALSE: check i,j, and move forward with s[i+1], p[j+1]
- TRUE:
- If next p char != '*', check curr s[i] ?= p[i]
- Improvement with memo with 2D Booelan[][] memo: much faster
- memo[i][j] records result the remaining strings: s.substring(i) compare with p.substring(j)
- use
Boolean
: when memo[i][j] != null, return something!
- Two sequence, DP, find if possible to match.
- The '*' takes effect of preceding/prior element, so we can start matching from end.
- DP[i][j]: is it possible to match s[0 ~ i - 1] and p[0 ~ j - 1].
- Check last index of s and p, there can be a few possibilities:
-
- s[i-1]==p[j-1] and they are normal characters => && dp[i - 1][j - 1];
-
- p[j-1] == '.', match => dp[i - 1][j - 1]
-
- p[j-1] == '*':
- a. ignore a* => |= dp[i][j - 2];
- b. use a* => |= dp[i - 1][j];
-
- init: dp[0][j] and dp[i][0] will all be false since there cannot be any match.
75. 122. Best Time to Buy and Sell Stock II.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Status DP]
和Stock I 的区别:可以买卖多次,求总和的最大盈利.
- Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
- 从低谷找peek, sell.
- DP. (old dp solution BuyOn[], SellOn[])
- DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)
- 画图, 因为可以无限买卖, 所以只要有上升, 就有profit
- 所有卖掉的, 平移加起来, 其实就是overall best profit
- 当天卖光, 再买进.
- O(n) time
- 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。
- 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.
- profit += prices[peek - 1] - prices[start]; 挺特别的。
- 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.
- 想知道前i天的最大profit, 那么用sequence DP:
- dp[i]: represents 前i天的最大profit
- 当天的是否能卖, 取决于昨天是否买进, 也就是
昨天买了或者卖了的状态
: 加状态, dp[i][0], dp[i][1] 买
的状态dp[i][0]
=-
- 今天买入, 昨天sell后的dp[i-1][1]值 - 今天的价格price[i];
-
- 今天不买, compare with 昨天buy的dp[i-1][0]值.
-
卖
的状态dp[i][1]
=-
- 今天卖出, 昨天buy的 dp[i-1][0]值 + price[i];
-
- 今天不卖, compare with 昨天sell后的 dp[i-1][1]值.
-
- 注意init:
- dp[0][0] = dp[0][1] = 0; // day 0 buy/sell: no profit regardless of buy/sell status
- dp[1][1] = 0; // day 1 sell: haven't bought, so just 0 profit.
- dp[1][0] = - prices[0]; // day 1 buy: just cost of prices[0]
- Note:
int[][] dp = new int[n+1][2]
以后, index是 1-based. for loop 注意使用prices[i-1]
- O(n) time, O(n) space
- [i] 和 [i - 1] 相关联, roll
76. 91. Decode Ways.java Level: Medium Tags: [DP, Partition DP, String]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
- 加法原理: 根据题意, 有 range = 1 的 [1, 9], range = 2 的 [10~26] 来作为partition.
- there can be 2 final states at dp[i]: single digit or double digit.
- validate if
single digit
, dp[i] += dp[i - 1]. Last 1 digit can be decoded. - validate if
double digit
, dp[i] += dp[i - 2]. Last 2 digits can be decoded.
- validate if
- note:
- get number from char:
c - '0'
- validatae single digit != '0', 因为'0' 不在条件之中(A-Z)
- get number from char:
- Option1: dp[i] as # ways to decode at index i, including letter i
- The validation is done on: 1) single digit i, or 2) double digit [i-1, i]
- Option2: Partition DP, dp[i] as # ways to decode for first i letters (excluding letter i)
- 定义
dp[i] = 前i个digits最多有多少种decode的方法
: new dp[n + 1]. - dp[i] += dp[i - x], where x = 1, 2
- The validation is done on: 1) single digit [i-1], or 2) double digit [i-2, i-1]
- Option2 is better in terms of clean coding. We assume
dp[0]=1
as 1 way to decode 0 digits.- No need to specially handle length == 1, because it is covered later
- No need to manualy init the first 2-digit case as in Option1
- Return of
dp[n]
is clean
- 定义
- 引申: 这里只有两种partition的情况 range=1, range =2. 如果有更多partition的种类, 就可能多一层for loop做循环
- if single-digit is working, sum += dfs(s, i+1);
- if double-digit is working, sum += dfs(s, i+2);
- end case: i >= n, return 0; i == n - 1; i == n - 2
- especially when i = n - 2, handle 2-digt edge case carefully:
-
- check if two-digit range [i, i+1] is valid
-
- check if single-digit [i] is valid; if so, += dfs(s, i + 1)
-
- especially when i = n - 2, handle 2-digt edge case carefully:
- memo[i]: # ways to decode from [i, n). init with
memo[i]=-1
77. 639. Decode Ways II.java Level: Hard Tags: [DP, Enumeration, Partition DP]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
其中字符可能是 "*", 可以代表 [1 - 9]
- 乘法原理, 加法原理
- 跟decode way I 一样, 加法原理, 切割点时: 当下是取了 1 digit 还是 2 digits 来decode
- 定义dp[i] = 前i个digits最多有多少种decode的方法. new dp[n + 1].
- 不同的情况是: 每一个partition里面, 如果有"*", 就会在自身延伸出很多不同的可能
- 那么: dp[i] = dp[i - 1] * (#variations of ss[i]) + dp[i - 2] * (#variations of ss[i,i+1])
- Enumeration:
- 具体分析 '*' 出现的位置, 枚举出数字, 基本功.
- 注意!!题目说 * in [1, 9]. (如果 0 ~ 9 会更难一些)
- 枚举好以后, 其实这个题目的写法和思考过程都不难
- Mode: 数字太大, 取mod来给最终结果: 其实在 10^9 + 7 这么大的 mod 下, 大部分例子是能通过的.
- DFS top-down approach is used to analyze the problem. The logic flow:
-
- consider the case of 1 letter or 2 letters.
-
- one letter:
- [*]: + 9 * dfs(s, i + 1)
- [0~9]: + dfs(s, i + 1)
-
- two letters:
- [*, *]: + 15 * dfs(s, i + 2)
- memo[i] records # of ways to decode from [i ~ n]
- space: O(n), Size of recursion tree can go upto n
- time: O(n),
memo array is filled exactly once
!!!
78. 64. Minimum Path Sum.java Level: Medium Tags: [Array, Coordinate DP, DP]
- Time, Space O(MN)
- 往右下角走, 计算最短的 path sum. 典型的坐标型.
- 注意: init 第一行的时候, 要accumulate dp[0][j - 1] + grid[i][j], 而不是单纯assign grid[i][j]
- Rolling Array
- Time O(MN), Space O(N)
-
- 需要在同一个for loop里面完成initialization, 2) reuse dp[i][j]
- Reason: dp[i % 2][j] 在被计算出来的时候, 马上在下一轮会被用. 被覆盖前不用,就白算
- Option3 below initialize dp outside of loop: 看起来固然简单, 但是不方便空间优化
- Enumerate the possible 2 paths: go right, go down
- sub problem: dfs(i+1,j), dfs(i,j+1); pick the min of the two
- memoization: after the path from a point (i,j) to end is computed, record memo[i][j] to avoid repatative calculation
- time: O(mn), only visite and record memo[i][j] once.
- space: O(mn) extrem case of m=100000, n = 2; the stack height is O(mn)
79. 121. Best Time to Buy and Sell Stock.java Level: Easy Tags: [Array, DP, Sequence DP]
给个array of stock prices, 限制能交易(买/买)一轮, 问如何找到最大profit.
- 每天都就交易价格,n天只让买卖一次,那就找个最低价买进,找个最高价卖出
- 记录每天最小值Min是多少. O(n)
- 每天都算和当下的Min买卖,profit最大多少.
- Find min value for first i items, new dp[n + 1].
- dp[i]: 前i天, prices最小的price是多少: min cost of first i days
- 然后用当天的price做减法dp[i]算max profit.
- Time, Space: O(n)
- 更进一步, 用一个min来表示min[i], 因为计算中只需要当下的min.
- index i only depend on [i - 2]
- Space O(n)
- 每天都试着买进,然后之后的每一天尝试卖出. double for loop, O(n^2). timeout.
- 其中很多都是没必要的计算:[7, 1, 5, 3, 6, 4]
- if we know buyin with 1 is cheapest, we dont need to buyin at 5, 3, 6, 4 later on;
- only need to sell on higher prices.
80. 304. Range Sum Query 2D - Immutable.java Level: Medium Tags: [DP, PreSum]
- build rangeSum[i][j]: square range sum from (0,0) to (i,j), O(mn) to init
- query: time O(1)
- build rowPreSum[i][j]: row i sum from [0 ~ j], O(mn) to init
- callign takes O(m); space O(mn)
81. 516. Longest Palindromic Subsequence.java Level: Medium Tags: [DFS, DP, Interval DP, Memoization]
给一个string s, 找最长的sub-sequence which is also palindrome.
注意!subsequence并不是substring, 是可以skip letter / non-continuous character sequence
- Use example to understand: for any given ending char, 3 cases of palindromes
- a. ss[i, j] is a palindrome. dp[i+1][j-1] + 2 since the two indexes are counted, assume dp[i + 1][j - 1] is calculated
- b. ss[i + 1, j] is a palindrome
- c. ss[i, j - 1] is a palindrome
- time/space: O(n^2)
- Option1: start processing substring from tail
- set:
i = [n-1 towards 0]
,j = i + 1
- consider ss[i, j], ss[i + 1, j], ss[i, j - 1]
- since i starts from n - 1 -> 0 and j = i + 1, these are calculated and ready to go: dp[i+1][j-1], dp[i+1][j] and dp[i][j-1]
- FAST: skipped the initialization
- set:
- Option2: start processing substring from head
- 用[i][j]表示区间的首尾: 考虑3个情况: 砍头, 砍尾, 砍头并砍尾 (考虑首尾关系)
- Iteration on len:
- len = j - i + 1; 那么反推, 如果len已知,
j = len + i - 1
; - 注意考虑len == 1, len == 2是的特殊情况.
- consider sub problems with 3 major cases
- a. ss[i, j] is a palindrome: dfs check ss[i + 1, j - 1]
- b. ss[i + 1, j] maybe a palindrome: dfs check ss[i + 1, j]
- c. ss[i, j - 1] maybe a palindrome: dfs check ss[i, j - 1]
- memo[i][j]: max palindrome length in range [i, j], if calculated, return directly
- Init memo[i][j] = -1 to track the progress, memoization
- 注意: init dp[i][j]=-1, dfs的时候查dp[i][j] 是否算过
- more about dfs: bottom-up, first dive deep into dfs(i+1,j-1) till the base cases.
- Space: O(n^2)
- Time: O(n^2)
- prepare dp[n][n]: O(n^2); dfs: visit all combinations of [i,j]: O(n^2)
82. 63. Unique Paths II.java Level: Medium Tags: [Array, Coordinate DP, DP]
跟unique path的grid一样, 目标走到右下角, 但是grid里面可能有obstacle, 不能跨越. 求unique path 的count.
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
83. 139. Word Break.java Level: Medium Tags: [DP, Hash Table, Sequence DP]
给一个String word, 和一个字典, 检查是否word可以被劈开, 而所有substring都应该是dictionary里面的words.
- Bottom-up, test simply case. Sequence DP.
- true/false problem, think about dp
- 子问题: 前i个字母, 是否可以有valid break
- check: 1) dp[j] && 2)
if substring(j, i) valid
, for all j = [0 ~ i] - dp = new boolean[n + 1]; dp[0] = true;
- test:
dp[i] |= dp[j] == true && word[j, n] in dict
. - Need iterate over i = [0 ~ n], also j = [0, i]
- When there is a way to make dp[i] == true, then break the [j ~ i] loop, move on to test dp[i++]
- Use set dict:
dict.contains()
- Improvement: O(n) to figure out max length, so we can skip some substring[j~i] dict.contains()
- overall O(n^2) time since the double for loop
- Top-Down, break into small problems: Check front subString, and put the rest substring into dfs to test
- Memoization: for tested failed substring, record and do NOT test them again.
- Same Improvement as in DP: use max/min length of dict words as boundary
84. 523. Continuous Subarray Sum.java Level: Medium Tags: [Coordinate DP, DP, Math, PreSum, Subarray]
给一个非负数的数列和数字k(可正负, 可为0). 找到连续子序列(长度超过2), 使得这个subarray的sum 是 k的倍数. 问: 是否可能?
- Check if mod result exist in earlier preSum
- Utilize
Pigeonhole principle
to optimize:-
- put positive integers into k slots
-
- when # of integers > 2*k, then there must be a range sum that is multipler of k
- more illustration here: https://leetcode.com/problems/continuous-subarray-sum/solution/
-
- Draw the presum and try to take mod of each presum and save to set, we realize
-
- assume a mod result = 7, and we mark it in the set
-
- some time later, after summing up more values, (7 + x + y ...+ z) % k == 7
- it means
(x + y ...+ z) % k == 0
- There is a
整除
exist; return true
-
- Meanwhile, if we want to record the list of indexes, we can use a Map rather than set.
- Note: if all we do to the presum is to % k, therefore
preSum % k
can representpresum
in some cases. - time: O(n)
- space: O(k), size restrited by mod result of
%k
- PreSum[]:
-
- cal preSum array
-
- preSum(i, j) = continuous range sum
-
- determine if
preSum(i, j) % k == 0
- determine if
-
- time: O(n^2)
- DP (坐标型. specifically, preSum[])
- 记录在0 ~ i点(包括nums[i], 以nums[i]结尾)的sum, 坐标型动态规划.
- dp[i] = dp[i - 1] + nums[i];
- 从sum = 每次[i ~ j]的所有情况
- time: O(n^2)
- space: O(1)
85. 1043. Partition Array for Maximum Sum.java Level: Medium Tags: [DFS, DP, Graph, Memoization]
- Pick a subset (max-size k), and produce sub problem to solve by dfs
- NOTE: no need to change actual index value. That makes this problem easier (no need to record the choice path)
- time: O(n), calc memo[n]
- space: O(n), memo + stack depth
86. 361. Bomb Enemy.java Level: Medium Tags: [Coordinate DP, DP]
2D grid, 每个格子里面可能是 'W' wall, 'E' enemy, 或者是 '0' empty.
一个bomb可以往4个方向炸. 求在grid上面, 最大能炸掉多少个敌人.
- Space, Time: O(MN)
- dp[i][j] 就是(i, j)上最多能炸掉的enemy数量
- dp[i][j] 需要从4个方向加起来, 也就是4个方向都要走一遍, 所以分割成 UP/Down/Left/Right 4个 int[][]
- 最后一步的时候求max
- 分方向考虑的方法很容易想到,但是四个方向移动的代码比较繁琐.
- 往上炸: 要从顶向下考虑
- 往下炸: 要从下向上考虑
- 熟练写2D array index 的变换.
- Inspired by: https://leetcode.com/problems/bomb-enemy/discuss/83387/Short-O(mn)-time-O(n)-space-solution
- Analyize the problem: need to add up 2 directions at any given point.
- Notice 1: if I traverse row by row, each colSum at a specific col j is likely to be the same
- Unless there is a Wall in last row, so we have to calclate the col sum starting from current row, below the Wall
- Notice 2: for row it is the same:
- If I in a new spot row[i][j], where (i-1) is Wall, i need to sum row from 0
- Notice 1: if I traverse row by row, each colSum at a specific col j is likely to be the same
- we will process each point:
- process row by row and add up row sum
- buffer col[j] in an array vertically so we can resue
- make sure to recalculate row sum or col sum if last row index or last col index is Wall
- time: O(mn) traversal
- space: O(n) only use a column array
87. 124. Binary Tree Maximum Path Sum.java Level: Hard Tags: [DFS, DP, Tree, Tree DP]
找max path sum, 可以从任意treeNode 到任意 treeNode.
- IMPORTANT: DO NOT ASSUME positive integers
- Overall idea: write example and realize 2 cases at each node:
-
combo sum
: left + right + root
-
single path sum WITH curr node
: left/right + root
-
- DFS returns the path over curr node: a path needs to be continuous, so we cannot skip curr node.
- IMPORTANT, key discovery: if left/right single path over curr node is less than 0: reutrn 0.
- Parent path will simply drop this path, since we want maximize the path sum.
- It is so IMPORTANT: when left or right becomes 0, when comparing with global combo path:
- it automatically covers a special case:
single left/right path + node
, since one of left/right == 0!!!
- it automatically covers a special case:
- With the above understanding: what if I want to skip curr node and just want left/right path w/o curr node:
- it is handled and compared with global in dfs(node.left) or dfs(node.right) automatically!
- time: O(n), go over whole tree
- space: O(logn), tree height.
- tree给我们2条branch, 每条branch就类似于 dp[i - 1], 这里类似于dp[left], dp[right] 这样
- 找到 dp[left], dp[right] 以后, 跟 curr node结合.
- 因为是找max sum, 并且可以skip nodes, 所以需要全局变量max
- 每次dfs() return的一定是可以继续
continuously link 的 path
, 所以returnone single path sum + curr value
.
- 用 PathSum 比较特别. 没有 data structure的时候, 写起来比较繁琐.
- 第一次做有点难理解,复杂原因是:因为可能有负值啊。不能乱assume正数。
- single path max 的计算是为了给后面的comboMax用的。
- 如果single path max小于0,那没有什么加到parent上面的意义,所以就被再次刷为0.
- combo的三种情况:(root可能小于0)
-
- 只有left
-
- 只有right
-
- root大于0,那么就left,right,curr全部加起来。
- 情况1和情况2取一个最大值,然后和情况三比较。做了两个Math.max(). 然后就有了这一层的comboMax
88. 689. Maximum Sum of 3 Non-Overlapping Subarrays.java Level: Hard Tags: [Array, DP]
- split into 3 parts [0, i -1], [i, i + k -1]. [i + k, n - 1]
- NOTE: be very careful about index handling:
presum[i + 1] - presum[0]
gives inclusive range of[0, i]
- Use DP to record the starting position of max sum,
- inspired by: https://leetcode.com/problems/maximum-sum-of-3-non-overlapping-subarrays/discuss/108231/C%2B%2BJava-DP-with-explanation-O(n)
-
- calculate preSum with range [0, n]
-
- calculate leftMaxIndex[], rightMaxIndex[]
-
- test middle range to find max solution
-
- Note: the test range for 1, 2, 3 always start with assumption that k has been consumed from one side
- Note: When need to record at max/min value change, we can check/assign it manually (rather than use a object to carry & sort)
89. 698. Partition to K Equal Sum Subsets.java Level: Medium Tags: [DFS, DP, Recursion]
- Target = total / k, fixed.
- DFS: Pick one num and dfs with the remaining nums for subproblem
- subproblem1: accumulate local sum to target
- EndState: accumulatedSum == target, continue with below
- subproblem2: after accumulatedSum == target, continue dfs with k-1
- EndState: k == 0, return overall true
- subproblem1: accumulate local sum to target
- Option1: DFS with nums, and boolean[] visited. Fast
- Option2: DFS with queue.
- Specially handling: dfs(size+1) to prevent
while(size-- >0)
from skipping last item at index 0
- Specially handling: dfs(size+1) to prevent
- the problem aims to find true/false capability
- bit-masking. TODO. The DP approach is kinda hard-level
- https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/335668/DP-with-Bit-Masking-Solution-%3A-Best-for-Interviews
90. 416. Partition Equal Subset Sum.java Level: Medium Tags: [Backpack, DP]
- the problem turns into: can we find a subset of items that sum up to target sum?
- create
boolean dp[j]
to represent if we can sum up to j, where j = sum value- want to try out all items in num,
- use dfs to find a subset of items that sum up to target sum?
91. 256. Paint House.java Level: Easy Tags: [DP, Sequence DP, Status DP]
要paint n个房子, 还有 nx3的cost[][]. 求最少用多少cost paint 所有房子.
- 求dp[i]的min cost, depends on the color of dp[n-1]
- 选中最后一个房子的颜色同时, 根据dp[i - 1]的颜色/cost + cost[i-1], 来找出最低的cost
- Need to have status with dp array: int[index][color status]
- dp[i][j]: 前i个house 刷成 j 号颜色的最小cost.
- dp[0][j] = 0: 0th house, no cost
- 计算顺序: 从每一个house开始算起 [0 ~ n], first for loop
- time: O(nm), m = # of colors
- space: O(nm)
- 观察发现 index[i] 只跟 [i-1] 相关, 所以2位就足够, %2
- space:O(1)
92. 265. Paint House II.java Level: Hard Tags: [DP, Sequence DP, Status DP]
一排n个房子, 每个房子可涂成k种颜色, 涂每个房子的价钱不一样, 用costs[][]表示.
costs[0][1]表示涂了index是0的房子, 用了color 1.
规则: 相邻的两个房子不能使同一种颜色
求: 最少的cost
- 跟Paint House I 几乎一模一样, 只不过paint color更多了: k colors.
- 先考虑单纯地用dp[i]表示涂前 i 个房子的最小cost
- 但是 dp[i] 和 dp[i-1] 两个index选什么颜色会互相影响, 难讨论, 于是加状态: 序列DP被加了状态变成2D.
- 考 虑最后位, 而前一位i-1又被i位的颜色限制, 于是在考虑 min dp[i] 时候, 又多了一层iteration.
- 做dp[i][j]: # cost for 前 i 个房子, 所以要先pick (i-1) 房子的cost, 然后在找出 (i-2)房子的cost
- K种颜色 => O(NK^2)
- 如果不优化, 跟Paint House I 几乎是一模一样的代码
- Time O(NK^2), space(NK)
- Rolling array: reduce space to O(K)
- 序列型dp[i]表示'前i-1个'的结果. 所以dp最好设定为 int[n + 1] size.
- 然而, 颜色在这里是状态, 所以保留在 j: [ 0~k)
- [[8]] 这样的edge case. 跑不进for loop, 所以特殊handle.
- Time: O(NK)
- 如果已知每次都要从cost里面选两个不同的最小cost,那么先把最小两个挑出来, 就不必有第三个for loop 找 min
- 每次在数列里面找: 除去自己之外的最小值, 利用最小值/次小值的思想
- 维持2个最值: 最小值/次小值.
- 计算的时候, 如果除掉的不是最小值的index, 就给出最小值; 如果除掉的是最小值的index, 就给出次小值.
- Every loop: 1. calculate the two min vlaues for each i; 2. calcualte dp[i][j]
- 如何想到优化: 把表达式写出来, 然后看哪里可以优化
- 另外, 还是可以rolling array, reduce space complexity to O(K)
93. 72. Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, String]
两个字符串, A要变成B, 可以 insert/delete/replace, 找最小变化operation count
- 考虑两个字符串的末尾index� s[i], t[j]: 如果需要让这两个字符一样, 可能使用题目给出的三种operation: insert/delete/replace?
- 先calculate最坏的情况, 3种operation count + 1; 然后在比较match的情况.
- 注意, 在i或者j为0的时候, 变成另外一个数字的steps只能是全变.
- 第一步, 空间时间都是O(MN), O(MN)
- 滚动数组优化, 空间O(N)
- insert: assume insert on s, �
#ofOperation = (s[0 ~ i] to t[0 ~ j-1]) + 1;
- delete: assume delete on t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j]) + 1;
- replace: replace both s and t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j - 1]) + 1;
- dp[i][j]�代表了两个 sequence 互相之间的性质: �s[0 ~ i] �转换成 s[0~j] 所需要的最少 operation count
- init: 当i==0, dp[0][j] = j; �每次都要 + j 个character; 同理, 当j==0, dp[i][0] = i;
- 而dp[i][j]有两种情况处理:
s[i] == t[j]
ors[i] != t[j]
- 这种判断取决于经验: 如果知道initialization可以再 double for loop 里面一起做, 那么可以留着那么做
- 这样属于
需要什么, initialize什么
- 事后在做space optimization的时候, 可以轻易在 1st dimension 上做rolling array
- 可以做, 但是不建议:这道题需要找 min count, 而不是search/find all solutions, 所以search会写的比较复杂, 牛刀杀鸡.
0. Missing Number.java Level: Easy Tags: [Array, Bit Manipulation, Math]
给一串unique数字, 数字取自 [0 ~ n], 无序, 找第一个skipped的数字.
- 跟First Missing Positive 非常像, 只有一行代码的区别.
- swap 所有的数字, 到自己的correct position
- 最后一个for loop找到错位的index, 也就是缺的数字.
- XOR will only retain bits that are different 1 ^ 0 = 1, but 0^0, 1^1 == 0
- Use that feature, 把所有value都和index XOR了
- 剩下的多余的数字, 其实是那个index无法被XOR消掉, 也就是那个缺的number value.
- 注意: 题目告诉数字是 [0 ~ n], 然而缺一个数字, 那么在[0 ~ n - 1] 里面, 最大的数字(不管缺没缺), 一定是 n = nums.length.
- 全存, 找missing
- O(n) space, 不合题意
- sort, 找1st missing
- O(n log n) 太慢, 不合题意
1. Linked List Cycle II.java Level: Medium Tags: [Linked List, Math, Two Pointers]
LinkedList 里面有 cycle, 找到cycle的起始点(第一个重复出现的element).
- 快慢指针, O(1)space.
-
- 确认有cycle后 2. 数学问题:找到开头.
- 当head == slow.next时候, head就是cycle starting point.
- 也就是说,当slow 移动到了那个回溯点,slow.next那个点就刚好是head的那个点...
-
- 假设慢指针走t步, 快指针走快一倍, 也就是2t.
-
- 我们假设cycle的长度是Y, 而进入cycle之前的长度为X.
-
- 假设慢指针走了m圈cycle, 而快指针走了n圈cycle之后, 两个pointer相遇.
-
- 最终在Y cycle里面的K点相遇, 也就是两个指针都在这最后一圈里面走了K 步.
- 那么:
- t = X + mY + K
- 2t = X + nY + K
- �整合公式: X + K = (n - 2m)Y
- 这里的m和n不过是整数的跑圈数, 也就是说X和K加在一起, 总归是结束cycle. X 和 K 互补
- 结论: 当slow/fast 指针在K点相遇后, 再走X步, 就到了cycle的起点, 也就是题目要求的起点.
2. Trailing Zeros.java Level: Easy Tags: [Math]
3. Plus One.java Level: Easy Tags: [Array, Math]
简单的实现, 加1, 进位. 唯一取巧的地方, 最后如果要多一位, 一定是10000...这个模式, 可以走捷径, 直接来个+1size的array, 然后第一位=1. 注意,转换成long也不合理,用太多memory.
4. Line Reflection.java Level: Medium Tags: [Hash Table, Math]
给一串点, 找是否有一个所有点中间的, 跟y-axis平行的中线.
-
- store in
Map<y, set<x>>
, 2. iterate over map, check head,tail against the mid point
- store in
- 很好的细节题目:
-
- 除以2, 需要存double
-
- (问面试官)可以有重复的点! 所以track
set<x>
- (问面试官)可以有重复的点! 所以track
-
- 处理 left==right时候, 就当做两个点来处理.
-
- 存进set里面没有sort, 但是最后做check的时候, 需要sort list
- 时间: visit all nodes 两遍, O(n)
5. Perfect Squares.java Level: Medium Tags: [BFS, DP, Math, Partition DP]
给一个数字n, 找到这个数字 最少能用多少个 平方数组成.
平方数比如: 1, 4, 9, 16 ... etc
- 遇到最值, 想到DP.
- 看到分割字眼, 想到分割型 DP.
- 思考, 如果 j * j = 9, 那么 j = 3 就是最少的一步; 但是如果是10呢? 就会分割成1 + 9 = 1 + j * j
- 考虑最后的数字: 要是12割个1出来, 剩下11怎么考虑? 割个4出来,剩下8怎么考虑?
- partion的方式: 在考虑dp[i - x]的时候, x 不是1, 而是 x = j*j.
- 就变成了dp = Min{dp[i - j^2] + 1}
- 乍一看是O(n*sqrt(n)). 实际也是. 但如何推导?
- 考虑上限: 把小的数字变成大的 推导上限; 考虑下限: 把数字整合归小, 找到下限.
- 考虑sqrt(1) + sqrt(2) + ....sqrt(n):找这个的upper bound and lower bound.
- 最后发现它的两边是 Ansqrt(n) <= actual time complexity <= Bnsqrt(n)
- 那么就是O(n*sqrt(n))啦
- minus all possible (i*i) and calculate the remain
- if the remain is new, add to queue (use a hashset to mark calculated item)
- find shortest path / lowest level number
- 一开始没clue.看了一下提示
- 1. 第一步想到了,从数学角度,可能是从最大的perfect square number开始算起。
- 2. 然后想法到了dp, 假设最后一步用了最大的maxSqrNum, 那么就在剩下的 dp[i - maxSqrNum^2] +1 不就好了?
- 3. 做了,发现有个问题...最后一步选不选maxSqrNum? 比如12就是个例子。
- 然后就根据提示,想到BFS。顺的。 把1~maxSqrNum 都试一试。找个最小的。
- 看我把12拆分的那个example. 那很形象的就是BFS了。
- 面试时候,如果拆分到这个阶段不确定,那跟面试官陶瓷一下,说不定也就提示BFS了。
6. Permutation Sequence.java Level: Medium Tags: [Backtracking, Math]
TODO: what about regular DFS/backtracking to compute the kth? dfs(rst, list, candiate list, k)
k是permutation的一个数位。而permutation是有规律的。
也就是说,可以根据k的大小来判断每一个数位的字符(从最大数位开始,因为默认factorio从最大数位开始变化)。
于是先求出n!, 然后 k/n!就可以推算出当下这一个数位的字符。然后分别把factorio 和 k减小。
另外, 用一个boolean[] visited来确保每个数字只出现一次。
这个方法比计算出每个permutation要efficient许多。
7. Orderly Queue.java Level: Hard Tags: [Math, String]
8. Encode and Decode TinyURL.java Level: Medium Tags: [Hash Table, Math]
其实想到了切入点, 是个可难可简单的题目. 这里的encode就是想办法把url存起来, 然后给个 key. 那么具体怎么做这个key, 简单就可以用一个map, 然后counting. 复杂一点就可以做random letter/number组成key.
9. Ugly Number.java Level: Medium Tags: [Math]
LeetCode: 判断数字是否是ugly number. (definition: factor only have 2, 3, 5)
- 看是否可以整除.
- 看整除最终结果是否== 1
LintCode: 找kth ugly number, 应该与 Ugly Number II是一样的
- 方法1: PriorityQueue排序。用ArrayList check 新的ugly Number是否出现过。
- 方法1-1:(解释不通,不可取)用PriorityQueue排序。神奇的3,5,7走位:按照题目答案的出发,定了3,5,7以什么规律出现。但是题目并没有特殊表明。
- 方法2: DP . Not Done yet.
10. Fraction to Recurring Decimal.java Level: Medium Tags: [Hash Table, Math]
TODO: no need of hashMap, just use set to store the existing
不难想到处理除法:考虑正负,考虑小数点前后。主要是小数点以后的要着重考虑。 很容易忽略的是integer的益处。
11. Friends Of Appropriate Ages.java Level: Medium Tags: [Array, Math]
- 这个问题更在于问题本身的分析 (而且还有多余条件); 最终的for loop 也比较不standard.
- People younger than 15 cannot make requests due to the first rule.
- From the age of 15, people can make requests to the same age: a[i] * (a[i] - 1) requests.
- People can make requests to younger people older than 0.5 * i + 7: a[j] * a[i] requests.
- The third rule is redundant as the condition is already covered by the second rule.
- TODO: the approach.
12. Power of Two.java Level: Easy Tags: [Bit Manipulation, Math]
跟powerOfThree一样: 可以loop, check mod; 也可以用binary search找合适的数字.
13. Number of Digit One.java Level: Hard Tags: [Math]
Pure math problem, not quite representative
Explanation https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython
14. Cracking the Safe.java Level: Hard Tags: [DFS, Greedy, Math]
- For 2 passwords, the shortest situation is both passwords overlap for n-1 chars.
- We can use a window to cut out last (n-1) substring and append with new candidate char from [k-1 ~ 0]
- Track the newly formed string; if new, add the new char to overall result
- Note: this operation will run for k^n times: for all spots of [0 ~ n - 1] each spot tries all k values [k-1 ~ 0]
- Same concept as dfs
- Same concept: use window to cut out tail, and append with new candidate
- do this for k^n = Math.pow(k, n) times
15. Ugly Number II.java Level: Medium Tags: [DP, Enumeration, Heap, Math, PriorityQueue]
- curr index is based on previous calculation: the min of all 3 previous factors
- O(n)
- 非常brutle的。
- 每次把dp[i-1]拿出来,不管三七二十一,分别乘以2,3,5. 出来的结果放进priority queue做比较。
- 最后时间是nlog(n3)
- 注意:use long, use HashSet确保没有重复
- O(nlogn)
16. Power of Three.java Level: Easy Tags: [Math]
方法1: Power of 3: 3 ^ x == n ? 意思是 n / 3 一直除, 最后是可以等于1的, 那么就有了 n/=3, check n%3, 最后看结果是不是整除到1的做法. 用while loop.
方法2: 如果n是power of 3, 那么 3 ^ x的这个 x一定是个比n小的数字. 那么可以在 0 ~ n 之间做binary serach, 但是就比较慢.
方法3: 巧妙的想法.最大的3^x integer是 3^19. 那么找到这个数, 一定可以被n整除. 一步到位.
17. Basic Calculator.java Level: Hard Tags: [Binary Tree, Expression Tree, Math, Minimum Binary Tree, Stack]
给一个expression String, 要evaluate这个expression的值.
Expression string 里面包括 +, -, 整数, 开合括号, 还有space.
- Expression Tree是一个 weight-based的 min-tree
- 基于 运算符号 + 数字的 tree: 数字永远在leaf, 然后符号是tree node, 括号不出现在tree里面
- 用 monotonuous stack 来构建这个tree
- Understand Expression Tree
- Use stack to build the expression tree + understand the weight system
- Use post-order traversal to evaluate the tree
- 注意, input里面的数字不会是single digit, 所以需要一个buffer存number string
- 整个题目的做法, 可以参照
Expression Evaluation
18. Strobogrammatic Number II.java Level: Medium Tags: [DFS, Enumeration, Math, Sequence DFS]
TODO:
- use list, iterative? keep candidates and populating
- clean up the dfs code, a bit messy
- edge case of "0001000" is invalid, right?
- A bit like BFS solution: find inner list, and then combine with outter left/right sides.
- find all solutions, DFS will be easier to write than iterative/BFS
- when n = 1, there can be list of candidates at bottom of the tree, so bottom->up is better
- bottom->up, dfs till leaf level, and return candidates.
- each level, pair with all the candidates
- 其实就是剥皮,一层一层,是一个central-depth-first的,钻到底时候,return n=1,或者n=2的case,然后开始backtracking。
- 难的case先不handle.到底之后来一次overall scan.
- every level have 5 choices of digital pairs to add on sides. Need to do for n-2 times.
- Time complexity: O(5^n)
19. Add Digits.java Level: Easy Tags: [Math]
方法1: 普通做法就是按照题意, double-while loop把数字加起来. 第一层循环是O(n), 然后第二层循环就少很多数位, overall O(n)
方法2: 找数学规律, 每过9个数字, 取mod就会开始重复, 所以给所有数字取mod 就可以间接找到答案. O(1)
20. Pow(x, n).java Level: Medium Tags: [Binary Search, Math]
傻做就O(n), 要更好就考虑O(logN). 减少重复计算, 一切两半.
注意:
- n/2的奇数偶数
- n的正负
- n == 0的情况, x == 0, x == 1 的情况.
21. Number Of Corner Rectangles.java Level: Medium Tags: [DP, Math]
具体看题目: count # of valid rectangles (four corner are 1) in a grid[][].
- Fix two rows and count matching columns
- Calculate number rectangles with
combination
concept: - total number of combinations of pick 2 points randomly: count * (count - 1) / 2
- TODO. HOW?
- O(m^2 * n^2), times out
22. Excel Sheet Column Number.java Level: Easy Tags: [Math]
- 26位的运算, 根据10位运算去思考
- 'A' - 'A' = 0. 所以 char - 'A' + 1 = 题目里的对应数位
- 或者: 26位运算和10位一样:num += 每位的digit * Math.pow(26, 数位号)
23. 360. Sort Transformed Array.java Level: Medium Tags: [Math, Two Pointers]
- Being able to analys the ax^2 + bx graph and find the
peak
orvalley
- Math basics: x^2 dominates the overall curve so it is up to a to determine:
valley
: if a < 0, both sides will be small and center will be large. Prioritize larger value.peak
: if a > 0, center will be small and both sides will be large. Prioritize smaller value.- starting index being 0 or n-1, is driven by
a
24. 415. Add Strings.java Level: Easy Tags: [Basic Implementation, Math, String]
- Use i, j to process from end of 2 strings
- handle edge case for i, j
- if i < 0, its num = 0 (since we are doing sum, blindly setting 0 is okay)
- Note:
sb.insert(0, x)
is much slower than doing a finalsb.reverse()
- when converting to int[], remember to reverse string.
- when converting to int[], remember to reserve extra space for carry
25. 7. Reverse Integer.java Level: Easy Tags: [Math]
每次加上x%10,然后x不断减小~0 注意处理MAX_VALUE, MIN_VALUE 符号不重要, 直接处理, 也会保留.
转换成String 然后 reverse Space O(n), time O(n)
26. 204. Count Primes.java Level: Easy Tags: [Hash Table, Math]
计数: 所有小于n的prime number.
-
=2的没有除自己和1以外公约数的数。
- 还有另外一个定义方法: 这个n,有没有小于n的一个i, 而达到: i * i + # of i = n. 如果有,那就不是 prime
- 一个boolean长条,存isPrime[]。 然后从i=2, 全部变true.
- hash key: the number itself
- 然后利用这个因子的性质,非prime满足条件: self*self, self * self + self ... etc.
- 所以就check每一个j, j+i, j+i+i, 然后把所有non-prime全部mark成false.
- 最后,数一遍还剩下的true个数就好了
27. 509. Fibonacci Number.java Level: Easy Tags: [DP, Math, Memoization]
- fib[n] = fibonacci(n - 1) + fibonacci(n - 2);
- 滚动数组, 简化DP
- recursively calculate fib(n - 1) + fib(n - 2). 公式没问题, 但是时间太长, timeout.
28. 67. Add Binary.java Level: Easy Tags: [Math, String, Two Pointers]
- 注意加法结果的位置.
- Use two pointers i, j to track the 2 strings
- Add when i and j are applicable. While (i >= 0 || j >= 0)
StringBuffer.insert(0, x);
- handle carry
- Reverse string -> Convert to Integer List, add up -> Convert back to string
- pointer 从前向后, 所以只需要 1个pointer.
- 操作复杂, 如上, 证明可以解决. 没必要reverse.
把binary换成数字作加法. 如果input很大,那么很可能int,long都hold不住。不保险。
29. 168. Excel Sheet Column Title.java Level: Easy Tags: [Math]
- 26位, 像10位一样去思考
- 从末尾开始, mod %26 可以拿到 末尾数字 remain = n % 26
- 特殊: remain = 0 的时候, 也就是说n是26的倍数, 末尾应该是 'Z'
- 记录'Z'了之后, n--
30. 9. Palindrome Number.java Level: Easy Tags: [Math]
- build reversed integer 直到midpoint, where x <= reverse
- 如果input双数: x == reverse
- 如果input单数 (而且x>reverse): x == reverse/10
- optionA: compare digit by digit
- optionB: reverse half of the string/int, and compare with other half.
31. 43. Multiply Strings.java Level: Medium Tags: [Math, String]
给两个integer String, 求乘积
- let num1 = multipier, num2 = base. mutiply and save into int[m + n].
- Loop over num1, each row num1[x] * num2, save to correct index (i + j + 1)
- Note: skip leading '0' during output, but do not delete string "0"
- time,space O(mn)
- Option1: Calculate carry on the fly
- index
curr = i + j + 1
, left indexleft = curr - 1
, since we start calculation from end of the array. - we only touch right side of the array once, so we can move the carry off from it, and carry to left index
- code is concise
- index
- Option2: save product first without calculating carry
- save product in each int index
- calculate carry on rst[] and
sb.insert(0, c)
(since we start from end of rst) - this is actaully faster than Option1, somehow.
-
- 数字‘123’, 在数组里面, index == 0 是 ‘1’。 但是我们平时习惯从最小位数开始乘积,就是末尾的'3'开始。
- 所以!翻转两个数字先!我去。这个是个大坑。
-
- 乘积product,和移动Carrier都很普通。
-
- !!最后不能忘了再翻转。
-
- 最后一个看坑。要是乘积是0,就返回‘0’。 但是这个其实可以在开头catch到没必要做到结尾catch。
- 用到几个StringBuffer的好东西: reverse(), sb.deleteCharAt(i)
- 找数字,或者26个字母,都可以: s.charAt(i) - '0'; s.charAt(i) - 'a';
32. 367. Valid Perfect Square.java Level: Easy Tags: [Binary Search, Math]
- binary search template: mid+1, mid-1,
start <= end
- define index as long.
33. 246. Strobogrammatic Number.java Level: Easy Tags: [Enumeration, Hash Table, Math, Two Pointers]
根据题意枚举, 再根据规则basic implementation
- compare left/right
- flip number (6 and 9), and then reverse the string, see if the string is the same.
- takes more
34. 319. Bulb Switcher.java Level: Medium Tags: [Brainteaser, Math]
- if just impl, it take O(n^2):
- repating: some pos are toggled mutiple times: if we know total times, easy to determin each pos.
- loop over [2, n], count times on each index
35. 12. Integer to Roman.java Level: Medium Tags: [Basic Implementation, Math, String]
- Parse each digit based on rules
-
- parse: analyze the situations
36. 202. Happy Number.java Level: Easy Tags: [Hash Table, Math]
Basic Implementation of the requirements.
用HashSet存查看过的数值。若重复,return false.
37. 69. Sqrt(x).java Level: Easy Tags: [Binary Search, Math]
- 理解题意, 从[0, x]找一个可以m*m=x的值.
- 注意, 如果找不到, 最后问考官该return一个什么值:按道理,因为return int, 会取整,那么return一个平方最close to x就可以.
- 注意 mid 用 long, 因为很可能超过最大int.
- 二分float number, 应该用精度来定义结尾.
- 还是二分, 但是判断条件变成: while ( end - start > eps)
- eps = 1e-12,也就是精度到1e-12
38. 2. Add Two Numbers.java Level: Medium Tags: [Linked List, Math]
LinkedList都已经反转好了,直接做. 跟Add Binary的理解方式一模一样.
- reverse order helps calculation
- add additional carry to end
- not same length: align on left
- traverse till both ends
- 遍历两个l1,l2把carry-on处理好,每次生成一个新node,最后检查carry-on.
39. 273. Integer to English Words.java Level: Hard Tags: [Enumeration, Math, String]
给一个小于 Integer.MAX_VALUE (2^31 - 1) 的数字, 转换成英语. (不需要加 'and')
- 基本implementation
分类讨论
: thounsand, million, billion.3个数字一格
.- 用array枚举 token
- 运用 % 和 / 来找到每个分段的英语翻译
- 3-digit 的部分, 可以用一个helper funtion来找到结果, 每段的处理方法都是一样的
- Note:
- StringBuffer 更有效率!
sb.insert(0, xxx)
append在sb前面 - 注意加 " " 的时候, 如果多余, 要
trim()
- 注意,
小于20的数字, 有自己的特殊写法, 需要额外handle
- 这道题目就是要细致
耐心
, 几乎么有什么算法, 就是想要写的efficient并且正确, 需要很小心
- StringBuffer 更有效率!
- Thinking process:
1 ~ 19
: [one, two ... nine, ten, eleven, ...., ninteen]20 ~ x0
: [twenty, thirty, fourty, ... ninety]x00
: hundred: 100- thousand: 10^3
- million: 10^6
- billion: 10^9
- trillian: 10^12 way over 2^31, not needed
- plan:
- parse 3 digits at a time
- convert the 3 digit to [xx hundred xx-ty x]
- come up with a string[]
- insert the thousands/million/billion to the string[]
40. 523. Continuous Subarray Sum.java Level: Medium Tags: [Coordinate DP, DP, Math, PreSum, Subarray]
给一个非负数的数列和数字k(可正负, 可为0). 找到连续子序列(长度超过2), 使得这个subarray的sum 是 k的倍数. 问: 是否可能?
- Check if mod result exist in earlier preSum
- Utilize
Pigeonhole principle
to optimize:-
- put positive integers into k slots
-
- when # of integers > 2*k, then there must be a range sum that is multipler of k
- more illustration here: https://leetcode.com/problems/continuous-subarray-sum/solution/
-
- Draw the presum and try to take mod of each presum and save to set, we realize
-
- assume a mod result = 7, and we mark it in the set
-
- some time later, after summing up more values, (7 + x + y ...+ z) % k == 7
- it means
(x + y ...+ z) % k == 0
- There is a
整除
exist; return true
-
- Meanwhile, if we want to record the list of indexes, we can use a Map rather than set.
- Note: if all we do to the presum is to % k, therefore
preSum % k
can representpresum
in some cases. - time: O(n)
- space: O(k), size restrited by mod result of
%k
- PreSum[]:
-
- cal preSum array
-
- preSum(i, j) = continuous range sum
-
- determine if
preSum(i, j) % k == 0
- determine if
-
- time: O(n^2)
- DP (坐标型. specifically, preSum[])
- 记录在0 ~ i点(包括nums[i], 以nums[i]结尾)的sum, 坐标型动态规划.
- dp[i] = dp[i - 1] + nums[i];
- 从sum = 每次[i ~ j]的所有情况
- time: O(n^2)
- space: O(1)
41. 65. Valid Number.java Level: Hard Tags: [Enumeration, Math, String]
分析edge case, 和各种情况, 然后判别是否是valid number
- 遇到
.
,e
,+/-
,int
的几种不同情况 - 分别遇到的顺序不同时候, 结果也不同.
- 这道题更多是分析情况, 然后把edge case enumerate出来, 算法的意义比较少.
42. 8. String to Integer (atoi).java Level: Medium Tags: [Math, String]
- Handling use cases
- Parse steps:
-
- trim space
- 1 parse operator
- 2 trim leading zero
-
- get number string
-
- Validation:
-
- max length over max integer length
-
- exceed min/max value
-
- Alternatively, regular expression, but not applicable in interview: if (!str.matches("[+-]?(?:\d+(?:\.\d*)?|\.\d+)")). 猛了一点
43. 149. Max Points on a Line.java Level: Hard Tags: [Array, Geometry, Hash Table, Math]
给list of (x,y) coordinates. Determine # of points on the same line
- If given n points, we can calculate all possible slopes. O(n^2) times
- For the two dots that generates the same slope, these dots could be on parallel slopes
- figure out how to prune the parallel dots
- GCD: greatest common divider
- Devide the x and y by their greatest common divider, such that x and y can be reduced to minimum value
- All other x and y can be reduced to such condition as well
- track the final reduced (x,y) in a map: they are the key to the count
- No need to use Map<Integer, Map<Integer, Integer>> to perform 2 level mapping; just
map<String, Integer>
, where the key is "x@y"
44. 13. Roman to Integer.java Level: Easy Tags: [Math, String]
- 熟悉罗马字母规则
-
- 'I V X L C D M' 分别代表的数字
-
- 列举combo的情况,需要从原sum里面减掉多加的部分: 'IV, IX'减2, 'XL, XC'减20, 'CD, CM'减200.
- Leading
I(1*2)
,X(10*2)
,C(100*2)
causes double counting
https://en.wikipedia.org/wiki/Roman_numerals
0. Longest Common Subsequence.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP]
给两个string, A, B. 找这两个string里面的LCS: 最长公共字符长度 (不需要是continuous substring)
- 设定dp长度为(n+1), 因为dp[i]要用来表示前i个(ith)时候的状态, 所以长度需要时i+1才可以在i位置, hold住i.
- 双序列: 两个sequence之间的关系, 都是从末尾字符看起, 分析2种情况:
-
- A最后字符不在common sequence 或者 B最后字符不在common sequence.
-
- A/B最后字符都在common sequence. 总体count + 1.
1. K Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, Trie]
给一串String, target string, int k. 找string array里面所有的candidate: 变化K次, 能变成target.
TODO
- Edit Distance的follow up.
- 其实就是改一下 minEditDistance的function, 带入K作比较罢了.
- 写起来跟Edit Distance 的主要逻辑是一模一样的.
- 但是LintCode 86% test case 时候timeout.
- Time O(mnh), where h = words.length, 如果 n ~ m, Time 就几乎是 O(n^2), 太慢.
2. Longest Common Substring.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP, String]
- 两个string, 找最值: longest common string length
- 序列型, 并且是双序列, 找两个序列 (两维的某种性质)
- dp[i][j]: 对于 A 的前i个字母, 对于 B 的前j个字母, 找最长公共substring的长度
- dp = new int[m + 1][n + 1]
- dp[i][j] = dp[i - 1][j - 1] + 1; only if A.charAt(i - 1) == B.charAt(j - 1)
- 注意track max, 最后return
- space O(n^2), time(n^2)
- 空间优化, [i] 只有和 [i - 1] 相关, 空间优化成 O(n)
- 找所有A的substring, 然后B.contains()
- track max substring length
- O(n^2) time
3. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
4. 10. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
IMPORTANT: '*' 需要有一个 prefix element [elm], so it becomes [elm]*
. There 2 possible cases:
- [elm] repeats 0 times: move p, j + 2
- [elm] repeats 1 or more times: need s[i] == p[i], then move s, i+1
- DFS on remaining of s and p. Analyze the different cases when next char == '*'
- End case: both i,j reached end true; or one of them reached end.
- The two different cases when given any index j on p, the p[j+1]=='*'
- TRUE:
- ignore p[j, j+1], continue from p[j+2]
- check if s[i]==p[j] or p[j]='.'; continue from s[i+1] and p
- FALSE: check i,j, and move forward with s[i+1], p[j+1]
- TRUE:
- If next p char != '*', check curr s[i] ?= p[i]
- Improvement with memo with 2D Booelan[][] memo: much faster
- memo[i][j] records result the remaining strings: s.substring(i) compare with p.substring(j)
- use
Boolean
: when memo[i][j] != null, return something!
- Two sequence, DP, find if possible to match.
- The '*' takes effect of preceding/prior element, so we can start matching from end.
- DP[i][j]: is it possible to match s[0 ~ i - 1] and p[0 ~ j - 1].
- Check last index of s and p, there can be a few possibilities:
-
- s[i-1]==p[j-1] and they are normal characters => && dp[i - 1][j - 1];
-
- p[j-1] == '.', match => dp[i - 1][j - 1]
-
- p[j-1] == '*':
- a. ignore a* => |= dp[i][j - 2];
- b. use a* => |= dp[i - 1][j];
-
- init: dp[0][j] and dp[i][0] will all be false since there cannot be any match.
5. 72. Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, String]
两个字符串, A要变成B, 可以 insert/delete/replace, 找最小变化operation count
- 考虑两个字符串的末尾index� s[i], t[j]: 如果需要让这两个字符一样, 可能使用题目给出的三种operation: insert/delete/replace?
- 先calculate最坏的情况, 3种operation count + 1; 然后在比较match的情况.
- 注意, 在i或者j为0的时候, 变成另外一个数字的steps只能是全变.
- 第一步, 空间时间都是O(MN), O(MN)
- 滚动数组优化, 空间O(N)
- insert: assume insert on s, �
#ofOperation = (s[0 ~ i] to t[0 ~ j-1]) + 1;
- delete: assume delete on t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j]) + 1;
- replace: replace both s and t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j - 1]) + 1;
- dp[i][j]�代表了两个 sequence 互相之间的性质: �s[0 ~ i] �转换成 s[0~j] 所需要的最少 operation count
- init: 当i==0, dp[0][j] = j; �每次都要 + j 个character; 同理, 当j==0, dp[i][0] = i;
- 而dp[i][j]有两种情况处理:
s[i] == t[j]
ors[i] != t[j]
- 这种判断取决于经验: 如果知道initialization可以再 double for loop 里面一起做, 那么可以留着那么做
- 这样属于
需要什么, initialize什么
- 事后在做space optimization的时候, 可以轻易在 1st dimension 上做rolling array
- 可以做, 但是不建议:这道题需要找 min count, 而不是search/find all solutions, 所以search会写的比较复杂, 牛刀杀鸡.
0. Binary Tree Level Order Traversal II.java Level: Medium Tags: [BFS, Tree]
如题, 但是output要倒序.
- 跟Binary Tree Level Order Traversal一样,只不过存result一直存在存在0位.
- 根据level来append每个list
- rst里面add(0,...)每次都add在list开头
1. Walls and Gates.java Level: Medium Tags: [BFS, DFS]
给一个room 2D grid. 里面有墙-1, 门0, 还有empty space INF(Math.MAX_VALUE).
对每个empty space而言, fill it with dist to nearest gate.
- Form empty room: it can reach different gate, but each shortest length will be determined by the 4 directions.
- Option1(NOT applicable). DFS on INF, mark visited, summerize results of 4 directions.
- hard to resue: we do not know the direction in cached result dist[i][j]
- Option2. DFS on gate, and each step taken to each direction will +1 on the spot: distance from one '0';
- Through dfs from all zeros, update each spot with shorter dist
- Worst time: O(mn), where entre rooms[][] are gates. It takes O(mn) to complete the iteration. Other gates be skipped by
if (rooms[x][y] <= dist) return;
- Exact same concept. Init with
Queue<int[]> queue = new LinkedList<int[]>()
2. The Maze.java Level: Medium Tags: [BFS, DFS]
- BFS on coordinates
- always attempt to move to end of border
- use boolean[][] visited to alingn with BFS solution in Maze II, III, where it uses Node[][] to store state on each item.
3. Perfect Squares.java Level: Medium Tags: [BFS, DP, Math, Partition DP]
给一个数字n, 找到这个数字 最少能用多少个 平方数组成.
平方数比如: 1, 4, 9, 16 ... etc
- 遇到最值, 想到DP.
- 看到分割字眼, 想到分割型 DP.
- 思考, 如果 j * j = 9, 那么 j = 3 就是最少的一步; 但是如果是10呢? 就会分割成1 + 9 = 1 + j * j
- 考虑最后的数字: 要是12割个1出来, 剩下11怎么考虑? 割个4出来,剩下8怎么考虑?
- partion的方式: 在考虑dp[i - x]的时候, x 不是1, 而是 x = j*j.
- 就变成了dp = Min{dp[i - j^2] + 1}
- 乍一看是O(n*sqrt(n)). 实际也是. 但如何推导?
- 考虑上限: 把小的数字变成大的 推导上限; 考虑下限: 把数字整合归小, 找到下限.
- 考虑sqrt(1) + sqrt(2) + ....sqrt(n):找这个的upper bound and lower bound.
- 最后发现它的两边是 Ansqrt(n) <= actual time complexity <= Bnsqrt(n)
- 那么就是O(n*sqrt(n))啦
- minus all possible (i*i) and calculate the remain
- if the remain is new, add to queue (use a hashset to mark calculated item)
- find shortest path / lowest level number
- 一开始没clue.看了一下提示
- 1. 第一步想到了,从数学角度,可能是从最大的perfect square number开始算起。
- 2. 然后想法到了dp, 假设最后一步用了最大的maxSqrNum, 那么就在剩下的 dp[i - maxSqrNum^2] +1 不就好了?
- 3. 做了,发现有个问题...最后一步选不选maxSqrNum? 比如12就是个例子。
- 然后就根据提示,想到BFS。顺的。 把1~maxSqrNum 都试一试。找个最小的。
- 看我把12拆分的那个example. 那很形象的就是BFS了。
- 面试时候,如果拆分到这个阶段不确定,那跟面试官陶瓷一下,说不定也就提示BFS了。
4. Redundant Connection.java Level: Medium Tags: [BFS, DFS, Graph, Tree, Union Find]
- keyword: tree has no
cycle
. - 一旦两个node在edge中出现, 并且parent相同, 说明这两个node不union, 也在同一个tree里面, 所以可以break them.
- Add graph using adjacent list, and verify cycle alone the way
- IMPORTANT: use
pre
node in dfs to prevent backward dfs - similar to
Graph Valid Tree
where it validates cycle and also needs to validate if all nodes are connected
- same concept as DFS, find first redundant edge that alreay exists in graph map.
5. Minimum Height Trees.java Level: Medium Tags: [BFS, Graph]
- Build graph
map<node, list of node>
- BFS to find the shortest path: when the neibhbor has the curr node as the only one neighbor, it is leaf.
- record shortest path in Map<Integer, List> as result
- TODO: code it up.
- removing leaf && edge
6. Subsets II.java Level: Medium Tags: [Array, BFS, Backtracking, DFS]
给一串integers(may have duplicates), 找到所有可能的subset. result里面不能有重复.
- DFS, 找准需要pass along的几个数据结构. 先
sort input
, 然后DFS - Using for loop approach: 每个dfs call是一种可能性,直接add into result.
- 为了除去duplicated result, skip used item at current level:
if (i > depth && nums[i] == nums[i - 1]) continue;
- sort O(nlogn), subset: O(2^n)
- space O(2^n), save results
- Regular BFS, 注意考虑如果让one level to generate next level
- skip duplicate:
if (i > endIndex && nums[i] == nums[i - 1]) continue;
-
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- srot O(nlogn), subset: O(2^n)
- should be same O(2^n). slower than dfs
- 在DFS种skip duplicate candidates, 基于sorted array的技巧:
- 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],
- 说明x=nums[i-1]已经在curr level 用过,不需要再用一次: [a,x1,x2],x1==x2
- i == index -> [a,x1]
- i == index + 1 -> [a,x2]. 我们要skip这一种
- 如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。
- 不能去用result.contains(), 这本身非常costly O(nlogn)
- 几遍是用 list.toString() 其实也是O(n) iteration, 其实也是增加了check的时间, 不建议
7. Word Ladder.java Level: Medium Tags: [BFS]
给一串string[], 需要找shortest distance to change from wordA -> wordB. (限制条件细节见原题)
- 通常, 给一个graph(这道题可以把beginWord看成一个graph的起始node), 找shortest path用BFS
- 在start string基础上,string的每个字母都遍历所有26个字母
- visited 过的 从wordList里去掉
- time: word length m, there can be n candidates => O(mn)
- 但是总是exceed time limit on LeetCode. However, it passes LintCode:
- 原因是 LeetCode给的是list, list.contains(), list.remove() 都是 O(logn) time!!!
- convert to set first.
- timeout, overkill
8. Invert Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
- 简单处理swap
- recursively swap children
- BFS with Queue
- 每次process一个node, swap children; 然后把child加进queue里面
- 直到queue process完
9. Number of Connected Components in an Undirected Graph.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
count这个graph里面有多少个独立的component.
- 跟Graph Valid Tree 几乎一模一样
- 建造简单的parent[] union find
- 每个edge都union.
- 注意 union 的时候, 只需要union if rootA != rootB
- build graph as adjacent list: Map<Integer, List>
- dfs for all nodes of the graph, and mark visited node
- count every dfs trip and that will be the total unions
10. Find the Connected Component in the Undirected Graph.java Level: Medium Tags: [BFS, DFS]
给一个undirected graph, return 所有的component. (这道题找不到了)
- BFS遍历,把每个node的neighbor都加进来.
- 一定注意要把visit过的node Mark一下。因为curr node也会是别人的neighbor,会无限循环。
- Component的定义:所有Component内的node必须被串联起来via path (反正这里是undirected, 只要链接上就好)
- 这道题:其实component在input里面都已经给好了,所有能一口气visit到的,全部加进queue里面,他们就是一个component里面的了。
- 而我们这里不需要判断他们是不是Component
- DFS 应该也可以 visit all nodes, mark visited.
11. Surrounded Regions.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2D board, 里面是 'X' 和 'O'. 把所有被X包围的area都涂成'X'.
从四个边的edge出发, 像感染僵尸病毒一样扩散, 把靠边的node全部mark, 然后将还是'O'的改成X, 最后回复marks -> 'O'
- UnionFind里面这次用到了一个rank的概念, 需要review. rank[] 也就是在tracking每一个node所在union的size.
- 目的是: always并到大的union里面
- note: 将2D coordinate (x,y) 转换成1D: index = x * n + y
- Reversed thinking: find surrounded nodes, how about filter out border nodes && their connections?
- Need to traverse all the border nodes, consider dfs, visit all.
- loop over border: find any 'O', and dfs to find all connected nodes, mark them as 'M'
- time: O(mn) loop over all nodes to replace remaining 'O' with 'X'
- More like a graph problem: traverse all 'O' spots, and mark as visited int[][] with area count [1 -> some number]
- Run dfs as top->bottom: mark area count and dsf into next level
- End condition: if any 'O' reaches border, mark the global map<count, false>
- keep dfs untill all connected nodes are visited.
- At the end, O(mn) loop over the matrix and mark 'X' for all the true area from map.
- Practice: write code to verify
- TODO
12. Shortest Distance from All Buildings.java Level: Hard Tags: [BFS]
给Walls and Gates很像, 不同的是, 这道题要选一个 coordinate, having shortest sum distance to all buildings (marked as 1).
- BFS 可以 mark shortest distance from bulding -> any possible spot.
- Try each building (marked as 1) -> BFS cover all 0.
- time: O(n^2) * # of building; use new visited[][] to mark visited for each building.
- O(n^2) find smallest point/aggregation value.
- 注意, 这道题我们update grid[][] sum up with shortest path value from building.
- 最后找个min value 就好了, 甚至不用return coordinate.
- 分析过, 还没有写.
13. The Maze II.java Level: Medium Tags: [BFS, DFS, PriorityQueue]
- if already found a good/shorter route, skip
if (distMap[node.x][node.y] <= node.dist) continue;
- This always terminates the possibility to go return to original route, because the dist will be double/higher
14. Trapping Rain Water II.java Level: Hard Tags: [BFS, Heap, MinHeap, PriorityQueue]
给一个2Dmap, 每个position 有 height. 找Trapping water sum.
- 用PriorityQueue把选中的height排序,为走位, create class Cell (x,y, height).
-
- 从matrix四周开始考虑,发现matrix能Hold住的水,取决于height低的block
-
- 必须从外围开始考虑,因为水是被包裹在里面,外面至少需要现有一层
- 以上两点就促使我们用min-heap: 也就是natural order的PriorityQueue.
-
- process的时候,画个图也可以搞清楚: 就是四个方向都走走,用curr cell的高度减去周围cell的高度.
-
- 若大于零,那么周围的cell就有积水: 因为cell已经是外围最低, 所以内部更低的, 一定有积水.
-
- 每个visited的cell都要mark, avoid revisit
-
- 根据4个方向的走位
(mX, mY)
创建新cell 加进queue里面: cell(mX, mY) 已经计算过积水后, 外围墙小时, �(mX, mY)
就会变成墙.
- 根据4个方向的走位
-
- 因为做的是缩小一圈的新围墙, height = Math.max(cell.h, neighbor.h);
- 和trapping water I 想法一样。刚刚从外围,只是能加到跟外围cell高度一致的水平面。往里面,很可能cell高度变化。
- 这里要附上curr cell 和 move-to cell的最大高度。
- 要找到bucket的最短板
- 每次需要最先处理最短的那条 (on top)
- 木桶理论, 包水, 是从外面包住里面
- 洋葱剥皮, 用完丢掉
15. Sliding Puzzle.java Level: Hard Tags: [BFS, Graph]
16. Complete Binary Tree.java Level: Easy Tags: [BFS, Tree]
A complete binary tree is a binary tree in which every level, except possibly the last,
is completely filled, and all nodes are as far left as possible
- 当出现了第一次有 null children的node的时候, 说明到了leaf level, mark flag = true;
- 自此以后,queue再不该有node再有child; queue后面出现的node的left/right child应该都是null
- 否则就是有问题, return false;
17. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start
18. The Maze III.java Level: Hard Tags: [BFS, DFS, PriorityQueue]
- 跟 Maze I, II 类似, 用一个 Node[][] 来存每一个(x,y)的state.
- Different from traditional BFS(shortest path):
it terminates BFS when good solution exists (distance), but will finish all possible routes
-
Termination condition
: if we already have a good/better solution on nodeMap[x][y], no need to add a new one
-
- Always cache the node if passed the test in step1
-
- Always offer the moved position as a new node to queue (as long as it fits condition)
-
- Finally the item at nodeMap[target.x][target.y] will have the best solution.
19. Bus Routes.java Level: Hard Tags: [BFS]
20. [tool]. Topological Sorting.java Level: Medium Tags: [BFS, DFS, Lint, Topological Sort]
- indegree tracking: Track all neighbors/childrens. 把所有的children都存在 inDegree<label, indegree count>里面
- Process with a queue: 先把所有的root加一遍(indegree == 0),可能多个root。并且全部加到queue里面。
- BFS with Queue:
- Only when map.get(label) == 0, add into queue && rst. (indegree剪完了, 就是root啦)
- inDegree在这里就 count down indegree, 确保在后面出现的node, 一定最后process.
- 几个graph的condition:
-
- 可能有多个root
-
- directed node, 可以direct backwards.
TODO:
- build
Map<DirectedGraphNode, Integer> inDegree = new HashMap<>();
and include the root itself - that is more traditional indegree building
21. 102. Binary Tree Level Order Traversal.java Level: Medium Tags: [BFS, DFS, Tree]
如题.
- 最普通,Non-recursive: BFS, queue, 用个queue.size()来end for loop:换行。
- 或者用两个queue. 当常规queue empty,把backup queue贴上去
- 每个level都应该有个ArrayList. 那么用一个int level来查看:是否每一层都有了相应的ArrayList。
- 如果没有,就加上一层。
- 之后每次都通过DFS在相应的level上面加数字。
22. 269. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
Build graph
:- 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先: form sequence between chars
- form graph Map<Char, List of Chars>, for topological sort usage.
- 也可以
List[26] edges
(Course Schedule problem)
Build InDegreeCountMap<Char, Count>
: based on the char diff of 2 words- 注意: indegree 是反向的 (跟 node to neighbors 相反的方式建立)
Topological Sort
, BFS:-
- use queue to find
inDegree == 0
node. It is the letter that points to others, 排在字母表前面.
- use queue to find
-
- reduce edges using Graph
map<Character, List<Character>>
(more generic than List[26], 26个字母的dictionary)
- reduce edges using Graph
-
- Edge Case:
inDegreeCountMap.size() != result.length()
: some nodes did not make it into result sequencecycle
: when inDegree of a one node would never reduce to 0, and will not be added to result- In this case, it will be treated as invalid input, and return ""
- space: O(n), n = # of graph edges
- time: O(n)
- TODO
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
23. 301. Remove Invalid Parentheses.java Level: Hard Tags: [BFS, DFS, DP]
给一个string, 里面有括号和其他字符. 以最少刀 剪出 valid string, 求所有这样的string.
这个题目有多种解法, 最强就是O(n) space and time
- Goal: identify invalid parentheses and remove (minimum removals)
- Step:
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
if(count<0)
- When invalid occurs:
- chance for correction. Remove the incorrect parentheses, one at a time
- dfs on the rest of the s that has not been tested yet: start index from index i
- Core edge cases:
- Do not correct twice of the same parenthesis by checking [j-1] pos
- Make sure to attempt correction of all possible parenthesis within tested range: because it outputs all results at the same level
- return/finish once correction done
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
- Success case:
- a string s passed test: make sure it passes REVERSED string test!
- Core Concept:
if a parenthese string is valid, the reverse of it should also be valid
- Test s with open='(', close=')' first; reverse s, and test it with open=')', close='('
- Minor details
- only procceed to remove invalid parenthese when
count<0
, and also break && return dfs after the recursive calls. - The above 2 facts eliminates all the redundant results.
- Reverse string before alternating open and close parentheses, so when returning final result, it will return the correct order.
- only procceed to remove invalid parenthese when
- How does it guarantee minimum removals?
- When seeing a chance to correct, it jumps into a for loop of DFS. It
return
after the for loop. This stops additional testing - When invalid occurs, correct it right away: minimum correction
- When seeing a chance to correct, it jumps into a for loop of DFS. It
- Complexity:
- O(nk), k being the # of recursive calls. It takes n calls to finish a full string case.
- Similar to DFS, we wnat to test: 1) test input s valid, 2) remove 1 invalid parenthesis at a time, 3) process substring
- instead of testing all substrings (timeout), we want to establish rules to improve reprocess:
- Test1: skip regular char. No need to test it.
- Test2: if redundant paren, do 1 is enough. skip adjacent ones.
- Test3: if last removed extra paren is '(', the next ')' must be a valid pair. LastRemoved char: pecial handling by using a struct:
class Node {String s, int index, char lastRemoved}
- How to end tests? When there is data in rst, stop adding to queue.
24. 111. Minimum Depth of Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
- Shortest path; minimum depth: 想到BFS, check level by level, BFS更能确保更快找到结果
- depth definition: reach to a leaf node, where node.left == null && node.right == null
- BFS using queue, track level.
- Divide and Conquer to find min depth.
- if one of child is null, return the other child depth + 1
- Pick the min of the two child depth + 1
- need to visit all nodes
25. 207. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- Concept of Indegree:
# of incoming node that depends on me
. It is a graph ofarrows pointing inward to me
structure.
- Remember: indegree是周围的node到我这里的次数count.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- Two structures:
-
- build inDegreeEdges:
List[] inDegreeEdges
: list of incoming nodes that depends onnode i
,
- build inDegreeEdges:
-
- build dependencyCount:
int[] dependencyCount
, count # of braches that curr node depends on
- build dependencyCount:
-
- any dependencyCount[node]==0, means this node is now a leaf, add to queue
- Topological Sort Process, Kahn algorithem:
- topologically process:
- add leaf node to queue, get ready to process;
- process leafNode, like cutting of leaf
- if any child node dependencyCount == 0, it is a leaf node now: add this node to queue.
- this problem aims for deteching cycle, not output final list. Simply: visit all nodes and verify cycle
- Option1: array of indegree lists, List[]
- 用 visited int[] 来确认是否有cycle. 1 means
visited
, -1 meansvisted from last dfs level
- Deteching
-1
: 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false. - dfs on curr node indegree dependencies; if all passes w/o failing, set visited[i] = 1
- Deteching
- Similarly, can use
HashMap<Integer, List<Integer>> map
to replace List[], but exact same idea.
- 用 visited int[] 来确认是否有cycle. 1 means
- Optoin2: use a struct
class Node {Boolean visiting; Map<Integer, Node> inDegreeMap}
to be more generic - topo sort may output the sort order: 1) at DFS bottom level, put record to a
stack
, 2) rst.insert(0, curr record)
- 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>. Though: map may be more flexible
- 是topological sort的题目。一般都是给有dependency的东西排序。
- 最终都会到一个sink/leaf node,no further dependency, 在那个点截止
- 画个图的话, prerequisite都是指向那个sink/leaf node
- when building the inDegreeMap/inDegreeEdge: we use sink/leaf node as key/index, which pionts back to inDegree/parent nodes
- BFS: when all braches/dependency count are reduced to 0, then it is now a leaf node, ready to be used.
- DFS Insert Order: rst.insert(0, node); Assume we want leaf/node at index 0 in final output:
- the very bottom-node depends on everybody
- any visited node should be added to 0 index of the list, so it will be at tail later
26. 987. Vertical Order Traversal of a Binary Tree.java Level: Medium Tags: [BFS, Binary Tree, DFS, Hash Table, Tree]
space: O(n)
Very similar to 314. Binary Tree Vertical Order Traversal
with 1 special condition: if 2 nodes at same (offset, level):
sort it by its value
- the special requirement causes: we have to track exact position of nodes
- Using
Node {int offset, level, val}
andMap<offset, Map<level, List<Val>>>
:- set all nodes to its correct position
- output all together
- the
max/min
offset allows us to loop over the map in a ordered manner (save efforts of sorting) - time: O(n) to mark all nodes at correct spot, but
O(nlogn)
to sort the vertical array - space: O(n), mark all nodes in the nested map
- A (offset, level) has 2 nodes: use nested
Map<offset, Map<level, List<Val>>>
to track nodes - Also need a
class Node{int offset; TreeNode node}
to build queue:- need
offset
: queue at each level cannot derive level index - need
TreeNode
:Node
extends originalTreeNode
so we can queue it.
- need
- lots code to write due to the
class Node
for BFS
27. 429. N-ary Tree Level Order Traversal.java Level: Medium Tags: [BFS, Tree]
- use queue to hold each level. O(n)
28. 199. Binary Tree Right Side View.java Level: Medium Tags: [BFS, DFS, Tree]
给一个binary tree, 从右边看过来, return all visible nodes
- 最右: 即level traversal每一行的最末尾.
- BFS, queue 来存每一行的内容, save end node into list
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n nodes in final results
- Use Map<Level, Integer> to override the result at each level
- dfs:
- dfs(node.left) and then dfs(node.right) because we want to log right side last
- record global max depth for iteration purpose
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n stacks (and n nodes in final results)
29. 1203. Sort Items by Groups Respecting Dependencies.java Level: Hard Tags: [BFS, DFS, Graph, Topological Sort]
- Realize we need to: 1) topo sort group, 2) topo sort items in the group.
- Luckily, the candidates to be sorted are all integers: groupIds, or item ids. We can have 1 generic topo sort function
- Overall workflow
-
- group items to map <GroupId, List>
-
- build group graph
-
- topo sort group -> return sorted group id list
-
- for each group: build item graph, topo sort items -> return sorted item list
-
- flatten and return results
-
30. 515. Find Largest Value in Each Tree Row.java Level: Medium Tags: [BFS, DFS, Tree]
- faster than BFS, using less space if not couting final rst: stack size, O(logn)
- time: O(n), visit all
- loop over queue level and record max
31. 1161. Maximum Level Sum of a Binary Tree.java Level: Medium Tags: [BFS, DFS, Graph]
- simply calc each level sum with BFS
- top-level is processed first, since we go from top level -> deeper level
- only update result if sum is truly > global MAX.
32. 1091. Shortest Path in Binary Matrix.java Level: Medium Tags: [BFS]
- find shortest path using queue
- time/space: O(n^2), n = grid length
- why SKIP
boolean visited[i][j]
? after a position grid[i][j] is used:-
- the curr path will not return to (i, j)
-
- other route that may eventually reach (i, j) need not to be recorded,
- because the other route is already longer than the curr path
- therefore, we just simply block the visited node by
grid[x][y] = 1
- note: block it right after it is added to the queue, so other nodes at same level will not attempt this visited node.
-
33. 339. Nested List Weight Sum.java Level: Easy Tags: [BFS, DFS, NestedInteger]
给一串integers, list里面可能有nest list. 算总的sum. 规则, 如果是nested list, 每深一个depth, sum要乘以depth.
- New interface to understand: object contains integer or object
- Visit all && sum, consider dfs.
- 简单的处理nested structure, dfs增加depth.
- time: visit all nodes eventually, O(n), space O(n)
- bfs, queue, 处理queue.size() for a level
- use a level variable to track levels
- slower since it uses extra space, worst case O(n) of all items
34. 1197. Minimum Knight Moves.java Level: Medium Tags: [BFS]
from starting point, find min steps to reach certain point
: think of BFS- similar: shortest path, shortest distance
- bfs: minimum steps, enumerate the possible moves
- move closer to x or y (test 8 possible directions)
- add possible moves in queue
- use visited to cache visited coordinates
- time: O(8^n), # of BFS branches
- space: O(8^n), # of BFS branche nodes
35. 1306. Jump Game III.java Level: Medium Tags: [BFS, Graph]
- Find possibility to reach certain point, we can BFS: faster to find shortest candidate
- use queue to hold left, right candidates
- use set to record visited
- attemp all nodes, use set to record visited.
- time: O(n)
- space: O(n)
36. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
37. 46. Permutations.java Level: Medium Tags: [BFS, Backtracking, DFS, Permutation]
- Best of all recursive approaches
- iterate over nums: pick or not pick
- reduce remaining item in next level:
- option1: use queue, restrict queue size; backtrack append to queue
- option2: remove element before passing into next level; backtrack: insert back
- time O(n!): visit all possible outcome
- T(n) = n * T(n-1) + O(1)
Method1-Option2: Recursive Backtracking, with list.contains()
to avoid reuse of index
- A bit worse than option1, uses more time:
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
contains
is nlogn time - also, each dfs, it has to iterate over entire nums list
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
Method1-Option3: Recursive Backtracking, with visited[]
to avoid reuse of index
- Use visited[] to track, still causes for(over n items), not efficient
- Best of all iterative approaches
- Each time pick 1 NEW element and find places to insert into candidate list:
-
- 一个一个element加进去
-
- 每一次把rst里面的每个list拿出来, 创建成新list, 然后选位置加上new element
-
- 加新元素的时候, 要在list的每个位置insert, 最终也要在原始的list末尾加上new element
-
- 还是O(n!), 因为rst insert O(n!)个permutations
- Better than the Option2/Option3 (
BFS+Queue
), because this solution does not need to check duplicates
- 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍
- Time O(n!)
- Slow: checking candidate.contains() is O(logn) each time
- Almost same as Method2-Option2, but it builds remainingCandidate list on the fly list.removeall(xyz): O(n)
- Even slower than Method2-Option2
38. 200. Number of Islands.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2Dmatrix, 里面是1和0, 找#of island.
- visit all nodes connected with the starting node
- double for loop, test all starting nodes
- val == 1: 1) count++; 2)DFS from this (i,j);
- Mark visited (x,y) = '0'
- time: O(n), visit all nodes
- space: O(n), stack
- 可以用union-find, 就像Number of island II 一样.
- 只不过这个不Return list, 而只是# of islands
- Union Find is independent from the problem: it models the union status of integers.
- Return the total # of unions (which is # of islands)
- in reality: it is a bit slow.
- time: visit all nodes just once, O(n). Union Find will visit all nodes once and union them
- space: O(n), union find takes O(n) space
- 记住UnionFind的模板和几个变化(Connecting Graph I, II, III), 最后归总的代码写起来就比较简单.
- use queue to hold 1 island, keep adding 4-direction islands; mark visited with '0'
- check entire board for any remaining one.
39. 144. Binary Tree Preorder Traversal.java Level: Medium Tags: [BFS, DFS, Stack, Tree]
- 加root, left, then right. Obvious
- Option1: recursive on preorderTraversal. the dfs function returns List
- Option2: pass in rst, and write a void dfs.
- 先加root, 然后push上需要末尾process的在stack垫底(root.right), 然后push root.left
- Stack: push curr, push right, push left.
40. 100. Same Tree.java Level: Easy Tags: [BFS, DFS, Tree]
给两个 binary tree, 看两个tree是否identical.
- DFS. 确定leaf条件, && with all dfs(sub1, sub2).
- 这里无论如何都要走过所有的node, 所以dfs更加合适, 好写.
- 两个queue存每个tree的所有current level node. Check equality, check queue size.
- Populate next level by nodes at current level.
41. 78. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n)
, 3ms - space: O(2^n) results
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- BFS, 注意考虑如果让one level to generate next level
-
- maintain a list of Indexe to store candidate indexes.
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
-
- should be same O(2^n), but actual run time 7ms, slower
- O(n) space
42. 210. Course Schedule II.java Level: Medium Tags: [BFS, DFS, Graph, Topological Sort]
207. Course Schedule
has more notes- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目求这个最后排好的课的list
- 如果排不好, 就给个空就好
- input是 numOfCourses, 还有这个prerequisites [[]]
- 用
List[] edges; edges[i] = new ArrayList<>();
来表示graph: 就是每个node, to all its neighbors - 每个没有 inDegree==0 node, 都是可以加进 final list里面的. 比如一开始找到的那些 inDegree = 0的 node
- 注意, 如果 prerequisites = [], 那么就是说这些课都independent, 开个int[0 ~ n-1]的数组并赋值就好.
- 如果有cycle, 严格意义上就做不了topological sort, 也无法涵盖所有nodes, 那么return [ ]
- 根据 Course Schedule 里面的DFS 修改
- 维持visited int[]全局变量
- 维持sortedList int[] 全局变量, 注意加进去的时候是 add(0, node) 加在开头这样
- 每次到一个node的children全部DFS走完之后, 就可以把他加进final list里面
- 如果有cycle, 也就是dfs return false的时候, 这个题目判定排课失败, return new int[] { }
43. 314. Binary Tree Vertical Order Traversal.java Level: Medium Tags: [BFS, DFS, Hash Table, Tree]
给一个Binary Tree, traverse所有node, 按照vertial order 排列成output: List
重点是: col里面有排序, lower level的排在前面; 如果node遇到collision在同一个位置: 根据他们的相对位置 先放left, 再放right
-
- level-traverse all nodes, 2) add node to appropriate col list(using map)
- For final output:
- Use min/max to track map keys, since the keys are continous
- Map does not provide random access; unless map key is marked with sequence i = [min, max]
- Since each vertical list is appended level by level: no need to sort during output. FAST
- time: O(n), visit all nodes
- spac: O(n) to store
- Regular DFS to traverse all nodes, and add node to appropriate col list (using map)
- Problem: DFS does not provide natural ordering for nodes on a row.
- Left side may have a super deep Right child branch, which will be added to col list first (since left side is visisted first)
- It is wrong because right branch may have nodes in same column but at higher level
- To Solve: preserve
level
for all nodes in same column
- Need to sort the final list, and slow: visit all nodes O(n) + O(KMlogM), K = # of cols, M = # of items in col
- Time: O(nLogN). O(n) + O(KMlogM), K = # of cols, M = # of items in col; in extrem, it can be a vertical line of nodes, then sort: O(nLogN)
- Space: O(n)
44. 103. Binary Tree Zigzag Level Order Traversal.java Level: Medium Tags: [BFS, Stack, Tree]
- 简单的level traversal.根据level奇数偶数而add到不同位子.
- Option1: based on level % 2, insert to front/end of list
- Option2: based on level, insert right/left of node into queue
45. 261. Graph Valid Tree.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
检查这些edge是否能合成一个 valid tree
- 复习Union-Find的另外一个种形式, track union size: tree does not have cycle, so eventually union size should == 1
-
- 查找2个元素是不是在一个union里面。如果不在,false. 如果在,那就合并成一个set, 共享parent.
-
- 验证cycle:
find(x) == find(y) => cycle
- ideally, this edges[i] should be the very first time x and y node connect;
- however, if they have been grouped together under same ancestor before, there exist a feedback loop (cycle) between them.
- 验证cycle:
-
father[x]
: element x (index x) stores its root ancestor- 注意: 结尾要检查, 是否只剩下1个union: Tree必须连接到所有给出的node.
- 另一个union-find, 用hashmap的:
- http://www.lintcode.com/en/problem/find-the-weak-connected-component-in-the-directed-graph/
- Deep Dive into UnionFind: https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf
- Very similar to
Redundant Connection
- Create adjacency list graph: Map<Integer, List>
- 检查:
-
- 是否有cycle using dfs, check boolean[] visited
-
- 是否所有的node全部链接起来: validate if all edge connected: # of visited node should match graph size
- IMPORTANT: use
pre
node to avoid linking backward/infinite loop such as (1)->(2), and (2)->(1)
- traverse through adjacency list graph:
Map<Integer, List<Integer>>
-
- validate cycle with set: if revisit same node
- avoid infinite loop: remove backward mapping from child node to parent node
-
- validate check set size for connected
46. 133. Clone Graph.java Level: Medium Tags: [BFS, DFS, Graph]
给一个graph node, 每个node有list of neighbors. 复制整个graph, return new head node.
实现起来就好像在crawl urls.
- Use HashMap to mark cloned nodes:
map<oldNode, newNode>
-
- make new curr node;
-
- clone all neibhors and add them
-
- Use the map to avoid visited nodes
- time: O(n). visit all nodes
- space: O(n). Technically only travels n levels/stacks to circle all nodes (undirected & connected)
- Given graph node obj
{val, list of neighbor}
: copy the node and all neighbors - Mark visited using map<oldNode, newNode>
- for loop on the each one of the neighbors: map copy, record in map, and further dfs
- once dfs completes, add newNeighbor as neighbor of the new node (get to it via map)
- 主要思想是: 一旦复制过了, 不必要重新复制
- Copy the root node, then copy all the neighbors.
- Mark copied node in map.
- Use queue to contain the newly added neighbors. Need to work on them in the future.
47. 743. Network Delay Time.java Level: Medium Tags: [BFS, DFS, Graph, Heap, PQ]
quesiton: sorting by travel delay/time will find better answer earlier?
Dijkstras algorithm
is based on repeatedly making the candidate move that has the least distance travelled.- PQ: pick close node to vist, and add siblings back to PQ
- avoid visited
- time: O(nLogn), visit n nodes, each time insert to heap takes O(logn) time
- space: O(n)
-
- build graph map, 2) traverse map, 3) prioritize short delay nodes first
- use a map
<node, timeElapsed>
globally track dealy to nodes; compare all at the end
48. 1123. Lowest Common Ancestor of Deepest Leaves.java Level: Medium Tags: [BFS, DFS, Tree]
- key concetpL the
common ancester of deppest leaves
must have itstwo branch being same depth
. problem sovled. - dfs on both branch
- if returned depth equals & equal to max depth, record common ancestor
- time: O(n) traversal 1 pass
- space: O(n) dfs worst case depth
-
- find leaf nodes, and store backward map to root (DFS/ BFS both work)
-
- use leaf nodes to find way backwards till common node is found; return
- time: O(n) but two passes
- space: O(n) dsf + map storage
- this approach is more brutle and uses exrtra spaces
49. 399. Evaluate Division.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
- build map of
x#y -> val
to store values[i] and 1/values[i] - build map of
x -> list children
- dfs to traverse the graph
- BFS should also work: build graph and valueMap
- for each starting item, add all next candidate to queue
- mark visited, loop until end item is found
50. 785. Is Graph Bipartite.java Level: Medium Tags: [BFS, DFS, Garph]
bipartite
require each node to be in exact 1 party, which means it only has 1 state- DFS to mark node with one state; and mark its edges as reversed state
- If any node state has been assigned by different from desired one, return false.
- Use
Boolean states[i]
to represent visted & state - Try all nodes with for loop, and skip visited nodes (similar validation rules as in dfs)
- In
int next : graph[curr]
, test next level first before adding.
51. 101. Symmetric Tree.java Level: Easy Tags: [BFS, DFS, Tree]
检查tree是否symmetric
注意Symmetric Binary Tree的例子和定义: 是镜面一样的对称. 并不是说左右两个sub-tree相等。
- Recursively check symmetrically相对应的Node.
- 每个node的children都和镜面另外一边相对的node的children刚好成镜面反射位置。
- put left or right children in pair
- stack1: 左手边sub-tree先加left, 再加right child;
- stack2: 右手边sub-tree先加right child, 再加left child。
- process时,若symmetric,所有stack里面出来的node会一一对应。
52. 671. Second Minimum Node In a Binary Tree.java Level: Easy Tags: [BFS, Tree]
- min tree: parent node is the min of left/right child
- BFS to traverse the tree and find 1st non-root smallest val
- Improvement area: when
node.val >= nextMin
, no need to dive into node children since it is a min Tree.
- Find left and right val:
- if left/right equals root.val, that means the left or right sub children could have larger number
- Therefore DFS into left or right
- compare and return min(left, right)
53. 254. Factor Combinations.java Level: Medium Tags: [BFS, Backtracking, DFS]
- build candidate into dfs: treat each list candidate as success, add to rst
- remove last item from the candidate, try to add factor to it, and supply it with remain element
- backtrack after dfs
- Check if the number can be devided by [2, sqrt(n)], return a list of possible factors. Only check till
Math.sqrt(n)
- build suffixes: use the factor to devide last element of list and replace last element
- build candidate: replace last element of the queue item with new list of suffixes; add to rst
- add success item back to queue: in case last element can be simplified
- remove dupilcates: since we start factor from [2, sqrt(n)], the final factor list should be ascending!!
- time: O(x), x is the # of results
- space: O(y), y is all ongoing candidates in queue
0. Interval Minimum Number.java Level: Medium Tags: [Binary Search, Divide and Conquer, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的最小值.
- SegtmentTree, methods: Build, Query. 这题是在SegmentTreeNode里面存min.
- 类似的有存:max, sum, min
1. Count of Smaller Number.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字, array size = n. 给一串query: 每个query是一个数, 目的找 count# items smaller than query element.
- 和平时的segment tree问题不同。 [0 ~ n] 代表实际数字: based on real value的segment tree.
- Modify时,把array里面的value带进去,找到特定的位子, 然后count + 1.
- 最终在SegmentTree leaf上面全是array里面实际的数字。
- node.count: 在node range里面的有多少个数字
- build() 只是 empty segment tree, 没有property
- modify() 需要: 1. 找到left, update count+=1; 2. aggregate all parent when after returning
- 所以每一个modify 都是在整个path上所有的node上 + count
- 在query前,给进去的start和end是: 0 ~ value-1.
value-1
: 找比自己所在range小1的range(那么自然而然地就不包括自己了),这样就找到了smaller number.
- [那么其他做过的SegmentTree是怎么样呢?]
- 那些构成好的SegmentTree(找min,max,sum)也有一个Array。但是构成Tree时候,随Array的index而构架。
- 也就是说,假如有Array[x,y,....]:在leaf,会有[0,0] with value = x. [1,1] with value = y.
- [但是这题]
- 构成时,是用actual value.也就是比如Array[x,y,....]会产生leaf:[x,x]with value = ..; [y,y]with value =...
- 其实很容易看穿:
- 若给出一个固定的array构成 SegmentTree,那估计很简单:按照index从0~array.lengh,leaf上就是[0,0] with value = x.
- 若题目让构造一个空心SegmentTree,
based on value 0 ~ n-1 (n <= 10000)
, 然后把一个Array的value modify 进去。 - 这样八成是另外一种咯。
2. Segment Tree Modify.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给一个segmentTree, node里面存max. 写一个modify function: modify(node, index, value).
- Recursively 在segment tree里面找index, update it with value.
- 每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下
- 最后轮回到头顶,头顶一下包括头顶,就全部都是max了
3. Segment Tree Query II.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
- 和 Segment Tree Query I 以及其他Segment Tree类似: 这个SegmentTreeNode return count of elements in range
- 这个题目考了validate input source:input 的start,end可能超出root[start,end]。
- 那么第一步就要先clear一下: 1. 完全不在range就return 0. 2. 有range重合就规整到root的range.
4. Interval Sum.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的sum.
- 其实是segment tree 每个node上面加个sum
- 记得Segment Tree methods: Build, Query
- Note: 存在SegmentTreeNode里面的是sum. 其他题目可能是min,max,count ... or something else.
5. Interval Sum II.java Level: Hard Tags: [Binary Search, Lint, Segment Tree]
SegmentTree大集合. Methods: build, query, modify
. 不难。只是要都记得不犯错.
- build: recursively build children based on index-mid and link to curr node
- query: recursively try to find
node.start == targetStart && node.end == targetEnd
. Compare with node.mid - modify: recursively try to find
node.start == targetStart && node.end == targetEnd
; modify and recursively assign upper interval with updated interval property.
6. [lint]. Segment Tree Build II.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个array, 建造segment tree structure,
每个treeNode 里面存这个range里的 max value, return root node.
-
给的是Array. 注意找区间内的max, assign给区间. 其余和普通的segment tree build一样
-
注意, segment tree是根据array index range 排位: 根据index in [0, array.length - 1]割开区间, break到底
-
最终start==end做结尾
-
这道题要trackmax, 那么在leaf node assign max=A[start] or A[end]
-
往上,parent一层的max:就是比较左右孩子,其实都是在两个sub-tree里面比较sub-tree的max。
-
Devide and Conquer
-
先分,找到left/right,比较max,在create current node,再append到当前node上面。
-
实际上是depth-first, 自底向上建立起的。
7. [lint]. Segment Tree Query.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给了segment Tree, node里面有Max value, 找[start,end]里面的max
- 根据[start,end]跟 mid of (root.start, root.end) 做比较:
-
- [start,end] on LEFT of mid
-
- [start, end] on RIGHT of mid
-
- [start, end] includes mid: break into 2 queries
- query [leftNode, start, node.left.end]
- query [rightNode, node.right.start, end]
-
8. [lint]. Segment Tree Build.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个区间[startIndex, endIndex], 建造segment tree structure, return root node.
- Usage
- which of these intervals contain a given point
- which of these points are in a given interval
- Recursively build the binary tree
- 左孩子:(A.left, (A.left+A.rigth)/2)
- 右孩子:((A.left+A.rigth)/2+1, A.right)
9. 327. Count of Range Sum.java Level: Hard Tags: [BIT, Divide and Conquer, Merge Sort, PreSum, Segment Tree]
TODO: Write the code + merge function
- https://leetcode.com/problems/count-of-range-sum/discuss/77990/Share-my-solution
-
- build preSum[n+1]: then sum range [i,j]= preSum[j+1] - preSum[i]
-
- Divide and Conquer:
- 先考虑[start, mid] range里的 ran sum result
- 再考虑[mid, end] range里面的结果
- 最后考虑[low, high]总体的结果
- NOTE: should write merge() function, but that is minor, just use
Arrays.sort(nums, start, end)
, OJ passed - Every mergeSort() has a for loop => O(n log n)
- 如何 count range?
- 这里比较特别的一个做法: 找一个 [low, mid]里面的i, mid 之后的preSum作比较 (解释源自: https://blog.csdn.net/qq508618087/article/details/51435944)
- 即在右边数组找到两个边界, 设为
m, n
, - 其中m是在右边数组中第一个使得
sum[m] - sum[i] >= lower
的位置, - n是第一个使得
sum[n] - sum[i] > upper
的位置, - 这样
n-m
就是与左边元素i所构成的位于[lower, upper]
范围的区间个数.
- 边界[lower, higher] 在 sorted array 好作比较, 一旦过界, 就可以停止计算, 减少不必要计算.
- 上面这个n,m的做法可行的前提: preSum[]里面前后两个 range[low, mid], [mid, high]已经sorted了
- 也就是说, 在recursively mergeSort()的时候, 真的需要merge sorted 2 partitions
- 也许会问: 能不能sort呢, sort不久打乱了顺序? 对,打乱的是preSum[]的顺序.
- 但是不要紧: 很巧妙的, 分治的时候, 前半段/后半段 都在原顺序保留的情况下 分开process完了, 最后才merge
- 在做m,n 的range的时候, 原理如下, 比如preSum被分成这么两段:
[A,B,C]
,[D,E,F]
- 每一个preSum value
A
在跟 preSum[i] 作比较的时候A - preSum < lower
, 都是单一作比较, 不牵扯到 B, C - 因此,
[A, B, C]
是否保留一开始 preSum的顺序在此时不重要
- 每一个preSum value
- 此时最重要的是,
[A,B,C]
以及排序好, 那么在于lower
boundary 作比较的时候, 一旦过界, 就可以停止计算(减少不必要的计算)
- TODO?
- This segment tree approach(https://leetcode.com/problems/count-of-range-sum/discuss/77987/Java-SegmentTree-Solution-36ms)
- does not build segment tree based on given nums index
- it is built on sorted preSum array.
- regular segment tree based on nums array does not work:
- segment tree based on input array is good for: search/query by index
- is NOT good at: given range sum/value, find indexes
- why? segment tree is built based on index division, not by range value division.
10. 308. Range Sum Query 2D - Mutable.java Level: Hard Tags: [Binary Indexed Tree, Segment Tree]
- Same concept as turning an array into a binary segment tree,
- HOWEVER, this is a 4-nary segmenet tree
- Reference. 307 Range Sum Query
- Range Query concept:
- Using the input range, sum up everything in the range
- sometimes the input range cover multiple segments, then dive into the segments (still use original range)
- once we found a bounded segment (completely surrounded by input range), return segment value.
- Handling end stage, there are two approaches:
- ApproachA: check at beginning of recursive call (i.e in
build()
,updateNode()
,rangeQuery()
).- pro: calling recursive function blindly; code is easy.
- con: be really clear about termination state, and catch it.
- ApproachB: check & come up with correct query condition before recursive call
- pro: input to recursive function is assumed to be correct
- con: sometimes really hard to write the conditions before recursive call; code is hard.
- ApproachA: check at beginning of recursive call (i.e in
11. 850. Rectangle Area II.java Level: Hard Tags: [Segment Tree, Sweep Line]
- Inspired by: https://leetcode.com/problems/rectangle-area-ii/discuss/137941/Java-TreeMap-solution-inspired-by-Skyline-and-Meeting-Room
- First consider regular sweep line and realize problem: each vertical line has multiple block segments
- Easy: take a list of vertical dots, and calculate the height diff
- We can use a TreeMap with y-coordinate as key, so to
natural sort by y-coordinate
- Trick: can NOT remove used y coordinate from map, because the rectangle may continue to expand to right side.
- apply simple equation to calc area:
(long)preY * (p.x - preX)
- time:
- sort initial queue: O(nlogn)
- process queue: O(n)
- TreeMap insertion: O(logn)
- TreeMap traversal: O(n)
- overall, process queue can be O(n^2)
- space: O(n)
- TODO lol
12. 493. Reverse Pairs.java Level: Medium Tags: [BST, Binary Indexed Tree, Divide and Conquer, Merge Sort, Segment Tree]
给一串数字, count total reverse pair nums[i] > 2*nums[j]
, i < j
This problem can be solved with Merge sort concept, BST, Segment Tree and Binary Indexed Tree. Good for learning/review.
- Using merge sort concept (NOT merge sort impl).
- One very simply desire: if we want to know # elements between [i, j] such that
nums[i] > 2*nums[j]
, it would be so great if array is sorted!- If sorted, fix index i, keep j++ for all
nums[i]/2.0 > nums[j]
- We CANNOT just sort entire array. WHY? Because it distrupts the value of curr index i, and the restriction is:
find matching elements on right side of curr index i
- BUT, what about just sort
right side of i
, and make sure the subproblem (i+1, end) is solved first?
- If sorted, fix index i, keep j++ for all
- 灵感: use merge sort concept.divide and conquer [i ~ n] into 2 sections:
-
- solve subProblem(start,mid) & subProblem(mid+1, end). sort the sub array so that it can be used recursively at parent level.
-
- solve the curr pblem: for all [i, mid], check against [mid+1, end].
-
- Question1: does it cover all use cases?
- First, subProblem(start,mid) & subProblem(mid+1, end) recursively solves its own range
- Last, the only range is the current level problem check
[i, mid]
against its entire right side range:[mid+1, end]
. DONE. all covered.
- Question2: what it is okay for
subProblem(start,mid) & subProblem(mid+1, end)
partially sort the array?- that is the goal: 1) we want the right side range to be sorted; 2) left range is sorted but it does not matter since we treat [start, mid] as 1 group
- use classic while loop
while(j<=e && nums[i]/2.0 > nums[j])
to count pairs
- TODO
- split the array into index-based segment tree, where each element is at leaf
- store min of range: use min to determine if certain range is needed for further query
- query for each element right side range (i + 1, end), where it recursively query&aggregate sub-range if meeting requirement
nums[i] > 2*nums[j]
- only when target > subRange.min * 2: there are possible candidates, query further
- worst case O(n^2) when all tailing elements are meeting requirement.
- TODO
- Build the BST based on node value. It will be not applicable if we search after entire tree is built (our goal is right range), so we need to build right elements, and search/count right after the elements is added
- Worst case is still O(n^2), if all added nodes are meeting requirement
- search(tree, curr / 2.0)
- check each one of them
13. 315. Count of Smaller Numbers After Self.java Level: Hard Tags: [BST, Binary Indexed Tree, Binary Search, Divide and Conquer, Segment Tree]
给一串数字nums[], 求一个新数组result, where result[i] = # of smaller items on right of nums[i]
- What if
the processed list is sorted
, so that I can BinarySeach for curr target?- process from end
- binary search for
index to insert new element
in sorted ascending list - that index = # of smaller numbers; record it for final result
- time: O(nlogn)
- space: O(n)
- Segment Tree functions:
Build
: construct segment tree based on min/max range: at leaf node, update count of numbers in rangemodify(SegmentTreeNode root, int value, int count)
: find leaft at with value, and update count for leaf & all parent nodesquery(SegmentTreeNode root, int start, int end)
: return count # of numbers in range [start, end]
- Very similar to
Count of Smaller Number
, where segment tree is built on actual value!! - IMPORTANT to drop processed number from left-hand-side:
- only find on remaining numbers.
- Utilize
modify(root, target, -1)
to erase element count & update the tree.
- time:
n * log(m)
, where m = Math.abs(max-min). log(m) is used to modify() the leaf element - space: O(m)
Define the positive range
- negative nubmer division
rounds up towards 0
(this is a problem). (i.e.(-2 - 1) / 2 = -1.5 = -1
), which causes range error. - We want the entire segment tree range to be ascending, and we want the mid = (start+end)/2 to round down.
- Solution:
- build entire segment tree based on [min, max], where min must be >= 0.
- we can do this by adding Math.abs(min) onto both min/max, as well as +offset during accessing nums[i]
- negative nubmer division
- https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
- Assume we have a BST, where each node has smallerCount and a val, for any new target, how to find smaller items?
-
- add the # of smaller count to current node
-
- compare:
- if target < node.val, keep searching
countVisit(node.left, target)
- if target > node.val: 1) add currNode.smallerCount, 2) minus node.right.smallertCount (reduce double-counting), 3) plus
countVisit(node.right, target)
- remember to create left/right node before dfs countVisit into the sides.
-
14. 307. Range Sum Query - Mutable.java Level: Medium Tags: [Binary Indexed Tree, Segment Tree]
- sample problem for segment tree
- build(), update(), rangeQuery()
- build and update are standard
- rangeQuery: handle the range split check
- Null leaf node handling: NO, ideally it will not encounter null leaf.
- in update/rangeQuery: when final state (
start==end
) is reached, the recursive call ends - there is no way for any node to dive futher into null child.
- in update/rangeQuery: when final state (
- Range Query concept:
- Using the input range, sum up everything in the range
- sometimes the input range cover multiple segments, then dive into the segments (still use original range)
- once we found a bounded segment (completely surrounded by input range), return segment value.
15. 715. Range Module.java Level: Hard Tags: [Segment Tree, TreeSet]
- start with considering array structure but operation are all O(n)
- what if we can easily find range, and update
- TreeSet:
- build a class
Interval {int start, end;}
- build a customized
compareTo
that sorts the interval by start at default, but sort by end if a.start==b.start - Query: TreeSet allow us to find element in O(logn)
- Add Range: finding the starting pointing takes O(logn), but update can be worst to update O(n)
- Remove Range: finding the starting pointing takes O(logn), but update can be worst to update O(n)
- build a class
16. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
Binary Indexed Tree?
0. 340. Longest Substring with At Most K Distinct Characters.java Level: Hard Tags: [Hash Table, LinkedHashMap, Sliding Window, String, Two Pointers]
- Method1 and Method2 are identical to
159. Longest Substring with At Most Two Distinct Characters
. - However, time complexity for Method2 in increases to O(nk). https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- we want to do better than that (Method3)
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == k, process and record max len
-
- if map.size() > k, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(k)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(nk) to find the left-most element
- space: O(k)
- https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- as mentioned above, Method2 uses O(nk), because it takes O(k) to find head item that was inserted first
- meanwhile, we still need the hash map feature to get/put/remove last occurance of a char with O(1)
- Solution: use a LinkedHashMap:
map.entrySet().iterator()
maintains the insertion order!
- Special handling:
- since we want the
lastOccurMap
to preserve laset insertion order - we need to
remove
the char every time before put.
- since we want the
- time: O(n)
- space: O(k)
0. House Robber III.java Level: Medium Tags: [DFS, DP, Status DP, Tree]
Houses被arrange成了binary tree, 规则还是一样, 连续相连的房子不能同时抄.
求Binary Tree neighbor max 能抄多少.
- 判断当下的node是否被采用,用一个boolean来表示.
- 如果curr node被采用,那么下面的child一定不能被采用.
- 如果curr node不被采用,那么下面的children有可能被采用,但也可能略过,所以这里用Math.max() 比较一下两种可能有的dfs结果。
- dfs重复计算:每个root都有4种dive in的可能性, 假设level高度是h, 那么时间O(4^(h)), where h = logN, 也就是O(n^2)
- 并不是单纯的DP, 是在发现DFS很费劲后, 想能不能代替一些重复计算?
- 基本思想是dfs解法一致: 取root找最大值, 或者不取root找最大值
- 在root上DFS, 不在dfs进入前分叉; 每一个level按照状态来存相应的值: dp[0] root not picked, dp[1] root picked.
- Optimization: DP里面, 一口气找leftDP[]会dfs到最底层, 然后自下向上做计算
- 这个过程里面, 因为没有在外面给dfs()分叉, 计算就不会重叠, 再也不用回去visit most-left-leaf了, 算过一遍就完事.
- 然而, 普通没有dp的dfs, 在算完visited的情况下的dfs, 还要重新dfs一遍!visited的情况.
- Space O(h), time O(n), 或者说是O(2^h), where h = log(n)
- 不为状态而分叉dfs
- 把不同状态model成dp
- 每一个dfs都return一个based on status的 dp array.
- 等于一次性dfs计算到底, 然后back track, 计算顶部的每一层.
- DP 并不一定要是以n为base的. 也可以是局部的去memorize状态->value.
1. Binary Tree Maximum Path Sum II.java Level: Medium Tags: [DFS, Tree]
找到从max path sum from root. 条件: 至少有一个node.
- 比Binary Tree Maximum Path Sum I 简单许多. 因为条件给的更多:at least 1 node + have to start from root
- root一定用到
- 3种情况: curr node, curr+left, curr+right
- 因为一定包括root, 说以从
dfs(root, sum=0)
开始, 每个level先加root, sum += root.val
2. Convert Expression to Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Polish Notation (PN).
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Pre-order-traversal 就能记录下 Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- Note: label需要是String. 虽然 Operator是长度为1的char, 但是数字可为多位
3. Restore IP Addresses.java Level: Medium Tags: [Backtracking, DFS, String]
给一串数字, 检查是否是valid IP, 如果合理, 给出所有valid 的IP组合方式.
- 递归的终点:list.zie() == 3, 解决最后一段
- 递归在一个index上面, pass s.toCharArray()
- validate string要注意leading '0'
- 注意: 递归的时候可以用一个start/level/index来跑路
- 但是尽量不要去改变Input source, 会变得非常confusing.
- note: code有点messy, 因为要考虑IP的valid情况
- 那个'remainValid', 其实是一个对于remain substring的判断优化, 不成立的就不dfs了
4. Triangles.java Level: Medium Tags: [Array, Coordinate DP, DFS, DP, Memoization]
给一个list<list> triangle, 细节原题. 找 min path sum from root.
- 其实跟给一个2D matrix没有什么区别, 可以做dfs, memoization.
- initialize memo: pathSum[i][j] = MAX_VALUE; 计算过的path省略
- Bottom-top: 先dfs到最深的path, 然后逐步网上返回
OR 原理: min(pathA, pathB) + currNode
- 浪费一点空间, pathSum[n][n]. space: O(n^2), where n = triangle height
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- 跟dfs的原理很像,
OR 原理: min(pathA, pathB) + currNode
- init dp[n-1][j] = node values
- build from bottom -> top: dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
- 跟传统的coordinate dp有所不同, inner for loop 是需要计算 j <= i, 原因是triangle的性质.
- 空间: dp[n][n]. space: O(n^2)
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- Based on the DP solution: the calculation always depend on
next row
for col atj
andj + 1
- 既然只depend on next row, 可以用rolling array来处理: reduce to O(n) space.
- Further: 可以降维, 把第一维彻底去掉, 变成 dp[n]
- 同样是double for loop, 但是只在乎column changes:
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
5. Fast Power.java Level: Medium Tags: [DFS, Divide and Conquer]
如题: Calculate the a^n % b where a, b and n are all 32bit integers.
- a^n可以被拆解成(aaa*a....*a), 是乘机形式,而%是可以把每一项都mod一下的。所以就拆开来take mod.
- 这里用个二分的方法,recursively二分下去,直到n/2为0或者1,然后分别对待.
- 注意1: 二分后要conquer,乘积可能大于Integer.MAX_VALUE, 所以用个long.
- 注意2: 要处理n%2==1的情况,二分时候自动省掉了一份 a,要乘一下。
6. Binary Tree Longest Consecutive Sequence II.java Level: Medium Tags: [DFS, Divide and Conquer, Double Recursive, Tree]
找到binary tree 里的最长 consecutive sequence. Sequence可以递增递减, Sequence顺序可以回溯parent.
- Similar to Binary Tree Longest Consecutive Sequence I
- 只不过可以递增递减, 还有连接上parent的方向.
- 对于任何一个节点, 都可能:
-
- 自己跟两个child链接, 成为一个sequence
-
- 左边孩子, 右边孩子各自是一个consecutive sequence, 但是不跟root相连
- main function 一开始就divide成这三份, 然后dfs
- dfs take diff == 1, diff == -1, 来做递增递减的校对.
- dfs rules:
-
- if node == null, leaf depth = 0
-
- if not consecutive, reset the depth = 0 (same for both left child, and right child)
-
- compare the leftDepth && rightDepth to find the maximum
-
- diff is the same in the same dfs loop to maintain consistant increase/decrease
- dfs的结果很可能是0, 如果没有任何结果, 那么上一层的caller depth = dfs() + 1 = 1
- 那么回归到root, dfs的结果很可能就是1.
- 可能会问: 那么在tree里面的partial sequence (不连接到root)的被忽略了?
- 这里
longestConsecutive(root.left)
就很重要了 - 这一步特地忽略掉了root, 然后走下去一层: 因为是recursive, 所以还会继续divde && conquer
- 最后, 任何一层的孩子都会被照顾到.
- 用两种recursive的方式handle skip root node的情况
- Recursive using dfs(), basically build child + parent
- Recursive using main function, but with value of child node: skipping root
7. Flip Game II.java Level: Medium Tags: [Backtracking, DFS, DP]
String 只包含 + , - 两个符号. 两个人轮流把consecutive连续的++
, 翻转成 --
.
如果其中一个人再无法翻转了, 另一个人就赢. 求: 给出string, 先手是否能赢.
- curr player 每走一步, 就生成一种新的局面, dfs on this
- 等到dfs结束, 不论成功与否, 都要backtracking
- curr level: 把"++" 改成 "--"; backtrack的时候, 改回 '--'
- 换成boolean[] 比 string/stringBuilder要快很多, 因为不需要重新生成string.
- ++ 可以走 (n - 1)个位置:
- T(N) = (N - 2) * T(N - 2) = (N - 4) * (N - 2) * T(N - 4) ... = O(N!)
- 做一个String s的 replica: string or stringBuilder
- 每次dfs后, 然后更替里面的字符 "+" => "-"
- 目的只是Mark已经用过的index
- 真正的dfs 还是在 original input string s 身上展开
- 每次都重新生成substring, 并不是很efficient
- 保证p1能胜利,就必须保持所有p2的move都不能赢
- 或者说, 在知道棋的所有情况时, 只要p2有一种路子会输, p1就一定能走对路能赢.
- 同时,p1只要在可走的Move里面,有一个move可以赢就足够了。
- p1: player1, p2: player2
- 需要Game Theory的功底, Nim game. https://www.jiuzhang.com/qa/941/
- http://www.1point3acres.com/bbs/thread-137953-1-1.html
- TODO: https://leetcode.com/problems/flip-game-ii/discuss/73954/Theory-matters-from-Backtracking(128ms)-to-DP-(0ms)
8. Subtree of Another Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
- Traverse tree: left, right
- Concept of partial compare vs. whole compare
9. Walls and Gates.java Level: Medium Tags: [BFS, DFS]
给一个room 2D grid. 里面有墙-1, 门0, 还有empty space INF(Math.MAX_VALUE).
对每个empty space而言, fill it with dist to nearest gate.
- Form empty room: it can reach different gate, but each shortest length will be determined by the 4 directions.
- Option1(NOT applicable). DFS on INF, mark visited, summerize results of 4 directions.
- hard to resue: we do not know the direction in cached result dist[i][j]
- Option2. DFS on gate, and each step taken to each direction will +1 on the spot: distance from one '0';
- Through dfs from all zeros, update each spot with shorter dist
- Worst time: O(mn), where entre rooms[][] are gates. It takes O(mn) to complete the iteration. Other gates be skipped by
if (rooms[x][y] <= dist) return;
- Exact same concept. Init with
Queue<int[]> queue = new LinkedList<int[]>()
10. Decode String.java Level: Medium Tags: [DFS, Divide and Conquer, Stack]
给一个expression string. 里面包括数字, 字母, 括号. 其中数字代表括号里面的内容重复几次.
括号里面可以是String, 也可能是expression.
目的: 把expression展开成一个正常的String.
- Process inner item first: last come, first serve, use stack.
- Record number globally and only use it when '[' is met.
- Stack存 [ ] 里面的内容, detect 括号开头结尾: 结尾时process inner string
- 有很多需要注意的细节才能做对:
- Stack 也可以用, 每个地方要注意 cast. 存进去的需要是Object: String, Integer
- 几个 type check: instanceof String, Character.isDigit(x), Integer.valueOf(int num)
- 出结果时候:
sb.insert(0, stack.pop())
- Bottom->up: find deepest inner string first and expand from inside of
[ ]
- 与Stack时需要考虑的一些function类似. 特别之处: 检查
[ ]
的结尾 - 因为DFS时候, 括号里的substring会被保留着进入下一个level, 所以我们在base level要keep track of substring.
- 用int paren 来track 括号的开合, 当paren再次==0的时候 找到closure ']'
- 其他时候, 都要继续 append to substring
- BFS on coordinates
- always attempt to move to end of border
- use boolean[][] visited to alingn with BFS solution in Maze II, III, where it uses Node[][] to store state on each item.
- 看到log(m+n), 就想到binary search, 或者是recursive 每次砍一半
- 两个sorted array 参差不齐, 肯定不能做简单的binary search
- 这里有个数学排除思想: 考虑A, B各自的中间点.
- 如果A[mid] < B[mid], 那么 A[0 ~ mid - 1] 就不在 median的range里面, 可以排除. divide/conquer就这么来的.
- 具体逻辑看代码, 大致意思就是: 每次都取比较A 和 B [x + k / 2 - 1] 的位置, 然后做range 排除法
- end cases:
-
- 如果我们发现dfs()里面A或者B的start index溢出了, 那么就是最简单的case: midian一定在另外那个array里面
-
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
Math.max(A[startA], B[startB])
就可以
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
- 总共的数字长度是 (m + n) 而且每次都有一般的内容被删除, 那么time就是 O(log(m + n))
- 找到开头的字母, 然后recursively DFS 去把word串到底:
- 每到一个字母, 朝四个方向走, 之中一个true就可以.
- Note:每次到一个字母,mark一下'#'. 4个path recurse回来后,mark it back.
- 用一个boolean visited[][]
- Use hash map, key = x@y
- track
boolean[][] visited
, validate before dfs - 写个Inorder和Postorder的例子。利用他们分left/right subtree的规律解题。
- Postorder array 的末尾, 就是当下层的root.
- 在Inorder array 里面找到这个root,就刚好把左右两边分割成left/right tree。
- 这题比较tricky地用了一个helper做recursive。 特别要注意处理index的变化, precisely考虑开头结尾
- runtime: O(n), visit && build all nodes
findMid(arr)
can be replaced with a map<value, index>, no need execute O(n) search at runtime- 计算 expression 的值: 1. 建造 expression tree. 2. DFS计算结果
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- build好Min Tree以后,做PostTraversal.
- Divde and Conquer: 先recursively找到 left和right的大小, 然后evaluate中间的符号
- Time, Space O(n), n = # expression nodes
-
- Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
-
- 若有个child是null,那就return另外一个node。
-
- prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.
- 跟 identical binary tree的写法很像
- 只有 current s.val = t.val 的时候才需要compare same tree.
- 其他情况, 继续recursively isSubtree
- 注意:即使找到T1 == T2, 但很可能只是数字相同(这里不是binary search tree!!), 而children不同
- 所以同时要继续recursively isSubtree(T1.left, T2) ...etc.
- keyword: tree has no
cycle
. - 一旦两个node在edge中出现, 并且parent相同, 说明这两个node不union, 也在同一个tree里面, 所以可以break them.
- Add graph using adjacent list, and verify cycle alone the way
- IMPORTANT: use
pre
node in dfs to prevent backward dfs - similar to
Graph Valid Tree
where it validates cycle and also needs to validate if all nodes are connected - same concept as DFS, find first redundant edge that alreay exists in graph map.
- for loop, recursive (dfs).
- 每个item用一次, 下一个level dfs(index + 1)
- Combination DFS. 画个图想想. 每次从1~n里面pick一个数字i
- 因为下一层不能重新回去 [0~i]选,所以下一层recursive要从i+1开始选。
-
虽然Easy, 但用到DFS最基本的想法.
-
- dive deep
-
- mark VISITED
-
- sum it up
-
Time: worst O(mn), traverse all possible nodes
-
更要注意, 要从符合条件value==1的地方开始dfs.
-
对于什么island都没有的情况,area应该位0, 而不是Integer.MIN_VALUE, 问清楚考官那小伙, 别写顺手。
- 相比之前的implementation, 有一些地方可以优化:
-
- Backtracking时候, 在board[][] 上面mark就可以, 不需要开一个visited[][]
-
- 不需要implement trie的所有方程, 用不到: 这里只需要insert.
- 普通的trie题目会让你search a word, 但是这里是用一个board, 看board的每一个字母能不能走出个Word.
- 也就是: 这里的search是自己手动写, 不是传统的trie search() funcombination
-
- TrieNode里面存在 end的时候存string word, 表示到底. 用完了 word = null, 刚好截断重复查找的问题.
- Build Trie with target words: insert, search, startWith. Sometimes, just:
buildTree(words)
and return root. - 依然要对board matrix做DFS。
- no for loop on words. 直接对board DFS:
- 每一层,都会有个up-to-this-point的string. 在Trie里面check它是不是存在。以此判断。
- 若不存在,就不必继续DFS下去了。
- Trie solution time complexity, much better:
- build Trie: n * wordMaxLength
- search: boardWidth * boardHeight * (4^wordMaxLength + wordMaxLength[Trie Search])
- for loop on words: inside, do board DFS based on each word.
- Time cpmplexity: word[].length * boardWidth * boardHeight * (4^wordMaxLength)
- Big improvement: use boolean visited on TrieNode!
- 不要用rst.contains(...), 因为这个是O(n) 在leetcode还是会timeout(lintcode竟然可以pass)!
- 在Trie search() method 里面,凡是visit过的,mark一下。
- Binary Search Tree特点: 左边的node都比右边的node小.
- height balance, subtree height 相差<1, 必须左右sub tree均分. 做DFS(num, start, end)
- 在每一个level, 找到中间点, 然后分割2半, 继续dfs
- Divide and Conquer
- time/space: O(n), visit all nodes, no redundant visits.
- 题目要求DFS. 想清楚了如何在DFS level把几种情况都考虑了, 写起来很简单. NOT BFS, because requires O(1) space
- 对于一个root来说, 只有几个点可以顾忌到: root.left, root.right, root.next.
- 想办法把这三个方向的点, 能连起来的都连起来:
-
node.left.next = node.right
-
- If
node.next != null
, linknode.right.next = node.next.left
;
- If
- 然后在dfs(root.left), dfs(root.right)
- Time: visit && connect all nodes, O(n)
- 不和题意,用了queue space,与Input成正比。太大。
- BFS over Tree。 用Queue 和 queue.size(),老规矩。
- process每层queue时, 注意把next pointer加上去就好.
- 我们不可能一口气准确定位(x,y), 但是我们可以再一个row/col里面, 找到1D array的 peak.
- 根据这个点, 再往剩下两个方向移动
-
- 在中间的一行i=midX, 找到peak所在的y.
-
- 在中间的一列j=midY, 找到peak所在的x. (有可能强势override之前找到的y, 也就是放弃那一行的peak, 在midY上找peak)
-
- 根据 (x,y) 的4个neighbor check (x,y)是不是 peak, 如果不是, 像更高的位置移动一格
-
- 根据之前算的 midX, midY 把board分成4个象限, 在每一份里面再继续找
- 这个题目LintCode不给做了, 所以思路对的, 但是解答还没有再次验证.
- 每次只是找到一个row/col里面的peak而已!
- 找到这个点, 就等于把board切成了两半.
- 然后, 再跟剩下的相邻的两个位置比较, 就知道了哪里更大, 就去哪里找peak, 也就是又切了第二刀.
- 切第二刀的时候, 也要把(x, y) 移到需要取的象限. 进行DFS
- 根据mid row 切割:
- http://www.jiuzhang.com/solution/find-peak-element-ii/#tag-highlight-lang-java
- http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf
- 每一个level都减一半
- T(n) = n + T(n/2) = n + n/2 + n/4 + ... + 1 = n(1 + 1/2 + .... + 1/n) = 2n = O(n)
- TODO
- O(nLogN)
- 在DFS的基础上, 比对左左,左右,右左,右右
- DFS, 找准需要pass along的几个数据结构. 先
sort input
, 然后DFS - Using for loop approach: 每个dfs call是一种可能性,直接add into result.
- 为了除去duplicated result, skip used item at current level:
if (i > depth && nums[i] == nums[i - 1]) continue;
- sort O(nlogn), subset: O(2^n)
- space O(2^n), save results
- Regular BFS, 注意考虑如果让one level to generate next level
- skip duplicate:
if (i > endIndex && nums[i] == nums[i - 1]) continue;
-
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- srot O(nlogn), subset: O(2^n)
- should be same O(2^n). slower than dfs
- 在DFS种skip duplicate candidates, 基于sorted array的技巧:
- 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],
- 说明x=nums[i-1]已经在curr level 用过,不需要再用一次: [a,x1,x2],x1==x2
- i == index -> [a,x1]
- i == index + 1 -> [a,x2]. 我们要skip这一种
- 如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。
- 不能去用result.contains(), 这本身非常costly O(nlogn)
- 几遍是用 list.toString() 其实也是O(n) iteration, 其实也是增加了check的时间, 不建议
- Recursively 在segment tree里面找index, update it with value.
- 每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下
- 最后轮回到头顶,头顶一下包括头顶,就全部都是max了
- 很容想到Inorder-binary-search-tree Traversal
- Iterative 稍微难想点:先把最左边的add, pop() stack, 加上右边(如果存在); 下一个轮回,如果又左孩子,又是一顿加。
- 然后稍微优化一下,确保rst.size() == k 时候,就可以return了
- check leaf => dfs left => add root => dfs right
- Different from regular dfs to visit all, the robot move() function need to be called, backtrack needs to move() manually and backtracking path shold not be blocked by visited positions
- IMPORTANT: Mark on the way in using set, but
backtrack directly without re-check against set
- Mark coordinate 'x@y'
- Backtrack: turn 2 times to revert, move 1 step, and turn 2 times to revert back.
- Direction has to be up, right, down, left.
int [] dx = {-1, 0, 1, 0};
,int[] dy = {0, 1, 0, -1};
- 简单处理swap
- recursively swap children
- BFS with Queue
- 每次process一个node, swap children; 然后把child加进queue里面
- 直到queue process完
- 跟Graph Valid Tree 几乎一模一样
- 建造简单的parent[] union find
- 每个edge都union.
- 注意 union 的时候, 只需要union if rootA != rootB
- build graph as adjacent list: Map<Integer, List>
- dfs for all nodes of the graph, and mark visited node
- count every dfs trip and that will be the total unions
- BFS遍历,把每个node的neighbor都加进来.
- 一定注意要把visit过的node Mark一下。因为curr node也会是别人的neighbor,会无限循环。
- Component的定义:所有Component内的node必须被串联起来via path (反正这里是undirected, 只要链接上就好)
- 这道题:其实component在input里面都已经给好了,所有能一口气visit到的,全部加进queue里面,他们就是一个component里面的了。
- 而我们这里不需要判断他们是不是Component
- DFS 应该也可以 visit all nodes, mark visited.
- UnionFind里面这次用到了一个rank的概念, 需要review. rank[] 也就是在tracking每一个node所在union的size.
- 目的是: always并到大的union里面
- note: 将2D coordinate (x,y) 转换成1D: index = x * n + y
- Reversed thinking: find surrounded nodes, how about filter out border nodes && their connections?
- Need to traverse all the border nodes, consider dfs, visit all.
- loop over border: find any 'O', and dfs to find all connected nodes, mark them as 'M'
- time: O(mn) loop over all nodes to replace remaining 'O' with 'X'
- More like a graph problem: traverse all 'O' spots, and mark as visited int[][] with area count [1 -> some number]
- Run dfs as top->bottom: mark area count and dsf into next level
- End condition: if any 'O' reaches border, mark the global map<count, false>
- keep dfs untill all connected nodes are visited.
- At the end, O(mn) loop over the matrix and mark 'X' for all the true area from map.
- Practice: write code to verify
- TODO
- 跟string相关, 写起来可能稍微繁琐一点
- 数字有 dfs([1,2,3...]) 组合方法
- operator有[
+
,-
,*
] 3种组合方法
- 注意1: 乘号要特殊处理, pass along 连乘的数字, 计算下一步乘积的时候, 要 sum - preProduct + product
- 注意2: '01' 这种数字要skip
- 注意3: 第一个选中数字不需要加操作符, 直接加进去
- Time: O(4^n), Space: O(4^n)
- T(n) = 3 * T(n-1) + 3 * T(n-2) + 3 * T(n-3) + ... + 3 *T(1);
- T(n-1) = 3 * T(n-2) + 3 * T(n-3) + ... 3 * T(1);
- Thus T(n) = 4T(n-1) = 4^2 * T(n - 1) = .... O(4^n)
- 逻辑一样, 代码更短, 只不过不做list, 直接pass
buffer + "+" + curr
- 因为每次都创建新string, 所以速度稍微慢一点. Time complexity 一样
- For 2 passwords, the shortest situation is both passwords overlap for n-1 chars.
- We can use a window to cut out last (n-1) substring and append with new candidate char from [k-1 ~ 0]
- Track the newly formed string; if new, add the new char to overall result
- Note: this operation will run for k^n times: for all spots of [0 ~ n - 1] each spot tries all k values [k-1 ~ 0]
- Same concept as dfs
- Same concept: use window to cut out tail, and append with new candidate
- do this for k^n = Math.pow(k, n) times
- 基础binary tree traversal. 注意对null child的判断
- 用remaining sum 来检测是否满足 input path sum 条件
- 满足的时候add to result list
- 两种backtracking:
-
- backtrack 当下node, 加进list, 然后dfs. dfs结束后删掉之前加进去的元素. 非常clean.
-
- backtrack 下一个dfs level增加的value. dfs return 之后, 删掉list里面的末尾元素: 但是删掉的dfs余下的value.
- 第一种backtrack更加好掌握.
- Binary Tree的一个基本题: 找到所有满足条件的path
- 遍历到底,比较sum vs. target
- 注意divide的情况。要把遍历的例子写写
- 和 Segment Tree Query I 以及其他Segment Tree类似: 这个SegmentTreeNode return count of elements in range
- 这个题目考了validate input source:input 的start,end可能超出root[start,end]。
- 那么第一步就要先clear一下: 1. 完全不在range就return 0. 2. 有range重合就规整到root的range.
- 接成圈是不行的, 所以visit过得 (x,y)就不能再去了.
- 斜角方向不能走, 只能走上下左右
- 无法按照坐标DP来做, 因为计算顺序4个方向都可以走.
- 最终要visit所有node, 所以用DFS搜索比较合适.
- 简单版: longest path, only allow right/down direction:
dp[x][y] = Math.max(dp[prevUpX][prevUpY], or dp[prevUpX][prevUpY] + 1)
; and compare the other direction as well- This problem, just compare the direction from dfs result
- DFS太多重复计算; memoization (dp[][], visited[][]) 省去了重复计算
- initialize dp[x][y] = 1, (x,y) 自己也算path里的一格
- dfs(matrix, x, y): 每次检查(x,y)的4个neighbor (nx, ny), 如果他们到(x,y)是递增, 那么就考虑和比较:
- Maht.max(dp[x][y], dp[nx][ny] + 1); where dp[n][ny] = dfs(matrix, nx, ny)
- top level: O(mn), 尝试从每一个 (x,y) 出发
- O(m * n * k), where k is the longest path
- BST inorder traversal should give small -> large sequence
- misplaced means: a large->small item would occur, and later a large>small would occur.
- The first large && second small item are the 2 candidates. Example
- [1, 5, 7, 10, 12, 15, 18]
- [1, 5,
15, 10
,12, 7
, 18] - traverse, and take note of the candidate
- at the end, swap value of the 2 candidates
- inorder traversal the nodes and save in array, find the 2 items misplanced and swap them
- But O(n) space should not be allowed
- use list, iterative? keep candidates and populating
- clean up the dfs code, a bit messy
- edge case of "0001000" is invalid, right?
- A bit like BFS solution: find inner list, and then combine with outter left/right sides.
- find all solutions, DFS will be easier to write than iterative/BFS
- when n = 1, there can be list of candidates at bottom of the tree, so bottom->up is better
- bottom->up, dfs till leaf level, and return candidates.
- each level, pair with all the candidates
- 其实就是剥皮,一层一层,是一个central-depth-first的,钻到底时候,return n=1,或者n=2的case,然后开始backtracking。
- 难的case先不handle.到底之后来一次overall scan.
- every level have 5 choices of digital pairs to add on sides. Need to do for n-2 times.
- Time complexity: O(5^n)
- if already found a good/shorter route, skip
if (distMap[node.x][node.y] <= node.dist) continue;
- This always terminates the possibility to go return to original route, because the dist will be double/higher
- Divide and Conquer
- 找到mid node
- 然后分割两半, 分别dfs做各自两个subtree: node.left,node.right
- 用长度来定位mid, 每次找中间点做root, 然后前半段, 后半段分别dfs with length.
- 用快慢pointer 找到mid. Better: 不用traverse entire linked list
- slowPointer = node;
- fastPointer = node.next;
- 然后把root = mid.next
- 然后开始sortedListToBST(mid.next.next); //后半段
- mid.next = null;//非常重要,要把后面拍过序的断掉
- sortedListToBST(head); //从头开始的前半段
- 最后root.left, root.right merge一下。
- �这个node的subtree涵盖最深level的所有leaves.
- 这个node必须是能找到的最deep那个 � 条件2的需求是因为: root本身就是满足条件1的node, 还有很多Higher-level node也是如此, 所以要找那个deepest.
- 分析题目, 思想是: 看到tree里面所有的leaves, 找到他们最deep的 common ancestor
- Maintain a map <Node, maxChildDepth>
- Recursively dfs: return deepest node that has all leaves by these comparisons:
-
- If left,right child same depth, return root: they need common ancestor
-
- If not same depth, return the one with larger depth
- 被传送去上一个level的, 永远都是subtree里面符合题意的: the node containing all leaf nodes
- Visit all nodes once O(n), space O(n)
- Find all leaves at deepest level
- Use map to track each node-parent
- Backtrack all nodes to find common ancestor
- 跟Combination Sum I, II 没什么太大区别, 只不过, 一定要用k个数字, 也就是一个for loop里面的特别条件
- 考虑input: 没有重复数字 [1 ~ 9]
- 考虑candidate重复利用: 不可以重复利用, next level dfs 时候, curr index + 1
- the result is trivial, save success list into result.
- Which one?
- worst case: tried all numbers and cannot find: O(m!), m = 9, all possible integers in [1~9]
- C(n,k), n choose k problem :
n! / (k! * (n-k)!)
=> ends up beingO(min(n^k, n^(n-k)))
- 对所给的input sum 做减法, 知道 sum 达到一个目标值截止
- 因为可以从任意点开始, 所以当sum达标时候, 需要继续recursive, 从而找到所有情况 (有正负数, sum可能继续增加/减少)
- 经典的 helper dfs recursive + self recursive
-
- helper dfs recursive 处理包括root的情况
-
- self recursive 来引领 skip root的情况.
- 与
Binary Tree Longest Consecutive Sequence II
在recursive的做法上很相似: - 利用dfs做包括root的recursive computation
- 利用这个function自己, 做
不包括root的recursive computation
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Post-order-traversal 就能记录下 Reverse Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start- 跟 Maze I, II 类似, 用一个 Node[][] 来存每一个(x,y)的state.
- Different from traditional BFS(shortest path):
it terminates BFS when good solution exists (distance), but will finish all possible routes
-
Termination condition
: if we already have a good/better solution on nodeMap[x][y], no need to add a new one
-
- Always cache the node if passed the test in step1
-
- Always offer the moved position as a new node to queue (as long as it fits condition)
-
- Finally the item at nodeMap[target.x][target.y] will have the best solution.
- Divide and Conquer. dfs
- 分开 看左边/右边
- 如果左边满足连续递增的规则, dfs (depth + 1), 如果不满足规则, dfs(depth = 1)
- 右边也是一样
- 对结果跟max作比较, return
- 因为
前两个digit可以uniquely identify
一个node, 所以可以把前两个digit作为key, 定位node. - 特点: 比如考虑root, 有 n 个leaf, 就会加 n 遍root, 因为有 n 个 unique path嘛.
- 实现: 每个node, 上来先把curr value加进sum; 只要有child, 到这个node位置的以上path sum 就要被重加一次.
- format: depth.position.value. (on same level, position may not be continuous)
- approach: map each number into: <depth.position, value>, and dfs.
- Start from dfs(map, rootKey, sum):
-
- add node value to sum
-
- compute potential child.
-
- check child existence, if exist, add sum to result (for both left/right child). Check existence using the map.
-
- also, if child exist, dfs into next level
- Space, time O(n)
- 用constant space 也就是不可以BFS, 但是mention了用dfs stack space没问题 (提示啊!)
-
- link leftChild -> rightChild
-
- resolve root.rightMost child -> first possible root.next.left/right child
-
- dfs connect(rightChild), connect(leftChild)
- Each level should be fully linked from left side, so every reach to parent will have valid path or end.
-
- 处理 nextNode -> next -> next ...的case: 找到第一个有child的next node才可以罢休. 这个case很容易miss
-
- 我们的假设是, 上一个level的所有node都应该是linked, 那么在dfs时候, 就应该先connect(root.right). 右孩子的全处理完毕, 那么trick1才可以施行.
- 根据[start,end]跟 mid of (root.start, root.end) 做比较:
-
- [start,end] on LEFT of mid
-
- [start, end] on RIGHT of mid
-
- [start, end] includes mid: break into 2 queries
- query [leftNode, start, node.left.end]
- query [rightNode, node.right.start, end]
-
- indegree tracking: Track all neighbors/childrens. 把所有的children都存在 inDegree<label, indegree count>里面
- Process with a queue: 先把所有的root加一遍(indegree == 0),可能多个root。并且全部加到queue里面。
- BFS with Queue:
- Only when map.get(label) == 0, add into queue && rst. (indegree剪完了, 就是root啦)
- inDegree在这里就 count down indegree, 确保在后面出现的node, 一定最后process.
- 几个graph的condition:
-
- 可能有多个root
-
- directed node, 可以direct backwards.
- build
Map<DirectedGraphNode, Integer> inDegree = new HashMap<>();
and include the root itself - that is more traditional indegree building
- 最普通,Non-recursive: BFS, queue, 用个queue.size()来end for loop:换行。
- 或者用两个queue. 当常规queue empty,把backup queue贴上去
- 每个level都应该有个ArrayList. 那么用一个int level来查看:是否每一层都有了相应的ArrayList。
- 如果没有,就加上一层。
- 之后每次都通过DFS在相应的level上面加数字。
Build graph
:- 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先: form sequence between chars
- form graph Map<Char, List of Chars>, for topological sort usage.
- 也可以
List[26] edges
(Course Schedule problem)
Build InDegreeCountMap<Char, Count>
: based on the char diff of 2 words- 注意: indegree 是反向的 (跟 node to neighbors 相反的方式建立)
Topological Sort
, BFS:-
- use queue to find
inDegree == 0
node. It is the letter that points to others, 排在字母表前面.
- use queue to find
-
- reduce edges using Graph
map<Character, List<Character>>
(more generic than List[26], 26个字母的dictionary)
- reduce edges using Graph
-
- Edge Case:
inDegreeCountMap.size() != result.length()
: some nodes did not make it into result sequencecycle
: when inDegree of a one node would never reduce to 0, and will not be added to result- In this case, it will be treated as invalid input, and return ""
- space: O(n), n = # of graph edges
- time: O(n)
- TODO
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
- start with empty string, need to go top->bottom
- 取或者不取
(
,)
- rule: open parentheses >= close parentheses
- Note: 在DFS时 pass a reference (StringBuffer) and maintain, instead of passing object (String) and re-create every time
- time: O(2^n), pick/not pick, the decision repat for all nodes at every level
- time: T(n) = 2 * T(n - 1) + O(1) = O(2^n)
- space: < than 2^n results = O(2^n)
- figure out n=1, n=2 => build n=3, and n=4
- dfs(n-1) return a list of candidates
- add a pair of
()
to the candidates: either in front, at end, or contain the candidates - 因为是 binary tree, 所以直接盲目搜索搜索path不efficient, use extra space and waste time
- 巧用DFS来找每一个node的common ancestor. Need the assumption: 1. unique nodes across tree; 2. must have a solution
- Base Case: 当root == null, p or q is found (
root == p || root == q
),那么就return the root as LCA - 三种情况:
-
- leftLCA and rightLCA all found:
each path has found one of p and q node as LCA
. Therefore, curr root is the lowest ancestor
- leftLCA and rightLCA all found:
-
- One of leftLCA and rightLCA is found: return whichever one found
-
- both LCAs are null, return null
-
- Base Case: 当root == null, p or q is found (
- Worst case, visit all nodes to find p q at last level, last two leaves: time O(n), stack space O(n)
- Goal: identify invalid parentheses and remove (minimum removals)
- Step:
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
if(count<0)
- When invalid occurs:
- chance for correction. Remove the incorrect parentheses, one at a time
- dfs on the rest of the s that has not been tested yet: start index from index i
- Core edge cases:
- Do not correct twice of the same parenthesis by checking [j-1] pos
- Make sure to attempt correction of all possible parenthesis within tested range: because it outputs all results at the same level
- return/finish once correction done
- Detect the incorrect parentheses by tracking/counting (similar to validation of the parentheses string):
- Success case:
- a string s passed test: make sure it passes REVERSED string test!
- Core Concept:
if a parenthese string is valid, the reverse of it should also be valid
- Test s with open='(', close=')' first; reverse s, and test it with open=')', close='('
- Minor details
- only procceed to remove invalid parenthese when
count<0
, and also break && return dfs after the recursive calls. - The above 2 facts eliminates all the redundant results.
- Reverse string before alternating open and close parentheses, so when returning final result, it will return the correct order.
- only procceed to remove invalid parenthese when
- How does it guarantee minimum removals?
- When seeing a chance to correct, it jumps into a for loop of DFS. It
return
after the for loop. This stops additional testing - When invalid occurs, correct it right away: minimum correction
- When seeing a chance to correct, it jumps into a for loop of DFS. It
- Complexity:
- O(nk), k being the # of recursive calls. It takes n calls to finish a full string case.
- Similar to DFS, we wnat to test: 1) test input s valid, 2) remove 1 invalid parenthesis at a time, 3) process substring
- instead of testing all substrings (timeout), we want to establish rules to improve reprocess:
- Test1: skip regular char. No need to test it.
- Test2: if redundant paren, do 1 is enough. skip adjacent ones.
- Test3: if last removed extra paren is '(', the next ')' must be a valid pair. LastRemoved char: pecial handling by using a struct:
class Node {String s, int index, char lastRemoved}
- How to end tests? When there is data in rst, stop adding to queue.
- Shortest path; minimum depth: 想到BFS, check level by level, BFS更能确保更快找到结果
- depth definition: reach to a leaf node, where node.left == null && node.right == null
- BFS using queue, track level.
- Divide and Conquer to find min depth.
- if one of child is null, return the other child depth + 1
- Pick the min of the two child depth + 1
- need to visit all nodes
- Transform the problem:
removing at most k items to make it a palindrome
- that is: find the longest palindrome subsequence with length x, such that
n - x <= k
516. Longest Palindromic Subsequence
utilizes Interval DP to find LPS length x- at the end, perform n - x <= k?
- time: O(n^2) to find LPS
- space: O(n^2) for dp
- Either times out or too much space used
- time: O(n^2)
- space: O(n^2) or O(k*n^2)
- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- Concept of Indegree:
# of incoming node that depends on me
. It is a graph ofarrows pointing inward to me
structure. - Remember: indegree是周围的node到我这里的次数count.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- Two structures:
-
- build inDegreeEdges:
List[] inDegreeEdges
: list of incoming nodes that depends onnode i
,
- build inDegreeEdges:
-
- build dependencyCount:
int[] dependencyCount
, count # of braches that curr node depends on
- build dependencyCount:
-
- any dependencyCount[node]==0, means this node is now a leaf, add to queue
- Topological Sort Process, Kahn algorithem:
- topologically process:
- add leaf node to queue, get ready to process;
- process leafNode, like cutting of leaf
- if any child node dependencyCount == 0, it is a leaf node now: add this node to queue.
- this problem aims for deteching cycle, not output final list. Simply: visit all nodes and verify cycle
- Option1: array of indegree lists, List[]
- 用 visited int[] 来确认是否有cycle. 1 means
visited
, -1 meansvisted from last dfs level
- Deteching
-1
: 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false. - dfs on curr node indegree dependencies; if all passes w/o failing, set visited[i] = 1
- Deteching
- Similarly, can use
HashMap<Integer, List<Integer>> map
to replace List[], but exact same idea.
- 用 visited int[] 来确认是否有cycle. 1 means
- Optoin2: use a struct
class Node {Boolean visiting; Map<Integer, Node> inDegreeMap}
to be more generic - topo sort may output the sort order: 1) at DFS bottom level, put record to a
stack
, 2) rst.insert(0, curr record) - 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>. Though: map may be more flexible
- 是topological sort的题目。一般都是给有dependency的东西排序。
- 最终都会到一个sink/leaf node,no further dependency, 在那个点截止
- 画个图的话, prerequisite都是指向那个sink/leaf node
- when building the inDegreeMap/inDegreeEdge: we use sink/leaf node as key/index, which pionts back to inDegree/parent nodes
- BFS: when all braches/dependency count are reduced to 0, then it is now a leaf node, ready to be used.
- DFS Insert Order: rst.insert(0, node); Assume we want leaf/node at index 0 in final output:
- the very bottom-node depends on everybody
- any visited node should be added to 0 index of the list, so it will be at tail later
- the special requirement causes: we have to track exact position of nodes
- Using
Node {int offset, level, val}
andMap<offset, Map<level, List<Val>>>
:- set all nodes to its correct position
- output all together
- the
max/min
offset allows us to loop over the map in a ordered manner (save efforts of sorting) - time: O(n) to mark all nodes at correct spot, but
O(nlogn)
to sort the vertical array - space: O(n), mark all nodes in the nested map
- A (offset, level) has 2 nodes: use nested
Map<offset, Map<level, List<Val>>>
to track nodes - Also need a
class Node{int offset; TreeNode node}
to build queue:- need
offset
: queue at each level cannot derive level index - need
TreeNode
:Node
extends originalTreeNode
so we can queue it.
- need
- lots code to write due to the
class Node
for BFS - DFS can find # of island, just like
200.Number of Islands
, aim to count total - We need to map same-shap land
- One approach: print the footprint starting from coordinate (0, 0)
- Another approach: print the actual island in its boundary, like a QR code. (too hard to code, skip)
- Footprint approach:
-
- always assume a newly found islands starts from (0, 0)
-
- take 4 direction from init pos and keep printing the footprint
-
- Since we always visit nodes from top->right->nextrow, we always visit top-left cornor of a new island, and the footprint will be identical
- Otherwises, if needed, we can sort the footprint and output the hash
-
- time: O(n), visit all
- space: O(n), store footprint, and dfs stacks worst case visit all nodes
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- 最右: 即level traversal每一行的最末尾.
- BFS, queue 来存每一行的内容, save end node into list
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n nodes in final results
- Use Map<Level, Integer> to override the result at each level
- dfs:
- dfs(node.left) and then dfs(node.right) because we want to log right side last
- record global max depth for iteration purpose
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n stacks (and n nodes in final results)
- Realize we need to: 1) topo sort group, 2) topo sort items in the group.
- Luckily, the candidates to be sorted are all integers: groupIds, or item ids. We can have 1 generic topo sort function
- Overall workflow
-
- group items to map <GroupId, List>
-
- build group graph
-
- topo sort group -> return sorted group id list
-
- for each group: build item graph, topo sort items -> return sorted item list
-
- flatten and return results
-
- This approach highly relies on the preorder rules
- we can use validation rules to navigate throug hteh preorder array
- use a global index
- time: O(n)
- faster than BFS, using less space if not couting final rst: stack size, O(logn)
- time: O(n), visit all
- loop over queue level and record max
- simply calc each level sum with BFS
- top-level is processed first, since we go from top level -> deeper level
- only update result if sum is truly > global MAX.
- 可以top->bottom: 遍历str, validate substring(start, i); if valid, add as candidate, and dfs; backtrack by remove candidate.
- 也可以bottom->up: 遍历str, validate substring(start, i); if valid, dfs(remaining str), return list of suffix; cross match with curr candidate.
- 在遍历str的时候,考虑从每个curr spot 到 str 结尾,是能有多少种palindorme?
- 那就从curr spot当个字符开始算,开始backtracking.
- 如果所选不是palindrome, 那move on.
- 若所选的确是palindrome, 加到path里面,DFS去下个level,等遍历到了结尾,这就产生了一种分割成palindrome的串。
- 每次DFS结尾,要把这一层加的所选palindrome删掉,backtracking嘛
- 可以再每一个dfs level 算 isPalindrome(S), 但是可以先把 boolean[][] isPalin 算出来, 每次O(1) 来用
- 注意: isPalin[i][j] 是 inclusive的, 所以用的时候要认准坐标
- Calculate isPalin[i][j]: pick mid point [0 ~ n]
- expand and validate palindrome at these indexes:
[mid, mid+1]
or[mid-1][mid+1]
- Overall Space O(n^2): 存 isPlain[][]
- Time O(2^n), 每一层都在做 pick/not pick index i 的选择, 所以worst case 2^n.
- 因为我们的isPalin[][]优化了palindrome的判断O(1), 所以overall Time: O(2^n)
- 每次看最左left depth和最右leaf depth 是不是一样
- 如果一样, 直接 2 ^ h - 1就好
- 不一样的话, 再DFS
- calculate
2^(h)
: 位运算, Math.pow(2, h) = 2 << (h - 1). 神奇!- 2 << 1就是把所有bits往左移动一位, 也就是 * 2
- time: O(n) visit all nodes on 1 side
- space: O(h) visit all nodes on 1 side
- See details in comments inline. 要对tree非常理解
- binary tree one child tree nodes # = 2 ^ h - 1; 所以一个child tree + root = 2 ^ h
- Find all paths, bfs/dfs all works. dfs will be simplier to write
- Recursive:分叉. dfs.
- top->bottom: enumerate current node into the list, carry to next level, and backtrack
- top->bottom is trivial to consider: path flows from top->bottom
- time: visit all n nodes
- space: to hold all paths, O(nlogn)
- O((n-1)/2) = O(n) nodes at leaf
- O(logn) depth
- We can also take current node.left or node.right to generate list of results from the subproblem
- let dfs return list of string candidates, and we can run pair the list with currenet node, once they come back.
- TODO: can write code to practice
- Iterative, 非递归练习了一下
- 因为要每次切短list, 所以再加了一个Stack 来存level
- 单这道题用dfs更简单, 因为找的就是从头到尾的path, 是dfs的pattern
- typical recursive visit all situation
- dfs function: have toDelete set, and a result list
- dive deep into child node FIRST, and test if a removal is needed at bottom of tree
- if remove, add orphan and return null; otherwise, return itself
- time: O(n), visit all nodes
- space: O(logn), height of the tree
- traverse tree and create
map <val, parent>
to fast O(1) removal. O(n) - set root into a rootSet
- after deleting a node A, the children of the node becomes 2 forests root
- children should be marked in rootSet
- also remove node A from rootSet (if appears)
- output: find all root in root set, traverse and output.
- This approach requires a dfs build of parentMap
- it is same amount of efforts to do the regular dfs removal.
- not a good solution
- time: O(n)
- space: O(n)
- New interface to understand: object contains integer or object
- Visit all && sum, consider dfs.
- 简单的处理nested structure, dfs增加depth.
- time: visit all nodes eventually, O(n), space O(n)
- bfs, queue, 处理queue.size() for a level
- use a level variable to track levels
- slower since it uses extra space, worst case O(n) of all items
- define dp[x], 积累到amount x, 最少用多少个coin
- function:
dp[x] = Math.min(dp[x], dp[x - coinValue] + 1)
. two branches based on choosing coinValue or not - initialization
- dp[0], zero amount uses 0 coin. so dp[0] = 0
- Utilize
Integer.MAX_VALUE
as default val for initialize dp[x]: 1) alert error stage; 2) easy comparison
- create subproblem: (coins, amount - pickedCoin)
- memo[i] 依然表示: min # of coints to make amount i
- initialize memo[i] = Integer.MAX_VALUE
- 先选最后一步(遍历coins), 然后dfs做同样的操作
- 记录memo[amount] 如果已经给过value, 不要重复计算, 直接return.
- time: O(n * S), worst case it runs n coins for S(amount) iterations
- space: O(S)
- IMPORANT, Memoization:
Map<prefix, List<suffix variations>>
to build substring segments. Reduces repeated calculation if the substring has been tried. - Realize the input s expands into a tree of possible prefixes.
- Find list of candidates from subproblem, and cross-match
- DFS returns List segments of target s: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....'
, anddict=[a,b,c,d,e,f...]
- DFS on string: find a valid prefix, dfs on the suffix, building individual candidate in list till substring exhaust.
- improvement:
- use memo to record failed case (solved the timeout issue explained below)
- use min/max to as boundary for dict check.
- core code is short; helper code is slightly longer
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
- Similar concept to Minimum Path Sum
- https://leetcode.com/problems/cherry-pickup/solution/
- realize r1 + c1 = r2 + c2. Knowing 3 parameters can uniquely identify the 4th.
- assume there are 2 people starting from origin, and the 2 people can go total 4 directions
- perform DFS based on the 4 directions
- concern: do they visit the same spot? possible. when that happens, make sure we do not double count the grid[i][j]
- when is the end state?
- then anyone, for example, (r1,c1) reaches end (n-1, n-1).
- it means the other person also reaches end
- use memo: memo[r1][c1][r2], it records any given (r1, c1, r2, c2) state
- 这里要走过所有的node, 所以dfs非常合适
- Divide and conquer.
- 维持一个最大值: Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
- 注意check root == null
- BFS is doable as well, but a bit more code to write: tracks largest level we reach
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
- Best of all recursive approaches
- iterate over nums: pick or not pick
- reduce remaining item in next level:
- option1: use queue, restrict queue size; backtrack append to queue
- option2: remove element before passing into next level; backtrack: insert back
- time O(n!): visit all possible outcome
- T(n) = n * T(n-1) + O(1)
- A bit worse than option1, uses more time:
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
contains
is nlogn time - also, each dfs, it has to iterate over entire nums list
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
- Use visited[] to track, still causes for(over n items), not efficient
- Best of all iterative approaches
- Each time pick 1 NEW element and find places to insert into candidate list:
-
- 一个一个element加进去
-
- 每一次把rst里面的每个list拿出来, 创建成新list, 然后选位置加上new element
-
- 加新元素的时候, 要在list的每个位置insert, 最终也要在原始的list末尾加上new element
-
- 还是O(n!), 因为rst insert O(n!)个permutations
- Better than the Option2/Option3 (
BFS+Queue
), because this solution does not need to check duplicates - 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍
- Time O(n!)
- Slow: checking candidate.contains() is O(logn) each time
- Almost same as Method2-Option2, but it builds remainingCandidate list on the fly list.removeall(xyz): O(n)
- Even slower than Method2-Option2
- visit all nodes connected with the starting node
- double for loop, test all starting nodes
- val == 1: 1) count++; 2)DFS from this (i,j);
- Mark visited (x,y) = '0'
- time: O(n), visit all nodes
- space: O(n), stack
- 可以用union-find, 就像Number of island II 一样.
- 只不过这个不Return list, 而只是# of islands
- Union Find is independent from the problem: it models the union status of integers.
- Return the total # of unions (which is # of islands)
- in reality: it is a bit slow.
- time: visit all nodes just once, O(n). Union Find will visit all nodes once and union them
- space: O(n), union find takes O(n) space
- 记住UnionFind的模板和几个变化(Connecting Graph I, II, III), 最后归总的代码写起来就比较简单.
- use queue to hold 1 island, keep adding 4-direction islands; mark visited with '0'
- check entire board for any remaining one.
- Sort the list, and on same level, if last element is the same as curr, skip this recursive call
- time O(n!)
- Idea from: https://www.sigmainfy.com/blog/leetcode-permutations-i-and-ii.html
- 用到 sublist sort
- 用 swap function, 在原数组上调节出来新的permutation
- 注意: 每次拿到新的candidate, 都要把没有permutate的数位sort, 然后再开始swap.
- 这是为了确保, [j]和[j-1]在重复时候, 不用重新记录.
- Construct graph:
map<String, List<Destination>>
; sort the list of destinations. - DFS:
- with any curr city, go over the destination list:
graph.get(curr)
- add visit city to rst
- remove visited city from the desitnation list
- backtrack
- with any curr city, go over the destination list:
- NOTE:
-
- the graph allows cycle: revisiting same city. Do NOT assume no cycle
-
- it asks to us to treat
smaller lexical order city
with priority; however:
- it does NOT mean visiting
smaller lexical order city
is THE correc anser - it can be a leaf sink node of the graph and does not provide correct trip plan
- it asks to us to treat
-
- time: O(n^n). n = # of cities. worst case, each city has (n-1) edges and need to try all combinations
- space: O(n^2), there can at most be n * (n - 1) edges
- 考虑input: 没有duplicate, 不需要sort
- 考虑重复使用的规则: 可以重复使用, 那么for loop里面dfs的时候, 使用curr index i
- the result is trivial, save success list into result.
- Time and Space complexity:
- transform the analysis as for
40. Combination Sum II
- Since this problem allows reuse of elemenets, assume they exist in original input as duplicates
- time: O(k * 2^n), k = avg rst length
- space: O(k) stack depth, if not counting result size
- transform the analysis as for
- Analyze the pattern: 1) single char, 2) with !, 3) with &, |
- Identify sub problem
- Use stack to parse the data in "()", which is a sub problem to solve with recursive call
- Handle &, | case: need to parse multiple
- Be comfortable with string parsing
- Slight improve:
- If see obvious result, directly return evaluation w/o further parsing
- use memo to store evaluated exp
- Use '(' and ')' to mark inner exp
- Evaluate the inner exp and save result back to Stack: the result will be 'f' or 't'
- This is slightly slow because:
- It requires all stack items on top to be processed before reaching the operator
- There is no room to optimize even there is simplification for specific operator
- 加root, left, then right. Obvious
- Option1: recursive on preorderTraversal. the dfs function returns List
- Option2: pass in rst, and write a void dfs.
- 先加root, 然后push上需要末尾process的在stack垫底(root.right), 然后push root.left
- Stack: push curr, push right, push left.
- DFS using depth marker: 每个depth都存一下。然后如果有不符合条件的,存为-1.
- 一旦有 <0 或者差值大于1, 就全部返回Integer.MIN_VALUE. Integer.MIN_VALUE比较极端, 确保结果的正确性。
- 最后比较返回结果是不是<0. 是<0,那就false.
- Traverse 整个tree, O(n)
- Same concept as above solution, but cost more traversal efforts
- 试图计算所有情况
- DFS. 确定leaf条件, && with all dfs(sub1, sub2).
- 这里无论如何都要走过所有的node, 所以dfs更加合适, 好写.
- 两个queue存每个tree的所有current level node. Check equality, check queue size.
- Populate next level by nodes at current level.
- 确定好结尾条件:
is leaf
&&val == sum
. - 每一层减掉node.val, 然后dfs.
- 写一写:
root == null => false
对parent nodes的影响. 这里发现没影响, 所以可以简化成用1个functionDFS. - cache parent max and min => produce current max and min
- pass the max and min to dfs
- compare and return the max of dfs(left), dfs(right)
- time: O(n)
- space: O(logn)
- easy to write, a bit hard to think of
- pass up the local (min, max) as object
Val{max, min}
- easy to think of, but more code to write
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n)
, 3ms - space: O(2^n) results
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- BFS, 注意考虑如果让one level to generate next level
-
- maintain a list of Indexe to store candidate indexes.
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
-
- should be same O(2^n), but actual run time 7ms, slower
- O(n) space
207. Course Schedule
has more notes- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目求这个最后排好的课的list
- 如果排不好, 就给个空就好
- input是 numOfCourses, 还有这个prerequisites [[]]
- 用
List[] edges; edges[i] = new ArrayList<>();
来表示graph: 就是每个node, to all its neighbors - 每个没有 inDegree==0 node, 都是可以加进 final list里面的. 比如一开始找到的那些 inDegree = 0的 node
- 注意, 如果 prerequisites = [], 那么就是说这些课都independent, 开个int[0 ~ n-1]的数组并赋值就好.
- 如果有cycle, 严格意义上就做不了topological sort, 也无法涵盖所有nodes, 那么return [ ]
- 根据 Course Schedule 里面的DFS 修改
- 维持visited int[]全局变量
- 维持sortedList int[] 全局变量, 注意加进去的时候是 add(0, node) 加在开头这样
- 每次到一个node的children全部DFS走完之后, 就可以把他加进final list里面
- 如果有cycle, 也就是dfs return false的时候, 这个题目判定排课失败, return new int[] { }
-
- level-traverse all nodes, 2) add node to appropriate col list(using map)
- For final output:
- Use min/max to track map keys, since the keys are continous
- Map does not provide random access; unless map key is marked with sequence i = [min, max]
- Since each vertical list is appended level by level: no need to sort during output. FAST
- time: O(n), visit all nodes
- spac: O(n) to store
- Regular DFS to traverse all nodes, and add node to appropriate col list (using map)
- Problem: DFS does not provide natural ordering for nodes on a row.
- Left side may have a super deep Right child branch, which will be added to col list first (since left side is visisted first)
- It is wrong because right branch may have nodes in same column but at higher level
- To Solve: preserve
level
for all nodes in same column
- Need to sort the final list, and slow: visit all nodes O(n) + O(KMlogM), K = # of cols, M = # of items in col
- Time: O(nLogN). O(n) + O(KMlogM), K = # of cols, M = # of items in col; in extrem, it can be a vertical line of nodes, then sort: O(nLogN)
- Space: O(n)
- 复习Union-Find的另外一个种形式, track union size: tree does not have cycle, so eventually union size should == 1
-
- 查找2个元素是不是在一个union里面。如果不在,false. 如果在,那就合并成一个set, 共享parent.
-
- 验证cycle:
find(x) == find(y) => cycle
- ideally, this edges[i] should be the very first time x and y node connect;
- however, if they have been grouped together under same ancestor before, there exist a feedback loop (cycle) between them.
- 验证cycle:
-
father[x]
: element x (index x) stores its root ancestor- 注意: 结尾要检查, 是否只剩下1个union: Tree必须连接到所有给出的node.
- 另一个union-find, 用hashmap的:
- http://www.lintcode.com/en/problem/find-the-weak-connected-component-in-the-directed-graph/
- Deep Dive into UnionFind: https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf
- Very similar to
Redundant Connection
- Create adjacency list graph: Map<Integer, List>
- 检查:
-
- 是否有cycle using dfs, check boolean[] visited
-
- 是否所有的node全部链接起来: validate if all edge connected: # of visited node should match graph size
- IMPORTANT: use
pre
node to avoid linking backward/infinite loop such as (1)->(2), and (2)->(1) - traverse through adjacency list graph:
Map<Integer, List<Integer>>
-
- validate cycle with set: if revisit same node
- avoid infinite loop: remove backward mapping from child node to parent node
-
- validate check set size for connected
- DFS to return the tail of the flattened list
- Careful handling the null child. Choose one and both works:
- Option1: put non-null as right child and continue dfs
- Option2: put non-null as left child and continue dfs
- latten the tree, no extra space.
-
- Set right node in buffer, and connect:
root.right = root.left
, DFS flatten(root.right)
- Set right node in buffer, and connect:
-
- 移花接木: traverse to tail of the current root.right and attach the buffer node.
-
- flatten the remaining of buffer
-
- 顺序一定要清楚, 不能写错, 写几个example可以看出来
- 移动的那些node, 要把node.left = null, 清扫干净
- Use example to understand: for any given ending char, 3 cases of palindromes
- a. ss[i, j] is a palindrome. dp[i+1][j-1] + 2 since the two indexes are counted, assume dp[i + 1][j - 1] is calculated
- b. ss[i + 1, j] is a palindrome
- c. ss[i, j - 1] is a palindrome
- time/space: O(n^2)
- Option1: start processing substring from tail
- set:
i = [n-1 towards 0]
,j = i + 1
- consider ss[i, j], ss[i + 1, j], ss[i, j - 1]
- since i starts from n - 1 -> 0 and j = i + 1, these are calculated and ready to go: dp[i+1][j-1], dp[i+1][j] and dp[i][j-1]
- FAST: skipped the initialization
- set:
- Option2: start processing substring from head
- 用[i][j]表示区间的首尾: 考虑3个情况: 砍头, 砍尾, 砍头并砍尾 (考虑首尾关系)
- Iteration on len:
- len = j - i + 1; 那么反推, 如果len已知,
j = len + i - 1
; - 注意考虑len == 1, len == 2是的特殊情况.
- consider sub problems with 3 major cases
- a. ss[i, j] is a palindrome: dfs check ss[i + 1, j - 1]
- b. ss[i + 1, j] maybe a palindrome: dfs check ss[i + 1, j]
- c. ss[i, j - 1] maybe a palindrome: dfs check ss[i, j - 1]
- memo[i][j]: max palindrome length in range [i, j], if calculated, return directly
- Init memo[i][j] = -1 to track the progress, memoization
- 注意: init dp[i][j]=-1, dfs的时候查dp[i][j] 是否算过
- more about dfs: bottom-up, first dive deep into dfs(i+1,j-1) till the base cases.
- Space: O(n^2)
- Time: O(n^2)
- prepare dp[n][n]: O(n^2); dfs: visit all combinations of [i,j]: O(n^2)
- Depth-first:
-
- process curr.child, return tailChild
-
- connect tailChild.next = curr.next
-
- function: link(Node a, Node b);
- use preorder to find root, one index at a time (global index)
- use the root to divide and conquer inorder int[] to 2 sides;
- root.left = dfs(left); root.right = dfs(right)
- end stage: start == end index, create a node
- can use a map to store inorder <val, index> for O(1) find
- 和Construct from Inorder && Postorder 想法一样。
- 写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。
- 跟Convert Sorted Array to Binary Tree类似, 找到对应的index, 然后:
- node.left = dfs(...), node.right = dfs(...)
- Divide and Conquer
- optimize on finding
mid node
: given value, find mid of inorder:
- optimize on finding
- Instead of searching linearly, just store inorder sequence in
map <value -> index>
, O(1) - IMPORATANT: the mid from inorder sequence will become the main baseline to tell range:
range of subTree = (mid - inStart)
- sapce: O(n), time: O(n) access
- when the input has duplicates, and want to skip redundant items? 考虑重复使用的规则: 不可以重复使用
-
- sort. 考虑input: 有duplicate, 必须sort
-
- in for loop, skip same neighbor:
if (i > index && candidates[i] == candidates[i - 1]) continue;
- 因为在同一个level里面重复的数字在下一个dfs level里面是会被考虑到的, 这里必须skip
- in for loop, skip same neighbor:
-
- the result is trivial, save success list into result.
- Time complexity: O(k * 2^n), k = average result length
-
- Assume on average, there are k elements in result
-
- Since element can be used ONLY once, so the total # of solutions can be
C(n, k)
:pick k out of n
- Since element can be used ONLY once, so the total # of solutions can be
-
- Now let k be any number [0, n], so total # of solutions can be:
C(n,0) + C(n,1) + C(n,2) + ... C(n,n) = 2^n
- Now let k be any number [0, n], so total # of solutions can be:
-
- Now:
the new ArrayList<>(list)
takes average O(k) time
- Now:
- Total: O(k * 2^n)
-
- Space: O(n), stack depth, if not counting results size
- Build a list of NestedInt
- DFS:
- sum up integers in the list are integers
- dfs on nested list
- overallSum = sum * (depth+1)
- End state: if no nested list (no more child dfs), return depth 1
- Parent level: sum up all ints and times the (depth+1)
- Using stack to flatten all nestedList, and process in the end
- Can actually use list, does not need to be stack.
- uses more memory
- Pick a subset (max-size k), and produce sub problem to solve by dfs
- NOTE: no need to change actual index value. That makes this problem easier (no need to record the choice path)
- time: O(n), calc memo[n]
- space: O(n), memo + stack depth
- Use HashMap to mark cloned nodes:
map<oldNode, newNode>
-
- make new curr node;
-
- clone all neibhors and add them
-
- Use the map to avoid visited nodes
- time: O(n). visit all nodes
- space: O(n). Technically only travels n levels/stacks to circle all nodes (undirected & connected)
- Given graph node obj
{val, list of neighbor}
: copy the node and all neighbors - Mark visited using map<oldNode, newNode>
- for loop on the each one of the neighbors: map copy, record in map, and further dfs
- once dfs completes, add newNeighbor as neighbor of the new node (get to it via map)
- 主要思想是: 一旦复制过了, 不必要重新复制
- Copy the root node, then copy all the neighbors.
- Mark copied node in map.
- Use queue to contain the newly added neighbors. Need to work on them in the future.
Dijkstras algorithm
is based on repeatedly making the candidate move that has the least distance travelled.- PQ: pick close node to vist, and add siblings back to PQ
- avoid visited
- time: O(nLogn), visit n nodes, each time insert to heap takes O(logn) time
- space: O(n)
-
- build graph map, 2) traverse map, 3) prioritize short delay nodes first
- use a map
<node, timeElapsed>
globally track dealy to nodes; compare all at the end - Regular convert BST to sored list: in-order traversal
- Carefully doubly link node head, tail
- traverse all nodes, DFS 好做:
left, curr, right
- Tail:
- Assume head is found for sub tree, then
tail = head.left
- Link
LeftTail <-> Curr Root <-> RightHead
- Link
RightTail <-> LeftHead
- Assume head is found for sub tree, then
- In place:
- 同
Node {val, left, right}
, w/o new doubley linked list class - different from
Convert Binary Search Tree to Sorted Doubly Linked List (extra space)
- 同
- 查看每个parent-child关系:
- leftchild < root < rightChild
- all of left child < curr < all of right child
- 方法: 把root.val 传下来作为 max 或者 min, valid child in (min, max)
- BST 有两个极端: left-most-leaf is the smallest element, and right-most-leaf is largest
- imagine we know the two extreme border: Long.MIN_VALUE, Long.MAX_VALUE
- min/max: long type to meet edge case: node.val = Integer.MAX_VALUE
- key concetpL the
common ancester of deppest leaves
must have itstwo branch being same depth
. problem sovled. - dfs on both branch
- if returned depth equals & equal to max depth, record common ancestor
- time: O(n) traversal 1 pass
- space: O(n) dfs worst case depth
-
- find leaf nodes, and store backward map to root (DFS/ BFS both work)
-
- use leaf nodes to find way backwards till common node is found; return
- time: O(n) but two passes
- space: O(n) dsf + map storage
- this approach is more brutle and uses exrtra spaces
- build map of
x#y -> val
to store values[i] and 1/values[i] - build map of
x -> list children
- dfs to traverse the graph
- BFS should also work: build graph and valueMap
- for each starting item, add all next candidate to queue
- mark visited, loop until end item is found
bipartite
require each node to be in exact 1 party, which means it only has 1 state- DFS to mark node with one state; and mark its edges as reversed state
- If any node state has been assigned by different from desired one, return false.
- Use
Boolean states[i]
to represent visted & state - Try all nodes with for loop, and skip visited nodes (similar validation rules as in dfs)
- In
int next : graph[curr]
, test next level first before adding. - IMPORTANT: DO NOT ASSUME positive integers
- Overall idea: write example and realize 2 cases at each node:
-
combo sum
: left + right + root
-
single path sum WITH curr node
: left/right + root
-
- DFS returns the path over curr node: a path needs to be continuous, so we cannot skip curr node.
- IMPORTANT, key discovery: if left/right single path over curr node is less than 0: reutrn 0.
- Parent path will simply drop this path, since we want maximize the path sum.
- It is so IMPORTANT: when left or right becomes 0, when comparing with global combo path:
- it automatically covers a special case:
single left/right path + node
, since one of left/right == 0!!!
- it automatically covers a special case:
- With the above understanding: what if I want to skip curr node and just want left/right path w/o curr node:
- it is handled and compared with global in dfs(node.left) or dfs(node.right) automatically!
- time: O(n), go over whole tree
- space: O(logn), tree height.
- tree给我们2条branch, 每条branch就类似于 dp[i - 1], 这里类似于dp[left], dp[right] 这样
- 找到 dp[left], dp[right] 以后, 跟 curr node结合.
- 因为是找max sum, 并且可以skip nodes, 所以需要全局变量max
- 每次dfs() return的一定是可以继续
continuously link 的 path
, 所以returnone single path sum + curr value
. - 用 PathSum 比较特别. 没有 data structure的时候, 写起来比较繁琐.
- 第一次做有点难理解,复杂原因是:因为可能有负值啊。不能乱assume正数。
- single path max 的计算是为了给后面的comboMax用的。
- 如果single path max小于0,那没有什么加到parent上面的意义,所以就被再次刷为0.
- combo的三种情况:(root可能小于0)
-
- 只有left
-
- 只有right
-
- root大于0,那么就left,right,curr全部加起来。
- 情况1和情况2取一个最大值,然后和情况三比较。做了两个Math.max(). 然后就有了这一层的comboMax
- time O(mn)
- 构建
Map<email, email parent>
, 然后再反向整合: parent -> list of email - init with <email, email> for all emails
- 因为不同account可能串email, 那么把所有email union的时候, 不同account 的email也会被串起来
- 最终: 所有的email都被union起来, 指向一个各自union的 parent email
- UnionFind 的 parent map 可以反向输出所有 child under parent.
- 同时要维护一个 <email -> account name> 的map, 最终用来输出.
- Definitely need iterate over accounts: merge them by email.
- Account object {name, list of email}
- map<email, account>
-
- iterate over accounts
-
- find if 'account' exist; if does, add emails
-
- if not, add account to list and to map. map all emails to accounts.
- output -> all accounts, and sort emails
- space O(mn): m row, n = emails
- time O(mn)
- TODO
- Recursively check symmetrically相对应的Node.
- 每个node的children都和镜面另外一边相对的node的children刚好成镜面反射位置。
- put left or right children in pair
- stack1: 左手边sub-tree先加left, 再加right child;
- stack2: 右手边sub-tree先加right child, 再加left child。
- process时,若symmetric,所有stack里面出来的node会一一对应。
- Target = total / k, fixed.
- DFS: Pick one num and dfs with the remaining nums for subproblem
- subproblem1: accumulate local sum to target
- EndState: accumulatedSum == target, continue with below
- subproblem2: after accumulatedSum == target, continue dfs with k-1
- EndState: k == 0, return overall true
- subproblem1: accumulate local sum to target
- Option1: DFS with nums, and boolean[] visited. Fast
- Option2: DFS with queue.
- Specially handling: dfs(size+1) to prevent
while(size-- >0)
from skipping last item at index 0
- Specially handling: dfs(size+1) to prevent
- the problem aims to find true/false capability
- bit-masking. TODO. The DP approach is kinda hard-level
- https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/335668/DP-with-Bit-Masking-Solution-%3A-Best-for-Interviews
- the leaves are at depth 0 and the root is at highest depth
- dfs: the depth = index of the rst, start from depth = 0 at leaf
- end state: leaf node, add to rst, and return depth
- 利用 BST 的性质,可以直接搜到target node,而做成两个长度不一定相等的 path
- 然后很简单找到LCA
- O(n) space, O(logn) time
- Beofre lasts common ancestor is found: p and q should follow same search pattern:
- compare with root, then dfs(left) or dfs(right)
- util p and q fall on two sides of root, then return root
- 非常巧妙, 但是也比较局限; 稍微变条件, 就很难recursive.
- 几种情况:
-
- one of p, q 在leaf, 那么此时的root其实就是lowest common ancestor
-
- 如果p, q 在root的左右两边, 这就是分叉口, 那么root就是lowest common ancestor
-
- 如果p, q 在root的同一边 (左,右), 那么继续dfs
-
- O(logn) extra space: recursive stack space
- O(logn) time
- Given that left & right nodes are always available in pair, at each level:
- perform dfs to find new root: return deepest left node as root
- pick out curr left node and fix current level:
- add right node as left node
- add root as right node
- build candidate into dfs: treat each list candidate as success, add to rst
- remove last item from the candidate, try to add factor to it, and supply it with remain element
- backtrack after dfs
- Check if the number can be devided by [2, sqrt(n)], return a list of possible factors. Only check till
Math.sqrt(n)
- build suffixes: use the factor to devide last element of list and replace last element
- build candidate: replace last element of the queue item with new list of suffixes; add to rst
- add success item back to queue: in case last element can be simplified
- remove dupilcates: since we start factor from [2, sqrt(n)], the final factor list should be ascending!!
- time: O(x), x is the # of results
- space: O(y), y is all ongoing candidates in queue
- Median还是用min-heap 和 max-heap. Time(logN)
- 加/减: prioirtyQueue, log(n)
- findMedian: O(1)
- 加一个数, 减一个数。
- 加减时看好,是从前面的maxheap里面抽,还是从后面的minHeap里面抽。
- 抽完balance一下
- 用maxHeap, minHeap时候, 习惯选择让maxHeap多一个数字:
- 左边的maxHeap总有 x+1或者x个数字
- 后边minHeap应该一直有x个数字
- 特点1: 所有给出的点(再找出没有specify的对角线点), 如果最后组成perfect rectangle, 都应该互相消除, 最后剩下4个corner
- 特点2: 找到所有点里面的min/max (x,y), 最后组成的 maxArea, 应该跟过程中accumulate的area相等
- 特点1确保中间没有空心的部分, 保证所有的重合点都会互相消除, 最后剩下4个顶点
- 特点2确保没有重合: 重合的area会最终超出maxArea
- 就是把2D list里面的element全部遍历一遍。
- 跟一个nxn的matrix遍历,是没区别的拉; 所有来个x,y,把2d list跑一变。
- list 方便remove, 考虑吧reduce input vector (就像给的是linked list 一样)
- 两个Queue,交互倒水
- 用一个Temp做swap
- 逻辑在push里面:
-
- x 放q2。
-
- q1全部offer/append到q2.
-
- 用一个Temp做swap q1, q2.
- q1的头,就一直是最后加进去的值.
- 逻辑在top()/pop()里, 每次换水,查看末尾项.
- 用HashMap, 理解题目规律,因为重复的计算可以被覆盖,所以是个优化题。没有什么太大的悬念和意义.
- 消灭重合点:
- 如果process当下col, 其实要减去过去所有加过的row的交接点。。。
- 再分析,就是每次碰到row 取一个单点, sumRow += xxx。
- 然后process当下col时候, sum += colValue * N - sumRow. 就等于把交叉所有row(曾经Process过的row)的点减去了。很方便。
- 最后read in 是O(P), process也是O(P).
- 找一个cache来存next()的值, 也就是: next value的值提前存在cache里面
- 因此peek()的时候, 就可以直接return cache, 而不用做 itt.next()
- 然后每次真的next()的时候, 里取下一个itt.next()维护这个cache
- 再一次理解错题意. peek() 就是头顶,但是不一定是最大值啊。
- 总是把PEEK想成了最大值,然后用2 STACK做了最大值的cache,练的一手好双stack,可惜错了。
-
具体看thoughts, 几种不同的方式使用map
-
regular object map
: map of <key, item>, whereitem : {int val; int count}
-
Use a Map<frequency count, doubly-linked node> to track the frequency
-
Track constant capacity, and minimum frequency
-
Every get(): update all frequency map as well
-
Every put(): update all frequency map as well, with optional removal (if over capacity)
-
Original post: http://www.cnblogs.com/grandyang/p/6258459.html
-
TODO: one doubly linked list might be good enough to replace below:
-
frequency list map
: map of <frequency count, List>, where the list preservesrecency
-
item location in frequency map
: map of <key, int location index in list>: -
index relative to the item in a particular list, not tracking which list here
- 做text的search, 毋庸置疑要用Prefix tree, trie.
- �Trie造好之后, 做prefix search, 然后DFS/BFS return all leaf items. [high runtime complexity]
- 在TrieNode里面存所有的possible words. [high space usage]
- in memory space 应该不是�大问题, 所以我们可以选择 store all� possible words
- �Store MinHeap with TrieNode: 因为会不断搜索新此条, 同样的prefix (尤其是在higher level), 会被多次搜索.
- [�complexity: need to update heaps across all visited TrieNodes once new sentence is completed]
- Compute MinHeap on the fly: 当然我们不能每次都来一个DFS�不然会很慢, 所以就必须要store list of possible candidates in TrieNode.
- 这里就用到了
Top K Frequent Words
里面的Map<String, freq>
, 这样O(m) 构建 min-heap其实很方便. - 每次
#
后 标记一个词条被add进search history. 那么就要insert it into trie
. - 这一条在最后遇到
#
再做就可以了, 非常简洁 - Trie Prefix Search + maintain top k frequent items
- 简单算出abbreviatioin
- 检查abbr是否存在; 如果存在, 是不是input word本身.
- map: avoid duplicate message, records timestamp for validation
- time: O(1)
- space: O(n)
-
- keep a trimmed queue and set (all tasks to be within 10 secs);
-
- use set to O(1) check if incoming message exists.
- time: O(x), trimQueue()
- space: O(n)
- Prep: 存Map<word, index list>
- Process: 相继从两个 index list 里面拿出 p1,p2
- 根据index的大小, 移动双指针: try to move the pointers closer; always calculate diff
- Optionally: if one list is much larger, do binary search on the larger list
- 用stack记录最小值, 放在top. O(h) space.
- 每次消耗TreeNode, 都看看rightNode(其实就是下一个最小的candidate), 并且一条龙stack叠上rightNode所有的left子孙.
- 用O(1)空间的做法:不存stack, 时刻update current为最小值。
- 找下一个最小值,
- 如果current有right child: 和用stack时的iteration类似,那么再找一遍current.right的left-most child,就是最小值了。
- 如果current没有right child: 那么就要找current node的右上parent, search in BinarySearchTree from root.
- 注意:
- 一定要确保找到的parent满足parent.left == current.
- 反而言之,如果current是parent的 right child, 那么下一轮就会重新process parent。
- 但是有错:binary search tree里面parent是小于right child的,也就是在之前一步肯定visit过,如此便会死循环。
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
- Follow up of 243. Shortested Word Distance
- 特别handle
word == word1 == word2
case:- p1 and p2 will always be the same
- when
word == word1 == word2
, simply calculate distance using theold p1 or p2
withcurr index i
- The rest impl aligns with 243.
- when
word1==word2
, make usre to skipp1==p2
by increasing i or j - The rest impl aligns with 244
- Time: still O(n), but slower than Method1: 2 passes
- Space: uses extra space O(n) to hold all indexes
- Trie Structure:
boolean isEnd
,HashMap<Character, TrieNode> children
- trie.addWord: 没node就加,有node就移动
- trie.search: 没node就return false,有node就移动
- Alternatively, the hash can be
TrieNode[26]
a fixed size array when applicable- I like map better for the simplicity to write (w/o converting char -> index)
- 把Input stream想成向上的山坡. 山坡中间那点,自然就是median.
- 前半段,作为maxHeap,关注点是PriorityQueue的峰点,也就是实际上的median.
- 后半段,作为minHeap,正常的PriorityQueue。 开头是最小的。
- 这里要首先定好, 哪一个queue是多存一个element的. 这里选maxHeap: maxHeap.size() == minHeap.size() + 1 || minHeap.size()
- 必须先维护maxHeap里面有个元素, 否则null了会在比较大小时出问题.
- 用了一个特别的双向的ListNode,有了head和tail,这样就大大加快了速度
- 主要加快的就是那个‘更新排位’的过程,找到item hashmap O(1), 做减法换位也都是O(1)
- Overall O(1)
- 巧妙点
-
- head和tail特别巧妙:除掉头和尾,和加上头和尾,都O(1)
-
- remove node: 把node.pre和node.next 连起来, node就自然而然的断开不要了
-
- 一旦知道怎么解决了,就不是很特别,并不是难写的算法
- moveToHead()
- insertHead()
- remove()
- Instead of building
Double Linked List
, utilize JavaDeque<E> queue = new LinkedList<>()
- works but problem:
queue.remove(E)
is O(n) - time: O(1) on average but much slower
- Use Map<number, count > to store the inputs
- Iterate over map to find the pair
- Use Set memo to store the success cases for fast return
- time: O(n), loop over all elements in map
- space: O(n), store all elements in map & memoization set
- IMPORTANT: the problem aims to put keys of same frequency in same node! This affects the design of node
- Main a class
Node {keySet, count, last/next pointers}
- Each operation:
-
- finds target node and extract the key
-
- calculate: count +/- 1
-
- find new spot to store the key (prior positions or later positions)
-
- Be careful when handling the cases in inc() and dec()
- Map:
map<val, index>, Lis: tracks
index->value` - list maintain 用来 insert/remove/random operations.
- Remove: swap input valueIndex & tialIndex = list.size() -1.
- list.remove(object) 应该是要O(logn) 做一个search的.
- list.remove(list.size() - 1) is cheapter
- 读懂题目, 注意average 和 window 的处理.
- 简单的queue.size() comparison
- Note: if we it is calculate moving-window-product, better to use deque :)
- Sliding window?
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
if (queue.size() > size)
- Though, the solution must use a data structure to store data; it is not the traditional sliding window type of
left/right
pointer problem
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
- Trie Structure:
- trace the char to children node: Map<char, TrieNode>
- boolean isEnd to indicate if there is string end with this node
TrieNode {boolean isEnd; Map<Character, TrieNode> children}
.
- No need to store letter c in TrieNode
- HashMap构建Trie. Trie三个Method:
-
- Inset: 加 word
-
- Search: 找word
-
- StartWith: 找prefix
- 只有两条children的是binary tree. 那么多个children就是Trie
- 那么没有left/right pointer怎么找孩子?
- 用HashMap,以child的label为Key,value就是child node。 HashMap走位
- node里的char在这是optional. 只要在每个TrieNode里面用map存储向下分布的children就好了.
- 另外有种题目,比如是跟其他种类的search相关,在结尾要return whole string,就可以在node里存一个up-to-this-point的String。
- 如果是遇到一个一个字查询的题,可以考虑一下。
- 构建TrieNode的时候要注意:如何找孩子?如果是个map的话,其实就挺好走位的。
- 而且,每个node里面的 char 或者string有时候用处不大,
- 可以为空。但是有些题目,比如在结尾要return一些什么String,就可以在end string那边存一个真的String。
- one to keep regular elements
- one to repat the max at current stack level
- time: O(n) for popMax() and O(1) for the rest operations
- space: O(n)
- Reference: https://leetcode.com/problems/max-stack/solution/
- Use TreeMap to store <Int, List of Nodes>, which gives: O(logN) insert, delete and find MAX
- Key reason to use
DoubleLinkedList
is to perform O(1) removal forpopMax()
- The problem becomes finding the target value & remove from DoubleLinkedList
- time: O(1) for popMax() and O(logN) for the rest
- space: O(n)
- Option1: always set integer on top of the stack everywhere
- if not, poping stack until the top is integer
- code is easy
- Option2: in hasNext(), faltten the list in stack
- 用queue to store all items. Kinda hack. Defeat the purpose of the problem.
- Super fast to query next(), however, needs to holds everything in memory
- O(n)
- 跟Coins in a line I 不一样: 每个coin的value不同.
- 用到MiniMax的思想, 这里其实是MaxiMin. Reference: http://www.cnblogs.com/grandyang/p/5864323.html
- Goal: 使得player拿到的coins value 最大化.
- 设定dp[i]: 从index i 到 index n的最大值. 所以dp[0]就是我们先手在[0 ~ n]的最大取值
- 于此同时, 你的对手playerB也想最大化, 而你的选择又不得不被对手的选择所牵制.
- 用MaxiMin的思想, 我们假设一个当下的状态, 假想对手playerB会做什么反应(从对手角度, 如何让我输)
- 在劣势中(对手让我输的目标下)找到最大的coins value sum
- Reference里面详细介绍了表达式如何推到出来, 简而言之:
- 如果我选了i, 那么对手就只能选(i+1), (i+2) 两个位置, 而我在对方掌控时的局面就是min(dp[i+2], dp[i+3])
- 如果我选了i和(i+1), 那么对手就只能选(i+2), (i+3) 两个位置, 而我在对方掌控时的局面就是min(dp[i+3], dp[i+4])
- 大家都是可选1个或者2个coins
- 目标是maximize上面两个最坏情况中的最好结果
- 更加简化一点: 如果我是先手, dp[i]代表我的最大值.
- 取决于我拿了[i], 还是[i] + [i+1], 对手可能是dp[i + 1], 或者是dp[i+2]
- 其实dp[i] = Math.max(sum - dp[i + 1], sum - dp[i + 2]);
- 这里的sum[i] = [i ~ n] 的sum, 减去dp[i+1], 剩下就是dp[i]的值没错了
- 这个做法是从最后往前推的, 注意initialize dp末尾的值.
- dp = new int[n + 1]; dp[n] = 0; // [n ~ n]啥也不选的时候, 为0.
- sum = new int[n + 1]; sum[n] = 0; // 啥也不选的时候, 自然等于0
- 然后记得initialize (n-1), (n-2)
- 跟Coins in a Line II 一样, MaxiMin的思想: 找到我的劣势中的最大值
dp[i][j] 代表在[i,j]区间上 选手最多能取的value 总和
- 同样, sum[i][j]表示[i] 到 [j]间的value总和
- 对手的最差情况, 也就是先手的最好情况:
- dp[i][j] = sum[i][j] - Math.min(dp[i][j - 1], dp[i + 1][j]);
- 这里需要search, 画出tree可以看明白是如何根据取前后而分段的.
- 因为是看区间[i,j]的情况, 所以可以想到是区间 DP
- 这个方法需要复习, 跟数学表达式的推断相关联: S(x) = - S(y) + m. 参考下面的公式推导.
- dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x).
- 其中一个S(x) = dp[i][j] = a[i] - dp[i + 1][j]
- m 取在开头, m 取在末尾的两种情况:
- dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}
- len = 1, 积分就是values[i]
- 最后判断 dp[0][n] >= 0, 最大数字和之差大于0, 就赢.
- 时间/空间 O(n^2)
- S(x) = X - Y, 找最大数字和之差, 这里X和Y是选手X的总分, 选手Y的总分.
- 对于选手X而言: 如果S(x)最大值大于0, 就是赢了; 如果最大值都小于0, 就一定是输了.
- 选手Y: S(y)来表示 对于Y, 最大数字和之差. S(y) = Y - X
- 根据S(x) 来看, 如果从 数字和X里面, 拿出一个数字 m, 也就是 X = m + Xwithout(m)
- S(x) = m + Xwithout(m) - Y = m + (Xwithout(m) - Y).
- 如果我们从全局里面索性去掉m, 那么 S(y'') = Y - Xwithout(m)
- 那么推算下来: S(x) = m + (Xwithout(m) - Y) = m - (Y - Xwithout(m)) = m - S(y'')
- 在这个问题里面, 我们model X 和 Y的时候, 其实都是 dp[i][j], 而区别在于先手/后手.
- 将公式套用, 某一个S(x) = a[i] - dp[i + 1][j], 也就是m=a[i], 而 S(y'') = dp[i + 1][j]
- 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维.
- 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.
- Trick: 利用差值公式, 推导有点难想到.
- 找出[i, j]区间内的性质: dp[i][j]下标表示区间范围 [i, j]
- 子问题: 砍头, 砍尾, 砍头砍尾
- loop应该基于区间的length
- template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;
- 要赢, 必须保证对手拿到棋盘时, 在所有他可走的情况中, '有可能败', 那就足够.
- 设计dp[i]:表示我面对i个coins的局面时是否能赢, 取决于我拿掉1个,或者2个时, 对手是不是会可能输?
- dp[i] = !dp[i - 1] || !dp[i-2]
- 时间: O(n), 空间O(n)
- 博弈问题, 常从'我的第一步'角度分析, 因为此时局面最简单.
- 著名Nim游戏
- 写一些,发现n=4,5,6,7,8...etc之后的情况有规律性: 谁先手拿到4就输了.
- 最终很简单n%4!=0就可以了, time, space O(1)
- 正规地找规律做, 就跟 coins in a line 一样, 按照先手后手来做
- 可以rolling array 优化空间
- Time O(n), 当然啦, 这个题目这样会timeout, 可以使用brainteaser的做法写出结果.
- An efficient implementation of a priority queue.
- The linear hash function monotonically maps keys to buckets, and each bucket is a heap
- https://xlinux.nist.gov/dads/HTML/hashheap.html
- Heap用的不多. 得用一下, 才好理解. 通常default 的PriorityQueue就是给了一个现成的min-heap:
- 所有后面的对应element都比curr element 小。
- Heapify里面的siftdown的部分:
- 只能从for(i = n/2-1 ~ 0), 而不能从for(i = 0 ~ n/2 -1): 必须中间开花,向上跑的时候才能确保脚下是符合heap规则的
- 确保在heap datastructure里面curr node下面的两个孩子,以及下面所有的node都遵循一个规律
- 比如在这里,若是min-heap,就是后面的两孩子都要比自己大。若不是,就要swap。
- for each element A[i], we will get A[i * 2 + 1] >= A[i] and A[i * 2 + 2] >= A[i].
- siftdown时:在curr node和两个child里面小的比较。如果的确curr < child, 搞定,break while.
- 但若curr 并不比child小,那么就要换位子,而且继续从child的位子往下面盘查。
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
- 递归的终点:list.zie() == 3, 解决最后一段
- 递归在一个index上面, pass s.toCharArray()
- validate string要注意leading '0'
- 注意: 递归的时候可以用一个start/level/index来跑路
- 但是尽量不要去改变Input source, 会变得非常confusing.
- note: code有点messy, 因为要考虑IP的valid情况
- 那个'remainValid', 其实是一个对于remain substring的判断优化, 不成立的就不dfs了
- curr player 每走一步, 就生成一种新的局面, dfs on this
- 等到dfs结束, 不论成功与否, 都要backtracking
- curr level: 把"++" 改成 "--"; backtrack的时候, 改回 '--'
- 换成boolean[] 比 string/stringBuilder要快很多, 因为不需要重新生成string.
- ++ 可以走 (n - 1)个位置:
- T(N) = (N - 2) * T(N - 2) = (N - 4) * (N - 2) * T(N - 4) ... = O(N!)
- 做一个String s的 replica: string or stringBuilder
- 每次dfs后, 然后更替里面的字符 "+" => "-"
- 目的只是Mark已经用过的index
- 真正的dfs 还是在 original input string s 身上展开
- 每次都重新生成substring, 并不是很efficient
- 保证p1能胜利,就必须保持所有p2的move都不能赢
- 或者说, 在知道棋的所有情况时, 只要p2有一种路子会输, p1就一定能走对路能赢.
- 同时,p1只要在可走的Move里面,有一个move可以赢就足够了。
- p1: player1, p2: player2
- 需要Game Theory的功底, Nim game. https://www.jiuzhang.com/qa/941/
- http://www.1point3acres.com/bbs/thread-137953-1-1.html
- TODO: https://leetcode.com/problems/flip-game-ii/discuss/73954/Theory-matters-from-Backtracking(128ms)-to-DP-(0ms)
- 找到开头的字母, 然后recursively DFS 去把word串到底:
- 每到一个字母, 朝四个方向走, 之中一个true就可以.
- Note:每次到一个字母,mark一下'#'. 4个path recurse回来后,mark it back.
- 用一个boolean visited[][]
- Use hash map, key = x@y
- for loop, recursive (dfs).
- 每个item用一次, 下一个level dfs(index + 1)
- Combination DFS. 画个图想想. 每次从1~n里面pick一个数字i
- 因为下一层不能重新回去 [0~i]选,所以下一层recursive要从i+1开始选。
- 相比之前的implementation, 有一些地方可以优化:
-
- Backtracking时候, 在board[][] 上面mark就可以, 不需要开一个visited[][]
-
- 不需要implement trie的所有方程, 用不到: 这里只需要insert.
- 普通的trie题目会让你search a word, 但是这里是用一个board, 看board的每一个字母能不能走出个Word.
- 也就是: 这里的search是自己手动写, 不是传统的trie search() funcombination
-
- TrieNode里面存在 end的时候存string word, 表示到底. 用完了 word = null, 刚好截断重复查找的问题.
- Build Trie with target words: insert, search, startWith. Sometimes, just:
buildTree(words)
and return root. - 依然要对board matrix做DFS。
- no for loop on words. 直接对board DFS:
- 每一层,都会有个up-to-this-point的string. 在Trie里面check它是不是存在。以此判断。
- 若不存在,就不必继续DFS下去了。
- Trie solution time complexity, much better:
- build Trie: n * wordMaxLength
- search: boardWidth * boardHeight * (4^wordMaxLength + wordMaxLength[Trie Search])
- for loop on words: inside, do board DFS based on each word.
- Time cpmplexity: word[].length * boardWidth * boardHeight * (4^wordMaxLength)
- Big improvement: use boolean visited on TrieNode!
- 不要用rst.contains(...), 因为这个是O(n) 在leetcode还是会timeout(lintcode竟然可以pass)!
- 在Trie search() method 里面,凡是visit过的,mark一下。
- backtracking, using set to perform contains()
- BFS: use queue to keep the mutations
- DFS, 找准需要pass along的几个数据结构. 先
sort input
, 然后DFS - Using for loop approach: 每个dfs call是一种可能性,直接add into result.
- 为了除去duplicated result, skip used item at current level:
if (i > depth && nums[i] == nums[i - 1]) continue;
- sort O(nlogn), subset: O(2^n)
- space O(2^n), save results
- Regular BFS, 注意考虑如果让one level to generate next level
- skip duplicate:
if (i > endIndex && nums[i] == nums[i - 1]) continue;
-
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- srot O(nlogn), subset: O(2^n)
- should be same O(2^n). slower than dfs
- 在DFS种skip duplicate candidates, 基于sorted array的技巧:
- 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],
- 说明x=nums[i-1]已经在curr level 用过,不需要再用一次: [a,x1,x2],x1==x2
- i == index -> [a,x1]
- i == index + 1 -> [a,x2]. 我们要skip这一种
- 如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。
- 不能去用result.contains(), 这本身非常costly O(nlogn)
- 几遍是用 list.toString() 其实也是O(n) iteration, 其实也是增加了check的时间, 不建议
- Different from regular dfs to visit all, the robot move() function need to be called, backtrack needs to move() manually and backtracking path shold not be blocked by visited positions
- IMPORTANT: Mark on the way in using set, but
backtrack directly without re-check against set
- Mark coordinate 'x@y'
- Backtrack: turn 2 times to revert, move 1 step, and turn 2 times to revert back.
- Direction has to be up, right, down, left.
int [] dx = {-1, 0, 1, 0};
,int[] dy = {0, 1, 0, -1};
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
- 跟string相关, 写起来可能稍微繁琐一点
- 数字有 dfs([1,2,3...]) 组合方法
- operator有[
+
,-
,*
] 3种组合方法
- 注意1: 乘号要特殊处理, pass along 连乘的数字, 计算下一步乘积的时候, 要 sum - preProduct + product
- 注意2: '01' 这种数字要skip
- 注意3: 第一个选中数字不需要加操作符, 直接加进去
- Time: O(4^n), Space: O(4^n)
- T(n) = 3 * T(n-1) + 3 * T(n-2) + 3 * T(n-3) + ... + 3 *T(1);
- T(n-1) = 3 * T(n-2) + 3 * T(n-3) + ... 3 * T(1);
- Thus T(n) = 4T(n-1) = 4^2 * T(n - 1) = .... O(4^n)
- 逻辑一样, 代码更短, 只不过不做list, 直接pass
buffer + "+" + curr
- 因为每次都创建新string, 所以速度稍微慢一点. Time complexity 一样
- 用remaining sum 来检测是否满足 input path sum 条件
- 满足的时候add to result list
- 两种backtracking:
-
- backtrack 当下node, 加进list, 然后dfs. dfs结束后删掉之前加进去的元素. 非常clean.
-
- backtrack 下一个dfs level增加的value. dfs return 之后, 删掉list里面的末尾元素: 但是删掉的dfs余下的value.
- 第一种backtrack更加好掌握.
- Binary Tree的一个基本题: 找到所有满足条件的path
- 遍历到底,比较sum vs. target
- 注意divide的情况。要把遍历的例子写写
- 跟Combination Sum I, II 没什么太大区别, 只不过, 一定要用k个数字, 也就是一个for loop里面的特别条件
- 考虑input: 没有重复数字 [1 ~ 9]
- 考虑candidate重复利用: 不可以重复利用, next level dfs 时候, curr index + 1
- the result is trivial, save success list into result.
- Which one?
- worst case: tried all numbers and cannot find: O(m!), m = 9, all possible integers in [1~9]
- C(n,k), n choose k problem :
n! / (k! * (n-k)!)
=> ends up beingO(min(n^k, n^(n-k)))
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start- validate Input 是不是可以做palindromic permutation. 这个就是(Palindrome Permutation I)
- 顺便存一下permutation string的前半部分和中间的single character(if any)
- DFS 做unique permutation: given input有duplicate characters。
Build graph
:- 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先: form sequence between chars
- form graph Map<Char, List of Chars>, for topological sort usage.
- 也可以
List[26] edges
(Course Schedule problem)
Build InDegreeCountMap<Char, Count>
: based on the char diff of 2 words- 注意: indegree 是反向的 (跟 node to neighbors 相反的方式建立)
Topological Sort
, BFS:-
- use queue to find
inDegree == 0
node. It is the letter that points to others, 排在字母表前面.
- use queue to find
-
- reduce edges using Graph
map<Character, List<Character>>
(more generic than List[26], 26个字母的dictionary)
- reduce edges using Graph
-
- Edge Case:
inDegreeCountMap.size() != result.length()
: some nodes did not make it into result sequencecycle
: when inDegree of a one node would never reduce to 0, and will not be added to result- In this case, it will be treated as invalid input, and return ""
- space: O(n), n = # of graph edges
- time: O(n)
- TODO
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
- start with empty string, need to go top->bottom
- 取或者不取
(
,)
- rule: open parentheses >= close parentheses
- Note: 在DFS时 pass a reference (StringBuffer) and maintain, instead of passing object (String) and re-create every time
- time: O(2^n), pick/not pick, the decision repat for all nodes at every level
- time: T(n) = 2 * T(n - 1) + O(1) = O(2^n)
- space: < than 2^n results = O(2^n)
- figure out n=1, n=2 => build n=3, and n=4
- dfs(n-1) return a list of candidates
- add a pair of
()
to the candidates: either in front, at end, or contain the candidates - 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- Concept of Indegree:
# of incoming node that depends on me
. It is a graph ofarrows pointing inward to me
structure. - Remember: indegree是周围的node到我这里的次数count.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- Two structures:
-
- build inDegreeEdges:
List[] inDegreeEdges
: list of incoming nodes that depends onnode i
,
- build inDegreeEdges:
-
- build dependencyCount:
int[] dependencyCount
, count # of braches that curr node depends on
- build dependencyCount:
-
- any dependencyCount[node]==0, means this node is now a leaf, add to queue
- Topological Sort Process, Kahn algorithem:
- topologically process:
- add leaf node to queue, get ready to process;
- process leafNode, like cutting of leaf
- if any child node dependencyCount == 0, it is a leaf node now: add this node to queue.
- this problem aims for deteching cycle, not output final list. Simply: visit all nodes and verify cycle
- Option1: array of indegree lists, List[]
- 用 visited int[] 来确认是否有cycle. 1 means
visited
, -1 meansvisted from last dfs level
- Deteching
-1
: 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false. - dfs on curr node indegree dependencies; if all passes w/o failing, set visited[i] = 1
- Deteching
- Similarly, can use
HashMap<Integer, List<Integer>> map
to replace List[], but exact same idea.
- 用 visited int[] 来确认是否有cycle. 1 means
- Optoin2: use a struct
class Node {Boolean visiting; Map<Integer, Node> inDegreeMap}
to be more generic - topo sort may output the sort order: 1) at DFS bottom level, put record to a
stack
, 2) rst.insert(0, curr record) - 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>. Though: map may be more flexible
- 是topological sort的题目。一般都是给有dependency的东西排序。
- 最终都会到一个sink/leaf node,no further dependency, 在那个点截止
- 画个图的话, prerequisite都是指向那个sink/leaf node
- when building the inDegreeMap/inDegreeEdge: we use sink/leaf node as key/index, which pionts back to inDegree/parent nodes
- BFS: when all braches/dependency count are reduced to 0, then it is now a leaf node, ready to be used.
- DFS Insert Order: rst.insert(0, node); Assume we want leaf/node at index 0 in final output:
- the very bottom-node depends on everybody
- any visited node should be added to 0 index of the list, so it will be at tail later
- 可以top->bottom: 遍历str, validate substring(start, i); if valid, add as candidate, and dfs; backtrack by remove candidate.
- 也可以bottom->up: 遍历str, validate substring(start, i); if valid, dfs(remaining str), return list of suffix; cross match with curr candidate.
- 在遍历str的时候,考虑从每个curr spot 到 str 结尾,是能有多少种palindorme?
- 那就从curr spot当个字符开始算,开始backtracking.
- 如果所选不是palindrome, 那move on.
- 若所选的确是palindrome, 加到path里面,DFS去下个level,等遍历到了结尾,这就产生了一种分割成palindrome的串。
- 每次DFS结尾,要把这一层加的所选palindrome删掉,backtracking嘛
- 可以再每一个dfs level 算 isPalindrome(S), 但是可以先把 boolean[][] isPalin 算出来, 每次O(1) 来用
- 注意: isPalin[i][j] 是 inclusive的, 所以用的时候要认准坐标
- Calculate isPalin[i][j]: pick mid point [0 ~ n]
- expand and validate palindrome at these indexes:
[mid, mid+1]
or[mid-1][mid+1]
- Overall Space O(n^2): 存 isPlain[][]
- Time O(2^n), 每一层都在做 pick/not pick index i 的选择, 所以worst case 2^n.
- 因为我们的isPalin[][]优化了palindrome的判断O(1), 所以overall Time: O(2^n)
- Find all paths, bfs/dfs all works. dfs will be simplier to write
- Recursive:分叉. dfs.
- top->bottom: enumerate current node into the list, carry to next level, and backtrack
- top->bottom is trivial to consider: path flows from top->bottom
- time: visit all n nodes
- space: to hold all paths, O(nlogn)
- O((n-1)/2) = O(n) nodes at leaf
- O(logn) depth
- We can also take current node.left or node.right to generate list of results from the subproblem
- let dfs return list of string candidates, and we can run pair the list with currenet node, once they come back.
- TODO: can write code to practice
- Iterative, 非递归练习了一下
- 因为要每次切短list, 所以再加了一个Stack 来存level
- 单这道题用dfs更简单, 因为找的就是从头到尾的path, 是dfs的pattern
- typical recursive visit all situation
- IMPORANT, Memoization:
Map<prefix, List<suffix variations>>
to build substring segments. Reduces repeated calculation if the substring has been tried. - Realize the input s expands into a tree of possible prefixes.
- Find list of candidates from subproblem, and cross-match
- DFS returns List segments of target s: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....'
, anddict=[a,b,c,d,e,f...]
- DFS on string: find a valid prefix, dfs on the suffix, building individual candidate in list till substring exhaust.
- improvement:
- use memo to record failed case (solved the timeout issue explained below)
- use min/max to as boundary for dict check.
- core code is short; helper code is slightly longer
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
- 用dfs找所有情况, 每一个iteration, 从找一行里挑合适的点, dfs
- 选中的点加进candidate list 里面, 记得要backtracking.
- 每一个candidate都需要validation, 检查 row, col, 2 diagnal 有没有queen
- Backtracking by replacement: each row has 1 queen, so just store it in int[] columns (CC book solution)
-
- array 里面不能有 target row#
-
- diagnal. 记得公式:
- row1 - row2 == col1 - col2. Diagnal elelment.fail
- row1 - row2 == - (col1 - col2). Diagnal element. fail
- Draw a 3x3 board to test the 2 scanarios:
- (0,0) and (3,3) are diagnal
- (0,2) and (2,0) are diagnal
- Best of all recursive approaches
- iterate over nums: pick or not pick
- reduce remaining item in next level:
- option1: use queue, restrict queue size; backtrack append to queue
- option2: remove element before passing into next level; backtrack: insert back
- time O(n!): visit all possible outcome
- T(n) = n * T(n-1) + O(1)
- A bit worse than option1, uses more time:
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
contains
is nlogn time - also, each dfs, it has to iterate over entire nums list
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
- Use visited[] to track, still causes for(over n items), not efficient
- Best of all iterative approaches
- Each time pick 1 NEW element and find places to insert into candidate list:
-
- 一个一个element加进去
-
- 每一次把rst里面的每个list拿出来, 创建成新list, 然后选位置加上new element
-
- 加新元素的时候, 要在list的每个位置insert, 最终也要在原始的list末尾加上new element
-
- 还是O(n!), 因为rst insert O(n!)个permutations
- Better than the Option2/Option3 (
BFS+Queue
), because this solution does not need to check duplicates - 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍
- Time O(n!)
- Slow: checking candidate.contains() is O(logn) each time
- Almost same as Method2-Option2, but it builds remainingCandidate list on the fly list.removeall(xyz): O(n)
- Even slower than Method2-Option2
- Trie Structure:
boolean isEnd
,HashMap<Character, TrieNode> children
- trie.addWord: 没node就加,有node就移动
- trie.search: 没node就return false,有node就移动
- Alternatively, the hash can be
TrieNode[26]
a fixed size array when applicable- I like map better for the simplicity to write (w/o converting char -> index)
- Sort the list, and on same level, if last element is the same as curr, skip this recursive call
- time O(n!)
- Idea from: https://www.sigmainfy.com/blog/leetcode-permutations-i-and-ii.html
- 用到 sublist sort
- 用 swap function, 在原数组上调节出来新的permutation
- 注意: 每次拿到新的candidate, 都要把没有permutate的数位sort, 然后再开始swap.
- 这是为了确保, [j]和[j-1]在重复时候, 不用重新记录.
- Construct graph:
map<String, List<Destination>>
; sort the list of destinations. - DFS:
- with any curr city, go over the destination list:
graph.get(curr)
- add visit city to rst
- remove visited city from the desitnation list
- backtrack
- with any curr city, go over the destination list:
- NOTE:
-
- the graph allows cycle: revisiting same city. Do NOT assume no cycle
-
- it asks to us to treat
smaller lexical order city
with priority; however:
- it does NOT mean visiting
smaller lexical order city
is THE correc anser - it can be a leaf sink node of the graph and does not provide correct trip plan
- it asks to us to treat
-
- time: O(n^n). n = # of cities. worst case, each city has (n-1) edges and need to try all combinations
- space: O(n^2), there can at most be n * (n - 1) edges
- 考虑input: 没有duplicate, 不需要sort
- 考虑重复使用的规则: 可以重复使用, 那么for loop里面dfs的时候, 使用curr index i
- the result is trivial, save success list into result.
- Time and Space complexity:
- transform the analysis as for
40. Combination Sum II
- Since this problem allows reuse of elemenets, assume they exist in original input as duplicates
- time: O(k * 2^n), k = avg rst length
- space: O(k) stack depth, if not counting result size
- transform the analysis as for
- [elm] repeats 0 times: move p, j + 2
- [elm] repeats 1 or more times: need s[i] == p[i], then move s, i+1
- DFS on remaining of s and p. Analyze the different cases when next char == '*'
- End case: both i,j reached end true; or one of them reached end.
- The two different cases when given any index j on p, the p[j+1]=='*'
- TRUE:
- ignore p[j, j+1], continue from p[j+2]
- check if s[i]==p[j] or p[j]='.'; continue from s[i+1] and p
- FALSE: check i,j, and move forward with s[i+1], p[j+1]
- TRUE:
- If next p char != '*', check curr s[i] ?= p[i]
- Improvement with memo with 2D Booelan[][] memo: much faster
- memo[i][j] records result the remaining strings: s.substring(i) compare with p.substring(j)
- use
Boolean
: when memo[i][j] != null, return something!
- Two sequence, DP, find if possible to match.
- The '*' takes effect of preceding/prior element, so we can start matching from end.
- DP[i][j]: is it possible to match s[0 ~ i - 1] and p[0 ~ j - 1].
- Check last index of s and p, there can be a few possibilities:
-
- s[i-1]==p[j-1] and they are normal characters => && dp[i - 1][j - 1];
-
- p[j-1] == '.', match => dp[i - 1][j - 1]
-
- p[j-1] == '*':
- a. ignore a* => |= dp[i][j - 2];
- b. use a* => |= dp[i - 1][j];
-
- init: dp[0][j] and dp[i][0] will all be false since there cannot be any match.
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n)
, 3ms - space: O(2^n) results
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- BFS, 注意考虑如果让one level to generate next level
-
- maintain a list of Indexe to store candidate indexes.
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
-
- should be same O(2^n), but actual run time 7ms, slower
- O(n) space
- Each row has just 1 Queen value
- As CC book suggests, use
int[] columns
of length n to store all queen col positions for n rowsint[] columns
is slightly easier to backtrack by updating certain index i with new col- list will usualy has the add/remove pattern for backtracking
- 当list.size() == n 的时候,说明找到了一个Solution。
-
- dfs function (List, n)
-
- validate function
- when the input has duplicates, and want to skip redundant items? 考虑重复使用的规则: 不可以重复使用
-
- sort. 考虑input: 有duplicate, 必须sort
-
- in for loop, skip same neighbor:
if (i > index && candidates[i] == candidates[i - 1]) continue;
- 因为在同一个level里面重复的数字在下一个dfs level里面是会被考虑到的, 这里必须skip
- in for loop, skip same neighbor:
-
- the result is trivial, save success list into result.
- Time complexity: O(k * 2^n), k = average result length
-
- Assume on average, there are k elements in result
-
- Since element can be used ONLY once, so the total # of solutions can be
C(n, k)
:pick k out of n
- Since element can be used ONLY once, so the total # of solutions can be
-
- Now let k be any number [0, n], so total # of solutions can be:
C(n,0) + C(n,1) + C(n,2) + ... C(n,n) = 2^n
- Now let k be any number [0, n], so total # of solutions can be:
-
- Now:
the new ArrayList<>(list)
takes average O(k) time
- Now:
- Total: O(k * 2^n)
-
- Space: O(n), stack depth, if not counting results size
- build candidate into dfs: treat each list candidate as success, add to rst
- remove last item from the candidate, try to add factor to it, and supply it with remain element
- backtrack after dfs
- Check if the number can be devided by [2, sqrt(n)], return a list of possible factors. Only check till
Math.sqrt(n)
- build suffixes: use the factor to devide last element of list and replace last element
- build candidate: replace last element of the queue item with new list of suffixes; add to rst
- add success item back to queue: in case last element can be simplified
- remove dupilcates: since we start factor from [2, sqrt(n)], the final factor list should be ascending!!
- time: O(x), x is the # of results
- space: O(y), y is all ongoing candidates in queue
- 原本想按照corrdiante dp 来做, 但是发现很多问题, 需要track 不同的 possible previous starting spot.
- 根据jiuzhang答案: 按照定义, 用一个 map of <stone, Set<possible # steps to reach stone>>
- 每次在处理一个stone的时候, 都根据他自己的 set of , 来走下三步: k-1, k, or k+1 steps.
- 每次走一步, 查看 stone + step 是否存在; 如果存在, 就加进 next position:
stone+step
的 hash set 里面 dp.put(stone, new HashSet<>())
mark 每个stone的存在dp.get(0).add(0)
init condition, 用来做 dp.put(1, 1)- 最终做下来思考模式, 更像是BFS的模式: starting from (0,0), add all possible ways
- 然后again, try next stone with all possible future ways ... etc
- 特点1: 所有给出的点(再找出没有specify的对角线点), 如果最后组成perfect rectangle, 都应该互相消除, 最后剩下4个corner
- 特点2: 找到所有点里面的min/max (x,y), 最后组成的 maxArea, 应该跟过程中accumulate的area相等
- 特点1确保中间没有空心的部分, 保证所有的重合点都会互相消除, 最后剩下4个顶点
- 特点2确保没有重合: 重合的area会最终超出maxArea
-
- store in
Map<y, set<x>>
, 2. iterate over map, check head,tail against the mid point
- store in
- 很好的细节题目:
-
- 除以2, 需要存double
-
- (问面试官)可以有重复的点! 所以track
set<x>
- (问面试官)可以有重复的点! 所以track
-
- 处理 left==right时候, 就当做两个点来处理.
-
- 存进set里面没有sort, 但是最后做check的时候, 需要sort list
- 时间: visit all nodes 两遍, O(n)
- 要想看连续元素, 必须要num++, num--这样搜索
-
- 需要O(1)找到元素
-
- 需要简单快速找到 num - 1, num + 1.
- 如果用min,max开array, 耗费空间
- 用HashSet来存, 用set.contains() 来查找 num - 1, num + 1 存在与否
- for loop. O(n)
- 里面的while loop 一般不会有O(n); 一旦O(n), 也意味着set 清零, for loop也不会有更多 inner while 的衍生.
- overall O(n) 时间复杂度
- 最终是要把相连的元素算一下总长, 其实也就是把元素group起来, 相连的group在一起, 于是想到UnionFind
- 这里用到了一个
int[] size
来帮助处理合并的时候parent是哪个
的问题: 永远往group大的union里去 - main function 里面, 有一个map来track, 每个元素, 只处理1遍.
- union的内容: current number - 1, current number + 1
- https://www.jianshu.com/p/e6b955ca208f
- Union Find 在index上做好像更加容易
- 其他union find function:
boolean connected(a,b){return find(a) == find(b)}
- PriorityQueue排序+分布排列的一个经典用法.
- Count frequency and store in pq.
- Consume element of pq for k rounds, each time pick one element from queue.
- Exception: if k still has content but queue is consumed: cannot complete valid string, return "";
- space, O(n) extra space in sb, O(26) constant space with pq.
- time: O(n) to add all items
- count[] = int[256], 不需要
c-'a'
- boolean visited[]: 一旦一个字母固定了位置后, 再次遇到时候, 直接跳过用过的character
- 如果tail字母可以变小, 那就delete掉tail, 重新接上新字母 (前提条件: 去掉的字母后面还会再出现, set visited[tail] = false)
- Space: O(1) count[], visited[].
- Time: Go through all letters O(n)
- Use stack instead of stringBuffer: keep append/remove last added item
- However, stringBuffer appears to be faster than stack.
- 用HashMap, 理解题目规律,因为重复的计算可以被覆盖,所以是个优化题。没有什么太大的悬念和意义.
- 消灭重合点:
- 如果process当下col, 其实要减去过去所有加过的row的交接点。。。
- 再分析,就是每次碰到row 取一个单点, sumRow += xxx。
- 然后process当下col时候, sum += colValue * N - sumRow. 就等于把交叉所有row(曾经Process过的row)的点减去了。很方便。
- 最后read in 是O(P), process也是O(P).
- 如果底边上的一个value==0, 那么算作没有height(以这个底边做rectangle, value==0的位置是空中楼阁, 不能用)
- 如果底边上的value==1, 那么就把上面的height加下来, 做成histogram
-
具体看thoughts, 几种不同的方式使用map
-
regular object map
: map of <key, item>, whereitem : {int val; int count}
-
Use a Map<frequency count, doubly-linked node> to track the frequency
-
Track constant capacity, and minimum frequency
-
Every get(): update all frequency map as well
-
Every put(): update all frequency map as well, with optional removal (if over capacity)
-
Original post: http://www.cnblogs.com/grandyang/p/6258459.html
-
TODO: one doubly linked list might be good enough to replace below:
-
frequency list map
: map of <frequency count, List>, where the list preservesrecency
-
item location in frequency map
: map of <key, int location index in list>: -
index relative to the item in a particular list, not tracking which list here
- 明白hashCode() function的意义: 拿到hashKey的时候, 用hashKey%capacity 来做hash code
- hashcode就是hash map里面的index
- 明白collision handling 的方式, 和如何double capacity而rehashing
- 都是基本操作, 概念实现
-
- 利用2Sum的原理,把4Sum分为连个2Sum。左一个pair,右一个pair,每个pair里面放2个数字。
-
- 以一个点,i,作为分界口,也要列举出所有i之前的pair,作为基础。
-
- 再尝试从所有i+1后面,找合适的2nd pair。
- Time: O(n^2 * x), where x = # of candidates, still slow
- 可以用HashSet, 可以直接比较list里面每一个元素, 保证set不重复.
- Previous Notes: 在造class Pair时候,要做@override的function: hashCode(), equals(Object d). 平时不太想得起来用。
- 参见 http://lifexplorer.me/leetcode-3sum-4sum-and-k-sum/
- 3Sum外面再加一层. 参考3Sum. 时间O(n^3)。 但此方法在k-sum时候,无疑过于费时间. O(n^k)
- 每一个word, 都可以拆分成 front + mid + end. 如果这个word + 其他word可以组成palindrome,那就是说
- 砍掉 (mid+end), front.reverse() 应该存在 words[] 里面.
- 砍掉 (front+mid), end.reverse() 应该存在 words[] 里面.
- 我们用HashMap存所有的<word, index>, 然后reverse, 找配对就好.
- 如果有 empty string "", 那么它跟任何一个palindrome word, 都可以配对, 并且根据位置前后变换, 凑成2份 distinct indexes.
- 这样就有了那个
if (reverseEnd.equals("")) {...}
的logic. - 注意: 虽然在处理砍头/砍尾的两个 for loop里面都在根据 empty string 重复记录, 但因为 "" 自己本身不能作为起点, 所以overall只会在被其他palindrome配对时记录一次.
- hash table solution not passing
- Find O(n) solution
- 与其他Majority Number一样。
- 出现次数多余1/k,就要分成k份count occurance.用HashMap。 存在的+1;不存在map里的,分情况:
- 若map.size() == k,说明candidate都满了,要在map里把所有现存的都-1;
- 若map.size() < k, 说明该加新candidate,那么map.put(xxx, 1);
- 最后在HashMap里找出所留下的occurance最大的那个数。
- 但这样的worst case是 O(nk)
- 做text的search, 毋庸置疑要用Prefix tree, trie.
- �Trie造好之后, 做prefix search, 然后DFS/BFS return all leaf items. [high runtime complexity]
- 在TrieNode里面存所有的possible words. [high space usage]
- in memory space 应该不是�大问题, 所以我们可以选择 store all� possible words
- �Store MinHeap with TrieNode: 因为会不断搜索新此条, 同样的prefix (尤其是在higher level), 会被多次搜索.
- [�complexity: need to update heaps across all visited TrieNodes once new sentence is completed]
- Compute MinHeap on the fly: 当然我们不能每次都来一个DFS�不然会很慢, 所以就必须要store list of possible candidates in TrieNode.
- 这里就用到了
Top K Frequent Words
里面的Map<String, freq>
, 这样O(m) 构建 min-heap其实很方便. - 每次
#
后 标记一个词条被add进search history. 那么就要insert it into trie
. - 这一条在最后遇到
#
再做就可以了, 非常简洁 - Trie Prefix Search + maintain top k frequent items
- use
Map<preSum value, index>
to store inline preSum and its index. -
- Build presum incline
-
- Use map to cache current preSum value and its index:
Map<preSum value, index>
- Use map to cache current preSum value and its index:
-
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
(preSum - k)
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
-
- Use the calculated preSum candidate to find index
-
- Use found index to calculate for result. ex: calculate range.
- 算出一个右下角点(i,j)到(0,0)的大小: 上一块 + 左一块 + curr node - overlap area
- preSum[i][j]: sum from (0,0) to (i-1,j-1)
- same approach as
subarray sum
: use hashmap to store diff->index; if diff re-appears, that means sum of 0 has occurred - sequence of calculation: 1. iterate over start row. 2. iterate over end row. 3. iterate over col number (this is where hashmap is stored based on)
- the iteration over col is like a screening: find previous sum and determine result
- Note: 其实并没有真的去找
== 0
的解答,而是根据特性来判断剩下的/后来加上的一定是0
- 简单算出abbreviatioin
- 检查abbr是否存在; 如果存在, 是不是input word本身.
- Find the vertical line (x-coordinate of the grid), where most gaps are found.
- Each gap has (x,y) coordinate
- Create
map<x-coordinate, #occurrance>
, and maintain a max occurance. - 计算: x-coordinate:
x = 0; x += brick[i] width
- Eventually: min-crossed bricks = wall.lenght - maxOccurrance
- 分析题意, 找到题目的目标
- 虽然Map自己不能有sort的规律, 但是可以maintain global variable
- store map as array:
Entry<K,V>[] table;
- store entry as linked list:
public Entry(K key, V value, Entry<K,V> next)
- compute hashKey:
Math.abs(key.hashCode()) % this.capacity
- Handle collision:
-
- Check if duplicate (matching key), if so, replace and return
-
- Check through the linked list, find find duplicate (matching key), replace and return.
-
- If no duplicate, add the entry to the tail
- Find item: compute hashKey -> find linked list -> iterate over list to find a matching key.
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start- shit by offset.
int offset = s.charAt(0) - 'a';
- increase if less than 'a':
if (newChar < 'a') newChar += 26;
- 相同shift规则的string, 能被推算到同一个零起始点,就是共同减去一个char,最后就相等。以此作为key,用HashMap。一目了然。
- 记得根据题目意思,一开始要String[] sort一下。
- 因为
前两个digit可以uniquely identify
一个node, 所以可以把前两个digit作为key, 定位node. - 特点: 比如考虑root, 有 n 个leaf, 就会加 n 遍root, 因为有 n 个 unique path嘛.
- 实现: 每个node, 上来先把curr value加进sum; 只要有child, 到这个node位置的以上path sum 就要被重加一次.
- format: depth.position.value. (on same level, position may not be continuous)
- approach: map each number into: <depth.position, value>, and dfs.
- Start from dfs(map, rootKey, sum):
-
- add node value to sum
-
- compute potential child.
-
- check child existence, if exist, add sum to result (for both left/right child). Check existence using the map.
-
- also, if child exist, dfs into next level
- Space, time O(n)
- <Integer,List>
- 存最长值, 最后map.get(max)
- 存在int[26], Arrays.toString(arr) 就是 string key: character frequency map
- anagram都有一样的key, 存进hashmap<string, list of anagrams>
- output anagrams
- HashMap 的做法. sort每个string, 存进HashMap, 重复的就是anagrams,最后输出。
- toCharArray
- Arrays.sort
- Stirng.valueOf(char[])
- 时间nLO(logL),L是最长string的长度。
- Arrays.toString(arr)的做法。arr是int[26], assuming only have 26 lowercase letters.
- Count occurrance, 然后convert to String,作为map的key.
- Time complexity: nO(L)
- 另一种做法:http://www.jiuzhang.com/solutions/anagrams/
-
- take each string, count the occurrance of the 26 letters. save in int[]count.
-
- hash the int[] count and output a unique hash value; hash = hash * a + num; a = a * b.
-
- save to hashmap in the same way as we do.
- 这一步把for s: strs 里面的时间复杂度降到了O(L). L = s.length().
- Need to work on the getHash() function.
- 时间变成n*O(L). Better.
- 这个题有个奇葩的地方, 每个node还有一个parent, 所以可以自底向上.
- save visited in hashset. 第一个duplicate, 就是A B 的 lowest common ancestor
- 自底向上。利用parent往root方向返回
- 把所有parent存下来, 然后在两个list里面找最后一个 common node
- 无法从root去直接搜target node 而做成两个list. 因为根本不是Binary Search Tree!
subarray sum equals k
的简单版: k = 0- 求preSum, 然后不断check
map.containsKey(preSum - k)
. - 如果
priorSum = preSum - k == 0
, 说明 [priorSum.index + 1, curr index] 就是我们要找的这一段
- 求preSum, 然后不断check
- 分析出,如果sum[0
a]=x, 然后sum[0b]=x, 说明sum[a+1 ~ b] == 0 - 用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组.
-
解释Hash怎么做.
-
Hash function例子:
-
hashcode("abcd") = (ascii(a) * 33^3 + ascii(b) * 33^2 + ascii(c) *33^1 + ascii(d)*33^0) % HASH_SIZE
-
用到的参数比如: magic number 33, HASH_SIZE.
-
Hash的意义是:给一个string key, 转换成数字,从而把size变得更小。
-
真实的implementation还要处理collision, 可能需要design hash function 等等。
- hashRst = hashRst * 33 + (int)(key[i]);
- hashRst = hashRst % HASH_SIZE;
- 原因是,hashRst会变得太大,所以不能算完和 再 %...
- 用HashSet存visited row/col/block.
- 在nest for loop里面validate row,col,and block.
- Special: validate block要利用i 和 j 增长的规律
- i, j are [0~n) can build block boundary in a for loop:
int c = 3 * (i % 3) + j % 3;
//make use of how i and j increasesint r = 3 * (i / 3) + j / 3;
- 单独做block validation: validate block的时候虽然看到了4层for. 其实也就是n^2
- 可能代码稍微复杂一点
- map: avoid duplicate message, records timestamp for validation
- time: O(1)
- space: O(n)
-
- keep a trimmed queue and set (all tasks to be within 10 secs);
-
- use set to O(1) check if incoming message exists.
- time: O(x), trimQueue()
- space: O(n)
- Use HashMap to store <num, freq>
- Bucket
List<Integer>[]
: stores <count, list unique element with that count>- Size of the data structure will be uniqe item size.
- The bucket[i] stores item at frequency i
- Simply loop from bucket.length -> 0, when bucket[i] not null, add to result.
- Solid O(n)
- Use regualr priorityQueue to sort by frequency ascendingly
- the queue.peek() record has lowest frequency, which is replacable
- Always only maintain k elements in the queue, so sorting is O(logk)
- IMPORTANT: remember to
rst.add(0, x)
for desired ordering - time faster than maxHeap: O(nlogk)
- option1: just use
map<num, freq>
; option2: useclass Record {int num; int freq}
- 题目有提醒: 必须beetter than O(nLog(n)).
- max heap approach stores all nodes: it is wrong
- even though freq count size m < n, but it can be m == n. ALL unique.
- then it is O(nlogN) again.
- therefore, storing all items into pq is INCORRECT.
- mark the char position
- check adjacent words
- Optimization
- a) If s1 equals s2, just return true, no need to continue.
- b) if s2 (app) is a substring of s1(apple), just return false.
- Use set to check
- Verify duplicates at end rst
- similar to Intersection of Two Sorted Arrays
- Start from front/back, process 1 item at a time
- if match, move all pointers
- Optoin1: check from back
- Optoin2: check from frotn
- 用个char[]存string的frequency.
- 2 pointer:
- move
end
to find a valid window; - once valid inwindow found: now move
start
to narrow down to minimum window. - once window invalid, continue moving
end
and repeat last 2 steps
- move
- HashMap的做法比char[]写起来要复杂一点, 但是更generic
- Almost identical approach as in
438. Find All Anagrams in a String
- use sliding window template:
-
- extend right pointer and reduce char count
-
- process when count == 0
-
- contract/shrink left side
-
- special on the
3) step
:- there is no hard length limit in this problem: in fact, the goal is to find the shortest length
3) step
now apperas in thewhile(counter == 0)
loop- shrink the left side of the window as long as counter == 0, until we break the
counter==0
balance.
- time: O(n) one pass
- space: O(1), freq[256] can be ignored.
- Two Pointers, use 1 char freq map + counter to determine valid state
- Inspired by: https://leetcode.com/problems/minimum-window-substring/discuss/26808/Here-is-a-10-line-template-that-can-solve-most-'substring'-problems
- Idea: use freqMap and counter to maintain a valid substring range, use two pointers to iterate; reduce to
counter==0
which is the valid substring state. - Steps:
-
- build valid freq count map based on target string
-
- use end index [0~n) to find valid char and reduce counter to find valid range
-
- count==0 gives valid range: process; then
map[s.charAt(start++)]++ == 0
to break the peace
- count==0 gives valid range: process; then
-
- Explain
if (map[s.charAt(start++)]++ == 0) counter++
:- when
count != 0
,map[s.charAt(end++)]--
reduces freq regardless of what char it visits (it can be ANY char, rather than T characters) - when
count == 0
,map[s.charAt(start++)]++
increases freq regardless of what char that is.- if
map[s.charAt(start)] == 0
: it is a T character being reduced to 0 previously (so we can break the balance on this char) - YES, map has other index that has 0 freq: however,
start
ONLY covers indexes thatend
has stepped through :)
- if
- when
- time: O(n)
- space: O(1)
- much faster than method2: skip the O(256*n) comparison logic.
- Note: from the concept, it is the reversed thinking of method2.
- Use 2 char freq maps: source/target.
- target map: fixed freq map, used for comparision
- source map: attempt to build a valid freq map on the fly
- two pointers:
- use index
start=[0, n)
as start index of source candidate - have a end pointer that will attempt to as far as possible to find 1st valid sequence
- use index
- time: have double while loop, but still O(n), why?
- end pointer will at most reach full length n, only once
- start pointer iterate source strichtly once O(n)
- overall, it will be O(n)
- space: O(1), only used a constant char[256]
- Option2: use map, a bit more generic
- Prep: 存Map<word, index list>
- Process: 相继从两个 index list 里面拿出 p1,p2
- 根据index的大小, 移动双指针: try to move the pointers closer; always calculate diff
- Optionally: if one list is much larger, do binary search on the larger list
- the special requirement causes: we have to track exact position of nodes
- Using
Node {int offset, level, val}
andMap<offset, Map<level, List<Val>>>
:- set all nodes to its correct position
- output all together
- the
max/min
offset allows us to loop over the map in a ordered manner (save efforts of sorting) - time: O(n) to mark all nodes at correct spot, but
O(nlogn)
to sort the vertical array - space: O(n), mark all nodes in the nested map
- A (offset, level) has 2 nodes: use nested
Map<offset, Map<level, List<Val>>>
to track nodes - Also need a
class Node{int offset; TreeNode node}
to build queue:- need
offset
: queue at each level cannot derive level index - need
TreeNode
:Node
extends originalTreeNode
so we can queue it.
- need
- lots code to write due to the
class Node
for BFS -
=2的没有除自己和1以外公约数的数。
- 还有另外一个定义方法: 这个n,有没有小于n的一个i, 而达到: i * i + # of i = n. 如果有,那就不是 prime
- 一个boolean长条,存isPrime[]。 然后从i=2, 全部变true.
- hash key: the number itself
- 然后利用这个因子的性质,非prime满足条件: self*self, self * self + self ... etc.
- 所以就check每一个j, j+i, j+i+i, 然后把所有non-prime全部mark成false.
- 最后,数一遍还剩下的true个数就好了
- use stack to hold all elements
- keep poping if
stack.peek() < num
- use map to record (top, num)
- keep poping if
- time O(n), run through base once and sub-sequence once
- space O(n), stack, map
- O(n) space, O(n^2) time worst case
- DFS can find # of island, just like
200.Number of Islands
, aim to count total - We need to map same-shap land
- One approach: print the footprint starting from coordinate (0, 0)
- Another approach: print the actual island in its boundary, like a QR code. (too hard to code, skip)
- Footprint approach:
-
- always assume a newly found islands starts from (0, 0)
-
- take 4 direction from init pos and keep printing the footprint
-
- Since we always visit nodes from top->right->nextrow, we always visit top-left cornor of a new island, and the footprint will be identical
- Otherwises, if needed, we can sort the footprint and output the hash
-
- time: O(n), visit all
- space: O(n), store footprint, and dfs stacks worst case visit all nodes
- store
Map<Word, Longest Chain Length>
- sort all words, try from short to long: short word will be calculated first to serve later words as candidate
- time: O(nlogn)
- space: O(n)
- store
Bucket: List[17] of words
, given word size limit [0 ~ 16] - time: O(n)
- space: O(n)
- check match chars
- check unmatched chars by counting and offset their frequency
- count++ on secret chars: if secretCount is ever < 0 =>
char g
has match, then cows++ - count-- on guess chars: if guessCount is ever >0 =>
char s
has match, then cows++
- count++ on secret chars: if secretCount is ever < 0 =>
- Improvement: since all digit, use int[10] to count
- count char occurrance
- 只可以接受一个odd # appearance.
- 考虑所有 256 ASCII code, 如果还要拓展, 就用HashMap<Character, Integer>
- 注意, 不能assume lower case letter. 应该至少是所有ASCII code
- Recall matric multiplication rules: result[i][j] = sum(A-row[i] * B-col[j])
sparse matric: lots positions are zero
- 平白地写matric multiplication 没有意义, 重点就是optimization:
optimization
: for A-zero-row, and B-zero-col, there is no need to calculate, just return 0.-
- Find A-zero-rows and store in setA, same for setB
-
- during multiplication, reduce time complexity.
- Base: O(mnk), where
m = A.row
,n = B.col
,k = A.col = B.row
- 乘法规则: result[i][j] = sum(A-row[i] * B-col[j])
- A column size == B row size. 并且: 计算顺序是iterate over A column size
- IMPORANT, Memoization:
Map<prefix, List<suffix variations>>
to build substring segments. Reduces repeated calculation if the substring has been tried. - Realize the input s expands into a tree of possible prefixes.
- Find list of candidates from subproblem, and cross-match
- DFS returns List segments of target s: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....'
, anddict=[a,b,c,d,e,f...]
- DFS on string: find a valid prefix, dfs on the suffix, building individual candidate in list till substring exhaust.
- improvement:
- use memo to record failed case (solved the timeout issue explained below)
- use min/max to as boundary for dict check.
- core code is short; helper code is slightly longer
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
- 用到hashset找unique && duplicate: O(m+n)
- 可以用binary search 找数字.
- Note:binary search一定需要array sorted: nLog(m)
- Follow up of 243. Shortested Word Distance
- 特别handle
word == word1 == word2
case:- p1 and p2 will always be the same
- when
word == word1 == word2
, simply calculate distance using theold p1 or p2
withcurr index i
- The rest impl aligns with 243.
- when
word1==word2
, make usre to skipp1==p2
by increasing i or j - The rest impl aligns with 244
- Time: still O(n), but slower than Method1: 2 passes
- Space: uses extra space O(n) to hold all indexes
- 给J 和 S两个string. J里的character是unique 的珠宝, S 里面的character包含珠宝和石头. 找S里面有多少珠宝
- Basic HashSet
- DIFFERENT from sliding window for substring (
76. Minimum Window Substring
)- because this problem rquries keeping the order of characters from the target string
- Use a
backtrack mechanism
based on target matching to find closest left starting point to right
- Simple two pointers:
-
- move sIndex and tIndex: find all T chars in S, in order.
-
- backtrack tIndex to 0; backtrack sIndex to initial char match
-
- record potential min result
-
- Be VERY careful about pointer and index.
- time: O(n^2), backtrack n steps
- Since it requires order of substring,
freqMap+counter+twoPointers
approach is NOT applicable - TODO
- 按照题意, 找到第一个 first index == last index的字母.
- 用hashmap存字母的index, 有些重复字母的index就会是个list.
- 找到单一index, 结合成list, sort, return list.get(0)
- slow due
- 用了一个特别的双向的ListNode,有了head和tail,这样就大大加快了速度
- 主要加快的就是那个‘更新排位’的过程,找到item hashmap O(1), 做减法换位也都是O(1)
- Overall O(1)
- 巧妙点
-
- head和tail特别巧妙:除掉头和尾,和加上头和尾,都O(1)
-
- remove node: 把node.pre和node.next 连起来, node就自然而然的断开不要了
-
- 一旦知道怎么解决了,就不是很特别,并不是难写的算法
- moveToHead()
- insertHead()
- remove()
- Instead of building
Double Linked List
, utilize JavaDeque<E> queue = new LinkedList<>()
- works but problem:
queue.remove(E)
is O(n) - time: O(1) on average but much slower
- compare left/right
- flip number (6 and 9), and then reverse the string, see if the string is the same.
- takes more
- 每个格子 +4 个墙;
- 每个shared的墙要减去: 从每个island走去另外一个, 都-1 (最终没面墙, -2)
- 把每个block相连的block全部存在以当下block为key的list里面. 那么这里需要把2D坐标, 转化成一个index.
- 最后得到的map, 所有的key-value应该都有value-key的反向mapping, 那么就可以消除干净, 每一次消除就是一个shared wall.
- 一点点optimization: DFS去找所有的island, 如果island的图非常大, 而island本身不大,那么适合optimize.
- 但是整体代码过于复杂. 不建议写.
- Use Map<number, count > to store the inputs
- Iterate over map to find the pair
- Use Set memo to store the success cases for fast return
- time: O(n), loop over all elements in map
- space: O(n), store all elements in map & memoization set
- use hash to store <key, list of values>
- binary serach on list of values
- use hash to store <key, TreeMap<Timestamp, Value>>
- treemap.floorKey(timestamp) finds the top item below certain timestamp
- Map:
map<val, index>, Lis: tracks
index->value` - list maintain 用来 insert/remove/random operations.
- Remove: swap input valueIndex & tialIndex = list.size() -1.
- list.remove(object) 应该是要O(logn) 做一个search的.
- list.remove(list.size() - 1) is cheapter
- Approach#4 of https://leetcode.com/problems/subarray-sum-equals-k/solution/
- Hash Table two sum 思想, but to save frequency of current sum:
preSumCount<sum, count>
- for loop 从左开始积累
preSumCount<sum, count>
- derive
priorSum = sum - k
: 看看前面有多少此种sum,preSumCount.get(priorSum)
# ways to reach priorSum
gives # of ways for thatpriorSum + k = curr Sum
- therefore, count += preSumCount.get(priorSum)
- for loop 从左开始积累
- O(n) time, O(n) space
- Note: 如果需要实际index, 可以存
Map<Integer, List<Index>>
- presum: socalled
cummulative sum
- move from starting point i = [0 ~ n -1] and test each
range = [i ~ j]
- use presum to verify k:
preSum[j + 1] - preSum[i]
- time: O(n^2):
1 + 2 + 3 + 4 ... + n ~= O(n^2)
- 很巧妙地根据k range地条件
- 把HashSet里面的值控制在[i - k, i]
- 每次不断地往set里面加新元素, 从set里减去末尾index的元素
- 而set.add(x)如果遇到重复, 会return false.
- 一旦在这个length k 的 range里面, 有重复, 就符合条件.
- Time O(n)
- Time O(nm), m = # of duplicates. 太慢
- 记录每个element value的index in the list
- 一旦有重复element重复, 就把整个list of indexes 端出来, 查看有没有符合条件的: (index - i) <= k
- 方法1是限定选拔的candidate, 不合格就去掉, 那么一旦有符合条件的(duplicates), 那么一定中, 剩下的就不看了.
- 方法2是把符合条件的index找出来, 集中处理, 但是所有candidate都会选出来
- 就好像招人一样: 一种是遇到好的就停止; 第二种是看过所有人, 从中选拔最好的. 显然第一种更快.
- check 2 failture cases:
- same key, value not matching
- two key maps to same value
-
- level-traverse all nodes, 2) add node to appropriate col list(using map)
- For final output:
- Use min/max to track map keys, since the keys are continous
- Map does not provide random access; unless map key is marked with sequence i = [min, max]
- Since each vertical list is appended level by level: no need to sort during output. FAST
- time: O(n), visit all nodes
- spac: O(n) to store
- Regular DFS to traverse all nodes, and add node to appropriate col list (using map)
- Problem: DFS does not provide natural ordering for nodes on a row.
- Left side may have a super deep Right child branch, which will be added to col list first (since left side is visisted first)
- It is wrong because right branch may have nodes in same column but at higher level
- To Solve: preserve
level
for all nodes in same column
- Need to sort the final list, and slow: visit all nodes O(n) + O(KMlogM), K = # of cols, M = # of items in col
- Time: O(nLogN). O(n) + O(KMlogM), K = # of cols, M = # of items in col; in extrem, it can be a vertical line of nodes, then sort: O(nLogN)
- Space: O(n)
- Method1 and Method2 are identical to
159. Longest Substring with At Most Two Distinct Characters
. - However, time complexity for Method2 in increases to O(nk). https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- we want to do better than that (Method3)
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == k, process and record max len
-
- if map.size() > k, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(k)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(nk) to find the left-most element
- space: O(k)
- https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- as mentioned above, Method2 uses O(nk), because it takes O(k) to find head item that was inserted first
- meanwhile, we still need the hash map feature to get/put/remove last occurance of a char with O(1)
- Solution: use a LinkedHashMap:
map.entrySet().iterator()
maintains the insertion order!
- Special handling:
- since we want the
lastOccurMap
to preserve laset insertion order - we need to
remove
the char every time before put.
- since we want the
- time: O(n)
- space: O(k)
- No brain: HashSet.
- Time O(n), Space O(n)
- Arrays.sort(x): Time O(nLogN), Space O(1)
- 排序后, 重复数会排在一起, 然后 binary search
- store efficiently: use List<Map<snapId, val>>. only preserve changed itemd
- if no match, find last modifed item based on snapId, use TreeMap.floorEntry
- map.floorEntry(id) return the item.key lower or equal to id
- Utilize a
buffer: Map<Integer, Integer>
and perform atomic save - 相对暴力简洁: 找到一个value, 存一个index
- 若在HashMap里面 match 到结果, 就return HashMap里存的index.
- O(n) space && time.
- 前后++, --搜索. Sort 用时O(nlogn).
-
- 第一步 two pointer 找 value.
-
- 注意,要利用额外的空间保留original array, 用来时候找index. (此处不能用HashMap,因为以value 为key,但value可能重复)
- O(n) space, O(nlogn) time.
- Bottom-up, test simply case. Sequence DP.
- true/false problem, think about dp
- 子问题: 前i个字母, 是否可以有valid break
- check: 1) dp[j] && 2)
if substring(j, i) valid
, for all j = [0 ~ i] - dp = new boolean[n + 1]; dp[0] = true;
- test:
dp[i] |= dp[j] == true && word[j, n] in dict
. - Need iterate over i = [0 ~ n], also j = [0, i]
- When there is a way to make dp[i] == true, then break the [j ~ i] loop, move on to test dp[i++]
- Use set dict:
dict.contains()
- Improvement: O(n) to figure out max length, so we can skip some substring[j~i] dict.contains()
- overall O(n^2) time since the double for loop
- Top-Down, break into small problems: Check front subString, and put the rest substring into dfs to test
- Memoization: for tested failed substring, record and do NOT test them again.
- Same Improvement as in DP: use max/min length of dict words as boundary
- use preorder to find root, one index at a time (global index)
- use the root to divide and conquer inorder int[] to 2 sides;
- root.left = dfs(left); root.right = dfs(right)
- end stage: start == end index, create a node
- can use a map to store inorder <val, index> for O(1) find
- 和Construct from Inorder && Postorder 想法一样。
- 写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。
- 跟Convert Sorted Array to Binary Tree类似, 找到对应的index, 然后:
- node.left = dfs(...), node.right = dfs(...)
- Divide and Conquer
- optimize on finding
mid node
: given value, find mid of inorder:
- optimize on finding
- Instead of searching linearly, just store inorder sequence in
map <value -> index>
, O(1) - IMPORATANT: the mid from inorder sequence will become the main baseline to tell range:
range of subTree = (mid - inStart)
- sapce: O(n), time: O(n) access
- 例子写出来,发现可以sort以后按照定义搜索一遍。 nlogn.
- 搜索一遍时候可以优化,用binary search. 但是没意义,因为array.sort已经用了nlogn
- 题目给的规则, 从小到大排序后: 剩下的 paper
n-h
, 全部要 <= h 个 citation. - time O(nlogn), search O(n)
- 从i = 0 开始找第一个
citations[i] >= h
, 就是第一个符合 h-index 规则的paper, return h - 如果从 h = n, 每次h--; 那么
x = n - h
就是从[0 ~ n)
开始找第一个dictations[x] >= h
, 就是结果 - 同时,
dictations[x-1]
就是最后一个(dictation最多的)其余的paper. - O(n)
- Bucket sort的思想(更像是counting sort?): 过一遍 input, 把dictation value 作为 index, 分布在bucket[index]上++
- bucket[x] 是 count when # of citation == x.
- 如果 x 大于 n的时候, 就超出了index范围, 但是刚好这个问题可以包容, 把这样的情况记位在bucket[n]就可以
- 巧妙:
sum += bucket[h]
whereh = [n ~ 0]
利用了h-index的definition: - #of papers (sum of bucket[n]...bucket[0]) has more than h cidations
- 这里运用到了bucket sort的思想, 但是并不是sorting, 而h-index的定义运用的很巧妙.
- Read more about actual bucket sort: https://en.wikipedia.org/wiki/Bucket_sort
- Application: for number/character range
- Steps:
- Find range, define countArray
- Count element and record in the array
- PreSum the countArray
- Start from beginning of the array,
print & decrese count
to produce the sorted elements
- Map of nums1: <num, # appearance>
- check nums2 against nums1 map
- time:O(n + m)
- space:O(n + m)
- 存anagram
- 用 character frequency 来做unique key
- 用固定长度的char[26] arr 存每个字母的frequency; 然后再 new string(arr).
- 因为每个位子上的frequency的变化,就能构建一个unique的string
- O(nk), k = max word length
- 和check anagram 想法一样:转化并sort char array,用来作为key。
- 把所有anagram 存在一起。注意结尾Collections.sort().
- O(NKlog(K)), N = string[] length, k = longest word length
- 先排序(lexicographically), 排序以后才能逐个看是否partial string已经存在
- 用 set.contains(substring(0, n - 1)) 来查看上一步的 substring 是否存在
- 如果找到, 因为已经按照字母表排序, 找到的这个肯定是这个长度里面最符合的解答.
- 然后brutally找下一个更大的.
- Sort O(n log n), O(n) set space
- 可以先sort words Array:
- 长 string 排在前;
- 相等length, 按照dictionary order 排序
- 全部放入Trie. Trie.insert()
- 针对sorted words array, 从最长的开始找 Trie.startWith.
- 一旦找到, 就是符合题意的, 直接return.
- 注意: startWith 必须每一个node都是 isEnd, 才满足'逐个字母拼出' 的条件.
- Time: build Trie O(mn) + sort:O(nlogn) => O(nlogn)
- Space: O(mn)
- 按大小排序 -> 从最大的开始做contains()的比较 -> 结果再按照字母表顺序(lexicographically) sort一下.
- 但是Collections.sort()了两次, 而且再list.contains(), 比较慢
- A creative way of using anagram char count
hash[c] >= 0
to determine if the curr c is a target char of the deesired anagram.- because we always reduce hash[c]-- for all characters
- so only the anagram chars would be
hash[c] >= 0
after reducing.
- https://leetcode.com/problems/minimum-window-substring/discuss/26808/here-is-a-10-line-template-that-can-solve-most-substring-problems
- Slinding window always has left/right pointer:
-
- at any given time move 1 index at a time: expand right window, process rsult, shrink left window
-
- one of the basic goal is to maintain fixed window size
-
- algo:
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
hash[c] >= 0
indicates a anagram char - expand right window: move right to expand the window; ONLY when meeting a anagram char, count--
- process result: if count reduces to 0, one anagram is found
- shrink left window: if (right - left) == p.length(), drop curr left char, and move forward
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
- how could we rely on only just
count == 0
?- the hidden pre-condition is
right - left must already be p.length()
, which is validaterd in prev iteration
- the hidden pre-condition is
- time: O(n)
- space: O(1)
- count character apperance -> hash table, here just a int[26]
- use a window to record count++ and count--, in order to compare with countP
- prep the countP takes O(m) time
- time: O(n) + O(m)
- space: O(n)
- First sort all of the items together by actual val using
Node {int val, int row}
- Slinding window goal:
-
- use right to find range that touches all rows,
-
- use left to shrink the range
-
- Sliding Window Template
- move right pointer
- Counts[i] = # of elements used in left/right range
- when counts[i] == 0, countUnique++; the number of row/list being included
- when count == row size:
- processing & save shorter range by using left/right Pointers
- move left pointer; when counts[i] == 0, countUnique--
- time: O(nlogn) for initial sort and then O(n) to process
- space: O(n)
- What is hard here? To think of the idea of counting one usage of each row:
- when each all rows are used at least 1 time
- calculate the min dist
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104893/Java-Code-using-PriorityQueue.-similar-to-merge-k-array
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/solution/
- Basic Implementation of copy linked list:
- use a iterator node to iterate over the list: 遍历head.next .... null.
- use a dummy node to hold reference to the iterator node.
- Map<original, new node>: 1. avoid creating same node; 2. return the new node if existing
- 每一步都check map里面有没有head. 没有? 加上
- 每一步都check map里面有没有head.random. 没有? 加上
- Note, there is a way to skip the extra map O(n): https://leetcode.com/problems/copy-list-with-random-pointer/discuss/43491/A-solution-with-constant-space-complexity-O(1)-and-linear-time-complexity-O(N)
- However, creating a deep clone of the list is already O(n) extra space, so it is NOT effectively O(1) w/o map
- It may be beneficial, if we can not hold all nodes in memory, then the approach w/o map is more applicable.
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == 2, process and record max len
-
- if map.size() > 2, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(1)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(n)
- space: O(1)
- HashMap 存index list
- 遍历一遍数组A, 列举出所有元素
-
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- option1: dfs + rst list to carry results
- option2: Divide and Conquer, 在自己的基础上recursive, 不用helper function
- O(n) time
-
- Add root.leftPath all the way to leaf, 2) process curr 3) Move to right if applicable 4) add all right.leftPath
- O(n) time, O(h) space
- process and check at the end, or, 2) sanitize first and process assume correct input
- Aggregate map<char, count>, and sort the entry with priority queue.(Optionally, can use object
Letter {char c, int count}
) - Naturally: we want to prioritize the largest population and exhaust it first, so we want to keep it in the a buffer queue
- it is a queue, first in first out
- monitor queue size k = 2, so that it holds off the just last-processed letter for 1 unit of time
- the buffer then sends the last-process item to the main priority queue (pq will sort it again)
- Error handling: largest population may have extra letter
- the main PQ has already exhausted
- but the largest-population-letter will end up stuck in the buffer queue
- it will never be picked up again so the final result sb will be shorter than orignal string: that is the error case
- Option0. Similar to
621. Task Scheduler
:- use a buffer to hold potential letter to add back, but NOT ADD BACK YET, until k slots have been filled.
- time: O(m), m = # of unique letters
- space: O(nmLogm), n = length, pq sorting requires mlogm, we will visit all n nodes.
- put all in map<char, count>
- Sanitize the input: if certain popular char count is over (n + 1)/2, then it should fail right away, just return empty map.
- Once the input is sanitized, when building results, we can be greedy and consume most popular char and then the rest
- Int[2] can be used store char and count
- PriorityQueue can sort int[]. Okay to not specific length of int[] when defining pq.
- Alternatively, can use a Letter {char c, int count} to represent
- time O(mn)
- 构建
Map<email, email parent>
, 然后再反向整合: parent -> list of email - init with <email, email> for all emails
- 因为不同account可能串email, 那么把所有email union的时候, 不同account 的email也会被串起来
- 最终: 所有的email都被union起来, 指向一个各自union的 parent email
- UnionFind 的 parent map 可以反向输出所有 child under parent.
- 同时要维护一个 <email -> account name> 的map, 最终用来输出.
- Definitely need iterate over accounts: merge them by email.
- Account object {name, list of email}
- map<email, account>
-
- iterate over accounts
-
- find if 'account' exist; if does, add emails
-
- if not, add account to list and to map. map all emails to accounts.
- output -> all accounts, and sort emails
- space O(mn): m row, n = emails
- time O(mn)
- TODO
- If given n points, we can calculate all possible slopes. O(n^2) times
- For the two dots that generates the same slope, these dots could be on parallel slopes
- figure out how to prune the parallel dots
- GCD: greatest common divider
- Devide the x and y by their greatest common divider, such that x and y can be reduced to minimum value
- All other x and y can be reduced to such condition as well
- track the final reduced (x,y) in a map: they are the key to the count
- No need to use Map<Integer, Map<Integer, Integer>> to perform 2 level mapping; just
map<String, Integer>
, where the key is "x@y" - Goal: given a index i, want right-side closest & higer number
- Draw example: right-most number at base, and builds up monotonous stack (mountain shape)
- add smaller item on top of stack
- keep popping if peek is higher than incoming
- space: O(n), time:O(n)
- Refernece: https://leetcode.com/problems/daily-temperatures/solution/
- From right side:
-
- record tempIndex[currTemp] = i;
-
- Brutle find smallest temp index in range [currTemp + 1, 100] and record as result
-
- Similar to Binary Tree Longest Consecutive Sequence I
- 只不过可以递增递减, 还有连接上parent的方向.
- 对于任何一个节点, 都可能:
-
- 自己跟两个child链接, 成为一个sequence
-
- 左边孩子, 右边孩子各自是一个consecutive sequence, 但是不跟root相连
- main function 一开始就divide成这三份, 然后dfs
- dfs take diff == 1, diff == -1, 来做递增递减的校对.
- dfs rules:
-
- if node == null, leaf depth = 0
-
- if not consecutive, reset the depth = 0 (same for both left child, and right child)
-
- compare the leftDepth && rightDepth to find the maximum
-
- diff is the same in the same dfs loop to maintain consistant increase/decrease
- dfs的结果很可能是0, 如果没有任何结果, 那么上一层的caller depth = dfs() + 1 = 1
- 那么回归到root, dfs的结果很可能就是1.
- 可能会问: 那么在tree里面的partial sequence (不连接到root)的被忽略了?
- 这里
longestConsecutive(root.left)
就很重要了 - 这一步特地忽略掉了root, 然后走下去一层: 因为是recursive, 所以还会继续divde && conquer
- 最后, 任何一层的孩子都会被照顾到.
- 用两种recursive的方式handle skip root node的情况
- Recursive using dfs(), basically build child + parent
- Recursive using main function, but with value of child node: skipping root
- 对所给的input sum 做减法, 知道 sum 达到一个目标值截止
- 因为可以从任意点开始, 所以当sum达标时候, 需要继续recursive, 从而找到所有情况 (有正负数, sum可能继续增加/减少)
- 经典的 helper dfs recursive + self recursive
-
- helper dfs recursive 处理包括root的情况
-
- self recursive 来引领 skip root的情况.
- 与
Binary Tree Longest Consecutive Sequence II
在recursive的做法上很相似: - 利用dfs做包括root的recursive computation
- 利用这个function自己, 做
不包括root的recursive computation
- for loop, recursive (dfs).
- 每个item用一次, 下一个level dfs(index + 1)
- Combination DFS. 画个图想想. 每次从1~n里面pick一个数字i
- 因为下一层不能重新回去 [0~i]选,所以下一层recursive要从i+1开始选。
- 跟Combination Sum I, II 没什么太大区别, 只不过, 一定要用k个数字, 也就是一个for loop里面的特别条件
- 考虑input: 没有重复数字 [1 ~ 9]
- 考虑candidate重复利用: 不可以重复利用, next level dfs 时候, curr index + 1
- the result is trivial, save success list into result.
- Which one?
- worst case: tried all numbers and cannot find: O(m!), m = 9, all possible integers in [1~9]
- C(n,k), n choose k problem :
n! / (k! * (n-k)!)
=> ends up beingO(min(n^k, n^(n-k)))
- 考虑input: 没有duplicate, 不需要sort
- 考虑重复使用的规则: 可以重复使用, 那么for loop里面dfs的时候, 使用curr index i
- the result is trivial, save success list into result.
- Time and Space complexity:
- transform the analysis as for
40. Combination Sum II
- Since this problem allows reuse of elemenets, assume they exist in original input as duplicates
- time: O(k * 2^n), k = avg rst length
- space: O(k) stack depth, if not counting result size
- transform the analysis as for
- when the input has duplicates, and want to skip redundant items? 考虑重复使用的规则: 不可以重复使用
-
- sort. 考虑input: 有duplicate, 必须sort
-
- in for loop, skip same neighbor:
if (i > index && candidates[i] == candidates[i - 1]) continue;
- 因为在同一个level里面重复的数字在下一个dfs level里面是会被考虑到的, 这里必须skip
- in for loop, skip same neighbor:
-
- the result is trivial, save success list into result.
- Time complexity: O(k * 2^n), k = average result length
-
- Assume on average, there are k elements in result
-
- Since element can be used ONLY once, so the total # of solutions can be
C(n, k)
:pick k out of n
- Since element can be used ONLY once, so the total # of solutions can be
-
- Now let k be any number [0, n], so total # of solutions can be:
C(n,0) + C(n,1) + C(n,2) + ... C(n,n) = 2^n
- Now let k be any number [0, n], so total # of solutions can be:
-
- Now:
the new ArrayList<>(list)
takes average O(k) time
- Now:
- Total: O(k * 2^n)
-
- Space: O(n), stack depth, if not counting results size
- BST structure not given, use TreeSet to build BST with each node
- Every time find last/next inorder element
treeSet.lower(x)
,treeSet.higher(x)
- 一旦位置相隔(k + 1), 就满足题目条件
- O(nlogn), good enough
- Reverse the array, save days index into days[], where the new index is slot.
- days[i]: at slot i, which day a flower will be planted
- O(n)
- Needs to understand: http://www.cnblogs.com/grandyang/p/8415880.html
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
- Use priority queue to sort by customized class interval{int dist; int x, y;}.
- Sort by larger distance and then sort by start index
- seat(): pq.poll() to find interval of largest distance. Split and add new intervals back to queue.
- leave(x): one seat will be in 2 intervals: remove both from pq, and merge to a new interval.
- 主方程写出来其实很好写, 就是 split + add interval, 然后 find + delete interval 而已. 最难的是构建data structure
- seat(): O(logn), leave(): O(n)
Trick: 构建虚拟 boundary
- 如果是开头的seat, 或者是结尾的seat, 比较难handle: 一开始坐在seat=0的时候, 没有interval啊!
- Trick就是, 我们自己定义个虚拟的座位
seat=-1
,seat=N
- 一开始有一个 interval[-1, N] 然后就建立了boundary.
- 从此以后, 每次split成小interval的时候:
- 遇到
interval[-1, y]
, distance就是(y - 0)
- 遇到
interval[x, N]
, distance就是(N - 1 - x)
- 当然正常的interval dist 就是
(y - x) / 2
- distance 中间点
- Interval.dist 我们其实做的是 distance的中间点
(y - x) / 2
- 这里的dist是
距离两边的距离
而不是 x, y 之间的距离. 这里要特别注意.
- Interval.dist 我们其实做的是 distance的中间点
- TreeSet
- TreeMap<starting Pos, Interval>
- seat(): O(logn)
- find largest dist with TreeSet.first()
- break into 2 intervals; save to set and save to map
- leave(x): O(logn)
- find the interval before starting point x using TreeMap.floorEntry()
- merge and store back to set/map
- for test case it is slower than PQ, because it saves to 2 data structure
- start with considering array structure but operation are all O(n)
- what if we can easily find range, and update
- TreeSet:
- build a class
Interval {int start, end;}
- build a customized
compareTo
that sorts the interval by start at default, but sort by end if a.start==b.start - Query: TreeSet allow us to find element in O(logn)
- Add Range: finding the starting pointing takes O(logn), but update can be worst to update O(n)
- Remove Range: finding the starting pointing takes O(logn), but update can be worst to update O(n)
- build a class
- Previous solution, we use stack to hold previous cached/unprocessed items: but do we need use catch to hold them?
- If moving left:
p.val < root.val
, then root (parent of left child) is a successor candidate, so saverst = root
. - If moving right or equal:
p.val >= root.val
, the successor has nothing to do with curr node, so just directly dive into root.right. - Both iterative and recursive solution can be simplified as such.
- Iteratively search
- Still need stack to store previously unprocessed items along the path
- 画inorder图,发现规律.每个node的后继node(successor)有几种情况:
-
- node.right 是个leaf到底了。那么就return.
-
- set rightNode = node.right, 但发现rightNode has a lot left children to leaf.
-
- 比如, node.right == null, 也就是node自己是leaf,要回头看山顶找Inorder traversal规则里的下一个。
- 发现:其实就是每层都把路过的curr node放在stack里,最上面的,就是当下改return的那个successor:) Done.
- 判断当下的node是否被采用,用一个boolean来表示.
- 如果curr node被采用,那么下面的child一定不能被采用.
- 如果curr node不被采用,那么下面的children有可能被采用,但也可能略过,所以这里用Math.max() 比较一下两种可能有的dfs结果。
- dfs重复计算:每个root都有4种dive in的可能性, 假设level高度是h, 那么时间O(4^(h)), where h = logN, 也就是O(n^2)
- 并不是单纯的DP, 是在发现DFS很费劲后, 想能不能代替一些重复计算?
- 基本思想是dfs解法一致: 取root找最大值, 或者不取root找最大值
- 在root上DFS, 不在dfs进入前分叉; 每一个level按照状态来存相应的值: dp[0] root not picked, dp[1] root picked.
- Optimization: DP里面, 一口气找leftDP[]会dfs到最底层, 然后自下向上做计算
- 这个过程里面, 因为没有在外面给dfs()分叉, 计算就不会重叠, 再也不用回去visit most-left-leaf了, 算过一遍就完事.
- 然而, 普通没有dp的dfs, 在算完visited的情况下的dfs, 还要重新dfs一遍!visited的情况.
- Space O(h), time O(n), 或者说是O(2^h), where h = log(n)
- 不为状态而分叉dfs
- 把不同状态model成dp
- 每一个dfs都return一个based on status的 dp array.
- 等于一次性dfs计算到底, 然后back track, 计算顶部的每一层.
- DP 并不一定要是以n为base的. 也可以是局部的去memorize状态->value.
- 比Binary Tree Maximum Path Sum I 简单许多. 因为条件给的更多:at least 1 node + have to start from root
- root一定用到
- 3种情况: curr node, curr+left, curr+right
- 因为一定包括root, 说以从
dfs(root, sum=0)
开始, 每个level先加root, sum += root.val - Similar to Binary Tree Longest Consecutive Sequence I
- 只不过可以递增递减, 还有连接上parent的方向.
- 对于任何一个节点, 都可能:
-
- 自己跟两个child链接, 成为一个sequence
-
- 左边孩子, 右边孩子各自是一个consecutive sequence, 但是不跟root相连
- main function 一开始就divide成这三份, 然后dfs
- dfs take diff == 1, diff == -1, 来做递增递减的校对.
- dfs rules:
-
- if node == null, leaf depth = 0
-
- if not consecutive, reset the depth = 0 (same for both left child, and right child)
-
- compare the leftDepth && rightDepth to find the maximum
-
- diff is the same in the same dfs loop to maintain consistant increase/decrease
- dfs的结果很可能是0, 如果没有任何结果, 那么上一层的caller depth = dfs() + 1 = 1
- 那么回归到root, dfs的结果很可能就是1.
- 可能会问: 那么在tree里面的partial sequence (不连接到root)的被忽略了?
- 这里
longestConsecutive(root.left)
就很重要了 - 这一步特地忽略掉了root, 然后走下去一层: 因为是recursive, 所以还会继续divde && conquer
- 最后, 任何一层的孩子都会被照顾到.
- 用两种recursive的方式handle skip root node的情况
- Recursive using dfs(), basically build child + parent
- Recursive using main function, but with value of child node: skipping root
- Traverse tree: left, right
- Concept of partial compare vs. whole compare
- 跟Binary Tree Level Order Traversal一样,只不过存result一直存在存在0位.
- 根据level来append每个list
- rst里面add(0,...)每次都add在list开头
-
用到bottom->top递减的stack: 最底下的root维持成最大的element.
-
过程当中, 一旦遇到currNode.val > stack.peek(), 就意味着需要把这个currNode放在 stack的底层位置.
-
也就是说, 遇到这个条件, process, pop()所有 currNode.val > stack.peek(), 最后把currNode加进去.
-
maxTree题目本身的要求是: 大的在最中间, 左右两边的subTree也要是maxTree:
-
Monotonous Stack在这里帮助 keep/track of max value, 但是left/right tree的logic是MaxTree独有的.
-
left/right node的assignment是根据题目要求: 中间最大值分开后, 左边的是左边subTree, 右边的作为右边subTree.
- Should memorize MaxTree. 依次类推,会做Min-Tree, Expression Tree
- Stack里,最大的值在下面。利用此性质,有这样几个step:
- 把所有小于curr node的,全Pop出来, while loop, keep it going.
- 最后pop出的这个小于Curr的node:它同时也是stack里面pop出来小于curr的最大的一个,最接近curr大小。(因为这个stack最大值靠下面)
- 把这个最大的小于curr的node放在curr.left.
- 那么,接下去stack里面的一定是大于curr:
- 那就变成curr的left parent. set stack.peek().right = curr.
- 结尾:stack底部一定是最大的那个,也就是max tree的头。
- 写个Inorder和Postorder的例子。利用他们分left/right subtree的规律解题。
- Postorder array 的末尾, 就是当下层的root.
- 在Inorder array 里面找到这个root,就刚好把左右两边分割成left/right tree。
- 这题比较tricky地用了一个helper做recursive。 特别要注意处理index的变化, precisely考虑开头结尾
- runtime: O(n), visit && build all nodes
findMid(arr)
can be replaced with a map<value, index>, no need execute O(n) search at runtime- 跟 identical binary tree的写法很像
- 只有 current s.val = t.val 的时候才需要compare same tree.
- 其他情况, 继续recursively isSubtree
- 注意:即使找到T1 == T2, 但很可能只是数字相同(这里不是binary search tree!!), 而children不同
- 所以同时要继续recursively isSubtree(T1.left, T2) ...etc.
- keyword: tree has no
cycle
. - 一旦两个node在edge中出现, 并且parent相同, 说明这两个node不union, 也在同一个tree里面, 所以可以break them.
- Add graph using adjacent list, and verify cycle alone the way
- IMPORTANT: use
pre
node in dfs to prevent backward dfs - similar to
Graph Valid Tree
where it validates cycle and also needs to validate if all nodes are connected - same concept as DFS, find first redundant edge that alreay exists in graph map.
- Binary Search Tree特点: 左边的node都比右边的node小.
- height balance, subtree height 相差<1, 必须左右sub tree均分. 做DFS(num, start, end)
- 在每一个level, 找到中间点, 然后分割2半, 继续dfs
- Divide and Conquer
- time/space: O(n), visit all nodes, no redundant visits.
- 题目要求DFS. 想清楚了如何在DFS level把几种情况都考虑了, 写起来很简单. NOT BFS, because requires O(1) space
- 对于一个root来说, 只有几个点可以顾忌到: root.left, root.right, root.next.
- 想办法把这三个方向的点, 能连起来的都连起来:
-
node.left.next = node.right
-
- If
node.next != null
, linknode.right.next = node.next.left
;
- If
- 然后在dfs(root.left), dfs(root.right)
- Time: visit && connect all nodes, O(n)
- 不和题意,用了queue space,与Input成正比。太大。
- BFS over Tree。 用Queue 和 queue.size(),老规矩。
- process每层queue时, 注意把next pointer加上去就好.
- 在DFS的基础上, 比对左左,左右,右左,右右
- 很容想到Inorder-binary-search-tree Traversal
- Iterative 稍微难想点:先把最左边的add, pop() stack, 加上右边(如果存在); 下一个轮回,如果又左孩子,又是一顿加。
- 然后稍微优化一下,确保rst.size() == k 时候,就可以return了
- check leaf => dfs left => add root => dfs right
- 简单处理swap
- recursively swap children
- BFS with Queue
- 每次process一个node, swap children; 然后把child加进queue里面
- 直到queue process完
- 根据BST规则, divide and conquer
- 取一个value, 然后分两半(start, value - 1), (value + 1, end) 分别dfs
- 然后左右两边的结果cross match
- 基础binary tree traversal. 注意对null child的判断
- 用remaining sum 来检测是否满足 input path sum 条件
- 满足的时候add to result list
- 两种backtracking:
-
- backtrack 当下node, 加进list, 然后dfs. dfs结束后删掉之前加进去的元素. 非常clean.
-
- backtrack 下一个dfs level增加的value. dfs return 之后, 删掉list里面的末尾元素: 但是删掉的dfs余下的value.
- 第一种backtrack更加好掌握.
- Binary Tree的一个基本题: 找到所有满足条件的path
- 遍历到底,比较sum vs. target
- 注意divide的情况。要把遍历的例子写写
- BST inorder traversal should give small -> large sequence
- misplaced means: a large->small item would occur, and later a large>small would occur.
- The first large && second small item are the 2 candidates. Example
- [1, 5, 7, 10, 12, 15, 18]
- [1, 5,
15, 10
,12, 7
, 18] - traverse, and take note of the candidate
- at the end, swap value of the 2 candidates
- inorder traversal the nodes and save in array, find the 2 items misplanced and swap them
- But O(n) space should not be allowed
- 会iterative traverse Binary Search Tree(Stack && handle left-dig-down)
- create Doubly-ListNode, 注意用一个dNode作为tail node of the list
- 在check right node的事后,
- 不论right == null or != null, 每次都要强行move to right.
- 如果不node = node.right,
- 很可能发生窘境:
- node always = stack.top(), 然后stack.top()一直是一开始把left 全部遍历的内容。所以就会infinite loop, 永远在左边上下上下。
- �这个node的subtree涵盖最深level的所有leaves.
- 这个node必须是能找到的最deep那个 � 条件2的需求是因为: root本身就是满足条件1的node, 还有很多Higher-level node也是如此, 所以要找那个deepest.
- 分析题目, 思想是: 看到tree里面所有的leaves, 找到他们最deep的 common ancestor
- Maintain a map <Node, maxChildDepth>
- Recursively dfs: return deepest node that has all leaves by these comparisons:
-
- If left,right child same depth, return root: they need common ancestor
-
- If not same depth, return the one with larger depth
- 被传送去上一个level的, 永远都是subtree里面符合题意的: the node containing all leaf nodes
- Visit all nodes once O(n), space O(n)
- Find all leaves at deepest level
- Use map to track each node-parent
- Backtrack all nodes to find common ancestor
- 对所给的input sum 做减法, 知道 sum 达到一个目标值截止
- 因为可以从任意点开始, 所以当sum达标时候, 需要继续recursive, 从而找到所有情况 (有正负数, sum可能继续增加/减少)
- 经典的 helper dfs recursive + self recursive
-
- helper dfs recursive 处理包括root的情况
-
- self recursive 来引领 skip root的情况.
- 与
Binary Tree Longest Consecutive Sequence II
在recursive的做法上很相似: - 利用dfs做包括root的recursive computation
- 利用这个function自己, 做
不包括root的recursive computation
- 当出现了第一次有 null children的node的时候, 说明到了leaf level, mark flag = true;
- 自此以后,queue再不该有node再有child; queue后面出现的node的left/right child应该都是null
- 否则就是有问题, return false;
- Divide and Conquer. dfs
- 分开 看左边/右边
- 如果左边满足连续递增的规则, dfs (depth + 1), 如果不满足规则, dfs(depth = 1)
- 右边也是一样
- 对结果跟max作比较, return
- 因为
前两个digit可以uniquely identify
一个node, 所以可以把前两个digit作为key, 定位node. - 特点: 比如考虑root, 有 n 个leaf, 就会加 n 遍root, 因为有 n 个 unique path嘛.
- 实现: 每个node, 上来先把curr value加进sum; 只要有child, 到这个node位置的以上path sum 就要被重加一次.
- format: depth.position.value. (on same level, position may not be continuous)
- approach: map each number into: <depth.position, value>, and dfs.
- Start from dfs(map, rootKey, sum):
-
- add node value to sum
-
- compute potential child.
-
- check child existence, if exist, add sum to result (for both left/right child). Check existence using the map.
-
- also, if child exist, dfs into next level
- Space, time O(n)
- 用constant space 也就是不可以BFS, 但是mention了用dfs stack space没问题 (提示啊!)
-
- link leftChild -> rightChild
-
- resolve root.rightMost child -> first possible root.next.left/right child
-
- dfs connect(rightChild), connect(leftChild)
- Each level should be fully linked from left side, so every reach to parent will have valid path or end.
-
- 处理 nextNode -> next -> next ...的case: 找到第一个有child的next node才可以罢休. 这个case很容易miss
-
- 我们的假设是, 上一个level的所有node都应该是linked, 那么在dfs时候, 就应该先connect(root.right). 右孩子的全处理完毕, 那么trick1才可以施行.
- 这个题有个奇葩的地方, 每个node还有一个parent, 所以可以自底向上.
- save visited in hashset. 第一个duplicate, 就是A B 的 lowest common ancestor
- 自底向上。利用parent往root方向返回
- 把所有parent存下来, 然后在两个list里面找最后一个 common node
- 无法从root去直接搜target node 而做成两个list. 因为根本不是Binary Search Tree!
- 最普通,Non-recursive: BFS, queue, 用个queue.size()来end for loop:换行。
- 或者用两个queue. 当常规queue empty,把backup queue贴上去
- 每个level都应该有个ArrayList. 那么用一个int level来查看:是否每一层都有了相应的ArrayList。
- 如果没有,就加上一层。
- 之后每次都通过DFS在相应的level上面加数字。
- 因为是 binary tree, 所以直接盲目搜索搜索path不efficient, use extra space and waste time
- 巧用DFS来找每一个node的common ancestor. Need the assumption: 1. unique nodes across tree; 2. must have a solution
- Base Case: 当root == null, p or q is found (
root == p || root == q
),那么就return the root as LCA - 三种情况:
-
- leftLCA and rightLCA all found:
each path has found one of p and q node as LCA
. Therefore, curr root is the lowest ancestor
- leftLCA and rightLCA all found:
-
- One of leftLCA and rightLCA is found: return whichever one found
-
- both LCAs are null, return null
-
- Base Case: 当root == null, p or q is found (
- Worst case, visit all nodes to find p q at last level, last two leaves: time O(n), stack space O(n)
- Shortest path; minimum depth: 想到BFS, check level by level, BFS更能确保更快找到结果
- depth definition: reach to a leaf node, where node.left == null && node.right == null
- BFS using queue, track level.
- Divide and Conquer to find min depth.
- if one of child is null, return the other child depth + 1
- Pick the min of the two child depth + 1
- need to visit all nodes
- the special requirement causes: we have to track exact position of nodes
- Using
Node {int offset, level, val}
andMap<offset, Map<level, List<Val>>>
:- set all nodes to its correct position
- output all together
- the
max/min
offset allows us to loop over the map in a ordered manner (save efforts of sorting) - time: O(n) to mark all nodes at correct spot, but
O(nlogn)
to sort the vertical array - space: O(n), mark all nodes in the nested map
- A (offset, level) has 2 nodes: use nested
Map<offset, Map<level, List<Val>>>
to track nodes - Also need a
class Node{int offset; TreeNode node}
to build queue:- need
offset
: queue at each level cannot derive level index - need
TreeNode
:Node
extends originalTreeNode
so we can queue it.
- need
- lots code to write due to the
class Node
for BFS - use queue to hold each level. O(n)
- 最右: 即level traversal每一行的最末尾.
- BFS, queue 来存每一行的内容, save end node into list
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n nodes in final results
- Use Map<Level, Integer> to override the result at each level
- dfs:
- dfs(node.left) and then dfs(node.right) because we want to log right side last
- record global max depth for iteration purpose
- time: O(n) visit all nodes
- space: O(n) worst case unbalanced tree to have n stacks (and n nodes in final results)
- This approach highly relies on the preorder rules
- we can use validation rules to navigate throug hteh preorder array
- use a global index
- time: O(n)
- faster than BFS, using less space if not couting final rst: stack size, O(logn)
- time: O(n), visit all
- loop over queue level and record max
- 每次看最左left depth和最右leaf depth 是不是一样
- 如果一样, 直接 2 ^ h - 1就好
- 不一样的话, 再DFS
- calculate
2^(h)
: 位运算, Math.pow(2, h) = 2 << (h - 1). 神奇!- 2 << 1就是把所有bits往左移动一位, 也就是 * 2
- time: O(n) visit all nodes on 1 side
- space: O(h) visit all nodes on 1 side
- See details in comments inline. 要对tree非常理解
- binary tree one child tree nodes # = 2 ^ h - 1; 所以一个child tree + root = 2 ^ h
- Option1: Use local single path max & global combined max
- Since the local combined diameter is used for comparision, but not used for specific calculation
- calculate path length (diameter), understand:
- for single path: child single path value + 1 (curr node)
- for combined path including curr node: left child single + right child path
- Option2: record local combined and single path for each iteration
int[]{combinedPath, singlePath}
;- single path: pick single path + 1:
singlePath = Math.max(left[1] , right[1]) + 1
; - combined path
combinedPath = Math.max(Math.max(left[0], right[0]), left[1] + right[1] + 1)
, find max from:-
- complete left child combined path
-
- complete right child combined path
-
- combined path with curr root
-
- Note: we treat a single node itself with diameter of 1, so we want to -1 in final result
- problem statement wants the path length (not # of nodes or depth)
- dfs function: have toDelete set, and a result list
- dive deep into child node FIRST, and test if a removal is needed at bottom of tree
- if remove, add orphan and return null; otherwise, return itself
- time: O(n), visit all nodes
- space: O(logn), height of the tree
- traverse tree and create
map <val, parent>
to fast O(1) removal. O(n) - set root into a rootSet
- after deleting a node A, the children of the node becomes 2 forests root
- children should be marked in rootSet
- also remove node A from rootSet (if appears)
- output: find all root in root set, traverse and output.
- This approach requires a dfs build of parentMap
- it is same amount of efforts to do the regular dfs removal.
- not a good solution
- time: O(n)
- space: O(n)
- 用stack记录最小值, 放在top. O(h) space.
- 每次消耗TreeNode, 都看看rightNode(其实就是下一个最小的candidate), 并且一条龙stack叠上rightNode所有的left子孙.
- 用O(1)空间的做法:不存stack, 时刻update current为最小值。
- 找下一个最小值,
- 如果current有right child: 和用stack时的iteration类似,那么再找一遍current.right的left-most child,就是最小值了。
- 如果current没有right child: 那么就要找current node的右上parent, search in BinarySearchTree from root.
- 注意:
- 一定要确保找到的parent满足parent.left == current.
- 反而言之,如果current是parent的 right child, 那么下一轮就会重新process parent。
- 但是有错:binary search tree里面parent是小于right child的,也就是在之前一步肯定visit过,如此便会死循环。
- 这里要走过所有的node, 所以dfs非常合适
- Divide and conquer.
- 维持一个最大值: Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
- 注意check root == null
- BFS is doable as well, but a bit more code to write: tracks largest level we reach
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
- 记录找到过的closest
- Binary Search, 根据current node走位, until null leaf
- time: O(logn), space O(1) since no extra space used
- when less than curr val, consider left
- when greater than curr val, consider right
- dfs到底, 然后每一层比较, 再return
- time: O(logn), space: O(logn)
- 加root, left, then right. Obvious
- Option1: recursive on preorderTraversal. the dfs function returns List
- Option2: pass in rst, and write a void dfs.
- 先加root, 然后push上需要末尾process的在stack垫底(root.right), 然后push root.left
- Stack: push curr, push right, push left.
- DFS using depth marker: 每个depth都存一下。然后如果有不符合条件的,存为-1.
- 一旦有 <0 或者差值大于1, 就全部返回Integer.MIN_VALUE. Integer.MIN_VALUE比较极端, 确保结果的正确性。
- 最后比较返回结果是不是<0. 是<0,那就false.
- Traverse 整个tree, O(n)
- Same concept as above solution, but cost more traversal efforts
- 试图计算所有情况
- DFS. 确定leaf条件, && with all dfs(sub1, sub2).
- 这里无论如何都要走过所有的node, 所以dfs更加合适, 好写.
- 两个queue存每个tree的所有current level node. Check equality, check queue size.
- Populate next level by nodes at current level.
- 确定好结尾条件:
is leaf
&&val == sum
. - 每一层减掉node.val, 然后dfs.
- 写一写:
root == null => false
对parent nodes的影响. 这里发现没影响, 所以可以简化成用1个functionDFS. - build tree recursively by definition
- O(n^2) time and space due to single visit to all nodes
- cache parent max and min => produce current max and min
- pass the max and min to dfs
- compare and return the max of dfs(left), dfs(right)
- time: O(n)
- space: O(logn)
- easy to write, a bit hard to think of
- pass up the local (min, max) as object
Val{max, min}
- easy to think of, but more code to write
- trivial, 先加left recursively, 再加right recursively, 然后组成头部.
- Option1 w/o helper; option2 with dfs helper.
- Option1: reversely add to list
- 双stack的思想, 需要在图纸上画一画
- 原本需要的顺序是: 先leftChild, rightChild, currNode.
- 营造一个stack, reversely process: 先currNode, 再rightChild, 再leftChild
- 这样出来的结果是reverse的, 那么翻转一下就可以了.
- reverse add:
list.add(0, x)
; - 利用stack的特点
- 每次加element进stack的时候, 想要在 bottom/后process的, 先加
- 想要下一轮立刻process的, 最后push进stack.
- Option2: regular sequence add to stack: add curr, right, left
- Use set to contain the processed children
- only process curr if its children is processed
- sum up the matching L & R
- Find (L,R) on left child
- Find (L,R) on right child
- Find (L,R) covering the root node
- space O(n), worst case O(logn), height of dfs.
- time O(n) to find all nodes between (L, R)
- Using stack, or queue, list: any data structure (we are not doing ordered search)
- space O(n)
- time O(n)
-
- level-traverse all nodes, 2) add node to appropriate col list(using map)
- For final output:
- Use min/max to track map keys, since the keys are continous
- Map does not provide random access; unless map key is marked with sequence i = [min, max]
- Since each vertical list is appended level by level: no need to sort during output. FAST
- time: O(n), visit all nodes
- spac: O(n) to store
- Regular DFS to traverse all nodes, and add node to appropriate col list (using map)
- Problem: DFS does not provide natural ordering for nodes on a row.
- Left side may have a super deep Right child branch, which will be added to col list first (since left side is visisted first)
- It is wrong because right branch may have nodes in same column but at higher level
- To Solve: preserve
level
for all nodes in same column
- Need to sort the final list, and slow: visit all nodes O(n) + O(KMlogM), K = # of cols, M = # of items in col
- Time: O(nLogN). O(n) + O(KMlogM), K = # of cols, M = # of items in col; in extrem, it can be a vertical line of nodes, then sort: O(nLogN)
- Space: O(n)
- 简单的level traversal.根据level奇数偶数而add到不同位子.
- Option1: based on level % 2, insert to front/end of list
- Option2: based on level, insert right/left of node into queue
- use preorder to find root, one index at a time (global index)
- use the root to divide and conquer inorder int[] to 2 sides;
- root.left = dfs(left); root.right = dfs(right)
- end stage: start == end index, create a node
- can use a map to store inorder <val, index> for O(1) find
- 和Construct from Inorder && Postorder 想法一样。
- 写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。
- 跟Convert Sorted Array to Binary Tree类似, 找到对应的index, 然后:
- node.left = dfs(...), node.right = dfs(...)
- Divide and Conquer
- optimize on finding
mid node
: given value, find mid of inorder:
- optimize on finding
- Instead of searching linearly, just store inorder sequence in
map <value -> index>
, O(1) - IMPORATANT: the mid from inorder sequence will become the main baseline to tell range:
range of subTree = (mid - inStart)
- sapce: O(n), time: O(n) access
- with BST, we can:
- skip adding the null nodes into the serialized string:
String NULL = "#"
- In deserialization: use min/max boundary to check if queue.peek() can be added:
- if not meeting BST condition, skip this dfs and let other call to consume the queue
- skip adding the null nodes into the serialized string:
- Faster because it shortens the serialized string
- Take reference in Serialize and Deserialize Binary Tree
- The approach works but does not utilize Binary Search Tree properties
- Regular convert BST to sored list: in-order traversal
- Carefully doubly link node head, tail
- traverse all nodes, DFS 好做:
left, curr, right
- Tail:
- Assume head is found for sub tree, then
tail = head.left
- Link
LeftTail <-> Curr Root <-> RightHead
- Link
RightTail <-> LeftHead
- Assume head is found for sub tree, then
- In place:
- 同
Node {val, left, right}
, w/o new doubley linked list class - different from
Convert Binary Search Tree to Sorted Doubly Linked List (extra space)
- 同
- option1: dfs + rst list to carry results
- option2: Divide and Conquer, 在自己的基础上recursive, 不用helper function
- O(n) time
-
- Add root.leftPath all the way to leaf, 2) process curr 3) Move to right if applicable 4) add all right.leftPath
- O(n) time, O(h) space
- 查看每个parent-child关系:
- leftchild < root < rightChild
- all of left child < curr < all of right child
- 方法: 把root.val 传下来作为 max 或者 min, valid child in (min, max)
- BST 有两个极端: left-most-leaf is the smallest element, and right-most-leaf is largest
- imagine we know the two extreme border: Long.MIN_VALUE, Long.MAX_VALUE
- min/max: long type to meet edge case: node.val = Integer.MAX_VALUE
- key concetpL the
common ancester of deppest leaves
must have itstwo branch being same depth
. problem sovled. - dfs on both branch
- if returned depth equals & equal to max depth, record common ancestor
- time: O(n) traversal 1 pass
- space: O(n) dfs worst case depth
-
- find leaf nodes, and store backward map to root (DFS/ BFS both work)
-
- use leaf nodes to find way backwards till common node is found; return
- time: O(n) but two passes
- space: O(n) dsf + map storage
- this approach is more brutle and uses exrtra spaces
- IMPORTANT: DO NOT ASSUME positive integers
- Overall idea: write example and realize 2 cases at each node:
-
combo sum
: left + right + root
-
single path sum WITH curr node
: left/right + root
-
- DFS returns the path over curr node: a path needs to be continuous, so we cannot skip curr node.
- IMPORTANT, key discovery: if left/right single path over curr node is less than 0: reutrn 0.
- Parent path will simply drop this path, since we want maximize the path sum.
- It is so IMPORTANT: when left or right becomes 0, when comparing with global combo path:
- it automatically covers a special case:
single left/right path + node
, since one of left/right == 0!!!
- it automatically covers a special case:
- With the above understanding: what if I want to skip curr node and just want left/right path w/o curr node:
- it is handled and compared with global in dfs(node.left) or dfs(node.right) automatically!
- time: O(n), go over whole tree
- space: O(logn), tree height.
- tree给我们2条branch, 每条branch就类似于 dp[i - 1], 这里类似于dp[left], dp[right] 这样
- 找到 dp[left], dp[right] 以后, 跟 curr node结合.
- 因为是找max sum, 并且可以skip nodes, 所以需要全局变量max
- 每次dfs() return的一定是可以继续
continuously link 的 path
, 所以returnone single path sum + curr value
. - 用 PathSum 比较特别. 没有 data structure的时候, 写起来比较繁琐.
- 第一次做有点难理解,复杂原因是:因为可能有负值啊。不能乱assume正数。
- single path max 的计算是为了给后面的comboMax用的。
- 如果single path max小于0,那没有什么加到parent上面的意义,所以就被再次刷为0.
- combo的三种情况:(root可能小于0)
-
- 只有left
-
- 只有right
-
- root大于0,那么就left,right,curr全部加起来。
- 情况1和情况2取一个最大值,然后和情况三比较。做了两个Math.max(). 然后就有了这一层的comboMax
- Recursively check symmetrically相对应的Node.
- 每个node的children都和镜面另外一边相对的node的children刚好成镜面反射位置。
- put left or right children in pair
- stack1: 左手边sub-tree先加left, 再加right child;
- stack2: 右手边sub-tree先加right child, 再加left child。
- process时,若symmetric,所有stack里面出来的node会一一对应。
- min tree: parent node is the min of left/right child
- BFS to traverse the tree and find 1st non-root smallest val
- Improvement area: when
node.val >= nextMin
, no need to dive into node children since it is a min Tree. - Find left and right val:
- if left/right equals root.val, that means the left or right sub children could have larger number
- Therefore DFS into left or right
- compare and return min(left, right)
- the leaves are at depth 0 and the root is at highest depth
- dfs: the depth = index of the rst, start from depth = 0 at leaf
- end state: leaf node, add to rst, and return depth
- 利用 BST 的性质,可以直接搜到target node,而做成两个长度不一定相等的 path
- 然后很简单找到LCA
- O(n) space, O(logn) time
- Beofre lasts common ancestor is found: p and q should follow same search pattern:
- compare with root, then dfs(left) or dfs(right)
- util p and q fall on two sides of root, then return root
- 非常巧妙, 但是也比较局限; 稍微变条件, 就很难recursive.
- 几种情况:
-
- one of p, q 在leaf, 那么此时的root其实就是lowest common ancestor
-
- 如果p, q 在root的左右两边, 这就是分叉口, 那么root就是lowest common ancestor
-
- 如果p, q 在root的同一边 (左,右), 那么继续dfs
-
- O(logn) extra space: recursive stack space
- O(logn) time
- Given that left & right nodes are always available in pair, at each level:
- perform dfs to find new root: return deepest left node as root
- pick out curr left node and fix current level:
- add right node as left node
- add root as right node
- find successors and predecessors using BST (both list will be sorted); in the end, we can easily get top k from the two sorted list
- with BST: **inorder traversal gives us sorted predecessors
- with BST: **reversed-inorder traversal gives us sorted successors
- smallest on top of the stack
- time: O(n) visit all nodes, O(k) to output
- space overall: O(n) to store all nodes
- Itereate over all nodes and maintain pq (improvemenet point: how to avoid traversing entire tree?)
- prioritize nodes that are closer to target, so we may stop early when result reaches k candidates
- time: O(n*logn)
- kinds slow and not utilizing BST
- 考虑 more significant spot 应该拿到更大的值
- 如果sort number, comparator 会比较难写: 每个digit的weight不同, 要分别讨论个位数和多位数.
- goal: 让较大的组合数排在前面, 让较小的组合数排在后面
- 不如: 组合两种情况, 用String比较一下大小 (也可以用 integer来比较组合数, 但是为保险不超Integer.MAX_VALUE, 这里比较String)
- String.compareTo() 是按照 lexicographically, 字典顺序排列的
- 利用compareTo, 来倒序排列 string, 刚好就得到我们要的结果.
- O(nlogn), 排序
- 首先partition. 返还一个partition的那个中间点的位置: 这个时候, 所有小于nums[partitionIndex] 都应该在 partitionIndex左边
- 然后劈开两半
- 前后各自 quick sort, recursively
- 注意:在partition里面, 比较的时候nums[start] < pivot, nums[end]>pivot, 如果写成了 <= 会 stack overflow.
- Time O(nlogn), Space: O(1)
-
- find middle. 快慢指针
-
- Sort: 切开两半,先sort前半, 如果先sort了mid.next~end, sort后,中间点mid.next == null,再sort前半段
-
- Merge: 假设given list A, B 已经是sorted, 然后按照大小,混合。
- 要recursively call sortList() on partial list.
- 想做可以看讲义:http://www.jiuzhang.com/solutions/sort-list/
- 但是quick sort不建议用在list上面。
- 排列list, merge sort可能更可行和合理。原因分析在下面, 以及: http://www.geeksforgeeks.org/why-quick-sort-preferred-for-arrays-and-merge-sort-for-linked-lists/
- 把Interval拆分成数轴上的Point
- 起飞mark 1
- 降落mark -1
- 用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。
- 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
- 当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
- 这避免了错误多count,或者少count
- O(nk)
- 其实就是quick sort里面的partition function的简化版
- Two pointers, 找一个 pivot 'a' 来区分大写小写字母
- ASCII code 里面 大写字母在小写字母前面, 数字更小
- 然后 while, move start++, end--,
- 每一轮都swap
- 直接用两个 pointer left/right 标记开头结尾
- 每次遇到
>= 'a'
就是小写字母, swap(chars, i, left); - 每次遇到
< 'a'
就是大写字母, swap(chars, i, right); - 注意: 每次处理完left swap, 任由for loop i++, 因为确定 [0 left] 都是准确的
- 每次处理完 right swap, 我们不确定从 right index 换过来的是不是正确的, 所以 i--, 跟for loop 的 i++抵消.
- 写 while loop 的 solution看起来更容易理解.
- 把list里面每个元素都拿出来,scan and insert一遍
- Time O(n^2), worst case, 每次放入n个数字里面的element, 刚好都是最大的
- 所以每次要traverse n nodes, 然后走n次
- 如果已经有个sorted list, insert一个element进去。怎么做?
- while 里面每个元素都小于 curr, keep going
- 一旦curr在某个点小了,加进去当下这个空隙。
- Can be a 2D array, or a
class Point
to store preSum + index - Sort preSum: smaller (有可能负数) 靠前, 大数字靠后
- 比较�preSum种相连接的两个节点, 找差值min; 因为最接近的两个preSum节点的差值肯定是最小
- min所在的两个节点的index, 就是�result candidate: 这两个index可能再原nums里面相差很远
- time O(nlogn), sort
- space: O(n)
- 因为map虽然能存 preSum + index, 但是无法有效排序
- 所以用一个class来存这两个信息, 然后合理排序
- partition the array by pivot k = {0, 1, 2}
- 每一次partition都return starting point of the current partition
- 然后根据下一个 color, 去还没有sort 干净的那个部分, 再sort一下就好
- time O(kn), where k = 0 => O(n)
- 这里只是partion, 并不需要recursively quick sort, 所以结果是简单的O(n)
- have two pointers, left/right
- start tracks red, end tracks blue. Swap red/blue to right position, and left++ or right--.
- leave white as is and it will be sorted automatically
- be very careful with index i: when swapping with index right, we do not know what is nums[right], so need to re-calculate index i .
- O(n)
- Note: this one pass solution does not work if there are more than 3 colors. Need to use the regular quick sorty.
- TODO: count occurance and reassign array
- Quick sort的基础.
- Partition Array根据pivot把array分成两半。
- 从array两边开始缩进。while loop到遍历完。非常直白的implement。
- 注意low/high,或者叫start/end不要越边界
- O(n)
- 注意: 这里第二个inner while
while(low <= high && nums[high] >= pivot) {..}
采用了nums[high] >= pivot
- 原因是题目要找第一个nums[i] >= k, 也就是说, 即便是nums[i]==k也应该swap到前面去
- 这个跟quick sort 原题有一点点不一样.
- Divide and conquer, recursively
- 先从中间分段, merge sort 左边 (dfs), merge sort 右边
- 最后merge起来
- merge的时候因为是做int[], 所以没办法必须要O(n) space
- Time O(nlogn), Space O(n)
- O(nlogn) time (PriorityQueue), O(n) space
-
- 扫描线+Count: when
count==0
, startFlags==endFlags. 是interval的开头/结尾 (write an example)
- 扫描线+Count: when
-
- Note: remember to merge points on same sweep line position
-
- Comparator:
new PriorityQueue<>(Comparator.comparing(p -> p.val))
; - Sort intervals: O(nlogn), extra space O(n) when creating rst list
Arrays.sort(intervals, Comparator.comparing(i -> i[0]));
- 找到结尾 interval, 满足条件就可以save
- 如果不到return的条件, 就继续延伸 interval.end
- Less applicable when input is
int[][] intervals
, but more applicable when we haveList<int[]> intervals
- Related example: Insert Interval
- Sort fist, loop over and merge, cut off overlapped interval.
- sort by Interval.start:
intervals.sort(Comparator.comparing(interval -> interval.start)); // O(nlogn)
- 用两个相连的Interval: curr, next
- 如果 curr.end覆盖了 next.start: 需要merge. 那么比较一下 curr.end vs. next.end
- 一旦merge, 需要remove被覆盖的 next interval:
list.remove(i+1)
- 若没有重合,就继续iteration
- sort by Interval.start:
- time O(nlogn), space O(1)
- 注意接头点要考虑所有开会结会的情况,不要恰巧漏掉相接的点
- 开会的是超人。瞬间移动接上下一个会议
- sort:
Arrays.sort(intervals, Comparator.comparing(i -> i[0]))
- time: O(nlogn), space: O(1)
- class Point{pos, flag}, PriorityQueue排序。计算count
- 跟 Number of Airplanes in the Sky 是一个类型的题目
- time: O(nlogn), space O(n)
- Not necessary for this problem, since it requires extra space with pq.
- Similar to 15. 3Sum, but simpler.
- 只需要count triplet, 但是不需要save triplet, 而且还不需要handle duplicated triplets
- 发现start, end满足条件时候,(end - start)就是所有 sum <target的情况了。
- 而一旦 > target, 那么就end--
- 两层循环, O(n2)
- Use priority queue to sort by customized class interval{int dist; int x, y;}.
- Sort by larger distance and then sort by start index
- seat(): pq.poll() to find interval of largest distance. Split and add new intervals back to queue.
- leave(x): one seat will be in 2 intervals: remove both from pq, and merge to a new interval.
- 主方程写出来其实很好写, 就是 split + add interval, 然后 find + delete interval 而已. 最难的是构建data structure
- seat(): O(logn), leave(): O(n)
Trick: 构建虚拟 boundary
- 如果是开头的seat, 或者是结尾的seat, 比较难handle: 一开始坐在seat=0的时候, 没有interval啊!
- Trick就是, 我们自己定义个虚拟的座位
seat=-1
,seat=N
- 一开始有一个 interval[-1, N] 然后就建立了boundary.
- 从此以后, 每次split成小interval的时候:
- 遇到
interval[-1, y]
, distance就是(y - 0)
- 遇到
interval[x, N]
, distance就是(N - 1 - x)
- 当然正常的interval dist 就是
(y - x) / 2
- distance 中间点
- Interval.dist 我们其实做的是 distance的中间点
(y - x) / 2
- 这里的dist是
距离两边的距离
而不是 x, y 之间的距离. 这里要特别注意.
- Interval.dist 我们其实做的是 distance的中间点
- TreeSet
- TreeMap<starting Pos, Interval>
- seat(): O(logn)
- find largest dist with TreeSet.first()
- break into 2 intervals; save to set and save to map
- leave(x): O(logn)
- find the interval before starting point x using TreeMap.floorEntry()
- merge and store back to set/map
- for test case it is slower than PQ, because it saves to 2 data structure
- Sort start times, and end times in 2 different arrays
- Loop over start time
- when start[i] < end[endIndex], Count++, need more room
- start[i] >= end[endIndex], done using some room, move to next end time, endIndex++ (like vacating a room)
- Note: we never decrese count because:
- what ever count reaches, it is the max
- since we keep moving endIndex, when start[i] >= end[endIndex], we will just reuse meeting room w/o count++
- time: O(nlogn)
- space: O(n)
- somehow, super fast, over 100%
- inspired by: https://leetcode.com/problems/meeting-rooms-ii/discuss/67855/Explanation-of-%22Super-Easy-Java-Solution-Beats-98.8%22-from-%40pinkfloyda
- Use sweep line to process, track max count as max # of rooms needed
- 跟 Number of Airpline in the sky是同一道题
- time: O(nlogn)
- space: O(n)
- store
Map<Word, Longest Chain Length>
- sort all words, try from short to long: short word will be calculated first to serve later words as candidate
- time: O(nlogn)
- space: O(n)
- store
Bucket: List[17] of words
, given word size limit [0 ~ 16] - time: O(n)
- space: O(n)
- 处理duplicate wthin triplets:
- 如果最外圈的移动点i重复, 一直顺到结尾的最后一个再用.
- 如果是triplet内有重复, 用完start point, 移动到结尾.
- Note:
-
- 找 value triplets, 多个结果。注意,并非找index。
-
- 要升序, 第一层for loop 从最后一个元素挑起, 保证了顺序。
-
- 去掉duplicate: check用过的同样的数字,都跳掉。不需要用同样的数字再计算一边已有结果。
-
- 时间 O(n^2), 两个nested loop
- HashMap 2Sum. Remember to handle duplicates
-
- For loop 挑个数字A
-
- 2Sum 出一堆2个数字的结果
-
- Cross match 步骤1里面的A
-
- 用到hashset找unique && duplicate: O(m+n)
- 可以用binary search 找数字.
- Note:binary search一定需要array sorted: nLog(m)
- define the bucket by index: the total distance is fixed [0, 1000]
- +/- capacities for each pos and save into the bucket
- go over the bucket and see if the total cap goes over input capacity
- O(n), trips size
- space: O(1), bucket size 1000 is constant
IMPORTANT
: before using PQ to sort, consider bucket sort:- if the boundary set and seems resonable? i.e., max size =
1000
- is the sorted items index based?
- if the boundary set and seems resonable? i.e., max size =
- Like meeting room, merge interval
- process items on same index
- Create customized Point{} class
- Sort by distance
- Maintain queue size <= K
- ?, select sort?
- Time, Space: O(n)
- 前提: input必须valid, 比较罕见少用. Moore Voting Algorithm: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
- 与当下candidate相同, vote++. 与之不同, vote--.
- Majority Number是指超半数, 多1个就行: 消减至最后, 会至少有vote>=1.
- 那么: vote++, vote--到最后剩下的就是winner.
- 这个方法比较greedy, 前提是: valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。
- time: O(n), space O(1)
- sort entire nums array
- assume there is a solution, then nums[n/2] must be that majority num
- time O(nlogn)
- Recursive approach
- For ange rangeA & rangeB, rangeA has majorElementA and rangeB has majorElementB
- majorElementA = majorElementB, of course this element will be the major number for whole range
- if majorElementA != majorElementB, then need to count both elements in whole range
- of course the larger occurance will be the major num
- TODO
- Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。
- Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。
- PQ can be used to sort on multiple attributes
- follow the specified rules, and build all possible pairs of visits vs. bike. Pair {int dist, workerIndex, bikeIndex}
- PQ to sort them
- first by dist
- if same dist, sort by workerIndex
- if same workderIndex, sort by bikeIndex
- process all candidates, and skip the ones (workers/bikes) visited
- Similar to using PQ: the goal is to find: 1) min dist; 2) closer worker index, 3) closer bike index
- can use bucket sort to hold all possible distances [0 ~ 2000]: bucket[List of pairs]
- do a hard iteration (ordered access from min dist).
- time: O(mn), no need to sort
- space: O(mn)
- put 3 elements into array, sort and follow below rules:
- min:
- if 3 elements consecutive, 0 move.
- if only 1 pair of the two elemnets consecutive or if they have 1 slot in between, it needs exactly 1 move
- otherwise, at most 2 moves
- max: # of open slots between them (high - low + 1) - n, where n = 3
- Follow up:
1040. Moving Stones Until Consecutive
is more interesting with special rulese (cannot move toending spot
), and it uses sliding window concept - 例子写出来,发现可以sort以后按照定义搜索一遍。 nlogn.
- 搜索一遍时候可以优化,用binary search. 但是没意义,因为array.sort已经用了nlogn
- 题目给的规则, 从小到大排序后: 剩下的 paper
n-h
, 全部要 <= h 个 citation. - time O(nlogn), search O(n)
- 从i = 0 开始找第一个
citations[i] >= h
, 就是第一个符合 h-index 规则的paper, return h - 如果从 h = n, 每次h--; 那么
x = n - h
就是从[0 ~ n)
开始找第一个dictations[x] >= h
, 就是结果 - 同时,
dictations[x-1]
就是最后一个(dictation最多的)其余的paper. - O(n)
- Bucket sort的思想(更像是counting sort?): 过一遍 input, 把dictation value 作为 index, 分布在bucket[index]上++
- bucket[x] 是 count when # of citation == x.
- 如果 x 大于 n的时候, 就超出了index范围, 但是刚好这个问题可以包容, 把这样的情况记位在bucket[n]就可以
- 巧妙:
sum += bucket[h]
whereh = [n ~ 0]
利用了h-index的definition: - #of papers (sum of bucket[n]...bucket[0]) has more than h cidations
- 这里运用到了bucket sort的思想, 但是并不是sorting, 而h-index的定义运用的很巧妙.
- Read more about actual bucket sort: https://en.wikipedia.org/wiki/Bucket_sort
- Application: for number/character range
- Steps:
- Find range, define countArray
- Count element and record in the array
- PreSum the countArray
- Start from beginning of the array,
print & decrese count
to produce the sorted elements
- Map of nums1: <num, # appearance>
- check nums2 against nums1 map
- time:O(n + m)
- space:O(n + m)
- process and check at the end, or, 2) sanitize first and process assume correct input
- Aggregate map<char, count>, and sort the entry with priority queue.(Optionally, can use object
Letter {char c, int count}
) - Naturally: we want to prioritize the largest population and exhaust it first, so we want to keep it in the a buffer queue
- it is a queue, first in first out
- monitor queue size k = 2, so that it holds off the just last-processed letter for 1 unit of time
- the buffer then sends the last-process item to the main priority queue (pq will sort it again)
- Error handling: largest population may have extra letter
- the main PQ has already exhausted
- but the largest-population-letter will end up stuck in the buffer queue
- it will never be picked up again so the final result sb will be shorter than orignal string: that is the error case
- Option0. Similar to
621. Task Scheduler
:- use a buffer to hold potential letter to add back, but NOT ADD BACK YET, until k slots have been filled.
- time: O(m), m = # of unique letters
- space: O(nmLogm), n = length, pq sorting requires mlogm, we will visit all n nodes.
- put all in map<char, count>
- Sanitize the input: if certain popular char count is over (n + 1)/2, then it should fail right away, just return empty map.
- Once the input is sanitized, when building results, we can be greedy and consume most popular char and then the rest
- Int[2] can be used store char and count
- PriorityQueue can sort int[]. Okay to not specific length of int[] when defining pq.
- Alternatively, can use a Letter {char c, int count} to represent
- 这里已经给了 sorted intervals by start point;
-
- 直接找到可以insert newInterval的位子. Insert and convert to list
-
- Merge: Use
pre, curr
to iterate over list, and remove curr after merging
- remove之前都会重新assgin
pre.end
, 确保被remove的node.end 被capture
- Merge: Use
-
- Convert back to int[][]
-
- time/space: O(n)
- code is slightly better to read
- handle edge cases:
- new interval is non-overlapping
-
- head
-
- tail
-
- in middle
-
- new interval is overlapping:
-
- end index in existing interval; reuse the existing interval end to close new range
-
- end index in the gap of 2 intervals, use new interval.end to close the new range
-
- new interval is non-overlapping
- time, space: O(n)
- Interval 拆点,PriorityQueue排点
- Merge时用count==0作判断点
- 注意, 一定要compare curr
p.x == queue.peek().x
确保重合的点全部被process:count+=p.x
- PriorityQueue: O(logN). 扫n点, 总共:O(nLogn). SLOW.
- 因为interval已经sort, 本想用Binary Search O(logn).
- 但是找到interval insert position 最后 merge还是要用 O(n), 所以不必要 binary Search
- Time, Space: O(n)
- 前提: input必须valid, 比较罕见少用. Moore Voting Algorithm: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
- 与当下candidate相同, vote++. 与之不同, vote--.
- Majority Number是指超半数, 多1个就行: 消减至最后, 会至少有vote>=1.
- 那么: vote++, vote--到最后剩下的就是winner.
- 这个方法比较greedy, 前提是: valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。
- time: O(n), space O(1)
- sort entire nums array
- assume there is a solution, then nums[n/2] must be that majority num
- time O(nlogn)
- Recursive approach
- For ange rangeA & rangeB, rangeA has majorElementA and rangeB has majorElementB
- majorElementA = majorElementB, of course this element will be the major number for whole range
- if majorElementA != majorElementB, then need to count both elements in whole range
- of course the larger occurance will be the major num
- TODO
- Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。
- Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。
- Moore voting: vote可以加减, 一旦为零, 换下一个candidate, 之前抵消掉的算作清零.
- 一个array里面, 最多也只有2个数字 出现次数大于2次, 所以用A/B表示.
- count overall apperance at the end for the two items: countA, countB
- save to result as valA, valB
- 有点 moore voting的意思:
- 当count == 0的时候, reset
- 两个candidate A/B都不等, 那么countA--, countB--
- 最终重新计数, 然后比较出结局.
- 注意: 按照if statement的顺序, valA&&countA 比valB&&countB有优先性
- O(nlogN)
- Edit Distance的follow up.
- 其实就是改一下 minEditDistance的function, 带入K作比较罢了.
- 写起来跟Edit Distance 的主要逻辑是一模一样的.
- 但是LintCode 86% test case 时候timeout.
- Time O(mnh), where h = words.length, 如果 n ~ m, Time 就几乎是 O(n^2), 太慢.
- 相比之前的implementation, 有一些地方可以优化:
-
- Backtracking时候, 在board[][] 上面mark就可以, 不需要开一个visited[][]
-
- 不需要implement trie的所有方程, 用不到: 这里只需要insert.
- 普通的trie题目会让你search a word, 但是这里是用一个board, 看board的每一个字母能不能走出个Word.
- 也就是: 这里的search是自己手动写, 不是传统的trie search() funcombination
-
- TrieNode里面存在 end的时候存string word, 表示到底. 用完了 word = null, 刚好截断重复查找的问题.
- Build Trie with target words: insert, search, startWith. Sometimes, just:
buildTree(words)
and return root. - 依然要对board matrix做DFS。
- no for loop on words. 直接对board DFS:
- 每一层,都会有个up-to-this-point的string. 在Trie里面check它是不是存在。以此判断。
- 若不存在,就不必继续DFS下去了。
- Trie solution time complexity, much better:
- build Trie: n * wordMaxLength
- search: boardWidth * boardHeight * (4^wordMaxLength + wordMaxLength[Trie Search])
- for loop on words: inside, do board DFS based on each word.
- Time cpmplexity: word[].length * boardWidth * boardHeight * (4^wordMaxLength)
- Big improvement: use boolean visited on TrieNode!
- 不要用rst.contains(...), 因为这个是O(n) 在leetcode还是会timeout(lintcode竟然可以pass)!
- 在Trie search() method 里面,凡是visit过的,mark一下。
- 每一个word, 都可以拆分成 front + mid + end. 如果这个word + 其他word可以组成palindrome,那就是说
- 砍掉 (mid+end), front.reverse() 应该存在 words[] 里面.
- 砍掉 (front+mid), end.reverse() 应该存在 words[] 里面.
- 我们用HashMap存所有的<word, index>, 然后reverse, 找配对就好.
- 如果有 empty string "", 那么它跟任何一个palindrome word, 都可以配对, 并且根据位置前后变换, 凑成2份 distinct indexes.
- 这样就有了那个
if (reverseEnd.equals("")) {...}
的logic. - 注意: 虽然在处理砍头/砍尾的两个 for loop里面都在根据 empty string 重复记录, 但因为 "" 自己本身不能作为起点, 所以overall只会在被其他palindrome配对时记录一次.
- 做text的search, 毋庸置疑要用Prefix tree, trie.
- �Trie造好之后, 做prefix search, 然后DFS/BFS return all leaf items. [high runtime complexity]
- 在TrieNode里面存所有的possible words. [high space usage]
- in memory space 应该不是�大问题, 所以我们可以选择 store all� possible words
- �Store MinHeap with TrieNode: 因为会不断搜索新此条, 同样的prefix (尤其是在higher level), 会被多次搜索.
- [�complexity: need to update heaps across all visited TrieNodes once new sentence is completed]
- Compute MinHeap on the fly: 当然我们不能每次都来一个DFS�不然会很慢, 所以就必须要store list of possible candidates in TrieNode.
- 这里就用到了
Top K Frequent Words
里面的Map<String, freq>
, 这样O(m) 构建 min-heap其实很方便. - 每次
#
后 标记一个词条被add进search history. 那么就要insert it into trie
. - 这一条在最后遇到
#
再做就可以了, 非常简洁 - Trie Prefix Search + maintain top k frequent items
-
枚举可能的A, 2. 然后一个个猜.
-
枚举A: 因为求MAX肯定是找leading-1最多的数字, 那么枚举A从(1000000...000)开始, 每次多一位取1或者0
-
因为枚举A的时候是按照每个bit来, 那么B和C也要以同样数位出现. 这里吧B和C变成了prefix的形式, 放在了set里面. 跟2sum用hashmap的思想类似, 每次用枚举的 A^B=C, 看看结果C是否已经在set里面. 如果在, 证明枚举的A可能被B^C得出, 那么就找到了一种情况.
- Trie Structure:
boolean isEnd
,HashMap<Character, TrieNode> children
- trie.addWord: 没node就加,有node就移动
- trie.search: 没node就return false,有node就移动
- Alternatively, the hash can be
TrieNode[26]
a fixed size array when applicable- I like map better for the simplicity to write (w/o converting char -> index)
- Trie Structure:
- trace the char to children node: Map<char, TrieNode>
- boolean isEnd to indicate if there is string end with this node
TrieNode {boolean isEnd; Map<Character, TrieNode> children}
.
- No need to store letter c in TrieNode
- HashMap构建Trie. Trie三个Method:
-
- Inset: 加 word
-
- Search: 找word
-
- StartWith: 找prefix
- 只有两条children的是binary tree. 那么多个children就是Trie
- 那么没有left/right pointer怎么找孩子?
- 用HashMap,以child的label为Key,value就是child node。 HashMap走位
- node里的char在这是optional. 只要在每个TrieNode里面用map存储向下分布的children就好了.
- 另外有种题目,比如是跟其他种类的search相关,在结尾要return whole string,就可以在node里存一个up-to-this-point的String。
- 如果是遇到一个一个字查询的题,可以考虑一下。
- 构建TrieNode的时候要注意:如何找孩子?如果是个map的话,其实就挺好走位的。
- 而且,每个node里面的 char 或者string有时候用处不大,
- 可以为空。但是有些题目,比如在结尾要return一些什么String,就可以在end string那边存一个真的String。
- 先排序(lexicographically), 排序以后才能逐个看是否partial string已经存在
- 用 set.contains(substring(0, n - 1)) 来查看上一步的 substring 是否存在
- 如果找到, 因为已经按照字母表排序, 找到的这个肯定是这个长度里面最符合的解答.
- 然后brutally找下一个更大的.
- Sort O(n log n), O(n) set space
- 可以先sort words Array:
- 长 string 排在前;
- 相等length, 按照dictionary order 排序
- 全部放入Trie. Trie.insert()
- 针对sorted words array, 从最长的开始找 Trie.startWith.
- 一旦找到, 就是符合题意的, 直接return.
- 注意: startWith 必须每一个node都是 isEnd, 才满足'逐个字母拼出' 的条件.
- Time: build Trie O(mn) + sort:O(nlogn) => O(nlogn)
- Space: O(mn)
- 按大小排序 -> 从最大的开始做contains()的比较 -> 结果再按照字母表顺序(lexicographically) sort一下.
- 但是Collections.sort()了两次, 而且再list.contains(), 比较慢
-
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- Build Trie for all combinations of
suffix#prefix
; all assigned with weight - how does it make sure to return largest weight/index?
- when we build trie, always update weight for the path nodes it goes through
- yes, it overrides, but this problem does not care if some words are not found
- Time:
- build: go through all words O(n) * word length * 2 => O(n)
- query: O(1) tree height is just at most 20.
- Space: O(N) store all words
- Previous solution, we use stack to hold previous cached/unprocessed items: but do we need use catch to hold them?
- If moving left:
p.val < root.val
, then root (parent of left child) is a successor candidate, so saverst = root
. - If moving right or equal:
p.val >= root.val
, the successor has nothing to do with curr node, so just directly dive into root.right. - Both iterative and recursive solution can be simplified as such.
- Iteratively search
- Still need stack to store previously unprocessed items along the path
- 画inorder图,发现规律.每个node的后继node(successor)有几种情况:
-
- node.right 是个leaf到底了。那么就return.
-
- set rightNode = node.right, 但发现rightNode has a lot left children to leaf.
-
- 比如, node.right == null, 也就是node自己是leaf,要回头看山顶找Inorder traversal规则里的下一个。
- 发现:其实就是每层都把路过的curr node放在stack里,最上面的,就是当下改return的那个successor:) Done.
- BST structure not given, use TreeSet to build BST with each node
- Every time find last/next inorder element
treeSet.lower(x)
,treeSet.higher(x)
- 一旦位置相隔(k + 1), 就满足题目条件
- O(nlogn), good enough
- Reverse the array, save days index into days[], where the new index is slot.
- days[i]: at slot i, which day a flower will be planted
- O(n)
- Needs to understand: http://www.cnblogs.com/grandyang/p/8415880.html
- 等于dfs遍历了所有k1<= x <= k2的x node。
- dfs left, process root, then dfs right
- 这里, 把 left/right/match的情况全部cover了,然后把k1,k2的边框限制好,中间就全部遍历了。
- 很容想到Inorder-binary-search-tree Traversal
- Iterative 稍微难想点:先把最左边的add, pop() stack, 加上右边(如果存在); 下一个轮回,如果又左孩子,又是一顿加。
- 然后稍微优化一下,确保rst.size() == k 时候,就可以return了
- check leaf => dfs left => add root => dfs right
- 根据BST规则, divide and conquer
- 取一个value, 然后分两半(start, value - 1), (value + 1, end) 分别dfs
- 然后左右两边的结果cross match
- BST inorder traversal should give small -> large sequence
- misplaced means: a large->small item would occur, and later a large>small would occur.
- The first large && second small item are the 2 candidates. Example
- [1, 5, 7, 10, 12, 15, 18]
- [1, 5,
15, 10
,12, 7
, 18] - traverse, and take note of the candidate
- at the end, swap value of the 2 candidates
- inorder traversal the nodes and save in array, find the 2 items misplanced and swap them
- But O(n) space should not be allowed
- Divide and Conquer
- 找到mid node
- 然后分割两半, 分别dfs做各自两个subtree: node.left,node.right
- 用长度来定位mid, 每次找中间点做root, 然后前半段, 后半段分别dfs with length.
- 用快慢pointer 找到mid. Better: 不用traverse entire linked list
- slowPointer = node;
- fastPointer = node.next;
- 然后把root = mid.next
- 然后开始sortedListToBST(mid.next.next); //后半段
- mid.next = null;//非常重要,要把后面拍过序的断掉
- sortedListToBST(head); //从头开始的前半段
- 最后root.left, root.right merge一下。
- TreeSet还是一个set, 我们用来装已经visit过得item
- 如果window大小超过K, 那么把nums[i - k - 1] 去掉, 并且加上新的element
- 这里有个公式推算: (Math.abs(A-B) <= t) =>>>>> (-t <= A - B <= t) =>>>>>> A >= B - t, A <= B + t
- 也就是说, 如果对于 B = nums[i], 来说, 能找到一个target A, 满足上面的公式, 那么就可以 return true.
- Time O(nLogk), treeSet的大小不会超过k, 而 treeSet.ceiling(), treeSet.add(), treeSet.remove() 都是 O(logK)
- Space O(k)
- 与Contains Duplicate II 类似概念. TreeSet有BST 因此可以直接用, 而不用自己构建BST
- 简化题目里面的重要条件 Math.abs(A-B) <= t 而推断出 A >= B - t, A <= B + t
- 并且需要需要用 TreeSet.ceiling(x): return number greater or equal to x. 这个用法要记住吧, 没别的捷径.
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
- 用stack记录最小值, 放在top. O(h) space.
- 每次消耗TreeNode, 都看看rightNode(其实就是下一个最小的candidate), 并且一条龙stack叠上rightNode所有的left子孙.
- 用O(1)空间的做法:不存stack, 时刻update current为最小值。
- 找下一个最小值,
- 如果current有right child: 和用stack时的iteration类似,那么再找一遍current.right的left-most child,就是最小值了。
- 如果current没有right child: 那么就要找current node的右上parent, search in BinarySearchTree from root.
- 注意:
- 一定要确保找到的parent满足parent.left == current.
- 反而言之,如果current是parent的 right child, 那么下一轮就会重新process parent。
- 但是有错:binary search tree里面parent是小于right child的,也就是在之前一步肯定visit过,如此便会死循环。
- Using merge sort concept (NOT merge sort impl).
- One very simply desire: if we want to know # elements between [i, j] such that
nums[i] > 2*nums[j]
, it would be so great if array is sorted!- If sorted, fix index i, keep j++ for all
nums[i]/2.0 > nums[j]
- We CANNOT just sort entire array. WHY? Because it distrupts the value of curr index i, and the restriction is:
find matching elements on right side of curr index i
- BUT, what about just sort
right side of i
, and make sure the subproblem (i+1, end) is solved first?
- If sorted, fix index i, keep j++ for all
- 灵感: use merge sort concept.divide and conquer [i ~ n] into 2 sections:
-
- solve subProblem(start,mid) & subProblem(mid+1, end). sort the sub array so that it can be used recursively at parent level.
-
- solve the curr pblem: for all [i, mid], check against [mid+1, end].
-
- Question1: does it cover all use cases?
- First, subProblem(start,mid) & subProblem(mid+1, end) recursively solves its own range
- Last, the only range is the current level problem check
[i, mid]
against its entire right side range:[mid+1, end]
. DONE. all covered.
- Question2: what it is okay for
subProblem(start,mid) & subProblem(mid+1, end)
partially sort the array?- that is the goal: 1) we want the right side range to be sorted; 2) left range is sorted but it does not matter since we treat [start, mid] as 1 group
- use classic while loop
while(j<=e && nums[i]/2.0 > nums[j])
to count pairs - TODO
- split the array into index-based segment tree, where each element is at leaf
- store min of range: use min to determine if certain range is needed for further query
- query for each element right side range (i + 1, end), where it recursively query&aggregate sub-range if meeting requirement
nums[i] > 2*nums[j]
- only when target > subRange.min * 2: there are possible candidates, query further
- worst case O(n^2) when all tailing elements are meeting requirement.
- TODO
- Build the BST based on node value. It will be not applicable if we search after entire tree is built (our goal is right range), so we need to build right elements, and search/count right after the elements is added
- Worst case is still O(n^2), if all added nodes are meeting requirement
- search(tree, curr / 2.0)
- check each one of them
- What if
the processed list is sorted
, so that I can BinarySeach for curr target?- process from end
- binary search for
index to insert new element
in sorted ascending list - that index = # of smaller numbers; record it for final result
- time: O(nlogn)
- space: O(n)
- Segment Tree functions:
Build
: construct segment tree based on min/max range: at leaf node, update count of numbers in rangemodify(SegmentTreeNode root, int value, int count)
: find leaft at with value, and update count for leaf & all parent nodesquery(SegmentTreeNode root, int start, int end)
: return count # of numbers in range [start, end]
- Very similar to
Count of Smaller Number
, where segment tree is built on actual value!! - IMPORTANT to drop processed number from left-hand-side:
- only find on remaining numbers.
- Utilize
modify(root, target, -1)
to erase element count & update the tree.
- time:
n * log(m)
, where m = Math.abs(max-min). log(m) is used to modify() the leaf element - space: O(m)
Define the positive range
- negative nubmer division
rounds up towards 0
(this is a problem). (i.e.(-2 - 1) / 2 = -1.5 = -1
), which causes range error. - We want the entire segment tree range to be ascending, and we want the mid = (start+end)/2 to round down.
- Solution:
- build entire segment tree based on [min, max], where min must be >= 0.
- we can do this by adding Math.abs(min) onto both min/max, as well as +offset during accessing nums[i]
- negative nubmer division
- https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
- Assume we have a BST, where each node has smallerCount and a val, for any new target, how to find smaller items?
-
- add the # of smaller count to current node
-
- compare:
- if target < node.val, keep searching
countVisit(node.left, target)
- if target > node.val: 1) add currNode.smallerCount, 2) minus node.right.smallertCount (reduce double-counting), 3) plus
countVisit(node.right, target)
- remember to create left/right node before dfs countVisit into the sides.
-
- 记录找到过的closest
- Binary Search, 根据current node走位, until null leaf
- time: O(logn), space O(1) since no extra space used
- when less than curr val, consider left
- when greater than curr val, consider right
- dfs到底, 然后每一层比较, 再return
- time: O(logn), space: O(logn)
- sum up the matching L & R
- Find (L,R) on left child
- Find (L,R) on right child
- Find (L,R) covering the root node
- space O(n), worst case O(logn), height of dfs.
- time O(n) to find all nodes between (L, R)
- Using stack, or queue, list: any data structure (we are not doing ordered search)
- space O(n)
- time O(n)
- Regular convert BST to sored list: in-order traversal
- Carefully doubly link node head, tail
- traverse all nodes, DFS 好做:
left, curr, right
- Tail:
- Assume head is found for sub tree, then
tail = head.left
- Link
LeftTail <-> Curr Root <-> RightHead
- Link
RightTail <-> LeftHead
- Assume head is found for sub tree, then
- In place:
- 同
Node {val, left, right}
, w/o new doubley linked list class - different from
Convert Binary Search Tree to Sorted Doubly Linked List (extra space)
- 同
- 查看每个parent-child关系:
- leftchild < root < rightChild
- all of left child < curr < all of right child
- 方法: 把root.val 传下来作为 max 或者 min, valid child in (min, max)
- BST 有两个极端: left-most-leaf is the smallest element, and right-most-leaf is largest
- imagine we know the two extreme border: Long.MIN_VALUE, Long.MAX_VALUE
- min/max: long type to meet edge case: node.val = Integer.MAX_VALUE
- 利用 BST 的性质,可以直接搜到target node,而做成两个长度不一定相等的 path
- 然后很简单找到LCA
- O(n) space, O(logn) time
- Beofre lasts common ancestor is found: p and q should follow same search pattern:
- compare with root, then dfs(left) or dfs(right)
- util p and q fall on two sides of root, then return root
- 非常巧妙, 但是也比较局限; 稍微变条件, 就很难recursive.
- 几种情况:
-
- one of p, q 在leaf, 那么此时的root其实就是lowest common ancestor
-
- 如果p, q 在root的左右两边, 这就是分叉口, 那么root就是lowest common ancestor
-
- 如果p, q 在root的同一边 (左,右), 那么继续dfs
-
- O(logn) extra space: recursive stack space
- O(logn) time
- Median还是用min-heap 和 max-heap. Time(logN)
- 加/减: prioirtyQueue, log(n)
- findMedian: O(1)
- 加一个数, 减一个数。
- 加减时看好,是从前面的maxheap里面抽,还是从后面的minHeap里面抽。
- 抽完balance一下
- 用maxHeap, minHeap时候, 习惯选择让maxHeap多一个数字:
- 左边的maxHeap总有 x+1或者x个数字
- 后边minHeap应该一直有x个数字
- 做text的search, 毋庸置疑要用Prefix tree, trie.
- �Trie造好之后, 做prefix search, 然后DFS/BFS return all leaf items. [high runtime complexity]
- 在TrieNode里面存所有的possible words. [high space usage]
- in memory space 应该不是�大问题, 所以我们可以选择 store all� possible words
- �Store MinHeap with TrieNode: 因为会不断搜索新此条, 同样的prefix (尤其是在higher level), 会被多次搜索.
- [�complexity: need to update heaps across all visited TrieNodes once new sentence is completed]
- Compute MinHeap on the fly: 当然我们不能每次都来一个DFS�不然会很慢, 所以就必须要store list of possible candidates in TrieNode.
- 这里就用到了
Top K Frequent Words
里面的Map<String, freq>
, 这样O(m) 构建 min-heap其实很方便. - 每次
#
后 标记一个词条被add进search history. 那么就要insert it into trie
. - 这一条在最后遇到
#
再做就可以了, 非常简洁 - Trie Prefix Search + maintain top k frequent items
- 用PriorityQueue把选中的height排序,为走位, create class Cell (x,y, height).
-
- 从matrix四周开始考虑,发现matrix能Hold住的水,取决于height低的block
-
- 必须从外围开始考虑,因为水是被包裹在里面,外面至少需要现有一层
- 以上两点就促使我们用min-heap: 也就是natural order的PriorityQueue.
-
- process的时候,画个图也可以搞清楚: 就是四个方向都走走,用curr cell的高度减去周围cell的高度.
-
- 若大于零,那么周围的cell就有积水: 因为cell已经是外围最低, 所以内部更低的, 一定有积水.
-
- 每个visited的cell都要mark, avoid revisit
-
- 根据4个方向的走位
(mX, mY)
创建新cell 加进queue里面: cell(mX, mY) 已经计算过积水后, 外围墙小时, �(mX, mY)
就会变成墙.
- 根据4个方向的走位
-
- 因为做的是缩小一圈的新围墙, height = Math.max(cell.h, neighbor.h);
- 和trapping water I 想法一样。刚刚从外围,只是能加到跟外围cell高度一致的水平面。往里面,很可能cell高度变化。
- 这里要附上curr cell 和 move-to cell的最大高度。
- 要找到bucket的最短板
- 每次需要最先处理最短的那条 (on top)
- 木桶理论, 包水, 是从外面包住里面
- 洋葱剥皮, 用完丢掉
- Need to maintain k large elements, where the smallest will be compared and dropped if applicable:
- Maintain k elements with min value: consider using minHeap
- add k base elements first
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分
- sort结束后是ascending的, 那么 n - k 就是第k大.
- partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
- 没找到继续partion recursively.
- sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)
- Steps:
- 每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
- 找到一个low>pivot, high<pivot, 也就可以swap了。
- 得到的low就是当下的partion point了
- Overall O(nlogN), average O(n) for this problem.
- 由Merge k sorted list启发。用PriorityQueue,存那k个首发element
- PriorityQueue需要存储单位: 自己建一个Class Node 存val, x, y index.
- 因为array里没有 'next' pointer,只能存x,y来推next element
- Not sure why
new PriorityQueue<>(Comparator.comparing(a -> a.val));
is slower - Heap用的不多. 得用一下, 才好理解. 通常default 的PriorityQueue就是给了一个现成的min-heap:
- 所有后面的对应element都比curr element 小。
- Heapify里面的siftdown的部分:
- 只能从for(i = n/2-1 ~ 0), 而不能从for(i = 0 ~ n/2 -1): 必须中间开花,向上跑的时候才能确保脚下是符合heap规则的
- 确保在heap datastructure里面curr node下面的两个孩子,以及下面所有的node都遵循一个规律
- 比如在这里,若是min-heap,就是后面的两孩子都要比自己大。若不是,就要swap。
- for each element A[i], we will get A[i * 2 + 1] >= A[i] and A[i * 2 + 2] >= A[i].
- siftdown时:在curr node和两个child里面小的比较。如果的确curr < child, 搞定,break while.
- 但若curr 并不比child小,那么就要换位子,而且继续从child的位子往下面盘查。
- Use HashMap to store <num, freq>
- Bucket
List<Integer>[]
: stores <count, list unique element with that count>- Size of the data structure will be uniqe item size.
- The bucket[i] stores item at frequency i
- Simply loop from bucket.length -> 0, when bucket[i] not null, add to result.
- Solid O(n)
- Use regualr priorityQueue to sort by frequency ascendingly
- the queue.peek() record has lowest frequency, which is replacable
- Always only maintain k elements in the queue, so sorting is O(logk)
- IMPORTANT: remember to
rst.add(0, x)
for desired ordering - time faster than maxHeap: O(nlogk)
- option1: just use
map<num, freq>
; option2: useclass Record {int num; int freq}
- 题目有提醒: 必须beetter than O(nLog(n)).
- max heap approach stores all nodes: it is wrong
- even though freq count size m < n, but it can be m == n. ALL unique.
- then it is O(nlogN) again.
- therefore, storing all items into pq is INCORRECT.
- 把Input stream想成向上的山坡. 山坡中间那点,自然就是median.
- 前半段,作为maxHeap,关注点是PriorityQueue的峰点,也就是实际上的median.
- 后半段,作为minHeap,正常的PriorityQueue。 开头是最小的。
- 这里要首先定好, 哪一个queue是多存一个element的. 这里选maxHeap: maxHeap.size() == minHeap.size() + 1 || minHeap.size()
- 必须先维护maxHeap里面有个元素, 否则null了会在比较大小时出问题.
-
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- This approach follows the pattern of finding min pair:
-
- only need to store k pairs
-
- always start from min of A list and min of B list
-
- pre-build k pairs honoring A list, and then pick the min pair, and start swapping with min of list B
-
- First attemp all first k pairs from nums1[i] against nums2[0] <=k : O(k)
- Use queue to pull min node and save results
- Use the nums1 val from the min node, pair up with nums2[j], add back to queue to sort
- overall runtime: O(klogk)
- space: O(k)
- Brutle: build all pairs time O(mn), sort with maxHeap pq with k size, and find top k
- overall time: O(mnLogK)
- space: O(k)
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- Permutation 实际上就是在list/array/... 上面给元素换位置
- 硬换位置, 每次换的位置不同, 用random来找到要换的index
- 维持同一个random seed
- O(n)
- compute all permutations 太慢, 不可行.
- validate Input 是不是可以做palindromic permutation. 这个就是(Palindrome Permutation I)
- 顺便存一下permutation string的前半部分和中间的single character(if any)
- DFS 做unique permutation: given input有duplicate characters。
- concept similar to
31. Next Permutation
-
- first pass: find the one that is in incorrect order
-
- second pass: find the right spot to swap
- Great write up: https://leetcode.com/problems/next-permutation/solution/
- next lexicographically permutation:
smallest
butlarger than curr
permutation:- find first low point from right [low]
- find the slight larger [high] to swap with [low]
- make sure right side of low is eventually the smallest
- Analyze the use cases, to find next low permutation, 2 major steps:
-
- Find
first low/drop candidate
from right
- Find
-
- Find
first high where nums[high] > nums[low]
from right
- Find
-
- swap(low, high).
- By now, [low, n-1] forms a greater permutation
- but it is not the smallest, because right side [low + 1, n - 1] is descending
-
- reverse(low + 1, n-1) to create ascending slopt on right of low (smallest next lexicographically permutation)
-
- Corner case: if input array is decending (1st low not found), reverse it all together O(n)
- time: O(n) visit all indexes
- space: O(1) not using additional
- Similar question:
1053. Previous Permutation With One Swap
- Best of all recursive approaches
- iterate over nums: pick or not pick
- reduce remaining item in next level:
- option1: use queue, restrict queue size; backtrack append to queue
- option2: remove element before passing into next level; backtrack: insert back
- time O(n!): visit all possible outcome
- T(n) = n * T(n-1) + O(1)
- A bit worse than option1, uses more time:
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
contains
is nlogn time - also, each dfs, it has to iterate over entire nums list
- list.contains() cost O(logn). Technically, it is O(n^n), plus the
- Use visited[] to track, still causes for(over n items), not efficient
- Best of all iterative approaches
- Each time pick 1 NEW element and find places to insert into candidate list:
-
- 一个一个element加进去
-
- 每一次把rst里面的每个list拿出来, 创建成新list, 然后选位置加上new element
-
- 加新元素的时候, 要在list的每个位置insert, 最终也要在原始的list末尾加上new element
-
- 还是O(n!), 因为rst insert O(n!)个permutations
- Better than the Option2/Option3 (
BFS+Queue
), because this solution does not need to check duplicates - 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍
- Time O(n!)
- Slow: checking candidate.contains() is O(logn) each time
- Almost same as Method2-Option2, but it builds remainingCandidate list on the fly list.removeall(xyz): O(n)
- Even slower than Method2-Option2
bipartite
require each node to be in exact 1 party, which means it only has 1 state- DFS to mark node with one state; and mark its edges as reversed state
- If any node state has been assigned by different from desired one, return false.
- Use
Boolean states[i]
to represent visted & state - Try all nodes with for loop, and skip visited nodes (similar validation rules as in dfs)
- In
int next : graph[curr]
, test next level first before adding. - 遇到最值, 想到DP.
- 看到分割字眼, 想到分割型 DP.
- 思考, 如果 j * j = 9, 那么 j = 3 就是最少的一步; 但是如果是10呢? 就会分割成1 + 9 = 1 + j * j
- 考虑最后的数字: 要是12割个1出来, 剩下11怎么考虑? 割个4出来,剩下8怎么考虑?
- partion的方式: 在考虑dp[i - x]的时候, x 不是1, 而是 x = j*j.
- 就变成了dp = Min{dp[i - j^2] + 1}
- 乍一看是O(n*sqrt(n)). 实际也是. 但如何推导?
- 考虑上限: 把小的数字变成大的 推导上限; 考虑下限: 把数字整合归小, 找到下限.
- 考虑sqrt(1) + sqrt(2) + ....sqrt(n):找这个的upper bound and lower bound.
- 最后发现它的两边是 Ansqrt(n) <= actual time complexity <= Bnsqrt(n)
- 那么就是O(n*sqrt(n))啦
- minus all possible (i*i) and calculate the remain
- if the remain is new, add to queue (use a hashset to mark calculated item)
- find shortest path / lowest level number
- 一开始没clue.看了一下提示
- 1. 第一步想到了,从数学角度,可能是从最大的perfect square number开始算起。
- 2. 然后想法到了dp, 假设最后一步用了最大的maxSqrNum, 那么就在剩下的 dp[i - maxSqrNum^2] +1 不就好了?
- 3. 做了,发现有个问题...最后一步选不选maxSqrNum? 比如12就是个例子。
- 然后就根据提示,想到BFS。顺的。 把1~maxSqrNum 都试一试。找个最小的。
- 看我把12拆分的那个example. 那很形象的就是BFS了。
- 面试时候,如果拆分到这个阶段不确定,那跟面试官陶瓷一下,说不定也就提示BFS了。
- 第一步, 理解题目要求的问题: 前k个人copy完n本书, 找到最少的用时; 也可以翻译成:
n本书, 让k个人来copy, 也就是分割成k段
. - 最后需要求出 dp[n][k]. 开: int[n+1][k+1].
- 原理:
-
- 考虑最后一步: 在[0 ~ n - 1]本书里, 最后一个人可以选择copy 1 本, 2 本....n本, 每一种切割的方法的结果都不一样
-
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
Math.max(dp[j][k - 1], sum)
.
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
-
- 其中:
dp[j][k-1]
是 [k-1]个人读完j本书的结果, 也就是著名的上一步
. 这里循环考虑的是第k个人不同的j种上一步 : )
- 其中:
-
- 循环的结果, 是要存在 dp[i][k] = Math.min(Math.max(dp[j][k - 1], sum[j, i]), loop over i, k, j = [i ~ 0])
- Time: O(kn^2), space O(nk)
- Init: dp[0][0] = 0, 0个人0本书
- Integer.MAX_VALUE的运用:
- 当 i = 1, k = 1, 表达式: dp[i][k] = Math.min(dp[i][k], Math.max(dp[j][k - 1], sum));
- 唯一可行的情况就只有一种: i=0, k=0, 刚好 0 个人 copy 0 本书, dp[0][0] = 0.
- 其他情况, i = 1, k = 0, 0 个人读 1本书, 不可能发生: 所以用Integer.MAX_VALUE来冲破 Math.max, 维持荒谬值.
- 当 i=0, k=0 的情况被讨论时候, 上面的方程式才会按照实际情况计算出 dp[i][k]
- 这道题的init是非常重要而tricky的
- k个人, 需要一个for loop;
- k个人, 从copy1本书开始, 2, 3, ... n-1,所以 i=[1, n], 需要第二个for loop
- 在每一个i上, 切割的方式可以有[0 ~ i] 中, 我们要计算每一种的worst time
- [k] 只有和 [k - 1] 相关
- Space: O(n)
- 根据: 每个人花的多少时间(time)来做binary search: 每个人花多久时间, 可以在K个人之内, 用最少的时间完成?
- time variable的范围不是index, 也不是page大小. 而是[minPage, pageSum]
- validation 的时候注意3种情况: 人够用 k>=0, 人不够所以结尾减成k<0, 还有一种是time(每个人最多花的时间)小于当下的页面, return -1
- O(nLogM). n = pages.length; m = sum of pages.
- Find minimum cut: 分割型DP
- dp[i]: 最少cut多少刀, 使得前 i 长度的string, 割出来都是palindrome
- 最终要得到 dp[n], 所以 int[n + 1]
- 移动切刀, 看在哪里切, index j in [0 ~ i]
- 考虑[j, i - 1] 是否是回文串, 如果是, 那么: dp[i]= min(dp[i], d[j] + 1).
- note: 估计遍历 j 的时候, 反过来遍历也可以.
- 利用palindrome的性质, 可以算出 boolean palindrome[i, j]的情况.
- 找一个任意mid point:
-
- 假设palindrome是奇数长度, 那么 mid 是单独的字符, 而两边的字符 [mid-1], [mid+1] 应该完全相等.
-
- 假设palindrome是偶数长度, 那么 [mid] 和 [mid + 1] 这样位置的字符应该相等.
- 这样做出来 palindrome[i, j]: 从字符 i 到 字符 j 的 substring 是否是 palindrome
- 这样就给我们的问题合理降维, 目前是time: O(n^2).
- 不然求一次palindrome, 就是n, 会变成O(n^3)
- Double for loop 检查每种substring string (i~j). 若i,j相邻或者同点,那么肯定isPal;否则,i,j之间的(i+1, j-1)一定得isPal。
- 看上去,在检查i,j的时候,中间按的(i+1, j-1)怎么可能先知道? 其实不然..在j慢慢长大的时候,所有的0~j的substring都检查过。所以isPal[i+1][j-1]一定是已经知道结果的。
- okay.那么假如以上任意一种情况成立,也就是说isPal[i][j] == true。那就要判断,切到第一层循环参数j的末尾点时,有多少种切法?
- 想法很顺:我们naturally会想到,把i之前的cut加上i~j之间发生的不就好了。
- 反正现在j不变,现在就看吧i定在哪里,cut[i - 1]是否更小/最小; 再在cut[i-1]基础上+1就完了。
- 当然,如果i==0, 而 i~j又是isPal,那没啥好谈的,不必切,0刀。
- 最终,刷到cut[s.length() - 1] 也就是最后一点。 return的理所应当。
- 加法原理: 根据题意, 有 range = 1 的 [1, 9], range = 2 的 [10~26] 来作为partition.
- there can be 2 final states at dp[i]: single digit or double digit.
- validate if
single digit
, dp[i] += dp[i - 1]. Last 1 digit can be decoded. - validate if
double digit
, dp[i] += dp[i - 2]. Last 2 digits can be decoded.
- validate if
- note:
- get number from char:
c - '0'
- validatae single digit != '0', 因为'0' 不在条件之中(A-Z)
- get number from char:
- Option1: dp[i] as # ways to decode at index i, including letter i
- The validation is done on: 1) single digit i, or 2) double digit [i-1, i]
- Option2: Partition DP, dp[i] as # ways to decode for first i letters (excluding letter i)
- 定义
dp[i] = 前i个digits最多有多少种decode的方法
: new dp[n + 1]. - dp[i] += dp[i - x], where x = 1, 2
- The validation is done on: 1) single digit [i-1], or 2) double digit [i-2, i-1]
- Option2 is better in terms of clean coding. We assume
dp[0]=1
as 1 way to decode 0 digits.- No need to specially handle length == 1, because it is covered later
- No need to manualy init the first 2-digit case as in Option1
- Return of
dp[n]
is clean
- 定义
- 引申: 这里只有两种partition的情况 range=1, range =2. 如果有更多partition的种类, 就可能多一层for loop做循环
- if single-digit is working, sum += dfs(s, i+1);
- if double-digit is working, sum += dfs(s, i+2);
- end case: i >= n, return 0; i == n - 1; i == n - 2
- especially when i = n - 2, handle 2-digt edge case carefully:
-
- check if two-digit range [i, i+1] is valid
-
- check if single-digit [i] is valid; if so, += dfs(s, i + 1)
-
- especially when i = n - 2, handle 2-digt edge case carefully:
- memo[i]: # ways to decode from [i, n). init with
memo[i]=-1
- 乘法原理, 加法原理
- 跟decode way I 一样, 加法原理, 切割点时: 当下是取了 1 digit 还是 2 digits 来decode
- 定义dp[i] = 前i个digits最多有多少种decode的方法. new dp[n + 1].
- 不同的情况是: 每一个partition里面, 如果有"*", 就会在自身延伸出很多不同的可能
- 那么: dp[i] = dp[i - 1] * (#variations of ss[i]) + dp[i - 2] * (#variations of ss[i,i+1])
- Enumeration:
- 具体分析 '*' 出现的位置, 枚举出数字, 基本功.
- 注意!!题目说 * in [1, 9]. (如果 0 ~ 9 会更难一些)
- 枚举好以后, 其实这个题目的写法和思考过程都不难
- Mode: 数字太大, 取mod来给最终结果: 其实在 10^9 + 7 这么大的 mod 下, 大部分例子是能通过的.
- DFS top-down approach is used to analyze the problem. The logic flow:
-
- consider the case of 1 letter or 2 letters.
-
- one letter:
- [*]: + 9 * dfs(s, i + 1)
- [0~9]: + dfs(s, i + 1)
-
- two letters:
- [*, *]: + 15 * dfs(s, i + 2)
- memo[i] records # of ways to decode from [i ~ n]
- space: O(n), Size of recursion tree can go upto n
- time: O(n),
memo array is filled exactly once
!!! - find slow/fast to detect the meeting point
- find begin node of the cycle: traverse from head, also move slow; utill head/slow meets slow
- Imagine two runners running on a track at different speed. What happens when the track is actually a circle?
- https://leetcode.com/problems/linked-list-cycle/solution/
- O(1) sapce: 用快慢指针,
start=head.next
,end=head.next.next
- Fast pointer will eventually catch up to slow pointer
- O(n) space: 用HashMap,一直add elements. 如果有重复,那么很显然是有Cycle
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- 做text的search, 毋庸置疑要用Prefix tree, trie.
- �Trie造好之后, 做prefix search, 然后DFS/BFS return all leaf items. [high runtime complexity]
- 在TrieNode里面存所有的possible words. [high space usage]
- in memory space 应该不是�大问题, 所以我们可以选择 store all� possible words
- �Store MinHeap with TrieNode: 因为会不断搜索新此条, 同样的prefix (尤其是在higher level), 会被多次搜索.
- [�complexity: need to update heaps across all visited TrieNodes once new sentence is completed]
- Compute MinHeap on the fly: 当然我们不能每次都来一个DFS�不然会很慢, 所以就必须要store list of possible candidates in TrieNode.
- 这里就用到了
Top K Frequent Words
里面的Map<String, freq>
, 这样O(m) 构建 min-heap其实很方便. - 每次
#
后 标记一个词条被add进search history. 那么就要insert it into trie
. - 这一条在最后遇到
#
再做就可以了, 非常简洁 - Trie Prefix Search + maintain top k frequent items
- 把Interval拆分成数轴上的Point
- 起飞mark 1
- 降落mark -1
- 用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。
- 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
- 当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
- 这避免了错误多count,或者少count
- curr index is based on previous calculation: the min of all 3 previous factors
- O(n)
- 非常brutle的。
- 每次把dp[i-1]拿出来,不管三七二十一,分别乘以2,3,5. 出来的结果放进priority queue做比较。
- 最后时间是nlog(n3)
- 注意:use long, use HashSet确保没有重复
- O(nlogn)
- if already found a good/shorter route, skip
if (distMap[node.x][node.y] <= node.dist) continue;
- This always terminates the possibility to go return to original route, because the dist will be double/higher
- 用PriorityQueue把选中的height排序,为走位, create class Cell (x,y, height).
-
- 从matrix四周开始考虑,发现matrix能Hold住的水,取决于height低的block
-
- 必须从外围开始考虑,因为水是被包裹在里面,外面至少需要现有一层
- 以上两点就促使我们用min-heap: 也就是natural order的PriorityQueue.
-
- process的时候,画个图也可以搞清楚: 就是四个方向都走走,用curr cell的高度减去周围cell的高度.
-
- 若大于零,那么周围的cell就有积水: 因为cell已经是外围最低, 所以内部更低的, 一定有积水.
-
- 每个visited的cell都要mark, avoid revisit
-
- 根据4个方向的走位
(mX, mY)
创建新cell 加进queue里面: cell(mX, mY) 已经计算过积水后, 外围墙小时, �(mX, mY)
就会变成墙.
- 根据4个方向的走位
-
- 因为做的是缩小一圈的新围墙, height = Math.max(cell.h, neighbor.h);
- 和trapping water I 想法一样。刚刚从外围,只是能加到跟外围cell高度一致的水平面。往里面,很可能cell高度变化。
- 这里要附上curr cell 和 move-to cell的最大高度。
- 要找到bucket的最短板
- 每次需要最先处理最短的那条 (on top)
- 木桶理论, 包水, 是从外面包住里面
- 洋葱剥皮, 用完丢掉
- Can be a 2D array, or a
class Point
to store preSum + index - Sort preSum: smaller (有可能负数) 靠前, 大数字靠后
- 比较�preSum种相连接的两个节点, 找差值min; 因为最接近的两个preSum节点的差值肯定是最小
- min所在的两个节点的index, 就是�result candidate: 这两个index可能再原nums里面相差很远
- time O(nlogn), sort
- space: O(n)
- 因为map虽然能存 preSum + index, 但是无法有效排序
- 所以用一个class来存这两个信息, 然后合理排序
- Need to maintain k large elements, where the smallest will be compared and dropped if applicable:
- Maintain k elements with min value: consider using minHeap
- add k base elements first
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分
- sort结束后是ascending的, 那么 n - k 就是第k大.
- partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
- 没找到继续partion recursively.
- sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)
- Steps:
- 每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
- 找到一个low>pivot, high<pivot, 也就可以swap了。
- 得到的low就是当下的partion point了
- Overall O(nlogN), average O(n) for this problem.
- 跟 Maze I, II 类似, 用一个 Node[][] 来存每一个(x,y)的state.
- Different from traditional BFS(shortest path):
it terminates BFS when good solution exists (distance), but will finish all possible routes
-
Termination condition
: if we already have a good/better solution on nodeMap[x][y], no need to add a new one
-
- Always cache the node if passed the test in step1
-
- Always offer the moved position as a new node to queue (as long as it fits condition)
-
- Finally the item at nodeMap[target.x][target.y] will have the best solution.
- 由Merge k sorted list启发。用PriorityQueue,存那k个首发element
- PriorityQueue需要存储单位: 自己建一个Class Node 存val, x, y index.
- 因为array里没有 'next' pointer,只能存x,y来推next element
- Not sure why
new PriorityQueue<>(Comparator.comparing(a -> a.val));
is slower - Use HashMap to store <num, freq>
- Bucket
List<Integer>[]
: stores <count, list unique element with that count>- Size of the data structure will be uniqe item size.
- The bucket[i] stores item at frequency i
- Simply loop from bucket.length -> 0, when bucket[i] not null, add to result.
- Solid O(n)
- Use regualr priorityQueue to sort by frequency ascendingly
- the queue.peek() record has lowest frequency, which is replacable
- Always only maintain k elements in the queue, so sorting is O(logk)
- IMPORTANT: remember to
rst.add(0, x)
for desired ordering - time faster than maxHeap: O(nlogk)
- option1: just use
map<num, freq>
; option2: useclass Record {int num; int freq}
- 题目有提醒: 必须beetter than O(nLog(n)).
- max heap approach stores all nodes: it is wrong
- even though freq count size m < n, but it can be m == n. ALL unique.
- then it is O(nlogN) again.
- therefore, storing all items into pq is INCORRECT.
- O(nlogn) time (PriorityQueue), O(n) space
-
- 扫描线+Count: when
count==0
, startFlags==endFlags. 是interval的开头/结尾 (write an example)
- 扫描线+Count: when
-
- Note: remember to merge points on same sweep line position
-
- Comparator:
new PriorityQueue<>(Comparator.comparing(p -> p.val))
; - Sort intervals: O(nlogn), extra space O(n) when creating rst list
Arrays.sort(intervals, Comparator.comparing(i -> i[0]));
- 找到结尾 interval, 满足条件就可以save
- 如果不到return的条件, 就继续延伸 interval.end
- Less applicable when input is
int[][] intervals
, but more applicable when we haveList<int[]> intervals
- Related example: Insert Interval
- Sort fist, loop over and merge, cut off overlapped interval.
- sort by Interval.start:
intervals.sort(Comparator.comparing(interval -> interval.start)); // O(nlogn)
- 用两个相连的Interval: curr, next
- 如果 curr.end覆盖了 next.start: 需要merge. 那么比较一下 curr.end vs. next.end
- 一旦merge, 需要remove被覆盖的 next interval:
list.remove(i+1)
- 若没有重合,就继续iteration
- sort by Interval.start:
- time O(nlogn), space O(1)
- 注意接头点要考虑所有开会结会的情况,不要恰巧漏掉相接的点
- 开会的是超人。瞬间移动接上下一个会议
- sort:
Arrays.sort(intervals, Comparator.comparing(i -> i[0]))
- time: O(nlogn), space: O(1)
- class Point{pos, flag}, PriorityQueue排序。计算count
- 跟 Number of Airplanes in the Sky 是一个类型的题目
- time: O(nlogn), space O(n)
- Not necessary for this problem, since it requires extra space with pq.
- Use priority queue to sort by customized class interval{int dist; int x, y;}.
- Sort by larger distance and then sort by start index
- seat(): pq.poll() to find interval of largest distance. Split and add new intervals back to queue.
- leave(x): one seat will be in 2 intervals: remove both from pq, and merge to a new interval.
- 主方程写出来其实很好写, 就是 split + add interval, 然后 find + delete interval 而已. 最难的是构建data structure
- seat(): O(logn), leave(): O(n)
Trick: 构建虚拟 boundary
- 如果是开头的seat, 或者是结尾的seat, 比较难handle: 一开始坐在seat=0的时候, 没有interval啊!
- Trick就是, 我们自己定义个虚拟的座位
seat=-1
,seat=N
- 一开始有一个 interval[-1, N] 然后就建立了boundary.
- 从此以后, 每次split成小interval的时候:
- 遇到
interval[-1, y]
, distance就是(y - 0)
- 遇到
interval[x, N]
, distance就是(N - 1 - x)
- 当然正常的interval dist 就是
(y - x) / 2
- distance 中间点
- Interval.dist 我们其实做的是 distance的中间点
(y - x) / 2
- 这里的dist是
距离两边的距离
而不是 x, y 之间的距离. 这里要特别注意.
- Interval.dist 我们其实做的是 distance的中间点
- TreeSet
- TreeMap<starting Pos, Interval>
- seat(): O(logn)
- find largest dist with TreeSet.first()
- break into 2 intervals; save to set and save to map
- leave(x): O(logn)
- find the interval before starting point x using TreeMap.floorEntry()
- merge and store back to set/map
- for test case it is slower than PQ, because it saves to 2 data structure
- Sort start times, and end times in 2 different arrays
- Loop over start time
- when start[i] < end[endIndex], Count++, need more room
- start[i] >= end[endIndex], done using some room, move to next end time, endIndex++ (like vacating a room)
- Note: we never decrese count because:
- what ever count reaches, it is the max
- since we keep moving endIndex, when start[i] >= end[endIndex], we will just reuse meeting room w/o count++
- time: O(nlogn)
- space: O(n)
- somehow, super fast, over 100%
- inspired by: https://leetcode.com/problems/meeting-rooms-ii/discuss/67855/Explanation-of-%22Super-Easy-Java-Solution-Beats-98.8%22-from-%40pinkfloyda
- Use sweep line to process, track max count as max # of rooms needed
- 跟 Number of Airpline in the sky是同一道题
- time: O(nlogn)
- space: O(n)
- define the bucket by index: the total distance is fixed [0, 1000]
- +/- capacities for each pos and save into the bucket
- go over the bucket and see if the total cap goes over input capacity
- O(n), trips size
- space: O(1), bucket size 1000 is constant
IMPORTANT
: before using PQ to sort, consider bucket sort:- if the boundary set and seems resonable? i.e., max size =
1000
- is the sorted items index based?
- if the boundary set and seems resonable? i.e., max size =
- Like meeting room, merge interval
- process items on same index
- 正面去做:
- count task出现的次数
- 然后PQ sort Task object in descending order
- 每个section: k slots = n + 1. Same task being n slots apart, meaning one section has n + 1 slots.
- 目标是穷尽 k, or 穷尽 pq (poll k times, but will save it back to queue if Task # > 0)
- 如果qp 真的穷尽, break, return count
- 不然, count += k, where k are just # of idle intervals
- time O(n) + constant time O(xlogx), where x = 26
- extra space O(x) ~ O(1)
- Enumerate to understand:
- 1.module tasks in module/section;
- 2.Only need sum the intervals/slots, not return actual layout
- Perfect case: all letters appear identical # times: just line them up separate in order.
- Real case: task appears different times
-
- Place maxCount task as header followed with n slots: define (maxCount-1) sections
-
- For tasks with less # than maxCount# can fill the (maxCount-1) sections; what about the tail section?
-
- Any task with same maxTask#, of if prior sections all filled, will fill the tail section
- To count overall slots/intervals, come up with this equation:
-
- Fixed sections:
(maxCount - 1) * (n + 1)
- Fixed sections:
-
- Plus all repeating maxCount tasks: calculate by couting identical maxCount of them
-
- Exception: if the first (max - 1) sections are all filled completely, and we still have extra task (ex: when n is not large enough), then just return tasks.length
-
- time O(n), space O(1)
- ??? Need to study
- 注意special case:
when less than 3 items, return maximum
- PQ是natural order: remain peek() will be the 3rd maximum
- PQ can be used to sort on multiple attributes
- follow the specified rules, and build all possible pairs of visits vs. bike. Pair {int dist, workerIndex, bikeIndex}
- PQ to sort them
- first by dist
- if same dist, sort by workerIndex
- if same workderIndex, sort by bikeIndex
- process all candidates, and skip the ones (workers/bikes) visited
- Similar to using PQ: the goal is to find: 1) min dist; 2) closer worker index, 3) closer bike index
- can use bucket sort to hold all possible distances [0 ~ 2000]: bucket[List of pairs]
- do a hard iteration (ordered access from min dist).
- time: O(mn), no need to sort
- space: O(mn)
- By Definition, merge sort: divide the list into 2 parts
- recursively merge them together.
- time complexity: O(nlogk) divide by log(k) times, each recursive call can work on n nodes.
- space: O(logk) stacks
- Iterative, PQ来排列所有list的leading node.
- Note: k lists need to be sorted (luckily, already given)
- 时间:n*O(logk), where n = total node number, and PriorityQueue: logk,
- Note:
-
- 不要忘记customized priority需要一个customized new Comparator()
-
- Given list 里面也可能有null node, 不要忘记查.
-
- 如果k很大,一个机器上放不下所有的k list怎么办?
- 如果Merge起来的很长,一个机器上放不下怎么办?
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
-
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- 这里已经给了 sorted intervals by start point;
-
- 直接找到可以insert newInterval的位子. Insert and convert to list
-
- Merge: Use
pre, curr
to iterate over list, and remove curr after merging
- remove之前都会重新assgin
pre.end
, 确保被remove的node.end 被capture
- Merge: Use
-
- Convert back to int[][]
-
- time/space: O(n)
- code is slightly better to read
- handle edge cases:
- new interval is non-overlapping
-
- head
-
- tail
-
- in middle
-
- new interval is overlapping:
-
- end index in existing interval; reuse the existing interval end to close new range
-
- end index in the gap of 2 intervals, use new interval.end to close the new range
-
- new interval is non-overlapping
- time, space: O(n)
- Interval 拆点,PriorityQueue排点
- Merge时用count==0作判断点
- 注意, 一定要compare curr
p.x == queue.peek().x
确保重合的点全部被process:count+=p.x
- PriorityQueue: O(logN). 扫n点, 总共:O(nLogn). SLOW.
- 因为interval已经sort, 本想用Binary Search O(logn).
- 但是找到interval insert position 最后 merge还是要用 O(n), 所以不必要 binary Search
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- sum up the matching L & R
- Find (L,R) on left child
- Find (L,R) on right child
- Find (L,R) covering the root node
- space O(n), worst case O(logn), height of dfs.
- time O(n) to find all nodes between (L, R)
- Using stack, or queue, list: any data structure (we are not doing ordered search)
- space O(n)
- time O(n)
- Target = total / k, fixed.
- DFS: Pick one num and dfs with the remaining nums for subproblem
- subproblem1: accumulate local sum to target
- EndState: accumulatedSum == target, continue with below
- subproblem2: after accumulatedSum == target, continue dfs with k-1
- EndState: k == 0, return overall true
- subproblem1: accumulate local sum to target
- Option1: DFS with nums, and boolean[] visited. Fast
- Option2: DFS with queue.
- Specially handling: dfs(size+1) to prevent
while(size-- >0)
from skipping last item at index 0
- Specially handling: dfs(size+1) to prevent
- the problem aims to find true/false capability
- bit-masking. TODO. The DP approach is kinda hard-level
- https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/335668/DP-with-Bit-Masking-Solution-%3A-Best-for-Interviews
- Median还是用min-heap 和 max-heap. Time(logN)
- 加/减: prioirtyQueue, log(n)
- findMedian: O(1)
- 加一个数, 减一个数。
- 加减时看好,是从前面的maxheap里面抽,还是从后面的minHeap里面抽。
- 抽完balance一下
- 用maxHeap, minHeap时候, 习惯选择让maxHeap多一个数字:
- 左边的maxHeap总有 x+1或者x个数字
- 后边minHeap应该一直有x个数字
- PriorityQueue排序+分布排列的一个经典用法.
- Count frequency and store in pq.
- Consume element of pq for k rounds, each time pick one element from queue.
- Exception: if k still has content but queue is consumed: cannot complete valid string, return "";
- space, O(n) extra space in sb, O(26) constant space with pq.
- time: O(n) to add all items
- curr index is based on previous calculation: the min of all 3 previous factors
- O(n)
- 非常brutle的。
- 每次把dp[i-1]拿出来,不管三七二十一,分别乘以2,3,5. 出来的结果放进priority queue做比较。
- 最后时间是nlog(n3)
- 注意:use long, use HashSet确保没有重复
- O(nlogn)
- 用PriorityQueue把选中的height排序,为走位, create class Cell (x,y, height).
-
- 从matrix四周开始考虑,发现matrix能Hold住的水,取决于height低的block
-
- 必须从外围开始考虑,因为水是被包裹在里面,外面至少需要现有一层
- 以上两点就促使我们用min-heap: 也就是natural order的PriorityQueue.
-
- process的时候,画个图也可以搞清楚: 就是四个方向都走走,用curr cell的高度减去周围cell的高度.
-
- 若大于零,那么周围的cell就有积水: 因为cell已经是外围最低, 所以内部更低的, 一定有积水.
-
- 每个visited的cell都要mark, avoid revisit
-
- 根据4个方向的走位
(mX, mY)
创建新cell 加进queue里面: cell(mX, mY) 已经计算过积水后, 外围墙小时, �(mX, mY)
就会变成墙.
- 根据4个方向的走位
-
- 因为做的是缩小一圈的新围墙, height = Math.max(cell.h, neighbor.h);
- 和trapping water I 想法一样。刚刚从外围,只是能加到跟外围cell高度一致的水平面。往里面,很可能cell高度变化。
- 这里要附上curr cell 和 move-to cell的最大高度。
- 要找到bucket的最短板
- 每次需要最先处理最短的那条 (on top)
- 木桶理论, 包水, 是从外面包住里面
- 洋葱剥皮, 用完丢掉
- Need to maintain k large elements, where the smallest will be compared and dropped if applicable:
- Maintain k elements with min value: consider using minHeap
- add k base elements first
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分
- sort结束后是ascending的, 那么 n - k 就是第k大.
- partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
- 没找到继续partion recursively.
- sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)
- Steps:
- 每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
- 找到一个low>pivot, high<pivot, 也就可以swap了。
- 得到的low就是当下的partion point了
- Overall O(nlogN), average O(n) for this problem.
- 和Merge K sorted Array/ List 类似:使用PriorityQueue。
- 因为Array的element无法直接找到next,所以用一个class Node 存value, x,y positions.
- Initial O(n) time, also find k O(k), sort O(logn) => O(n + klogn)
- 变型: Kth Largest in N Arrays
- we know where the boundary is start/end are the min/max value.
- locate the kth smallest item (x, y) by cutt off partition in binary fasion:
- find mid-value, and count # of items < mid-value based on the ascending matrix
- O(nlogn)
- 由Merge k sorted list启发。用PriorityQueue,存那k个首发element
- PriorityQueue需要存储单位: 自己建一个Class Node 存val, x, y index.
- 因为array里没有 'next' pointer,只能存x,y来推next element
- Not sure why
new PriorityQueue<>(Comparator.comparing(a -> a.val));
is slower - An efficient implementation of a priority queue.
- The linear hash function monotonically maps keys to buckets, and each bucket is a heap
- https://xlinux.nist.gov/dads/HTML/hashheap.html
- Heap用的不多. 得用一下, 才好理解. 通常default 的PriorityQueue就是给了一个现成的min-heap:
- 所有后面的对应element都比curr element 小。
- Heapify里面的siftdown的部分:
- 只能从for(i = n/2-1 ~ 0), 而不能从for(i = 0 ~ n/2 -1): 必须中间开花,向上跑的时候才能确保脚下是符合heap规则的
- 确保在heap datastructure里面curr node下面的两个孩子,以及下面所有的node都遵循一个规律
- 比如在这里,若是min-heap,就是后面的两孩子都要比自己大。若不是,就要swap。
- for each element A[i], we will get A[i * 2 + 1] >= A[i] and A[i * 2 + 2] >= A[i].
- siftdown时:在curr node和两个child里面小的比较。如果的确curr < child, 搞定,break while.
- 但若curr 并不比child小,那么就要换位子,而且继续从child的位子往下面盘查。
- Use HashMap to store <num, freq>
- Bucket
List<Integer>[]
: stores <count, list unique element with that count>- Size of the data structure will be uniqe item size.
- The bucket[i] stores item at frequency i
- Simply loop from bucket.length -> 0, when bucket[i] not null, add to result.
- Solid O(n)
- Use regualr priorityQueue to sort by frequency ascendingly
- the queue.peek() record has lowest frequency, which is replacable
- Always only maintain k elements in the queue, so sorting is O(logk)
- IMPORTANT: remember to
rst.add(0, x)
for desired ordering - time faster than maxHeap: O(nlogk)
- option1: just use
map<num, freq>
; option2: useclass Record {int num; int freq}
- 题目有提醒: 必须beetter than O(nLog(n)).
- max heap approach stores all nodes: it is wrong
- even though freq count size m < n, but it can be m == n. ALL unique.
- then it is O(nlogN) again.
- therefore, storing all items into pq is INCORRECT.
- Sort start times, and end times in 2 different arrays
- Loop over start time
- when start[i] < end[endIndex], Count++, need more room
- start[i] >= end[endIndex], done using some room, move to next end time, endIndex++ (like vacating a room)
- Note: we never decrese count because:
- what ever count reaches, it is the max
- since we keep moving endIndex, when start[i] >= end[endIndex], we will just reuse meeting room w/o count++
- time: O(nlogn)
- space: O(n)
- somehow, super fast, over 100%
- inspired by: https://leetcode.com/problems/meeting-rooms-ii/discuss/67855/Explanation-of-%22Super-Easy-Java-Solution-Beats-98.8%22-from-%40pinkfloyda
- Use sweep line to process, track max count as max # of rooms needed
- 跟 Number of Airpline in the sky是同一道题
- time: O(nlogn)
- space: O(n)
- define the bucket by index: the total distance is fixed [0, 1000]
- +/- capacities for each pos and save into the bucket
- go over the bucket and see if the total cap goes over input capacity
- O(n), trips size
- space: O(1), bucket size 1000 is constant
IMPORTANT
: before using PQ to sort, consider bucket sort:- if the boundary set and seems resonable? i.e., max size =
1000
- is the sorted items index based?
- if the boundary set and seems resonable? i.e., max size =
- Like meeting room, merge interval
- process items on same index
- Create customized Point{} class
- Sort by distance
- Maintain queue size <= K
- ?, select sort?
- 把Input stream想成向上的山坡. 山坡中间那点,自然就是median.
- 前半段,作为maxHeap,关注点是PriorityQueue的峰点,也就是实际上的median.
- 后半段,作为minHeap,正常的PriorityQueue。 开头是最小的。
- 这里要首先定好, 哪一个queue是多存一个element的. 这里选maxHeap: maxHeap.size() == minHeap.size() + 1 || minHeap.size()
- 必须先维护maxHeap里面有个元素, 否则null了会在比较大小时出问题.
- 维持monotonuous queue:
front is always at max
and thetail end is min
. Always need to return the max end of queue. - when adding new elements x:
-
- start from small-end of the queue
-
- drop all smaller elements
-
- append to the ending element that is larger than x.
- This is to maintain a front->tail decreasing queue
-
- when sliding window: queue curr window 里面 最大的已经在max-end, remove it if needed.
- 妙:用deque数据结构(实际上采用LinkedList的形式)来做一个
递减的queue
: better than using arraylist, since DeQueue(linked list) removes at O(1) cost - 每次把小于当前node的,全部剔除,剩下的,自然就是:最大的>第二大的>第三大的...ETC.
- 我们只在乎最大值的存在;而任何小于当前(正要新就加进去的)值的,反正以后也成不了最大值,于是扔掉!
- Option1: sliding window template using right/left + while loop
-
- tailing the new number to max queue, if applicable
-
- process: record max
-
- contract/shrink left: remove top max if the topMax is the left-most val: rst[i - k + 1]
-
- Option2: same concept, but use index
i
to mark right, andi - k + 1
to mark left. - time: O(n), one pass
- space: O(k), store the deque
- can always build a
class Node{index, val}
; and sort them with PQ of size k - time: O(nlogK)
- space: O(k)
- this is not linear time, not as good as method1
- By Definition, merge sort: divide the list into 2 parts
- recursively merge them together.
- time complexity: O(nlogk) divide by log(k) times, each recursive call can work on n nodes.
- space: O(logk) stacks
- Iterative, PQ来排列所有list的leading node.
- Note: k lists need to be sorted (luckily, already given)
- 时间:n*O(logk), where n = total node number, and PriorityQueue: logk,
- Note:
-
- 不要忘记customized priority需要一个customized new Comparator()
-
- Given list 里面也可能有null node, 不要忘记查.
-
- 如果k很大,一个机器上放不下所有的k list怎么办?
- 如果Merge起来的很长,一个机器上放不下怎么办?
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
Dijkstras algorithm
is based on repeatedly making the candidate move that has the least distance travelled.- PQ: pick close node to vist, and add siblings back to PQ
- avoid visited
- time: O(nLogn), visit n nodes, each time insert to heap takes O(logn) time
- space: O(n)
-
- build graph map, 2) traverse map, 3) prioritize short delay nodes first
- use a map
<node, timeElapsed>
globally track dealy to nodes; compare all at the end -
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- process and check at the end, or, 2) sanitize first and process assume correct input
- Aggregate map<char, count>, and sort the entry with priority queue.(Optionally, can use object
Letter {char c, int count}
) - Naturally: we want to prioritize the largest population and exhaust it first, so we want to keep it in the a buffer queue
- it is a queue, first in first out
- monitor queue size k = 2, so that it holds off the just last-processed letter for 1 unit of time
- the buffer then sends the last-process item to the main priority queue (pq will sort it again)
- Error handling: largest population may have extra letter
- the main PQ has already exhausted
- but the largest-population-letter will end up stuck in the buffer queue
- it will never be picked up again so the final result sb will be shorter than orignal string: that is the error case
- Option0. Similar to
621. Task Scheduler
:- use a buffer to hold potential letter to add back, but NOT ADD BACK YET, until k slots have been filled.
- time: O(m), m = # of unique letters
- space: O(nmLogm), n = length, pq sorting requires mlogm, we will visit all n nodes.
- put all in map<char, count>
- Sanitize the input: if certain popular char count is over (n + 1)/2, then it should fail right away, just return empty map.
- Once the input is sanitized, when building results, we can be greedy and consume most popular char and then the rest
- Int[2] can be used store char and count
- PriorityQueue can sort int[]. Okay to not specific length of int[] when defining pq.
- Alternatively, can use a Letter {char c, int count} to represent
- This approach follows the pattern of finding min pair:
-
- only need to store k pairs
-
- always start from min of A list and min of B list
-
- pre-build k pairs honoring A list, and then pick the min pair, and start swapping with min of list B
-
- First attemp all first k pairs from nums1[i] against nums2[0] <=k : O(k)
- Use queue to pull min node and save results
- Use the nums1 val from the min node, pair up with nums2[j], add back to queue to sort
- overall runtime: O(klogk)
- space: O(k)
- Brutle: build all pairs time O(mn), sort with maxHeap pq with k size, and find top k
- overall time: O(mnLogK)
- space: O(k)
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- 给两个string S, T. 检验他们是不是scramble string.
- scramble string 定义: string可以被分拆成binary tree的形式, 也就是切割成substring;
- 旋转了不是leaf的node之后, 形成新的substring, 这就是原来string的 scramble.
- 降维打击, 分割, 按照长度来dp.
- dp[i][j][k]: 数组S从index i 开始, T从index j 开始, 长度为k的子串, 是否为scramble string
- 一切两半以后, 看两种情况: , 或者不rotate这两半. 对于这些substring, 各自验证他们是否scramble.
- 不rotate分割的两半: S[part1] 对应 T[part1] && S[part2] 对应 T[part2].
- rotate分割的两半: S[part1] 对应 T[part2] && S[part2] 对应 T[part1].
- len == 1的时候, 其实无法旋转, 也就是看S,T的相对应的index是否字符相等.
- initialization非常非常重要. 很神奇, 这个initailization 打好了DP的基础, 后面一蹴而就, 用数学表达式就算出了结果.
- input s1, s2 在整个题目的主要内容里面, 几乎没有用到, 只是用在initialization时候.
- More details, 看解答
- 跟Coins in a Line II 一样, MaxiMin的思想: 找到我的劣势中的最大值
dp[i][j] 代表在[i,j]区间上 选手最多能取的value 总和
- 同样, sum[i][j]表示[i] 到 [j]间的value总和
- 对手的最差情况, 也就是先手的最好情况:
- dp[i][j] = sum[i][j] - Math.min(dp[i][j - 1], dp[i + 1][j]);
- 这里需要search, 画出tree可以看明白是如何根据取前后而分段的.
- 因为是看区间[i,j]的情况, 所以可以想到是区间 DP
- 这个方法需要复习, 跟数学表达式的推断相关联: S(x) = - S(y) + m. 参考下面的公式推导.
- dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x).
- 其中一个S(x) = dp[i][j] = a[i] - dp[i + 1][j]
- m 取在开头, m 取在末尾的两种情况:
- dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}
- len = 1, 积分就是values[i]
- 最后判断 dp[0][n] >= 0, 最大数字和之差大于0, 就赢.
- 时间/空间 O(n^2)
- S(x) = X - Y, 找最大数字和之差, 这里X和Y是选手X的总分, 选手Y的总分.
- 对于选手X而言: 如果S(x)最大值大于0, 就是赢了; 如果最大值都小于0, 就一定是输了.
- 选手Y: S(y)来表示 对于Y, 最大数字和之差. S(y) = Y - X
- 根据S(x) 来看, 如果从 数字和X里面, 拿出一个数字 m, 也就是 X = m + Xwithout(m)
- S(x) = m + Xwithout(m) - Y = m + (Xwithout(m) - Y).
- 如果我们从全局里面索性去掉m, 那么 S(y'') = Y - Xwithout(m)
- 那么推算下来: S(x) = m + (Xwithout(m) - Y) = m - (Y - Xwithout(m)) = m - S(y'')
- 在这个问题里面, 我们model X 和 Y的时候, 其实都是 dp[i][j], 而区别在于先手/后手.
- 将公式套用, 某一个S(x) = a[i] - dp[i + 1][j], 也就是m=a[i], 而 S(y'') = dp[i + 1][j]
- 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维.
- 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.
- Trick: 利用差值公式, 推导有点难想到.
- 找出[i, j]区间内的性质: dp[i][j]下标表示区间范围 [i, j]
- 子问题: 砍头, 砍尾, 砍头砍尾
- loop应该基于区间的length
- template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;
- 因为数组规律会变, 所以很难找'第一个burst的球'. 反之, 想哪一个是最后burst?
- 最后burst的那个变成一堵墙: 分开两边, 分开考虑, 加法原理; 最后再把中间的加上.
- dp[i][j] represent max value on range [i, j)
- Need to calculate dp[i][j] incrementally, starting from range size == 3 ---> n
- Use k to divide the range [i, j) and conquer each side.
- 中间劈开
- 砍断首或尾
- Range区间作为iteration的根本
- use pi[i][j] and print recursively.
- Print k, using pi[i][j]: max value taken at k
- 其实会做之后挺好想的一个DP
- dp[i][j] = balloons i~j 之间的 max.
- 然后找哪个点开始burst? 设为x。
- For loop 所有的点作为x, 去burst。
- 每次burst都切成了三份:左边可以recusive 求左边剩下的部分的最大值 + 中间3项相乘 + 右边递归下去求最大值。
- Note: 这个是Memoization, 而不纯是DP
- 因为recursive了,其实还是搜索,但是memorize了求过的值,节省了Processing
- Use example to understand: for any given ending char, 3 cases of palindromes
- a. ss[i, j] is a palindrome. dp[i+1][j-1] + 2 since the two indexes are counted, assume dp[i + 1][j - 1] is calculated
- b. ss[i + 1, j] is a palindrome
- c. ss[i, j - 1] is a palindrome
- time/space: O(n^2)
- Option1: start processing substring from tail
- set:
i = [n-1 towards 0]
,j = i + 1
- consider ss[i, j], ss[i + 1, j], ss[i, j - 1]
- since i starts from n - 1 -> 0 and j = i + 1, these are calculated and ready to go: dp[i+1][j-1], dp[i+1][j] and dp[i][j-1]
- FAST: skipped the initialization
- set:
- Option2: start processing substring from head
- 用[i][j]表示区间的首尾: 考虑3个情况: 砍头, 砍尾, 砍头并砍尾 (考虑首尾关系)
- Iteration on len:
- len = j - i + 1; 那么反推, 如果len已知,
j = len + i - 1
; - 注意考虑len == 1, len == 2是的特殊情况.
- consider sub problems with 3 major cases
- a. ss[i, j] is a palindrome: dfs check ss[i + 1, j - 1]
- b. ss[i + 1, j] maybe a palindrome: dfs check ss[i + 1, j]
- c. ss[i, j - 1] maybe a palindrome: dfs check ss[i, j - 1]
- memo[i][j]: max palindrome length in range [i, j], if calculated, return directly
- Init memo[i][j] = -1 to track the progress, memoization
- 注意: init dp[i][j]=-1, dfs的时候查dp[i][j] 是否算过
- more about dfs: bottom-up, first dive deep into dfs(i+1,j-1) till the base cases.
- Space: O(n^2)
- Time: O(n^2)
- prepare dp[n][n]: O(n^2); dfs: visit all combinations of [i,j]: O(n^2)
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Pre-order-traversal 就能记录下 Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- Note: label需要是String. 虽然 Operator是长度为1的char, 但是数字可为多位
- Process inner item first: last come, first serve, use stack.
- Record number globally and only use it when '[' is met.
- Stack存 [ ] 里面的内容, detect 括号开头结尾: 结尾时process inner string
- 有很多需要注意的细节才能做对:
- Stack 也可以用, 每个地方要注意 cast. 存进去的需要是Object: String, Integer
- 几个 type check: instanceof String, Character.isDigit(x), Integer.valueOf(int num)
- 出结果时候:
sb.insert(0, stack.pop())
- Bottom->up: find deepest inner string first and expand from inside of
[ ]
- 与Stack时需要考虑的一些function类似. 特别之处: 检查
[ ]
的结尾 - 因为DFS时候, 括号里的substring会被保留着进入下一个level, 所以我们在base level要keep track of substring.
- 用int paren 来track 括号的开合, 当paren再次==0的时候 找到closure ']'
- 其他时候, 都要继续 append to substring
- count[] = int[256], 不需要
c-'a'
- boolean visited[]: 一旦一个字母固定了位置后, 再次遇到时候, 直接跳过用过的character
- 如果tail字母可以变小, 那就delete掉tail, 重新接上新字母 (前提条件: 去掉的字母后面还会再出现, set visited[tail] = false)
- Space: O(1) count[], visited[].
- Time: Go through all letters O(n)
- Use stack instead of stringBuffer: keep append/remove last added item
- However, stringBuffer appears to be faster than stack.
- 两个Queue,交互倒水
- 用一个Temp做swap
- 逻辑在push里面:
-
- x 放q2。
-
- q1全部offer/append到q2.
-
- 用一个Temp做swap q1, q2.
- q1的头,就一直是最后加进去的值.
- 逻辑在top()/pop()里, 每次换水,查看末尾项.
-
用到bottom->top递减的stack: 最底下的root维持成最大的element.
-
过程当中, 一旦遇到currNode.val > stack.peek(), 就意味着需要把这个currNode放在 stack的底层位置.
-
也就是说, 遇到这个条件, process, pop()所有 currNode.val > stack.peek(), 最后把currNode加进去.
-
maxTree题目本身的要求是: 大的在最中间, 左右两边的subTree也要是maxTree:
-
Monotonous Stack在这里帮助 keep/track of max value, 但是left/right tree的logic是MaxTree独有的.
-
left/right node的assignment是根据题目要求: 中间最大值分开后, 左边的是左边subTree, 右边的作为右边subTree.
- Should memorize MaxTree. 依次类推,会做Min-Tree, Expression Tree
- Stack里,最大的值在下面。利用此性质,有这样几个step:
- 把所有小于curr node的,全Pop出来, while loop, keep it going.
- 最后pop出的这个小于Curr的node:它同时也是stack里面pop出来小于curr的最大的一个,最接近curr大小。(因为这个stack最大值靠下面)
- 把这个最大的小于curr的node放在curr.left.
- 那么,接下去stack里面的一定是大于curr:
- 那就变成curr的left parent. set stack.peek().right = curr.
- 结尾:stack底部一定是最大的那个,也就是max tree的头。
- 如果底边上的一个value==0, 那么算作没有height(以这个底边做rectangle, value==0的位置是空中楼阁, 不能用)
- 如果底边上的value==1, 那么就把上面的height加下来, 做成histogram
- 计算 expression 的值: 1. 建造 expression tree. 2. DFS计算结果
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- build好Min Tree以后,做PostTraversal.
- Divde and Conquer: 先recursively找到 left和right的大小, 然后evaluate中间的符号
- Time, Space O(n), n = # expression nodes
-
- Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
-
- 若有个child是null,那就return另外一个node。
-
- prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.
- 很容想到Inorder-binary-search-tree Traversal
- Iterative 稍微难想点:先把最左边的add, pop() stack, 加上右边(如果存在); 下一个轮回,如果又左孩子,又是一顿加。
- 然后稍微优化一下,确保rst.size() == k 时候,就可以return了
- check leaf => dfs left => add root => dfs right
- ArrayList: return/remove ArrayList的末尾项。
- 2 Queues
- use dp[i] track local max, maintain global max
- int[] dp. dp[i]: longest valid string that ends on i.
- 结尾是 ')', 2种情况: 1. 刚好s[i-1]是'('; 2. s[i]的')'更前面的一个起始'(' 对应
- 注意, 结尾如果是'('属于不合理情况, 忽略.
- init: dp[0] = 0, 单个char不可能成型.
- 计算顺序: 从左到右, 找local max, maintain global max
- O(n) space, O(n) runtime
- Stack 里面存所有的open/close parentheses.
- 如果遇到stack.top()刚好开合结掉, 就stack.pop().
- 剩下的都是不合理的elements.
- 有点像negatively找 solution:
endIndex - 最后一个failedIndex(stack.pop()) - 1
, 应该就是最后一个succeeded string的长度 - 每次更新 endIndex 为stack.top(), 然后从stack继续找下一个failedIndex
- 所有的length作比较, 就可以找出最长length
- O(n) stack space, O(n) runtime. 应该比dp慢一点, 因为做了2遍O(n)
- 和Max-tree一样,https://leetcode.com/problems/maximum-binary-tree
- 用到bottom->top递增的stack: 最底下的root维持成最小的element.
- 这个题目是Min-tree, 头上最小,Logic 和max-tree如出一辙
- Space: O(n)
- Time on average: O(n).
- TreeNode: 用一个并不是最终结果的TreeNode, 存weight, 用来排序
- 用base weight的概念权衡同一个层面的 符号, 数字 顺序
- 每一个character都是一个节点, 都有自己的weight. 用一个TreeNode来存weight value, 利用用weight来判断:
-
- (while loop) 如果node.val <= stack.peek().nodeValue, 把当前stack.peek() 变成 left child.
-
- (if condition) 如果stack有残余, 把当前node变成 stack.peek().rightChild
- stack 里面 存数字
- 每次遇到operator, 都拿前2个数字计算
- 计算结果存回到stack里面, 方便下一轮使用.
- Time,Space O(n)
- Expression Tree是一个 weight-based的 min-tree
- 基于 运算符号 + 数字的 tree: 数字永远在leaf, 然后符号是tree node, 括号不出现在tree里面
- 用 monotonuous stack 来构建这个tree
- Understand Expression Tree
- Use stack to build the expression tree + understand the weight system
- Use post-order traversal to evaluate the tree
- 注意, input里面的数字不会是single digit, 所以需要一个buffer存number string
- 整个题目的做法, 可以参照
Expression Evaluation
- 会iterative traverse Binary Search Tree(Stack && handle left-dig-down)
- create Doubly-ListNode, 注意用一个dNode作为tail node of the list
- 在check right node的事后,
- 不论right == null or != null, 每次都要强行move to right.
- 如果不node = node.right,
- 很可能发生窘境:
- node always = stack.top(), 然后stack.top()一直是一开始把left 全部遍历的内容。所以就会infinite loop, 永远在左边上下上下。
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Post-order-traversal 就能记录下 Reverse Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- 重点是根据找Histogram里面rectangle的性质, 维持一个单调递增的Stack
- 在loop over indexes的时候:
- 如果高度>= previous peek(), 那么对于那个peek, 就意味着, 往下走, 一直走高嘛, 之前的peek总可以继续抄底
- 什么时候不能抄底了呢? 就是有一个下降趋势的时候
- 这时候并不是calculate所有前面的peek, 而是考虑 大于 current height的之前所有的peek.
- 把这些peek到 current height 前一格的rectangle全部找出来: stack.pop()
- 这个stack.pop()的过程里面, 其实没有算上 current height, 因为需要留到下一轮, 把current index加进stack 再说
- 为什么用stack? 因为需要知道连续递增的peek, stack.peek() O(1), 好用 而其实不用stack, 也可以用其他方式记录所有height, 只不过要 O(n)去找peek不方便
- 理解monotonous stack 是如何被维护的
- 维护monotonous stack 是题目需要, 而不是stack本身性质, 是一种借助 stack.peek() O(1)的巧妙用法.
- Array: Left Max Wall vs Right Max Wall.
- 对于每个index而言, vertically 能存放的最大水柱, 就是靠 左 右 最高墙决定的:
- min(leftHighestWall, rightHighestWall) - currHeight.
- time: O(n)
- space: O(n)
- Optimization from Method1: two pointer, 还是找左边最高和右边最高. O(1) space.
- 利用到了方法3里面的想法一样: 整个structure是被中间的最高bar 二分天下:
- always limited by the shorter wall: 左边按照maxLeft来计算, 右边按照maxRight来计算.
- time: O(n)
- space: O(1)
-
- 找中间最高bar的index
-
- 两面往中心扫:每次加上(topBarIndex - currIndex)* (elevation from previous index).也就是每次加一个横条
-
- 每次还要减去block自身的height
- time: O(n)
- space: O(1)
- 主要想法和方法3一致: 在山坡下坡的基础上, 一直用stack堆积bottom.
- 最后遇到上升之前, 此时bottom可以用来跟stack之前堆积的所有下坡index做比较, 算跟他们高度相差的积水.
- 用了stack记录下坡, 然后用个while loop一挖到底的想法非常棒.
- time: O(n)
- space: O(n)
- use stack to hold potential pair
- when stack is empty: detect outtermost element, dont add to final result
- time: O(n), space O(n)
- solution from discussion, time O(n), space O(1)
- save space, but less scalable: think about if there are 100 different pairs, then the couting will be a bit complex to handle.
- use stack to hold all elements
- keep poping if
stack.peek() < num
- use map to record (top, num)
- keep poping if
- time O(n), run through base once and sub-sequence once
- space O(n), stack, map
- O(n) space, O(n^2) time worst case
- Goal: remove extra '(' or ')' so it is valid.
- Forward thinking: use stack to track '(' and ')', then keep appending partial string to output
- Backward thinking: use stack to filter out false indexes, and remove them in the end
- 用stack记录最小值, 放在top. O(h) space.
- 每次消耗TreeNode, 都看看rightNode(其实就是下一个最小的candidate), 并且一条龙stack叠上rightNode所有的left子孙.
- 用O(1)空间的做法:不存stack, 时刻update current为最小值。
- 找下一个最小值,
- 如果current有right child: 和用stack时的iteration类似,那么再找一遍current.right的left-most child,就是最小值了。
- 如果current没有right child: 那么就要找current node的右上parent, search in BinarySearchTree from root.
- 注意:
- 一定要确保找到的parent满足parent.left == current.
- 反而言之,如果current是parent的 right child, 那么下一轮就会重新process parent。
- 但是有错:binary search tree里面parent是小于right child的,也就是在之前一步肯定visit过,如此便会死循环。
- time: O(n)
- space: O(1)
- need to remove entity just added
- use stack to hold array content; pop if # is found
- Analyze the pattern: 1) single char, 2) with !, 3) with &, |
- Identify sub problem
- Use stack to parse the data in "()", which is a sub problem to solve with recursive call
- Handle &, | case: need to parse multiple
- Be comfortable with string parsing
- Slight improve:
- If see obvious result, directly return evaluation w/o further parsing
- use memo to store evaluated exp
- Use '(' and ')' to mark inner exp
- Evaluate the inner exp and save result back to Stack: the result will be 'f' or 't'
- This is slightly slow because:
- It requires all stack items on top to be processed before reaching the operator
- There is no room to optimize even there is simplification for specific operator
- 加root, left, then right. Obvious
- Option1: recursive on preorderTraversal. the dfs function returns List
- Option2: pass in rst, and write a void dfs.
- 先加root, 然后push上需要末尾process的在stack垫底(root.right), 然后push root.left
- Stack: push curr, push right, push left.
- 剥皮过程。解铃还须系铃人
- 左边的外皮'{['在stack底部
- 右边的外皮应该和stack顶上的左外皮一一对应
- trivial, 先加left recursively, 再加right recursively, 然后组成头部.
- Option1 w/o helper; option2 with dfs helper.
- Option1: reversely add to list
- 双stack的思想, 需要在图纸上画一画
- 原本需要的顺序是: 先leftChild, rightChild, currNode.
- 营造一个stack, reversely process: 先currNode, 再rightChild, 再leftChild
- 这样出来的结果是reverse的, 那么翻转一下就可以了.
- reverse add:
list.add(0, x)
; - 利用stack的特点
- 每次加element进stack的时候, 想要在 bottom/后process的, 先加
- 想要下一轮立刻process的, 最后push进stack.
- Option2: regular sequence add to stack: add curr, right, left
- Use set to contain the processed children
- only process curr if its children is processed
- 简单的level traversal.根据level奇数偶数而add到不同位子.
- Option1: based on level % 2, insert to front/end of list
- Option2: based on level, insert right/left of node into queue
- Task time range:
- start range = next task timestamp - start.timestamp
- end range = curr task timestamp - last task timestamp + 1; because end node is inclusive.
- How to think of using stack: a task cannot finish until end is met; a early task cannot stop until a later task ends
- Alternatively, we can use a hashmap to track as well
- Keep track of the timestamp
- make sure to +1 when end node is met because end task is inclusive to this finishing task
- option1: dfs + rst list to carry results
- option2: Divide and Conquer, 在自己的基础上recursive, 不用helper function
- O(n) time
-
- Add root.leftPath all the way to leaf, 2) process curr 3) Move to right if applicable 4) add all right.leftPath
- O(n) time, O(h) space
- Greedy: Remove 1) earlier digits(数位靠前权值大), 2) large digits
- Keep a increasing stack that:
- use stack.peek() to guard incoming digit
- if peek is larger than incoming digit, continue
stack.pop()
- Result: monotonous increasing stack. Print it in correct order.
- 理解unix path:
-
.
代表current directory, 可以忽略.
-
../
表示previous level.
-
- double slash 可以忽略.
-
- empty string 要output
/
- empty string 要output
-
- parse by '/', and go over using stack
- put [folder] in stack
- ".." pop() 1 element of the stack, if anything
- "." stays the same
- output stack reversely: connect with '/', skip tail
- one to keep regular elements
- one to repat the max at current stack level
- time: O(n) for popMax() and O(1) for the rest operations
- space: O(n)
- Reference: https://leetcode.com/problems/max-stack/solution/
- Use TreeMap to store <Int, List of Nodes>, which gives: O(logN) insert, delete and find MAX
- Key reason to use
DoubleLinkedList
is to perform O(1) removal forpopMax()
- The problem becomes finding the target value & remove from DoubleLinkedList
- time: O(1) for popMax() and O(logN) for the rest
- space: O(n)
- Option1: always set integer on top of the stack everywhere
- if not, poping stack until the top is integer
- code is easy
- Option2: in hasNext(), faltten the list in stack
- 用queue to store all items. Kinda hack. Defeat the purpose of the problem.
- Super fast to query next(), however, needs to holds everything in memory
- O(n)
- Goal: given a index i, want right-side closest & higer number
- Draw example: right-most number at base, and builds up monotonous stack (mountain shape)
- add smaller item on top of stack
- keep popping if peek is higher than incoming
- space: O(n), time:O(n)
- Refernece: https://leetcode.com/problems/daily-temperatures/solution/
- From right side:
-
- record tempIndex[currTemp] = i;
-
- Brutle find smallest temp index in range [currTemp + 1, 100] and record as result
-
- find successors and predecessors using BST (both list will be sorted); in the end, we can easily get top k from the two sorted list
- with BST: **inorder traversal gives us sorted predecessors
- with BST: **reversed-inorder traversal gives us sorted successors
- smallest on top of the stack
- time: O(n) visit all nodes, O(k) to output
- space overall: O(n) to store all nodes
- Itereate over all nodes and maintain pq (improvemenet point: how to avoid traversing entire tree?)
- prioritize nodes that are closer to target, so we may stop early when result reaches k candidates
- time: O(n*logn)
- kinds slow and not utilizing BST
- 快慢指针, O(1)space.
-
- 确认有cycle后 2. 数学问题:找到开头.
- 当head == slow.next时候, head就是cycle starting point.
- 也就是说,当slow 移动到了那个回溯点,slow.next那个点就刚好是head的那个点...
-
- 假设慢指针走t步, 快指针走快一倍, 也就是2t.
-
- 我们假设cycle的长度是Y, 而进入cycle之前的长度为X.
-
- 假设慢指针走了m圈cycle, 而快指针走了n圈cycle之后, 两个pointer相遇.
-
- 最终在Y cycle里面的K点相遇, 也就是两个指针都在这最后一圈里面走了K 步.
- 那么:
- t = X + mY + K
- 2t = X + nY + K
- �整合公式: X + K = (n - 2m)Y
- 这里的m和n不过是整数的跑圈数, 也就是说X和K加在一起, 总归是结束cycle. X 和 K 互补
- 结论: 当slow/fast 指针在K点相遇后, 再走X步, 就到了cycle的起点, 也就是题目要求的起点.
- 记得用dummy.next来存head.
- 特殊: 这里k可能大于list总长. 写一写linked node 移动的步数, 然后 k = k % n.
- 找到newTail, newHead, 然后利用dummy, 换位子
-
- find middle. 快慢指针
-
- Sort: 切开两半,先sort前半, 如果先sort了mid.next~end, sort后,中间点mid.next == null,再sort前半段
-
- Merge: 假设given list A, B 已经是sorted, 然后按照大小,混合。
- 要recursively call sortList() on partial list.
- 想做可以看讲义:http://www.jiuzhang.com/solutions/sort-list/
- 但是quick sort不建议用在list上面。
- 排列list, merge sort可能更可行和合理。原因分析在下面, 以及: http://www.geeksforgeeks.org/why-quick-sort-preferred-for-arrays-and-merge-sort-for-linked-lists/
- 在基本的reverse linked list 上面 多了一层: 找到front node, 接下来的 [m ~ n] node 需要被reverse
- 只需要reverse中间的部分.
- Reverse的时候: 用一个dummyNode, 这道题里面, 其实就用 nodeFront, 那么 dummy.next 就是整个reversed list.
- 一定要Mark开头的那个mth node, 最后用它接上 剩下node tail. 不然后面的node会断掉
- 遍历到M前,
- 存一下那个点,
- 从M开始, for loop, reverse [m~n]。 然后把三段链接在一起。
- reverse list, find mid of list, merge two list
- 先find mid, 然后把 mid.next reverse了, 最后merge 两段.
- 注意, 用完mid.next之后, 一定要 mid.next = null, 不然merge会出问题
- hash table solution not passing
- Find O(n) solution
- 与其他Majority Number一样。
- 出现次数多余1/k,就要分成k份count occurance.用HashMap。 存在的+1;不存在map里的,分情况:
- 若map.size() == k,说明candidate都满了,要在map里把所有现存的都-1;
- 若map.size() < k, 说明该加新candidate,那么map.put(xxx, 1);
- 最后在HashMap里找出所留下的occurance最大的那个数。
- 但这样的worst case是 O(nk)
- linked list 不能像partitioin array一样从两边遍历
- 把小于value的加在前半段, 把 >= value的加在后半段
- 做法很普通: 建造两个list, midTail pointer, post pointer
- 把满足条件(<x, >=x)的数字分别放到两个list里面
- 记得用dummyNode track head.
- 最终midTail.next = post链接起来。
- find middle.
- recursively: right = sort(mid.next); left = sort(head).
- within sort(), at the end call merge(left, right)
- sorted list, 重复元素都在一起
- 运用 dummyHead: 如果要去掉所有重复元素, 就要有个dummyHead作为局外人在开头牵线
- 只要发现一个 node.val == node.next.val, 就记下这个duplicated val, move forward, 过掉所有重复过的元素
- 思想:
- 用第二个 inner while loop, 把所有的重复元素都处理干净, 然后再move forward
- 优点: outter while loop 不需要考虑太多case, 在inner loop 都把主要的business logic 解决了.
- 当我们有了DummyHead 作为Linked List 的局外线头, 其实可以选择每次遇到duplicate, 就把更加后面的元素 强行assign 给 dummyHead.next
- 下面还尝试过一种做法: 但是需要考虑的edge case 太多了: 不断移动node, 知道不重复, assign prev.next = node.
- 这样的做法比较直白, 但是需要考虑很多edge case, 而且并没有很好利用到 dummy head, 注意规避.
- 斩草除根。
- 多个node,check node.next ?= node.next.next
- sum = carry
- carry = sum / 10
- sum = sum % 10;
- 把list里面每个元素都拿出来,scan and insert一遍
- Time O(n^2), worst case, 每次放入n个数字里面的element, 刚好都是最大的
- 所以每次要traverse n nodes, 然后走n次
- 如果已经有个sorted list, insert一个element进去。怎么做?
- while 里面每个元素都小于 curr, keep going
- 一旦curr在某个点小了,加进去当下这个空隙。
- Divide and Conquer
- 找到mid node
- 然后分割两半, 分别dfs做各自两个subtree: node.left,node.right
- 用长度来定位mid, 每次找中间点做root, 然后前半段, 后半段分别dfs with length.
- 用快慢pointer 找到mid. Better: 不用traverse entire linked list
- slowPointer = node;
- fastPointer = node.next;
- 然后把root = mid.next
- 然后开始sortedListToBST(mid.next.next); //后半段
- mid.next = null;//非常重要,要把后面拍过序的断掉
- sortedListToBST(head); //从头开始的前半段
- 最后root.left, root.right merge一下。
- 会iterative traverse Binary Search Tree(Stack && handle left-dig-down)
- create Doubly-ListNode, 注意用一个dNode作为tail node of the list
- 在check right node的事后,
- 不论right == null or != null, 每次都要强行move to right.
- 如果不node = node.right,
- 很可能发生窘境:
- node always = stack.top(), 然后stack.top()一直是一开始把left 全部遍历的内容。所以就会infinite loop, 永远在左边上下上下。
- 先找到nth node
- 然后head开始跑
- node 到底,而head ~ node刚好是 n 距离。所以head就是要找的last nth
- 小的放前。每次比head大小
- while过后,把没完的list一口气接上。
- 一开始建一个node用来跑路, 每次都存node.next = xxx。存一个dummy。用来return dummy.next.
- update node.val
- Link curr.next to curr.next.next
- find slow/fast to detect the meeting point
- find begin node of the cycle: traverse from head, also move slow; utill head/slow meets slow
- sorted list, 重复元素都在一起
- 知道如何构建Linked List.
- 一点遇到重复元素: node.val == node.next.val, 就去掉.
- 用一个dummy node 来跑路
- 注意:
- 只有当没有重复的时候, 才node = node.next;
- 有重复的时候, 当后面第三个元素被提上来之后, 还是可能跟当下元素重复, 所以不能前移node.
- ex: A -> A -> A
- while loop 里面check node 和 node.next 比较好, 这样ending position会非常清晰
- 如果match: node.next = head.next;
- 如果不match, node 和 head 一起移动
- 1 end pointer to define the window based n steps
- 1 pre pointer to track the node before the targeting node
- when end reaches null, remove nth node: link pre and head.next
- Linked List的基本操作: 每次insert在开头
- 用head来循环所有node
- 不需要额外空间
- Time O(n), Space O(1)
- source node: head
- target node: new head
- Imagine two runners running on a track at different speed. What happens when the track is actually a circle?
- https://leetcode.com/problems/linked-list-cycle/solution/
- O(1) sapce: 用快慢指针,
start=head.next
,end=head.next.next
- Fast pointer will eventually catch up to slow pointer
- O(n) space: 用HashMap,一直add elements. 如果有重复,那么很显然是有Cycle
- Need add from the back and calculate carry
- Reverse list, so insignificant digit at head; calculate carry
- Reverse back when output
- 用了一个特别的双向的ListNode,有了head和tail,这样就大大加快了速度
- 主要加快的就是那个‘更新排位’的过程,找到item hashmap O(1), 做减法换位也都是O(1)
- Overall O(1)
- 巧妙点
-
- head和tail特别巧妙:除掉头和尾,和加上头和尾,都O(1)
-
- remove node: 把node.pre和node.next 连起来, node就自然而然的断开不要了
-
- 一旦知道怎么解决了,就不是很特别,并不是难写的算法
- moveToHead()
- insertHead()
- remove()
- Instead of building
Double Linked List
, utilize JavaDeque<E> queue = new LinkedList<>()
- works but problem:
queue.remove(E)
is O(n) - time: O(1) on average but much slower
- Palindrome概念很简单: 两边回溯相等. However:
-
- cannot random access index on linkded list
-
- cannot reverse iterating linked list
-
- solution: reverse linked list: 遍历接开头
-
- 用快慢指正找到mid point
-
- reverse 2nd half
-
- compare leftList and rightList
-
- Time O(n), 而且不需要用额外的空间(只是调换半个list的内部顺序), 所以空间O(1)
- 不在乎slow是不是到底,因为fast肯定先到。
- 确保fast, fast.next不是Null就好
- reverse order helps calculation
- add additional carry to end
- not same length: align on left
- traverse till both ends
- 遍历两个l1,l2把carry-on处理好,每次生成一个新node,最后检查carry-on.
- By Definition, merge sort: divide the list into 2 parts
- recursively merge them together.
- time complexity: O(nlogk) divide by log(k) times, each recursive call can work on n nodes.
- space: O(logk) stacks
- Iterative, PQ来排列所有list的leading node.
- Note: k lists need to be sorted (luckily, already given)
- 时间:n*O(logk), where n = total node number, and PriorityQueue: logk,
- Note:
-
- 不要忘记customized priority需要一个customized new Comparator()
-
- Given list 里面也可能有null node, 不要忘记查.
-
- 如果k很大,一个机器上放不下所有的k list怎么办?
- 如果Merge起来的很长,一个机器上放不下怎么办?
- Depth-first:
-
- process curr.child, return tailChild
-
- connect tailChild.next = curr.next
-
- function: link(Node a, Node b);
- 长短list,找重合点
- 长度不同的话,切掉长的list那个的extra length
- 那么起点一样后,重合点就会同时到达
- Time O(n) * 2, constant space
- Basic Implementation of copy linked list:
- use a iterator node to iterate over the list: 遍历head.next .... null.
- use a dummy node to hold reference to the iterator node.
- Map<original, new node>: 1. avoid creating same node; 2. return the new node if existing
- 每一步都check map里面有没有head. 没有? 加上
- 每一步都check map里面有没有head.random. 没有? 加上
- Note, there is a way to skip the extra map O(n): https://leetcode.com/problems/copy-list-with-random-pointer/discuss/43491/A-solution-with-constant-space-complexity-O(1)-and-linear-time-complexity-O(N)
- However, creating a deep clone of the list is already O(n) extra space, so it is NOT effectively O(1) w/o map
- It may be beneficial, if we can not hold all nodes in memory, then the approach w/o map is more applicable.
- Regular convert BST to sored list: in-order traversal
- Carefully doubly link node head, tail
- traverse all nodes, DFS 好做:
left, curr, right
- Tail:
- Assume head is found for sub tree, then
tail = head.left
- Link
LeftTail <-> Curr Root <-> RightHead
- Link
RightTail <-> LeftHead
- Assume head is found for sub tree, then
- In place:
- 同
Node {val, left, right}
, w/o new doubley linked list class - different from
Convert Binary Search Tree to Sorted Doubly Linked List (extra space)
- 同
- check (mid, mid+1), or (mid-1, mid+1).
- If the two position matches, that is a palindrome candidate
- 比较front string 是否是 end string 的substring
- O(n^2)
- timeout on last case: ["aaaaaa....aaaacdaaa...aaaaaa"]
- TODO
- use
Map<preSum value, index>
to store inline preSum and its index. -
- Build presum incline
-
- Use map to cache current preSum value and its index:
Map<preSum value, index>
- Use map to cache current preSum value and its index:
-
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
(preSum - k)
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
-
- Use the calculated preSum candidate to find index
-
- Use found index to calculate for result. ex: calculate range.
- 算出一个右下角点(i,j)到(0,0)的大小: 上一块 + 左一块 + curr node - overlap area
- preSum[i][j]: sum from (0,0) to (i-1,j-1)
- same approach as
subarray sum
: use hashmap to store diff->index; if diff re-appears, that means sum of 0 has occurred - sequence of calculation: 1. iterate over start row. 2. iterate over end row. 3. iterate over col number (this is where hashmap is stored based on)
- the iteration over col is like a screening: find previous sum and determine result
- Note: 其实并没有真的去找
== 0
的解答,而是根据特性来判断剩下的/后来加上的一定是0
- Binary Search的思想, 用在所要找的这个 average sum 上面. 大小是在[min, max]之中
- 找k的时候, 是>=k都可以, 巧用一个 min(preSum)的概念.
- 找k的时候, 画图, 可以看出来, 其实要的是 k window 里面的sum [x, i], 所以要用 sum[0, i] - sum[0, x]
- Can be a 2D array, or a
class Point
to store preSum + index - Sort preSum: smaller (有可能负数) 靠前, 大数字靠后
- 比较�preSum种相连接的两个节点, 找差值min; 因为最接近的两个preSum节点的差值肯定是最小
- min所在的两个节点的index, 就是�result candidate: 这两个index可能再原nums里面相差很远
- time O(nlogn), sort
- space: O(n)
- 因为map虽然能存 preSum + index, 但是无法有效排序
- 所以用一个class来存这两个信息, 然后合理排序
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
subarray sum equals k
的简单版: k = 0- 求preSum, 然后不断check
map.containsKey(preSum - k)
. - 如果
priorSum = preSum - k == 0
, 说明 [priorSum.index + 1, curr index] 就是我们要找的这一段
- 求preSum, 然后不断check
- 分析出,如果sum[0
a]=x, 然后sum[0b]=x, 说明sum[a+1 ~ b] == 0 - 用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组.
- pre sum 的definition
- preSum也是dp[]一种最简易的形式把.
- dp[i], preSum[i]: 前(i-1)个元素的和.
- https://leetcode.com/problems/count-of-range-sum/discuss/77990/Share-my-solution
-
- build preSum[n+1]: then sum range [i,j]= preSum[j+1] - preSum[i]
-
- Divide and Conquer:
- 先考虑[start, mid] range里的 ran sum result
- 再考虑[mid, end] range里面的结果
- 最后考虑[low, high]总体的结果
- NOTE: should write merge() function, but that is minor, just use
Arrays.sort(nums, start, end)
, OJ passed - Every mergeSort() has a for loop => O(n log n)
- 如何 count range?
- 这里比较特别的一个做法: 找一个 [low, mid]里面的i, mid 之后的preSum作比较 (解释源自: https://blog.csdn.net/qq508618087/article/details/51435944)
- 即在右边数组找到两个边界, 设为
m, n
, - 其中m是在右边数组中第一个使得
sum[m] - sum[i] >= lower
的位置, - n是第一个使得
sum[n] - sum[i] > upper
的位置, - 这样
n-m
就是与左边元素i所构成的位于[lower, upper]
范围的区间个数.
- 边界[lower, higher] 在 sorted array 好作比较, 一旦过界, 就可以停止计算, 减少不必要计算.
- 上面这个n,m的做法可行的前提: preSum[]里面前后两个 range[low, mid], [mid, high]已经sorted了
- 也就是说, 在recursively mergeSort()的时候, 真的需要merge sorted 2 partitions
- 也许会问: 能不能sort呢, sort不久打乱了顺序? 对,打乱的是preSum[]的顺序.
- 但是不要紧: 很巧妙的, 分治的时候, 前半段/后半段 都在原顺序保留的情况下 分开process完了, 最后才merge
- 在做m,n 的range的时候, 原理如下, 比如preSum被分成这么两段:
[A,B,C]
,[D,E,F]
- 每一个preSum value
A
在跟 preSum[i] 作比较的时候A - preSum < lower
, 都是单一作比较, 不牵扯到 B, C - 因此,
[A, B, C]
是否保留一开始 preSum的顺序在此时不重要
- 每一个preSum value
- 此时最重要的是,
[A,B,C]
以及排序好, 那么在于lower
boundary 作比较的时候, 一旦过界, 就可以停止计算(减少不必要的计算) - TODO?
- This segment tree approach(https://leetcode.com/problems/count-of-range-sum/discuss/77987/Java-SegmentTree-Solution-36ms)
- does not build segment tree based on given nums index
- it is built on sorted preSum array.
- regular segment tree based on nums array does not work:
- segment tree based on input array is good for: search/query by index
- is NOT good at: given range sum/value, find indexes
- why? segment tree is built based on index division, not by range value division.
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- Approach#4 of https://leetcode.com/problems/subarray-sum-equals-k/solution/
- Hash Table two sum 思想, but to save frequency of current sum:
preSumCount<sum, count>
- for loop 从左开始积累
preSumCount<sum, count>
- derive
priorSum = sum - k
: 看看前面有多少此种sum,preSumCount.get(priorSum)
# ways to reach priorSum
gives # of ways for thatpriorSum + k = curr Sum
- therefore, count += preSumCount.get(priorSum)
- for loop 从左开始积累
- O(n) time, O(n) space
- Note: 如果需要实际index, 可以存
Map<Integer, List<Index>>
- presum: socalled
cummulative sum
- move from starting point i = [0 ~ n -1] and test each
range = [i ~ j]
- use presum to verify k:
preSum[j + 1] - preSum[i]
- time: O(n^2):
1 + 2 + 3 + 4 ... + n ~= O(n^2)
- build rangeSum[i][j]: square range sum from (0,0) to (i,j), O(mn) to init
- query: time O(1)
- build rowPreSum[i][j]: row i sum from [0 ~ j], O(mn) to init
- callign takes O(m); space O(mn)
- want to find
nums[i - 1] == nums[n - 1] - nums[i]
, given:- preSum[i], sum from [0, i] inclusive
- preSum[j] - preSum[i] = [i+1, j] inclusive
- O(n) to build preSum
- O(n) to find pivot
- Check if mod result exist in earlier preSum
- Utilize
Pigeonhole principle
to optimize:-
- put positive integers into k slots
-
- when # of integers > 2*k, then there must be a range sum that is multipler of k
- more illustration here: https://leetcode.com/problems/continuous-subarray-sum/solution/
-
- Draw the presum and try to take mod of each presum and save to set, we realize
-
- assume a mod result = 7, and we mark it in the set
-
- some time later, after summing up more values, (7 + x + y ...+ z) % k == 7
- it means
(x + y ...+ z) % k == 0
- There is a
整除
exist; return true
-
- Meanwhile, if we want to record the list of indexes, we can use a Map rather than set.
- Note: if all we do to the presum is to % k, therefore
preSum % k
can representpresum
in some cases. - time: O(n)
- space: O(k), size restrited by mod result of
%k
- PreSum[]:
-
- cal preSum array
-
- preSum(i, j) = continuous range sum
-
- determine if
preSum(i, j) % k == 0
- determine if
-
- time: O(n^2)
- DP (坐标型. specifically, preSum[])
- 记录在0 ~ i点(包括nums[i], 以nums[i]结尾)的sum, 坐标型动态规划.
- dp[i] = dp[i - 1] + nums[i];
- 从sum = 每次[i ~ j]的所有情况
- time: O(n^2)
- space: O(1)
- Same concept as turning an array into a binary segment tree,
- HOWEVER, this is a 4-nary segmenet tree
- Reference. 307 Range Sum Query
- Range Query concept:
- Using the input range, sum up everything in the range
- sometimes the input range cover multiple segments, then dive into the segments (still use original range)
- once we found a bounded segment (completely surrounded by input range), return segment value.
- Handling end stage, there are two approaches:
- ApproachA: check at beginning of recursive call (i.e in
build()
,updateNode()
,rangeQuery()
).- pro: calling recursive function blindly; code is easy.
- con: be really clear about termination state, and catch it.
- ApproachB: check & come up with correct query condition before recursive call
- pro: input to recursive function is assumed to be correct
- con: sometimes really hard to write the conditions before recursive call; code is hard.
- ApproachA: check at beginning of recursive call (i.e in
- Using merge sort concept (NOT merge sort impl).
- One very simply desire: if we want to know # elements between [i, j] such that
nums[i] > 2*nums[j]
, it would be so great if array is sorted!- If sorted, fix index i, keep j++ for all
nums[i]/2.0 > nums[j]
- We CANNOT just sort entire array. WHY? Because it distrupts the value of curr index i, and the restriction is:
find matching elements on right side of curr index i
- BUT, what about just sort
right side of i
, and make sure the subproblem (i+1, end) is solved first?
- If sorted, fix index i, keep j++ for all
- 灵感: use merge sort concept.divide and conquer [i ~ n] into 2 sections:
-
- solve subProblem(start,mid) & subProblem(mid+1, end). sort the sub array so that it can be used recursively at parent level.
-
- solve the curr pblem: for all [i, mid], check against [mid+1, end].
-
- Question1: does it cover all use cases?
- First, subProblem(start,mid) & subProblem(mid+1, end) recursively solves its own range
- Last, the only range is the current level problem check
[i, mid]
against its entire right side range:[mid+1, end]
. DONE. all covered.
- Question2: what it is okay for
subProblem(start,mid) & subProblem(mid+1, end)
partially sort the array?- that is the goal: 1) we want the right side range to be sorted; 2) left range is sorted but it does not matter since we treat [start, mid] as 1 group
- use classic while loop
while(j<=e && nums[i]/2.0 > nums[j])
to count pairs - TODO
- split the array into index-based segment tree, where each element is at leaf
- store min of range: use min to determine if certain range is needed for further query
- query for each element right side range (i + 1, end), where it recursively query&aggregate sub-range if meeting requirement
nums[i] > 2*nums[j]
- only when target > subRange.min * 2: there are possible candidates, query further
- worst case O(n^2) when all tailing elements are meeting requirement.
- TODO
- Build the BST based on node value. It will be not applicable if we search after entire tree is built (our goal is right range), so we need to build right elements, and search/count right after the elements is added
- Worst case is still O(n^2), if all added nodes are meeting requirement
- search(tree, curr / 2.0)
- check each one of them
- What if
the processed list is sorted
, so that I can BinarySeach for curr target?- process from end
- binary search for
index to insert new element
in sorted ascending list - that index = # of smaller numbers; record it for final result
- time: O(nlogn)
- space: O(n)
- Segment Tree functions:
Build
: construct segment tree based on min/max range: at leaf node, update count of numbers in rangemodify(SegmentTreeNode root, int value, int count)
: find leaft at with value, and update count for leaf & all parent nodesquery(SegmentTreeNode root, int start, int end)
: return count # of numbers in range [start, end]
- Very similar to
Count of Smaller Number
, where segment tree is built on actual value!! - IMPORTANT to drop processed number from left-hand-side:
- only find on remaining numbers.
- Utilize
modify(root, target, -1)
to erase element count & update the tree.
- time:
n * log(m)
, where m = Math.abs(max-min). log(m) is used to modify() the leaf element - space: O(m)
Define the positive range
- negative nubmer division
rounds up towards 0
(this is a problem). (i.e.(-2 - 1) / 2 = -1.5 = -1
), which causes range error. - We want the entire segment tree range to be ascending, and we want the mid = (start+end)/2 to round down.
- Solution:
- build entire segment tree based on [min, max], where min must be >= 0.
- we can do this by adding Math.abs(min) onto both min/max, as well as +offset during accessing nums[i]
- negative nubmer division
- https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
- Assume we have a BST, where each node has smallerCount and a val, for any new target, how to find smaller items?
-
- add the # of smaller count to current node
-
- compare:
- if target < node.val, keep searching
countVisit(node.left, target)
- if target > node.val: 1) add currNode.smallerCount, 2) minus node.right.smallertCount (reduce double-counting), 3) plus
countVisit(node.right, target)
- remember to create left/right node before dfs countVisit into the sides.
-
- sample problem for segment tree
- build(), update(), rangeQuery()
- build and update are standard
- rangeQuery: handle the range split check
- Null leaf node handling: NO, ideally it will not encounter null leaf.
- in update/rangeQuery: when final state (
start==end
) is reached, the recursive call ends - there is no way for any node to dive futher into null child.
- in update/rangeQuery: when final state (
- Range Query concept:
- Using the input range, sum up everything in the range
- sometimes the input range cover multiple segments, then dive into the segments (still use original range)
- once we found a bounded segment (completely surrounded by input range), return segment value.
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- keyword: tree has no
cycle
. - 一旦两个node在edge中出现, 并且parent相同, 说明这两个node不union, 也在同一个tree里面, 所以可以break them.
- Add graph using adjacent list, and verify cycle alone the way
- IMPORTANT: use
pre
node in dfs to prevent backward dfs - similar to
Graph Valid Tree
where it validates cycle and also needs to validate if all nodes are connected - same concept as DFS, find first redundant edge that alreay exists in graph map.
- Build graph
map<node, list of node>
- BFS to find the shortest path: when the neibhbor has the curr node as the only one neighbor, it is leaf.
- record shortest path in Map<Integer, List> as result
- TODO: code it up.
- removing leaf && edge
- 跟Graph Valid Tree 几乎一模一样
- 建造简单的parent[] union find
- 每个edge都union.
- 注意 union 的时候, 只需要union if rootA != rootB
- build graph as adjacent list: Map<Integer, List>
- dfs for all nodes of the graph, and mark visited node
- count every dfs trip and that will be the total unions
Build graph
:- 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先: form sequence between chars
- form graph Map<Char, List of Chars>, for topological sort usage.
- 也可以
List[26] edges
(Course Schedule problem)
Build InDegreeCountMap<Char, Count>
: based on the char diff of 2 words- 注意: indegree 是反向的 (跟 node to neighbors 相反的方式建立)
Topological Sort
, BFS:-
- use queue to find
inDegree == 0
node. It is the letter that points to others, 排在字母表前面.
- use queue to find
-
- reduce edges using Graph
map<Character, List<Character>>
(more generic than List[26], 26个字母的dictionary)
- reduce edges using Graph
-
- Edge Case:
inDegreeCountMap.size() != result.length()
: some nodes did not make it into result sequencecycle
: when inDegree of a one node would never reduce to 0, and will not be added to result- In this case, it will be treated as invalid input, and return ""
- space: O(n), n = # of graph edges
- time: O(n)
- TODO
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- Concept of Indegree:
# of incoming node that depends on me
. It is a graph ofarrows pointing inward to me
structure. - Remember: indegree是周围的node到我这里的次数count.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- Two structures:
-
- build inDegreeEdges:
List[] inDegreeEdges
: list of incoming nodes that depends onnode i
,
- build inDegreeEdges:
-
- build dependencyCount:
int[] dependencyCount
, count # of braches that curr node depends on
- build dependencyCount:
-
- any dependencyCount[node]==0, means this node is now a leaf, add to queue
- Topological Sort Process, Kahn algorithem:
- topologically process:
- add leaf node to queue, get ready to process;
- process leafNode, like cutting of leaf
- if any child node dependencyCount == 0, it is a leaf node now: add this node to queue.
- this problem aims for deteching cycle, not output final list. Simply: visit all nodes and verify cycle
- Option1: array of indegree lists, List[]
- 用 visited int[] 来确认是否有cycle. 1 means
visited
, -1 meansvisted from last dfs level
- Deteching
-1
: 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false. - dfs on curr node indegree dependencies; if all passes w/o failing, set visited[i] = 1
- Deteching
- Similarly, can use
HashMap<Integer, List<Integer>> map
to replace List[], but exact same idea.
- 用 visited int[] 来确认是否有cycle. 1 means
- Optoin2: use a struct
class Node {Boolean visiting; Map<Integer, Node> inDegreeMap}
to be more generic - topo sort may output the sort order: 1) at DFS bottom level, put record to a
stack
, 2) rst.insert(0, curr record) - 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>. Though: map may be more flexible
- 是topological sort的题目。一般都是给有dependency的东西排序。
- 最终都会到一个sink/leaf node,no further dependency, 在那个点截止
- 画个图的话, prerequisite都是指向那个sink/leaf node
- when building the inDegreeMap/inDegreeEdge: we use sink/leaf node as key/index, which pionts back to inDegree/parent nodes
- BFS: when all braches/dependency count are reduced to 0, then it is now a leaf node, ready to be used.
- DFS Insert Order: rst.insert(0, node); Assume we want leaf/node at index 0 in final output:
- the very bottom-node depends on everybody
- any visited node should be added to 0 index of the list, so it will be at tail later
- Realize we need to: 1) topo sort group, 2) topo sort items in the group.
- Luckily, the candidates to be sorted are all integers: groupIds, or item ids. We can have 1 generic topo sort function
- Overall workflow
-
- group items to map <GroupId, List>
-
- build group graph
-
- topo sort group -> return sorted group id list
-
- for each group: build item graph, topo sort items -> return sorted item list
-
- flatten and return results
-
- analysis:
-
- should not have mult-origin cases: 1 char maps to 1 char at maximum
-
- need a buffer char NOT exist in target to hold inter-media transformation
- check open char (out of 26 lower letter) that is NOT in target chars
-
- impl the validation rules
- more to read in https://leetcode.com/problems/string-transforms-into-another-string/discuss?currentPage=1&orderBy=most_votes&query=
- simply calc each level sum with BFS
- top-level is processed first, since we go from top level -> deeper level
- only update result if sum is truly > global MAX.
- Find possibility to reach certain point, we can BFS: faster to find shortest candidate
- use queue to hold left, right candidates
- use set to record visited
- attemp all nodes, use set to record visited.
- time: O(n)
- space: O(n)
- Given assumption: 1)
only 1 celebrity
, 2) person k, who knows nobody ahead of him or after him. - if first pass finds candidate,
person k
, it means:- person [0, k-1] are not celebrity: they know a previous or current candidate
- person k knows no one between [k + 1, n): k+1 to n-1 can not be the celebrity either.
- person k is just the last standing possible celebrity
- second pass validation: we do not know if
knows(celeb, [0~k-1] )
. Do a final O(n) check - time:O(n), space O(1)
- DO NOT: Brutle compare all -> all: O(n^2) handshakes.
- 先写出来[0 ~ n - 1], 最简单的方式 O(n^2) 检查, 记录每个人的状态.
- 逐渐发现, 因为 celeb 谁都不会认识, 那么当任何candidate knows anyone, 他自身就不是celeb.
- 我们可以greedy地, 一旦fail一个, 就立刻假设下一个是celeb candidate
- 最终还是要检查一遍, 避免错漏.
- 想一下happy case: 如果 celeb=0, 那么 know(celeb, i) 永远都是false, 然后 celeb一直保持0, 坚持到verify所有人.
- Construct graph:
map<String, List<Destination>>
; sort the list of destinations. - DFS:
- with any curr city, go over the destination list:
graph.get(curr)
- add visit city to rst
- remove visited city from the desitnation list
- backtrack
- with any curr city, go over the destination list:
- NOTE:
-
- the graph allows cycle: revisiting same city. Do NOT assume no cycle
-
- it asks to us to treat
smaller lexical order city
with priority; however:
- it does NOT mean visiting
smaller lexical order city
is THE correc anser - it can be a leaf sink node of the graph and does not provide correct trip plan
- it asks to us to treat
-
- time: O(n^n). n = # of cities. worst case, each city has (n-1) edges and need to try all combinations
- space: O(n^2), there can at most be n * (n - 1) edges
- analyze problem, and realize we want to eliminate
isolated servers
- count row[], count col[]
- cross-reference row[] and col[]:
row[i]==1 & col[j]==1
indicates a isolated server - Unfortunately, this problems checks unconnected items, so dfs needs to brutlely check entire row or column
- Only add if
vertical + horizontal count
> 1 - time: O(mn) * O(m + n)
207. Course Schedule
has more notes- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目求这个最后排好的课的list
- 如果排不好, 就给个空就好
- input是 numOfCourses, 还有这个prerequisites [[]]
- 用
List[] edges; edges[i] = new ArrayList<>();
来表示graph: 就是每个node, to all its neighbors - 每个没有 inDegree==0 node, 都是可以加进 final list里面的. 比如一开始找到的那些 inDegree = 0的 node
- 注意, 如果 prerequisites = [], 那么就是说这些课都independent, 开个int[0 ~ n-1]的数组并赋值就好.
- 如果有cycle, 严格意义上就做不了topological sort, 也无法涵盖所有nodes, 那么return [ ]
- 根据 Course Schedule 里面的DFS 修改
- 维持visited int[]全局变量
- 维持sortedList int[] 全局变量, 注意加进去的时候是 add(0, node) 加在开头这样
- 每次到一个node的children全部DFS走完之后, 就可以把他加进final list里面
- 如果有cycle, 也就是dfs return false的时候, 这个题目判定排课失败, return new int[] { }
- 复习Union-Find的另外一个种形式, track union size: tree does not have cycle, so eventually union size should == 1
-
- 查找2个元素是不是在一个union里面。如果不在,false. 如果在,那就合并成一个set, 共享parent.
-
- 验证cycle:
find(x) == find(y) => cycle
- ideally, this edges[i] should be the very first time x and y node connect;
- however, if they have been grouped together under same ancestor before, there exist a feedback loop (cycle) between them.
- 验证cycle:
-
father[x]
: element x (index x) stores its root ancestor- 注意: 结尾要检查, 是否只剩下1个union: Tree必须连接到所有给出的node.
- 另一个union-find, 用hashmap的:
- http://www.lintcode.com/en/problem/find-the-weak-connected-component-in-the-directed-graph/
- Deep Dive into UnionFind: https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf
- Very similar to
Redundant Connection
- Create adjacency list graph: Map<Integer, List>
- 检查:
-
- 是否有cycle using dfs, check boolean[] visited
-
- 是否所有的node全部链接起来: validate if all edge connected: # of visited node should match graph size
- IMPORTANT: use
pre
node to avoid linking backward/infinite loop such as (1)->(2), and (2)->(1) - traverse through adjacency list graph:
Map<Integer, List<Integer>>
-
- validate cycle with set: if revisit same node
- avoid infinite loop: remove backward mapping from child node to parent node
-
- validate check set size for connected
- Pick a subset (max-size k), and produce sub problem to solve by dfs
- NOTE: no need to change actual index value. That makes this problem easier (no need to record the choice path)
- time: O(n), calc memo[n]
- space: O(n), memo + stack depth
- Use HashMap to mark cloned nodes:
map<oldNode, newNode>
-
- make new curr node;
-
- clone all neibhors and add them
-
- Use the map to avoid visited nodes
- time: O(n). visit all nodes
- space: O(n). Technically only travels n levels/stacks to circle all nodes (undirected & connected)
- Given graph node obj
{val, list of neighbor}
: copy the node and all neighbors - Mark visited using map<oldNode, newNode>
- for loop on the each one of the neighbors: map copy, record in map, and further dfs
- once dfs completes, add newNeighbor as neighbor of the new node (get to it via map)
- 主要思想是: 一旦复制过了, 不必要重新复制
- Copy the root node, then copy all the neighbors.
- Mark copied node in map.
- Use queue to contain the newly added neighbors. Need to work on them in the future.
Dijkstras algorithm
is based on repeatedly making the candidate move that has the least distance travelled.- PQ: pick close node to vist, and add siblings back to PQ
- avoid visited
- time: O(nLogn), visit n nodes, each time insert to heap takes O(logn) time
- space: O(n)
-
- build graph map, 2) traverse map, 3) prioritize short delay nodes first
- use a map
<node, timeElapsed>
globally track dealy to nodes; compare all at the end - build map of
x#y -> val
to store values[i] and 1/values[i] - build map of
x -> list children
- dfs to traverse the graph
- BFS should also work: build graph and valueMap
- for each starting item, add all next candidate to queue
- mark visited, loop until end item is found
- 著名Nim游戏
- 写一些,发现n=4,5,6,7,8...etc之后的情况有规律性: 谁先手拿到4就输了.
- 最终很简单n%4!=0就可以了, time, space O(1)
- 正规地找规律做, 就跟 coins in a line 一样, 按照先手后手来做
- 可以rolling array 优化空间
- Time O(n), 当然啦, 这个题目这样会timeout, 可以使用brainteaser的做法写出结果.
- if just impl, it take O(n^2):
- repating: some pos are toggled mutiple times: if we know total times, easy to determin each pos.
- loop over [2, n], count times on each index
- 用while loop, 不断地尝试把 number 送到该放的地方
- 如果 index = nums[i] 超过了nums.length, 当然就不移动了
- 注意: 检查 val != nums[val], avoid infinitely loop
- 检验: nums[i] 是否等于 i, 如果不对, 就找到了结果
- 如果nums==null, 其实missing positive integer 自然而然是 1
- 有可能这串数字里没有断开的integer, 但是最大的integer在首位 (因为index超标, 无法被放到正确的地方)
- 这种时候, n被放在 index 0, 其实就是说, 下一个integer应该是 n + 1
- 最终, 如果array本来就是完全sorted, 也不缺, 还符合角标的条件, 那么唯一下一个就是array范围外的第一个positive number: n
- 跟传统的UnionFind有两点不同:
-
- 用 Map<Integer, Integer> 代替 int[], 因为没有给出 graph node label的 boundary.
-
- find(x)时候, 没有去update
parent[x]/map.put(x, ..)
. 因为我们最终需要找到这个path.
- find(x)时候, 没有去update
- 无法用传统dfs: directed node 无法point到上一个点; 必须用
存parent的方式把所有node遍历掉
- 看到了weak component的形式: 一个点指向所有,那么所有的点都有一个公共的parent,然后就是要找出这些点。
- 为何不能从一个点出发,比如A,直接print它所有的neighbors呢:
- 如果轮到了B点,那因为是directed,它也不知道A的情况,也不知道改如何继续加,或者下手。
- 所以,要把所有跟A有关系的点,或者接下去和A的neighbor有关系的点,都放进union-find里面,让这些点有Common parents.
- 最后output的想法:
- 做一个 map <parent ID, list>。
- 之前我们不是给每个num都存好了parent了嘛。
- 每个num都有个parent, 然后不同的parent就创造一个不同的list。
- 最后,把Map里面所有的list拿出来就好了。
- 要想看连续元素, 必须要num++, num--这样搜索
-
- 需要O(1)找到元素
-
- 需要简单快速找到 num - 1, num + 1.
- 如果用min,max开array, 耗费空间
- 用HashSet来存, 用set.contains() 来查找 num - 1, num + 1 存在与否
- for loop. O(n)
- 里面的while loop 一般不会有O(n); 一旦O(n), 也意味着set 清零, for loop也不会有更多 inner while 的衍生.
- overall O(n) 时间复杂度
- 最终是要把相连的元素算一下总长, 其实也就是把元素group起来, 相连的group在一起, 于是想到UnionFind
- 这里用到了一个
int[] size
来帮助处理合并的时候parent是哪个
的问题: 永远往group大的union里去 - main function 里面, 有一个map来track, 每个元素, 只处理1遍.
- union的内容: current number - 1, current number + 1
- https://www.jianshu.com/p/e6b955ca208f
- Union Find 在index上做好像更加容易
- 其他union find function:
boolean connected(a,b){return find(a) == find(b)}
- keyword: tree has no
cycle
. - 一旦两个node在edge中出现, 并且parent相同, 说明这两个node不union, 也在同一个tree里面, 所以可以break them.
- Add graph using adjacent list, and verify cycle alone the way
- IMPORTANT: use
pre
node in dfs to prevent backward dfs - similar to
Graph Valid Tree
where it validates cycle and also needs to validate if all nodes are connected - same concept as DFS, find first redundant edge that alreay exists in graph map.
- 跟Graph Valid Tree 几乎一模一样
- 建造简单的parent[] union find
- 每个edge都union.
- 注意 union 的时候, 只需要union if rootA != rootB
- build graph as adjacent list: Map<Integer, List>
- dfs for all nodes of the graph, and mark visited node
- count every dfs trip and that will be the total unions
- UnionFind里面这次用到了一个rank的概念, 需要review. rank[] 也就是在tracking每一个node所在union的size.
- 目的是: always并到大的union里面
- note: 将2D coordinate (x,y) 转换成1D: index = x * n + y
- Reversed thinking: find surrounded nodes, how about filter out border nodes && their connections?
- Need to traverse all the border nodes, consider dfs, visit all.
- loop over border: find any 'O', and dfs to find all connected nodes, mark them as 'M'
- time: O(mn) loop over all nodes to replace remaining 'O' with 'X'
- More like a graph problem: traverse all 'O' spots, and mark as visited int[][] with area count [1 -> some number]
- Run dfs as top->bottom: mark area count and dsf into next level
- End condition: if any 'O' reaches border, mark the global map<count, false>
- keep dfs untill all connected nodes are visited.
- At the end, O(mn) loop over the matrix and mark 'X' for all the true area from map.
- Practice: write code to verify
- TODO
-
- 我们知道大部分的brick可能都是连着ceiling, 所以每次正向检查都traverse all and timeout
-
- 能否用union, 把connect都装在一起, 然后drop brick的时候把连着的都drop掉? 难: 因为还是要check所有brick当下的status.
- 受其他人的解答启发, 由于是计算count,我们可以
反向考虑
: - 把hit-brick全部mark=2 (就当舍弃不算), 观察整个局面的最后一步, 先把所有还连着ceiling的brick算一下总数, 统计在unionFind的 全部统计在count[0] 里面.
- 剩下的不连着ceiling的也就是一个个isolated island
- 做法: 把hit-brick 一个个加回去, 然后再做一次union, 看看最终连到ceiling的有多少个. 增加的count, 就是正向思考时 dropped brick 数量!
- 还是用数字index做union find, 但是把每一个index都+1, 右移一位, 而[0]留下来做特殊用途:
- 用union at 0来 统计总共的remain count of ceiling-connected bricks, where
x = 0
. - 如果在其他其他题目种, 条件可能就不是
x=0
, 但也可以用这个 union index = 0 来做一个root的统计 - 关键: 把最后一个hit brick加回去, 然后再重新union一下这个hit-brick周围: count增加的变化, 不就是缺少hit-brick时候掉下去的数量.
- 考虑每个hit的四周, 全部traverse, 没有连着ceiling就全部:
- 比如是 200 x 200 的 全部是1的matrix, 任何一次traverse都要到顶; 重复计算, 所以timeout
- 算法是没错, 但是不efficient.
- 想要减少重复计算, 但是又不能提前计算: grid在不断变化. 所以看能不能把连着ceiling的都group起来, 可以O(1)快速check?
- union(), find()
- Path Compresion: store skip father after found, which makes find O(1)
- 把board转换成1D array, 就可以用union-find来判断了.
- 用int[] father 的unionFind, 需要转换2D position into 1D index. 这样比较clean
- 判断时,是在四个方向各走一步,判断是否是同一个Land.
- 每走一次operator,都会count++. 若发现是同一个island, count--
- count的加减, 都放在了UnionFind自己的function里面, 方便tracking, 给几个helper function就对了.
- Time: O(k * log(mn))
- 用HashMap的Union-find.
- Proof of UnionFind log(n) time: https://en.wikipedia.org/wiki/Proof_of_O(log*n)_time_complexity_of_union%E2%80%93find
- visit all nodes connected with the starting node
- double for loop, test all starting nodes
- val == 1: 1) count++; 2)DFS from this (i,j);
- Mark visited (x,y) = '0'
- time: O(n), visit all nodes
- space: O(n), stack
- 可以用union-find, 就像Number of island II 一样.
- 只不过这个不Return list, 而只是# of islands
- Union Find is independent from the problem: it models the union status of integers.
- Return the total # of unions (which is # of islands)
- in reality: it is a bit slow.
- time: visit all nodes just once, O(n). Union Find will visit all nodes once and union them
- space: O(n), union find takes O(n) space
- 记住UnionFind的模板和几个变化(Connecting Graph I, II, III), 最后归总的代码写起来就比较简单.
- use queue to hold 1 island, keep adding 4-direction islands; mark visited with '0'
- check entire board for any remaining one.
- 复习Union-Find的另外一个种形式, track union size: tree does not have cycle, so eventually union size should == 1
-
- 查找2个元素是不是在一个union里面。如果不在,false. 如果在,那就合并成一个set, 共享parent.
-
- 验证cycle:
find(x) == find(y) => cycle
- ideally, this edges[i] should be the very first time x and y node connect;
- however, if they have been grouped together under same ancestor before, there exist a feedback loop (cycle) between them.
- 验证cycle:
-
father[x]
: element x (index x) stores its root ancestor- 注意: 结尾要检查, 是否只剩下1个union: Tree必须连接到所有给出的node.
- 另一个union-find, 用hashmap的:
- http://www.lintcode.com/en/problem/find-the-weak-connected-component-in-the-directed-graph/
- Deep Dive into UnionFind: https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf
- Very similar to
Redundant Connection
- Create adjacency list graph: Map<Integer, List>
- 检查:
-
- 是否有cycle using dfs, check boolean[] visited
-
- 是否所有的node全部链接起来: validate if all edge connected: # of visited node should match graph size
- IMPORTANT: use
pre
node to avoid linking backward/infinite loop such as (1)->(2), and (2)->(1) - traverse through adjacency list graph:
Map<Integer, List<Integer>>
-
- validate cycle with set: if revisit same node
- avoid infinite loop: remove backward mapping from child node to parent node
-
- validate check set size for connected
- build map of
x#y -> val
to store values[i] and 1/values[i] - build map of
x -> list children
- dfs to traverse the graph
- BFS should also work: build graph and valueMap
- for each starting item, add all next candidate to queue
- mark visited, loop until end item is found
- time O(mn)
- 构建
Map<email, email parent>
, 然后再反向整合: parent -> list of email - init with <email, email> for all emails
- 因为不同account可能串email, 那么把所有email union的时候, 不同account 的email也会被串起来
- 最终: 所有的email都被union起来, 指向一个各自union的 parent email
- UnionFind 的 parent map 可以反向输出所有 child under parent.
- 同时要维护一个 <email -> account name> 的map, 最终用来输出.
- Definitely need iterate over accounts: merge them by email.
- Account object {name, list of email}
- map<email, account>
-
- iterate over accounts
-
- find if 'account' exist; if does, add emails
-
- if not, add account to list and to map. map all emails to accounts.
- output -> all accounts, and sort emails
- space O(mn): m row, n = emails
- time O(mn)
- TODO
- 把Interval拆分成数轴上的Point
- 起飞mark 1
- 降落mark -1
- 用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。
- 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
- 当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
- 这避免了错误多count,或者少count
- O(nlogn) time (PriorityQueue), O(n) space
-
- 扫描线+Count: when
count==0
, startFlags==endFlags. 是interval的开头/结尾 (write an example)
- 扫描线+Count: when
-
- Note: remember to merge points on same sweep line position
-
- Comparator:
new PriorityQueue<>(Comparator.comparing(p -> p.val))
; - Sort intervals: O(nlogn), extra space O(n) when creating rst list
Arrays.sort(intervals, Comparator.comparing(i -> i[0]));
- 找到结尾 interval, 满足条件就可以save
- 如果不到return的条件, 就继续延伸 interval.end
- Less applicable when input is
int[][] intervals
, but more applicable when we haveList<int[]> intervals
- Related example: Insert Interval
- Sort fist, loop over and merge, cut off overlapped interval.
- sort by Interval.start:
intervals.sort(Comparator.comparing(interval -> interval.start)); // O(nlogn)
- 用两个相连的Interval: curr, next
- 如果 curr.end覆盖了 next.start: 需要merge. 那么比较一下 curr.end vs. next.end
- 一旦merge, 需要remove被覆盖的 next interval:
list.remove(i+1)
- 若没有重合,就继续iteration
- sort by Interval.start:
- time O(nlogn), space O(1)
- 注意接头点要考虑所有开会结会的情况,不要恰巧漏掉相接的点
- 开会的是超人。瞬间移动接上下一个会议
- sort:
Arrays.sort(intervals, Comparator.comparing(i -> i[0]))
- time: O(nlogn), space: O(1)
- class Point{pos, flag}, PriorityQueue排序。计算count
- 跟 Number of Airplanes in the Sky 是一个类型的题目
- time: O(nlogn), space O(n)
- Not necessary for this problem, since it requires extra space with pq.
- Inspired by: https://leetcode.com/problems/rectangle-area-ii/discuss/137941/Java-TreeMap-solution-inspired-by-Skyline-and-Meeting-Room
- First consider regular sweep line and realize problem: each vertical line has multiple block segments
- Easy: take a list of vertical dots, and calculate the height diff
- We can use a TreeMap with y-coordinate as key, so to
natural sort by y-coordinate
- Trick: can NOT remove used y coordinate from map, because the rectangle may continue to expand to right side.
- apply simple equation to calc area:
(long)preY * (p.x - preX)
- time:
- sort initial queue: O(nlogn)
- process queue: O(n)
- TreeMap insertion: O(logn)
- TreeMap traversal: O(n)
- overall, process queue can be O(n^2)
- space: O(n)
- TODO lol
- Sort start times, and end times in 2 different arrays
- Loop over start time
- when start[i] < end[endIndex], Count++, need more room
- start[i] >= end[endIndex], done using some room, move to next end time, endIndex++ (like vacating a room)
- Note: we never decrese count because:
- what ever count reaches, it is the max
- since we keep moving endIndex, when start[i] >= end[endIndex], we will just reuse meeting room w/o count++
- time: O(nlogn)
- space: O(n)
- somehow, super fast, over 100%
- inspired by: https://leetcode.com/problems/meeting-rooms-ii/discuss/67855/Explanation-of-%22Super-Easy-Java-Solution-Beats-98.8%22-from-%40pinkfloyda
- Use sweep line to process, track max count as max # of rooms needed
- 跟 Number of Airpline in the sky是同一道题
- time: O(nlogn)
- space: O(n)
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
- 这里已经给了 sorted intervals by start point;
-
- 直接找到可以insert newInterval的位子. Insert and convert to list
-
- Merge: Use
pre, curr
to iterate over list, and remove curr after merging
- remove之前都会重新assgin
pre.end
, 确保被remove的node.end 被capture
- Merge: Use
-
- Convert back to int[][]
-
- time/space: O(n)
- code is slightly better to read
- handle edge cases:
- new interval is non-overlapping
-
- head
-
- tail
-
- in middle
-
- new interval is overlapping:
-
- end index in existing interval; reuse the existing interval end to close new range
-
- end index in the gap of 2 intervals, use new interval.end to close the new range
-
- new interval is non-overlapping
- time, space: O(n)
- Interval 拆点,PriorityQueue排点
- Merge时用count==0作判断点
- 注意, 一定要compare curr
p.x == queue.peek().x
确保重合的点全部被process:count+=p.x
- PriorityQueue: O(logN). 扫n点, 总共:O(nLogn). SLOW.
- 因为interval已经sort, 本想用Binary Search O(logn).
- 但是找到interval insert position 最后 merge还是要用 O(n), 所以不必要 binary Search
- UnionFind里面这次用到了一个rank的概念, 需要review. rank[] 也就是在tracking每一个node所在union的size.
- 目的是: always并到大的union里面
- note: 将2D coordinate (x,y) 转换成1D: index = x * n + y
- Reversed thinking: find surrounded nodes, how about filter out border nodes && their connections?
- Need to traverse all the border nodes, consider dfs, visit all.
- loop over border: find any 'O', and dfs to find all connected nodes, mark them as 'M'
- time: O(mn) loop over all nodes to replace remaining 'O' with 'X'
- More like a graph problem: traverse all 'O' spots, and mark as visited int[][] with area count [1 -> some number]
- Run dfs as top->bottom: mark area count and dsf into next level
- End condition: if any 'O' reaches border, mark the global map<count, false>
- keep dfs untill all connected nodes are visited.
- At the end, O(mn) loop over the matrix and mark 'X' for all the true area from map.
- Practice: write code to verify
- TODO
- visit all nodes connected with the starting node
- double for loop, test all starting nodes
- val == 1: 1) count++; 2)DFS from this (i,j);
- Mark visited (x,y) = '0'
- time: O(n), visit all nodes
- space: O(n), stack
- 可以用union-find, 就像Number of island II 一样.
- 只不过这个不Return list, 而只是# of islands
- Union Find is independent from the problem: it models the union status of integers.
- Return the total # of unions (which is # of islands)
- in reality: it is a bit slow.
- time: visit all nodes just once, O(n). Union Find will visit all nodes once and union them
- space: O(n), union find takes O(n) space
- 记住UnionFind的模板和几个变化(Connecting Graph I, II, III), 最后归总的代码写起来就比较简单.
- use queue to hold 1 island, keep adding 4-direction islands; mark visited with '0'
- check entire board for any remaining one.
- 看到 min value, 至少考虑dp:
- Consider last num: min sum will be (preMinSum + curr, or curr)
- Use preMinSum to cache previouly calcualted min sum, also compare with +curr.
- Have a global min to track: because the preMinSum can be dis-continuous.
- 也可以写成 dp[i] 但是没什么必要
- 全部是positive integer, 那么preSum一定是增长的.
- 那其实就用two pointer:
start=0, end=0
不断往前移动. 策略: -
- end++ until a solution where sum >= s is reached
-
- 然后移动start; 记录每个solution, Math.min(min, end - start);
-
- 然后再移动end,往下找
- Note: 虽然一眼看上去是nested loop.但是分析后,发现其实就是按照end pointer移动的Loop。start每次移动一格。总体上,还是O(n)
- O(nlogn) NOT DONE.
- O(n^2), inefficient
- use
Map<preSum value, index>
to store inline preSum and its index. -
- Build presum incline
-
- Use map to cache current preSum value and its index:
Map<preSum value, index>
- Use map to cache current preSum value and its index:
-
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
(preSum - k)
- Each iteration: calculate possible preSum candidate that prior target sequence. ex:
-
- Use the calculated preSum candidate to find index
-
- Use found index to calculate for result. ex: calculate range.
- Can be a 2D array, or a
class Point
to store preSum + index - Sort preSum: smaller (有可能负数) 靠前, 大数字靠后
- 比较�preSum种相连接的两个节点, 找差值min; 因为最接近的两个preSum节点的差值肯定是最小
- min所在的两个节点的index, 就是�result candidate: 这两个index可能再原nums里面相差很远
- time O(nlogn), sort
- space: O(n)
- 因为map虽然能存 preSum + index, 但是无法有效排序
- 所以用一个class来存这两个信息, 然后合理排序
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
subarray sum equals k
的简单版: k = 0- 求preSum, 然后不断check
map.containsKey(preSum - k)
. - 如果
priorSum = preSum - k == 0
, 说明 [priorSum.index + 1, curr index] 就是我们要找的这一段
- 求preSum, 然后不断check
- 分析出,如果sum[0
a]=x, 然后sum[0b]=x, 说明sum[a+1 ~ b] == 0 - 用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组.
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- Continuous product can be positive/negative/zero
- If nums[i] > 0, want prior largest product[i-1] * nums[i]
- If nums[i] < 0, want prior smallest product[i-1] * nums[i]
- If nums[i] == 0, product = 0
- `prior product[i-1]: 想到DP
-
- 正负数情况, 需要用两个
PreProduct
array: minProduct[], maxProduct[]
- 正负数情况, 需要用两个
-
- continuous prodct: it has to utilize curr nums[i]
- 是跟nums[x]当下值比较的, 如果当下值更适合, 会舍去之前的continous product, 然后重新开始.
- Use a global variable to hold overall result.
-
- Time/Space O (n)
- Space optimization, rolling array
- maxProduct && minProduct 里面的 index i, 都只能 i - 1相关, 所以可以省去redundant operatoins
- Time: O(n)
- space: O(1)
- same concept as method1, but simplified: given that we always have to use nums[i], so only 1 result can be passed on
- FAST, simple to write and read
- time: O(n)
- space: O(1)
- working solution, BUT Time/Space complexity O(n^2) are too much
- Approach#4 of https://leetcode.com/problems/subarray-sum-equals-k/solution/
- Hash Table two sum 思想, but to save frequency of current sum:
preSumCount<sum, count>
- for loop 从左开始积累
preSumCount<sum, count>
- derive
priorSum = sum - k
: 看看前面有多少此种sum,preSumCount.get(priorSum)
# ways to reach priorSum
gives # of ways for thatpriorSum + k = curr Sum
- therefore, count += preSumCount.get(priorSum)
- for loop 从左开始积累
- O(n) time, O(n) space
- Note: 如果需要实际index, 可以存
Map<Integer, List<Index>>
- presum: socalled
cummulative sum
- move from starting point i = [0 ~ n -1] and test each
range = [i ~ j]
- use presum to verify k:
preSum[j + 1] - preSum[i]
- time: O(n^2):
1 + 2 + 3 + 4 ... + n ~= O(n^2)
- Check if mod result exist in earlier preSum
- Utilize
Pigeonhole principle
to optimize:-
- put positive integers into k slots
-
- when # of integers > 2*k, then there must be a range sum that is multipler of k
- more illustration here: https://leetcode.com/problems/continuous-subarray-sum/solution/
-
- Draw the presum and try to take mod of each presum and save to set, we realize
-
- assume a mod result = 7, and we mark it in the set
-
- some time later, after summing up more values, (7 + x + y ...+ z) % k == 7
- it means
(x + y ...+ z) % k == 0
- There is a
整除
exist; return true
-
- Meanwhile, if we want to record the list of indexes, we can use a Map rather than set.
- Note: if all we do to the presum is to % k, therefore
preSum % k
can representpresum
in some cases. - time: O(n)
- space: O(k), size restrited by mod result of
%k
- PreSum[]:
-
- cal preSum array
-
- preSum(i, j) = continuous range sum
-
- determine if
preSum(i, j) % k == 0
- determine if
-
- time: O(n^2)
- DP (坐标型. specifically, preSum[])
- 记录在0 ~ i点(包括nums[i], 以nums[i]结尾)的sum, 坐标型动态规划.
- dp[i] = dp[i - 1] + nums[i];
- 从sum = 每次[i ~ j]的所有情况
- time: O(n^2)
- space: O(1)
- 把Interval拆分成数轴上的Point
- 起飞mark 1
- 降落mark -1
- 用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。
- 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
- 当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
- 这避免了错误多count,或者少count
- 最多2个fence 颜色相同
- 假设i是和 i-1不同,那么结果就是 (k-1)*dp[i - 1]
- 假设i是何 i-1相同,那么根据条件,i-1和i-2肯定不同。那么所有的结果就是(k-1)*dp[i-2]
- dp[i]: count # of ways to paint 前i个 fence
- 加法原理
- time, space: O(n)
- rolling array: space O(1)
- 这题目很有意思. 一开始分析的太复杂, 最后按照这个哥们的想法(http://yuanhsh.iteye.com/blog/2219891) 的来做,反而简单了许多。
- 设定T(n)的做法,最后题目化简以后就跟Fibonacci number一样一样的。详细分析如下。
- 做完,还是觉得如有神。本来是个Easy题,想不到,就是搞不出。
- 看到 min value, 至少考虑dp:
- Consider last num: min sum will be (preMinSum + curr, or curr)
- Use preMinSum to cache previouly calcualted min sum, also compare with +curr.
- Have a global min to track: because the preMinSum can be dis-continuous.
- 也可以写成 dp[i] 但是没什么必要
- dp[i][status]: 在 status=[0,1] 情况下, 前i个 房子拿到的 max rob gain. status=0, 1st house robbed; status=1, 1st house skipped
- 根据dp[i-1]是否被rob来讨论dp[i]: dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i - 1]);
- 特别的是,末尾的last house 和 first house相连. 这里就需要分别讨论两种情况: 第一个房子被搜刮, 或者第一个房子没被搜刮
- be careful with edge case nums = [0], only with 1 element.
- Time,space: O(n)
- 是否搜刮了第一个房子, 分出两个branch, 可以看做两种状态.
- 可以考虑用两个DP array; 也可以加一dp维度, 补充这个状态.
- 连个维度表示的是2种状态(1st house being robbed or not); 这两种状态是平行世界的两种状态, 互不相关.
- 与House Robber I一样, 可以用%2 来操作rolling array, space reduced to O(1)
- 设定dp长度为(n+1), 因为dp[i]要用来表示前i个(ith)时候的状态, 所以长度需要时i+1才可以在i位置, hold住i.
- 双序列: 两个sequence之间的关系, 都是从末尾字符看起, 分析2种情况:
-
- A最后字符不在common sequence 或者 B最后字符不在common sequence.
-
- A/B最后字符都在common sequence. 总体count + 1.
- Edit Distance的follow up.
- 其实就是改一下 minEditDistance的function, 带入K作比较罢了.
- 写起来跟Edit Distance 的主要逻辑是一模一样的.
- 但是LintCode 86% test case 时候timeout.
- Time O(mnh), where h = words.length, 如果 n ~ m, Time 就几乎是 O(n^2), 太慢.
- 两个string, 找最值: longest common string length
- 序列型, 并且是双序列, 找两个序列 (两维的某种性质)
- dp[i][j]: 对于 A 的前i个字母, 对于 B 的前j个字母, 找最长公共substring的长度
- dp = new int[m + 1][n + 1]
- dp[i][j] = dp[i - 1][j - 1] + 1; only if A.charAt(i - 1) == B.charAt(j - 1)
- 注意track max, 最后return
- space O(n^2), time(n^2)
- 空间优化, [i] 只有和 [i - 1] 相关, 空间优化成 O(n)
- 找所有A的substring, 然后B.contains()
- track max substring length
- O(n^2) time
- 只卖2次, 把买卖分割成5个状态模块.
- 在状态index 0, 2, 4: 没有持有股票. 1. 一直在此状态, max profit不变; 2. 刚卖掉, dp[i][前状态] + profit
- 在状态index 1, 3: 持有股票. 1. 一直在此状态, daily profit. 2. 刚刚买进, 状态改变, 但是没有profit yet: dp[i][前状态]
- 把每天的partial profit (diff)加在一起, 最终的overall profit是一样的. 唯一更好的是, 不需要记录中间买入的时间点.
- 什么时候会积累profit呢?
-
- 原本就持有股票的, 如果毫无动作, 那么状态不变, 积累profit diff.
-
- 卖出了股票, 状态改变, 积累profit diff.
- 注意: 只有在状态index: 0, 2, 4, 也就是卖掉股票的时候, 才可以积累profit
- [i] 只有和 [i-1] 打交道, reduce space
- O(1) space, O(n) time
- 找峰头;然后往下再找一个峰头。
- 怎么样在才能Optimize两次巅峰呢?从两边同时开始找Max!(棒棒的想法)
- leftProfit是从左往右,每个i点上的最大Profit。
- rightProfit是从i点开始到结尾,每个点上的最大profit.
- 那么在i点上,就是leftProfit,和右边rightProfit的分割点。在i点,leftProfit+rightProfit相加,找最大值。
- 三个O(n),还是O(n)
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
- 根据StockIII, 不难发现StockIV就是把状态划分为2k+1份. 那么同样的代码, 移植.
- 如果k很大, k>n/2, 那么长度为n的数组里面, 最多也只能n/2个transaction
- 那么题目简化为stockII, 给n数组, 无限次transaction.
- 注意, status的数量是 2k+1
- Time O(NK), Space O(2k+1) to store the status
- 最后状态是'没有stock'的都该考虑, 做一个 for 循环比较max.
- 当然, 来一个profit variable, 不断比较, 也是可以的.
- (previous notes, 熟练第一种方法的思考就可以)
- 记得要理解:为什么 i-1天的卖了又买,可以和第 i 天的卖合成一次交易?
- 因为每天交易的price是定的。所以卖了又买,等于没卖!这就是可以合并的原因。要对价格敏感啊少年。
- Inspired from here: http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html
-
local[i][j] = max(global[i – 1][j – 1] + diff, local[i – 1][j] + diff)
-
global[i][j] = max(global[i – 1][j], local[i][j])
-
local[i][j]: 第i天,当天一定进行第j次交易的profit
-
global[i][j]: 第i天,总共进行了j次交易的profit.
-
local[i][j]和global[i][j]的区别是:local[i][j]意味着在第i天一定有交易(卖出)发生。
-
当第i天的价格高于第i-1天(即diff > 0)时,那么可以把这次交易(第i-1天买入第i天卖出)跟第i-1天的交易(卖出)合并为一次交易,即local[i][j]=local[i-1][j]+diff;
-
当第i天的价格不高于第i-1天(即diff<=0)时,那么local[i][j]=global[i-1][j-1]+diff,而由于diff<=0,所以可写成local[i][j]=global[i-1][j-1]。
-
(Note:在我下面这个solution里面没有省去 +diff)
-
global[i][j]就是我们所求的前i天最多进行k次交易的最大收益,可分为两种情况:
-
如果第i天没有交易(卖出),那么global[i][j]=global[i-1][j];
-
如果第i天有交易(卖出),那么global[i][j]=local[i][j]。
- 与StockII一样, dp[i]: represents 前i天的最大profit.
- sell 的时候, 才完成了一次transaction, 需要扣fee; 而买入不扣fee.
- model sell on dp[i] day (which depends on dp[i-1]) and each day can be sell/buy => add status to dp[i][status]
- status[0] buy on this day, status[1] sell on this day
- dp[i][0] = Math.max(dp[i-1][0], dp[i - 1][0] - prices[i]);
- dp[i][1] = Math.max(dp[i-1][1], dp[i - 1][1] + prices[i] - fee);
- init: dp[0][0,1] = 0; dp[1][1] = 0; dp[1][0] = - prices;
- return dp[n][1]
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
- dp[i]: 前i个房子拿到的max gain
- 看最后结尾状态的前一个或前两个的情况,再综合考虑当下的
- 搞清楚当下[i]的和之前[i-x]的情况的关系: 不可以连着house, 那么就直接考虑 dp[i-2]的情况
- Sequence DP, new dp[n + 1];
- Rolling Array
- [i]'只和前两个位子 [i-1], [i - 2]'相关
- 用%2来标记 [i], [i - 1], [i - 2]三个位置.
- 其他滚动时惯用curr/prev来表示坐标, 这里%2虽然抽象, 但是更加实用.
- dp[i] depends on nums[i-1] or nums[i-2] based on the state at (i-1)
- use dp[n][2] to store dp[i] and stages
- dp[0][0] = 0; dp[0][1] = nums[0]
- calculation
- dp[i][0] = Math.max(dp[i - 1][1], dp[i - 1][0]). The prior house can be either state.
- dp[i][1] = dp[i - 1][0] + nums[i]. The prior house must be
NOT ROBBED
.
- return
Math.max(dp[n - 1][0], dp[n - 1][1])
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- 递归很好写, 但是重复计算, timeout. time: O(2^n)
- O(2^n): each n can spawn 2 dfs child, at next level, it will keep spawn. Total 2^n nodes will spawn.
- 用全局变量int[] memo 帮助减少重复计算
- O(n) time, space
- 加法原理, 最后一步被前两种走法决定: dp[i] = dp[i - 1] + dp[i - 2]
- 基础sequence DP, int[] dp = int[n + 1];
- DP[]存的是以 1-based index的状态
- dp[i]: count # of ways to finish 前i个 台阶
- 需要知道dp[n] 的状态, 但是最大坐标是[n-1], 所以int[n+1]
- dp[0]往往是有特殊状态的. 这里, dp[0]: 1种方式可以原地不动
- dp[1]=1, 1种方式到达index=1,
- 之后的
dp[2] = dp[0]+dp[1]
: 就是dp[0]的走法 or dp[1]的走法
- O(n) space, time
- [i] only associates with [i-2], [i-1].
- %2
- O(1) space
- [elm] repeats 0 times: move p, j + 2
- [elm] repeats 1 or more times: need s[i] == p[i], then move s, i+1
- DFS on remaining of s and p. Analyze the different cases when next char == '*'
- End case: both i,j reached end true; or one of them reached end.
- The two different cases when given any index j on p, the p[j+1]=='*'
- TRUE:
- ignore p[j, j+1], continue from p[j+2]
- check if s[i]==p[j] or p[j]='.'; continue from s[i+1] and p
- FALSE: check i,j, and move forward with s[i+1], p[j+1]
- TRUE:
- If next p char != '*', check curr s[i] ?= p[i]
- Improvement with memo with 2D Booelan[][] memo: much faster
- memo[i][j] records result the remaining strings: s.substring(i) compare with p.substring(j)
- use
Boolean
: when memo[i][j] != null, return something!
- Two sequence, DP, find if possible to match.
- The '*' takes effect of preceding/prior element, so we can start matching from end.
- DP[i][j]: is it possible to match s[0 ~ i - 1] and p[0 ~ j - 1].
- Check last index of s and p, there can be a few possibilities:
-
- s[i-1]==p[j-1] and they are normal characters => && dp[i - 1][j - 1];
-
- p[j-1] == '.', match => dp[i - 1][j - 1]
-
- p[j-1] == '*':
- a. ignore a* => |= dp[i][j - 2];
- b. use a* => |= dp[i - 1][j];
-
- init: dp[0][j] and dp[i][0] will all be false since there cannot be any match.
- Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
- 从低谷找peek, sell.
- DP. (old dp solution BuyOn[], SellOn[])
- DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)
- 画图, 因为可以无限买卖, 所以只要有上升, 就有profit
- 所有卖掉的, 平移加起来, 其实就是overall best profit
- 当天卖光, 再买进.
- O(n) time
- 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。
- 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.
- profit += prices[peek - 1] - prices[start]; 挺特别的。
- 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.
- 想知道前i天的最大profit, 那么用sequence DP:
- dp[i]: represents 前i天的最大profit
- 当天的是否能卖, 取决于昨天是否买进, 也就是
昨天买了或者卖了的状态
: 加状态, dp[i][0], dp[i][1] 买
的状态dp[i][0]
=-
- 今天买入, 昨天sell后的dp[i-1][1]值 - 今天的价格price[i];
-
- 今天不买, compare with 昨天buy的dp[i-1][0]值.
-
卖
的状态dp[i][1]
=-
- 今天卖出, 昨天buy的 dp[i-1][0]值 + price[i];
-
- 今天不卖, compare with 昨天sell后的 dp[i-1][1]值.
-
- 注意init:
- dp[0][0] = dp[0][1] = 0; // day 0 buy/sell: no profit regardless of buy/sell status
- dp[1][1] = 0; // day 1 sell: haven't bought, so just 0 profit.
- dp[1][0] = - prices[0]; // day 1 buy: just cost of prices[0]
- Note:
int[][] dp = new int[n+1][2]
以后, index是 1-based. for loop 注意使用prices[i-1]
- O(n) time, O(n) space
- [i] 和 [i - 1] 相关联, roll
- 每天都就交易价格,n天只让买卖一次,那就找个最低价买进,找个最高价卖出
- 记录每天最小值Min是多少. O(n)
- 每天都算和当下的Min买卖,profit最大多少.
- Find min value for first i items, new dp[n + 1].
- dp[i]: 前i天, prices最小的price是多少: min cost of first i days
- 然后用当天的price做减法dp[i]算max profit.
- Time, Space: O(n)
- 更进一步, 用一个min来表示min[i], 因为计算中只需要当下的min.
- index i only depend on [i - 2]
- Space O(n)
- 每天都试着买进,然后之后的每一天尝试卖出. double for loop, O(n^2). timeout.
- 其中很多都是没必要的计算:[7, 1, 5, 3, 6, 4]
- if we know buyin with 1 is cheapest, we dont need to buyin at 5, 3, 6, 4 later on;
- only need to sell on higher prices.
- Bottom-up, test simply case. Sequence DP.
- true/false problem, think about dp
- 子问题: 前i个字母, 是否可以有valid break
- check: 1) dp[j] && 2)
if substring(j, i) valid
, for all j = [0 ~ i] - dp = new boolean[n + 1]; dp[0] = true;
- test:
dp[i] |= dp[j] == true && word[j, n] in dict
. - Need iterate over i = [0 ~ n], also j = [0, i]
- When there is a way to make dp[i] == true, then break the [j ~ i] loop, move on to test dp[i++]
- Use set dict:
dict.contains()
- Improvement: O(n) to figure out max length, so we can skip some substring[j~i] dict.contains()
- overall O(n^2) time since the double for loop
- Top-Down, break into small problems: Check front subString, and put the rest substring into dfs to test
- Memoization: for tested failed substring, record and do NOT test them again.
- Same Improvement as in DP: use max/min length of dict words as boundary
- 求dp[i]的min cost, depends on the color of dp[n-1]
- 选中最后一个房子的颜色同时, 根据dp[i - 1]的颜色/cost + cost[i-1], 来找出最低的cost
- Need to have status with dp array: int[index][color status]
- dp[i][j]: 前i个house 刷成 j 号颜色的最小cost.
- dp[0][j] = 0: 0th house, no cost
- 计算顺序: 从每一个house开始算起 [0 ~ n], first for loop
- time: O(nm), m = # of colors
- space: O(nm)
- 观察发现 index[i] 只跟 [i-1] 相关, 所以2位就足够, %2
- space:O(1)
- 跟Paint House I 几乎一模一样, 只不过paint color更多了: k colors.
- 先考虑单纯地用dp[i]表示涂前 i 个房子的最小cost
- 但是 dp[i] 和 dp[i-1] 两个index选什么颜色会互相影响, 难讨论, 于是加状态: 序列DP被加了状态变成2D.
- 考 虑最后位, 而前一位i-1又被i位的颜色限制, 于是在考虑 min dp[i] 时候, 又多了一层iteration.
- 做dp[i][j]: # cost for 前 i 个房子, 所以要先pick (i-1) 房子的cost, 然后在找出 (i-2)房子的cost
- K种颜色 => O(NK^2)
- 如果不优化, 跟Paint House I 几乎是一模一样的代码
- Time O(NK^2), space(NK)
- Rolling array: reduce space to O(K)
- 序列型dp[i]表示'前i-1个'的结果. 所以dp最好设定为 int[n + 1] size.
- 然而, 颜色在这里是状态, 所以保留在 j: [ 0~k)
- [[8]] 这样的edge case. 跑不进for loop, 所以特殊handle.
- Time: O(NK)
- 如果已知每次都要从cost里面选两个不同的最小cost,那么先把最小两个挑出来, 就不必有第三个for loop 找 min
- 每次在数列里面找: 除去自己之外的最小值, 利用最小值/次小值的思想
- 维持2个最值: 最小值/次小值.
- 计算的时候, 如果除掉的不是最小值的index, 就给出最小值; 如果除掉的是最小值的index, 就给出次小值.
- Every loop: 1. calculate the two min vlaues for each i; 2. calcualte dp[i][j]
- 如何想到优化: 把表达式写出来, 然后看哪里可以优化
- 另外, 还是可以rolling array, reduce space complexity to O(K)
- 考虑两个字符串的末尾index� s[i], t[j]: 如果需要让这两个字符一样, 可能使用题目给出的三种operation: insert/delete/replace?
- 先calculate最坏的情况, 3种operation count + 1; 然后在比较match的情况.
- 注意, 在i或者j为0的时候, 变成另外一个数字的steps只能是全变.
- 第一步, 空间时间都是O(MN), O(MN)
- 滚动数组优化, 空间O(N)
- insert: assume insert on s, �
#ofOperation = (s[0 ~ i] to t[0 ~ j-1]) + 1;
- delete: assume delete on t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j]) + 1;
- replace: replace both s and t,
#ofOperatoin = (s[0 ~ i - 1] to t[0 ~ j - 1]) + 1;
- dp[i][j]�代表了两个 sequence 互相之间的性质: �s[0 ~ i] �转换成 s[0~j] 所需要的最少 operation count
- init: 当i==0, dp[0][j] = j; �每次都要 + j 个character; 同理, 当j==0, dp[i][0] = i;
- 而dp[i][j]有两种情况处理:
s[i] == t[j]
ors[i] != t[j]
- 这种判断取决于经验: 如果知道initialization可以再 double for loop 里面一起做, 那么可以留着那么做
- 这样属于
需要什么, initialize什么
- 事后在做space optimization的时候, 可以轻易在 1st dimension 上做rolling array
- 可以做, 但是不建议:这道题需要找 min count, 而不是search/find all solutions, 所以search会写的比较复杂, 牛刀杀鸡.
- 计算 expression 的值: 1. 建造 expression tree. 2. DFS计算结果
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- build好Min Tree以后,做PostTraversal.
- Divde and Conquer: 先recursively找到 left和right的大小, 然后evaluate中间的符号
- Time, Space O(n), n = # expression nodes
-
- Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
-
- 若有个child是null,那就return另外一个node。
-
- prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.
- 和Max-tree一样,https://leetcode.com/problems/maximum-binary-tree
- 用到bottom->top递增的stack: 最底下的root维持成最小的element.
- 这个题目是Min-tree, 头上最小,Logic 和max-tree如出一辙
- Space: O(n)
- Time on average: O(n).
- TreeNode: 用一个并不是最终结果的TreeNode, 存weight, 用来排序
- 用base weight的概念权衡同一个层面的 符号, 数字 顺序
- 每一个character都是一个节点, 都有自己的weight. 用一个TreeNode来存weight value, 利用用weight来判断:
-
- (while loop) 如果node.val <= stack.peek().nodeValue, 把当前stack.peek() 变成 left child.
-
- (if condition) 如果stack有残余, 把当前node变成 stack.peek().rightChild
- Expression Tree是一个 weight-based的 min-tree
- 基于 运算符号 + 数字的 tree: 数字永远在leaf, 然后符号是tree node, 括号不出现在tree里面
- 用 monotonuous stack 来构建这个tree
- Understand Expression Tree
- Use stack to build the expression tree + understand the weight system
- Use post-order traversal to evaluate the tree
- 注意, input里面的数字不会是single digit, 所以需要一个buffer存number string
- 整个题目的做法, 可以参照
Expression Evaluation
- 对于每一个数字, 其实很简单就能算出来: 每次 >>1, 然后 & 1 就可以count 1s. Time: 一个数字可以 >>1 O(logN) 次
- 现在要对[0 ~ num] 都计算, 也就是N个数字, 时间复杂度: O(nLogN).
- 用DP来优化, 查找过的number的1s count, 存下来在 dp[number]里面.
- 计算你顺序从 0 -> num, count过的数字就可以重复利用.
- Bit题目 用num的数值本身表示DP的状态.
- 这里, dp[i] 并不是和 dp[i-1]有逻辑关系; 而是dp[i] 和dp[i>>1], 从binary representation看出有直接关系.
- 跟Coins in a line I 不一样: 每个coin的value不同.
- 用到MiniMax的思想, 这里其实是MaxiMin. Reference: http://www.cnblogs.com/grandyang/p/5864323.html
- Goal: 使得player拿到的coins value 最大化.
- 设定dp[i]: 从index i 到 index n的最大值. 所以dp[0]就是我们先手在[0 ~ n]的最大取值
- 于此同时, 你的对手playerB也想最大化, 而你的选择又不得不被对手的选择所牵制.
- 用MaxiMin的思想, 我们假设一个当下的状态, 假想对手playerB会做什么反应(从对手角度, 如何让我输)
- 在劣势中(对手让我输的目标下)找到最大的coins value sum
- Reference里面详细介绍了表达式如何推到出来, 简而言之:
- 如果我选了i, 那么对手就只能选(i+1), (i+2) 两个位置, 而我在对方掌控时的局面就是min(dp[i+2], dp[i+3])
- 如果我选了i和(i+1), 那么对手就只能选(i+2), (i+3) 两个位置, 而我在对方掌控时的局面就是min(dp[i+3], dp[i+4])
- 大家都是可选1个或者2个coins
- 目标是maximize上面两个最坏情况中的最好结果
- 更加简化一点: 如果我是先手, dp[i]代表我的最大值.
- 取决于我拿了[i], 还是[i] + [i+1], 对手可能是dp[i + 1], 或者是dp[i+2]
- 其实dp[i] = Math.max(sum - dp[i + 1], sum - dp[i + 2]);
- 这里的sum[i] = [i ~ n] 的sum, 减去dp[i+1], 剩下就是dp[i]的值没错了
- 这个做法是从最后往前推的, 注意initialize dp末尾的值.
- dp = new int[n + 1]; dp[n] = 0; // [n ~ n]啥也不选的时候, 为0.
- sum = new int[n + 1]; sum[n] = 0; // 啥也不选的时候, 自然等于0
- 然后记得initialize (n-1), (n-2)
擒贼先擒王
: find the candidate that has largest set of correlations with the rest candidates, and eliminate based on this candidate.approach A
: count the candidate that has 0 overlaps, find min of this pollapproach B
: count the candidate that has largest # of connections
- cross-compare, count
match==0
<word, count>: find candidates that has 0 overlap with others- pick
min-count candidate A
: it is a candidate that has overlaps with most strings (since 0-match-count is lowest) - the above candidate will help to eliminate a largerset of overlapped candidates
- guess A, return matchCount.
- pick
- filter set with matchCount: eliminateCandidate
- 快慢指针, O(1)space.
-
- 确认有cycle后 2. 数学问题:找到开头.
- 当head == slow.next时候, head就是cycle starting point.
- 也就是说,当slow 移动到了那个回溯点,slow.next那个点就刚好是head的那个点...
-
- 假设慢指针走t步, 快指针走快一倍, 也就是2t.
-
- 我们假设cycle的长度是Y, 而进入cycle之前的长度为X.
-
- 假设慢指针走了m圈cycle, 而快指针走了n圈cycle之后, 两个pointer相遇.
-
- 最终在Y cycle里面的K点相遇, 也就是两个指针都在这最后一圈里面走了K 步.
- 那么:
- t = X + mY + K
- 2t = X + nY + K
- �整合公式: X + K = (n - 2m)Y
- 这里的m和n不过是整数的跑圈数, 也就是说X和K加在一起, 总归是结束cycle. X 和 K 互补
- 结论: 当slow/fast 指针在K点相遇后, 再走X步, 就到了cycle的起点, 也就是题目要求的起点.
- 全部是positive integer, 那么preSum一定是增长的.
- 那其实就用two pointer:
start=0, end=0
不断往前移动. 策略: -
- end++ until a solution where sum >= s is reached
-
- 然后移动start; 记录每个solution, Math.min(min, end - start);
-
- 然后再移动end,往下找
- Note: 虽然一眼看上去是nested loop.但是分析后,发现其实就是按照end pointer移动的Loop。start每次移动一格。总体上,还是O(n)
- O(nlogn) NOT DONE.
- O(n^2), inefficient
- 记得用dummy.next来存head.
- 特殊: 这里k可能大于list总长. 写一写linked node 移动的步数, 然后 k = k % n.
- 找到newTail, newHead, 然后利用dummy, 换位子
- 跟
kth largest element in an Array
的 template一样. - quickSelect 可以找到 kth 最小的元素
- 利用这个原理, 找这个kth最小值, 然后如果 == target index, 就找到了我们的median
- 主要步骤:
-
- partition
-
- check end state
pivot index ?= target index
- check end state
-
- recursive call one part of the array
-
- time: 与quickSort不同在于, 每次只要在一半list里面recurring, 所以把O(logn)的时间复杂度降到O(n)
- n + n/2 + n/4 + n/8 + ....+ 1 = O(2n) = O(n)
- space: O(logn), based on recursive stacks
- 木桶理论。盛水的最高取决于最低的那面墙。
- 左右两墙,往中间跑动。
- 另:若一面墙已经小于另外一面,就要移动,换掉矮墙(可能下一面更高,或更低)
- 但决不能换掉当下的高墙,因为低墙已经limit的盛水的上限,若高墙移动,导致两墙之间距离减少,就注定水量更少了。(弄啥来,不能缺心眼啊)
- linked list 不能像partitioin array一样从两边遍历
- 把小于value的加在前半段, 把 >= value的加在后半段
- 做法很普通: 建造两个list, midTail pointer, post pointer
- 把满足条件(<x, >=x)的数字分别放到两个list里面
- 记得用dummyNode track head.
- 最终midTail.next = post链接起来。
- O(nk)
- 其实就是quick sort里面的partition function的简化版
- Two pointers, 找一个 pivot 'a' 来区分大写小写字母
- ASCII code 里面 大写字母在小写字母前面, 数字更小
- 然后 while, move start++, end--,
- 每一轮都swap
- 直接用两个 pointer left/right 标记开头结尾
- 每次遇到
>= 'a'
就是小写字母, swap(chars, i, left); - 每次遇到
< 'a'
就是大写字母, swap(chars, i, right); - 注意: 每次处理完left swap, 任由for loop i++, 因为确定 [0 left] 都是准确的
- 每次处理完 right swap, 我们不确定从 right index 换过来的是不是正确的, 所以 i--, 跟for loop 的 i++抵消.
- 写 while loop 的 solution看起来更容易理解.
- 排序好的array. Two pointer移动start和end,核查sum.
- 注意sum用long.
- O(n) time
- 定住一个valueA, 然后在剩下的里面 binary serach 找 (target - valueB)
- for loop O(n), binary search O(logn)
- overall time: O(nLogN), 就不写了
- 更正常的start/end partition pointer类似: when condition meet, swap
- Clean up TODO
- partition the array by pivot k = {0, 1, 2}
- 每一次partition都return starting point of the current partition
- 然后根据下一个 color, 去还没有sort 干净的那个部分, 再sort一下就好
- time O(kn), where k = 0 => O(n)
- 这里只是partion, 并不需要recursively quick sort, 所以结果是简单的O(n)
- have two pointers, left/right
- start tracks red, end tracks blue. Swap red/blue to right position, and left++ or right--.
- leave white as is and it will be sorted automatically
- be very careful with index i: when swapping with index right, we do not know what is nums[right], so need to re-calculate index i .
- O(n)
- Note: this one pass solution does not work if there are more than 3 colors. Need to use the regular quick sorty.
- TODO: count occurance and reassign array
- 需要知道正负的位置, 所以排序 O(nlogN)
- 考虑: 正数多还是负数多的问题, 举栗子就看出来端倪了
- 然后Two Pointer, swap
- Time O(nlogn), space O(n)
- 用extra O(n) space, 把正负分成两个list
- 然后分别按照index填回去
- time O(n). space O(n)
- 但是就么有用到Two pointer了
- Quick sort的基础.
- Partition Array根据pivot把array分成两半。
- 从array两边开始缩进。while loop到遍历完。非常直白的implement。
- 注意low/high,或者叫start/end不要越边界
- O(n)
- 注意: 这里第二个inner while
while(low <= high && nums[high] >= pivot) {..}
采用了nums[high] >= pivot
- 原因是题目要找第一个nums[i] >= k, 也就是说, 即便是nums[i]==k也应该swap到前面去
- 这个跟quick sort 原题有一点点不一样.
- Array: Left Max Wall vs Right Max Wall.
- 对于每个index而言, vertically 能存放的最大水柱, 就是靠 左 右 最高墙决定的:
- min(leftHighestWall, rightHighestWall) - currHeight.
- time: O(n)
- space: O(n)
- Optimization from Method1: two pointer, 还是找左边最高和右边最高. O(1) space.
- 利用到了方法3里面的想法一样: 整个structure是被中间的最高bar 二分天下:
- always limited by the shorter wall: 左边按照maxLeft来计算, 右边按照maxRight来计算.
- time: O(n)
- space: O(1)
-
- 找中间最高bar的index
-
- 两面往中心扫:每次加上(topBarIndex - currIndex)* (elevation from previous index).也就是每次加一个横条
-
- 每次还要减去block自身的height
- time: O(n)
- space: O(1)
- 主要想法和方法3一致: 在山坡下坡的基础上, 一直用stack堆积bottom.
- 最后遇到上升之前, 此时bottom可以用来跟stack之前堆积的所有下坡index做比较, 算跟他们高度相差的积水.
- 用了stack记录下坡, 然后用个while loop一挖到底的想法非常棒.
- time: O(n)
- space: O(n)
- find slow/fast to detect the meeting point
- find begin node of the cycle: traverse from head, also move slow; utill head/slow meets slow
- Being able to analys the ax^2 + bx graph and find the
peak
orvalley
- Math basics: x^2 dominates the overall curve so it is up to a to determine:
valley
: if a < 0, both sides will be small and center will be large. Prioritize larger value.peak
: if a > 0, center will be small and both sides will be large. Prioritize smaller value.- starting index being 0 or n-1, is driven by
a
- start/end point, 然后比较大小记录dist
- 注意1: 如果第一个座位没有人, 特殊处理, dist = [0 ~ end]
- 注意2: �如果最后一个座位没有人, 特殊处理: dist = [n - 1 - start];
- 其余:
dist = Math.max(dist, (end - start) / 2)
- 相关题目: 几乎同样概念
Binary Gap
, 升级复杂版Exam Room
- Use set to check
- Verify duplicates at end rst
- similar to Intersection of Two Sorted Arrays
- Start from front/back, process 1 item at a time
- if match, move all pointers
- Optoin1: check from back
- Optoin2: check from frotn
- There can be 1 overlapping on any interval, calculate the inner intersection: lo(A[i][0], B[j][0]), hi(A[i][1], B[j][1])
- if low <= hi, a valid intersection exist; add
- also, if A[i][1] < B[j][1]; that is A[i].end < B[j].end, then i++; otherwise j++
- because the further-away
end
has been used, so move on.
- because the further-away
- O(n)
- code is much more complex (pq, Point, process code... etc) than method1
- we can use point to track open/close, also want to specify if point belongs to A/B
- mark 2 global parameters: aOpen, bOpen.
- process when A/B close, record if (aOpen, bOpen) has overlaop
- clear up corresponding global parameter after A/B closes
- sort all pointers in priority queue by index
- Point: {boolean isOpen; int index}
- process the queue and remember to clean up all items on same index
- time: O(nlogn)
- space: O(n)
- 用个char[]存string的frequency.
- 2 pointer:
- move
end
to find a valid window; - once valid inwindow found: now move
start
to narrow down to minimum window. - once window invalid, continue moving
end
and repeat last 2 steps
- move
- HashMap的做法比char[]写起来要复杂一点, 但是更generic
- Almost identical approach as in
438. Find All Anagrams in a String
- use sliding window template:
-
- extend right pointer and reduce char count
-
- process when count == 0
-
- contract/shrink left side
-
- special on the
3) step
:- there is no hard length limit in this problem: in fact, the goal is to find the shortest length
3) step
now apperas in thewhile(counter == 0)
loop- shrink the left side of the window as long as counter == 0, until we break the
counter==0
balance.
- time: O(n) one pass
- space: O(1), freq[256] can be ignored.
- Two Pointers, use 1 char freq map + counter to determine valid state
- Inspired by: https://leetcode.com/problems/minimum-window-substring/discuss/26808/Here-is-a-10-line-template-that-can-solve-most-'substring'-problems
- Idea: use freqMap and counter to maintain a valid substring range, use two pointers to iterate; reduce to
counter==0
which is the valid substring state. - Steps:
-
- build valid freq count map based on target string
-
- use end index [0~n) to find valid char and reduce counter to find valid range
-
- count==0 gives valid range: process; then
map[s.charAt(start++)]++ == 0
to break the peace
- count==0 gives valid range: process; then
-
- Explain
if (map[s.charAt(start++)]++ == 0) counter++
:- when
count != 0
,map[s.charAt(end++)]--
reduces freq regardless of what char it visits (it can be ANY char, rather than T characters) - when
count == 0
,map[s.charAt(start++)]++
increases freq regardless of what char that is.- if
map[s.charAt(start)] == 0
: it is a T character being reduced to 0 previously (so we can break the balance on this char) - YES, map has other index that has 0 freq: however,
start
ONLY covers indexes thatend
has stepped through :)
- if
- when
- time: O(n)
- space: O(1)
- much faster than method2: skip the O(256*n) comparison logic.
- Note: from the concept, it is the reversed thinking of method2.
- Use 2 char freq maps: source/target.
- target map: fixed freq map, used for comparision
- source map: attempt to build a valid freq map on the fly
- two pointers:
- use index
start=[0, n)
as start index of source candidate - have a end pointer that will attempt to as far as possible to find 1st valid sequence
- use index
- time: have double while loop, but still O(n), why?
- end pointer will at most reach full length n, only once
- start pointer iterate source strichtly once O(n)
- overall, it will be O(n)
- space: O(1), only used a constant char[256]
- Option2: use map, a bit more generic
- Prep: 存Map<word, index list>
- Process: 相继从两个 index list 里面拿出 p1,p2
- 根据index的大小, 移动双指针: try to move the pointers closer; always calculate diff
- Optionally: if one list is much larger, do binary search on the larger list
- sorted array, 重复元素都在一起
- 跟
Remove Duplicates from Sorted Array
几乎一模一样, 只不过unique index现在可以 validate 2 位 - 其余一模一样, use index to track unique item; skip if duplicated for more than 2 times
- O(n) time, O(1) space
- 这里也可以真的用2个pointers 写while loop, 但是没有必要, 只是单纯地走一个for loop其实就足够.
- when index i and i-1 are diff, use count=1 to start
- in while loop, keep count++ until count==k
- reset when next diff comes in
- sorted array, 重复元素都在一起
- Two pointers 其实也可以是一个 for loop pointer, 另一个 dynamic variable.
- track unique index
- skip duplicated items
- O(n)
- Remove Duplicate from Array 不同于remove from linked list.
- LinkedList里面我们是最好不要动node.val的,直接把node去掉。
- 而array我们很难直接把node去掉,又不能用新array,那么就要:
- 把不重复的element一个个放到最前面。
- 这个思想跟merge two sorted array (其中一个后续非常长的array可以放下arr1,arr2) 类似。
- 就是找个不会事后mess up,不会去动得index,把满足条件的element 填进去。这样保证了in place.
- 反向思维:remove duplicate, 实际上也是找unique elements, and insert into original array
- Similar to 15. 3Sum, but simpler.
- 只需要count triplet, 但是不需要save triplet, 而且还不需要handle duplicated triplets
- 发现start, end满足条件时候,(end - start)就是所有 sum <target的情况了。
- 而一旦 > target, 那么就end--
- 两层循环, O(n2)
- negative index i, positive index j
- 注意加法结果的位置.
- Use two pointers i, j to track the 2 strings
- Add when i and j are applicable. While (i >= 0 || j >= 0)
StringBuffer.insert(0, x);
- handle carry
- Reverse string -> Convert to Integer List, add up -> Convert back to string
- pointer 从前向后, 所以只需要 1个pointer.
- 操作复杂, 如上, 证明可以解决. 没必要reverse.
- 处理duplicate wthin triplets:
- 如果最外圈的移动点i重复, 一直顺到结尾的最后一个再用.
- 如果是triplet内有重复, 用完start point, 移动到结尾.
- Note:
-
- 找 value triplets, 多个结果。注意,并非找index。
-
- 要升序, 第一层for loop 从最后一个元素挑起, 保证了顺序。
-
- 去掉duplicate: check用过的同样的数字,都跳掉。不需要用同样的数字再计算一边已有结果。
-
- 时间 O(n^2), 两个nested loop
- HashMap 2Sum. Remember to handle duplicates
-
- For loop 挑个数字A
-
- 2Sum 出一堆2个数字的结果
-
- Cross match 步骤1里面的A
-
- 1 end pointer to define the window based n steps
- 1 pre pointer to track the node before the targeting node
- when end reaches null, remove nth node: link pre and head.next
- 用到hashset找unique && duplicate: O(m+n)
- 可以用binary search 找数字.
- Note:binary search一定需要array sorted: nLog(m)
- time: O(n)
- space: O(1)
- need to remove entity just added
- use stack to hold array content; pop if # is found
- Follow up of 243. Shortested Word Distance
- 特别handle
word == word1 == word2
case:- p1 and p2 will always be the same
- when
word == word1 == word2
, simply calculate distance using theold p1 or p2
withcurr index i
- The rest impl aligns with 243.
- when
word1==word2
, make usre to skipp1==p2
by increasing i or j - The rest impl aligns with 244
- Time: still O(n), but slower than Method1: 2 passes
- Space: uses extra space O(n) to hold all indexes
- Imagine two runners running on a track at different speed. What happens when the track is actually a circle?
- https://leetcode.com/problems/linked-list-cycle/solution/
- O(1) sapce: 用快慢指针,
start=head.next
,end=head.next.next
- Fast pointer will eventually catch up to slow pointer
- O(n) space: 用HashMap,一直add elements. 如果有重复,那么很显然是有Cycle
- Sliding window template:
-
- Check right pointer and move right
-
- Move left when necessary
-
- Verify count == 0 & end state
- Note: normally 2) and 3) are in reversed order; this problem is a bit different
-
- This is efficient when the number of characters is not limited to 26, the runtime is still O(m + n)
- time: O(m + n), m = s1 length, n = s2 length
- space: O(k), k = # of possible chars, 26 in this case
-
如果做s1的permudation, 时间复杂度是O(n!) 肯定不可以
-
这里用HashTable的做法 (因为26字母, 所以用int[26]简化) 来记录window内的 character count
-
如果window内的character count 相等, 那么就是permudation
-
更进一步优化: 找两个map相互对应, 不如用一个 int[26]: s1对遇到的character做加法, s2对遇到的character做减法
-
two pointer 运用在 n1, n2 的把控; 以及 s2.charAt(i - n1) 这一步
-
time: (m + n)
- However, if # of possible chars is more than 26
- For example,
k unique characters
, then the runtime will become: O(m + nk)
-
space: O(k), k = # of possible chars, 26 in this case
- DIFFERENT from sliding window for substring (
76. Minimum Window Substring
)- because this problem rquries keeping the order of characters from the target string
- Use a
backtrack mechanism
based on target matching to find closest left starting point to right
- Simple two pointers:
-
- move sIndex and tIndex: find all T chars in S, in order.
-
- backtrack tIndex to 0; backtrack sIndex to initial char match
-
- record potential min result
-
- Be VERY careful about pointer and index.
- time: O(n^2), backtrack n steps
- Since it requires order of substring,
freqMap+counter+twoPointers
approach is NOT applicable - TODO
- 前后两个指针, 在while loop里面跑.
- 注意 i<j, 一旦相遇, 就break.
- 找到合适的, 就做swap.
- StringBuffer可以 sb.setCharAt()记得用.
- O(n)
- 找到B在A中的起始位置, 然后看一下从这个点开始的substring是否等于B就可以了
- 还挺多坑的, 这些可以帮助优化:
-
- 当B是“”的时候,也就是能在A的其实位置找到B....index = 0.
-
- edge condition: 如果 haystack.length() < needle.length() 的话, 必须错, return -1
-
- 如果在某个index, A后面剩下的长度, 比B的长度短, 也是误解, return -1
- compare left/right
- flip number (6 and 9), and then reverse the string, see if the string is the same.
- takes more
- A够长,那么可以从A的尾部开始加新元素: 从尾部,是大数字优先排末尾的.
- Deal with remaining:
- When A values are used up, put remian of B into it
- When B values are finished, there is nothing todo. The remain of A is already in place.
- Use 2 pointers to record most recent pos of word1 and word2
- move pointer i [0 ~ n) and keep refreshing pos1 and pos2
- both pos1 and pos2 will be as adjacent as possible since they both moving towards same direction
- keep recalculating best distance when either word is matched
- 而同一时间,只需要计算一个最近的curr distance: greedy不断变更A/B index, 做比较
- Palindrome概念很简单: 两边回溯相等. However:
-
- cannot random access index on linkded list
-
- cannot reverse iterating linked list
-
- solution: reverse linked list: 遍历接开头
-
- 用快慢指正找到mid point
-
- reverse 2nd half
-
- compare leftList and rightList
-
- Time O(n), 而且不需要用额外的空间(只是调换半个list的内部顺序), 所以空间O(1)
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- Method1 and Method2 are identical to
159. Longest Substring with At Most Two Distinct Characters
. - However, time complexity for Method2 in increases to O(nk). https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- we want to do better than that (Method3)
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == k, process and record max len
-
- if map.size() > k, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(k)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(nk) to find the left-most element
- space: O(k)
- https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- as mentioned above, Method2 uses O(nk), because it takes O(k) to find head item that was inserted first
- meanwhile, we still need the hash map feature to get/put/remove last occurance of a char with O(1)
- Solution: use a LinkedHashMap:
map.entrySet().iterator()
maintains the insertion order!
- Special handling:
- since we want the
lastOccurMap
to preserve laset insertion order - we need to
remove
the char every time before put.
- since we want the
- time: O(n)
- space: O(k)
- https://leetcode.com/problems/max-consecutive-ones-iii/solution/
- Start with DFS thought, but realize redundant calculations:
- we never need to flip 2 indexes [A], [C] from 0 -> 1, if there is a [B] in middle that is 0 too
- the flipped k zeroes must be consecutive too
- we can utilize two pointers to establish a window that captures k zeroes
- always expend right pointer; if seeing an zero, k--
- note:
len = right - left + 1
is the ongoing max length
- note:
- when k < 0 (too many zeros), we need to slide the left side of the window to make sure:
- keep window len
- potentially do k++ when A[left]==0
- always expend right pointer; if seeing an zero, k--
- goal: matain a max size of the window, until right == n
- return (right - left). at this moment, right == n, so no need to (right - left + 1)
- Outside pointer that moves in certain condition.
- Save appropirate elements
- Time O(n), Space O(1).
- 普通方法, 两边check, 速度相比较regular expression更快. leetcode 4ms.
- Use helper functions.
- 前后两个指针, 往中间移动, 查看是否字母重合
- 可以用 ASCII code 来手动过滤, 只要 '0' ~ '9', 'a' ~ 'z', 'A' - 'Z' 之间的
- 也可以用 regular expression: match 所有这些字母, 是 [a-zA-Z0-9]
- 那凡是不是这些字母的 match, 就是取反: "[^a-zA-Z0-9]". 测试: https://regex101.com/
- Map of nums1: <num, # appearance>
- check nums2 against nums1 map
- time:O(n + m)
- space:O(n + m)
- A creative way of using anagram char count
hash[c] >= 0
to determine if the curr c is a target char of the deesired anagram.- because we always reduce hash[c]-- for all characters
- so only the anagram chars would be
hash[c] >= 0
after reducing.
- https://leetcode.com/problems/minimum-window-substring/discuss/26808/here-is-a-10-line-template-that-can-solve-most-substring-problems
- Slinding window always has left/right pointer:
-
- at any given time move 1 index at a time: expand right window, process rsult, shrink left window
-
- one of the basic goal is to maintain fixed window size
-
- algo:
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
hash[c] >= 0
indicates a anagram char - expand right window: move right to expand the window; ONLY when meeting a anagram char, count--
- process result: if count reduces to 0, one anagram is found
- shrink left window: if (right - left) == p.length(), drop curr left char, and move forward
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
- how could we rely on only just
count == 0
?- the hidden pre-condition is
right - left must already be p.length()
, which is validaterd in prev iteration
- the hidden pre-condition is
- time: O(n)
- space: O(1)
- count character apperance -> hash table, here just a int[26]
- use a window to record count++ and count--, in order to compare with countP
- prep the countP takes O(m) time
- time: O(n) + O(m)
- space: O(n)
- First sort all of the items together by actual val using
Node {int val, int row}
- Slinding window goal:
-
- use right to find range that touches all rows,
-
- use left to shrink the range
-
- Sliding Window Template
- move right pointer
- Counts[i] = # of elements used in left/right range
- when counts[i] == 0, countUnique++; the number of row/list being included
- when count == row size:
- processing & save shorter range by using left/right Pointers
- move left pointer; when counts[i] == 0, countUnique--
- time: O(nlogn) for initial sort and then O(n) to process
- space: O(n)
- What is hard here? To think of the idea of counting one usage of each row:
- when each all rows are used at least 1 time
- calculate the min dist
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104893/Java-Code-using-PriorityQueue.-similar-to-merge-k-array
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/solution/
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == 2, process and record max len
-
- if map.size() > 2, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(1)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(n)
- space: O(1)
- Sort nums: O(nlogn)
- Set max value fixed on right side at k
- set 2nd value from right index j
- set last value at min index i
- if
nums[i] + nums[j] > nums[k]
: with fixed j, i can pick from [i, j-1] combinations- then j--, to pick another j candidate
- maintain a window [i,j]; if invalid, move i++
- time: O(n^2)
- Note: very similar to 3-sum, fixing 1 index and use 2 pointers to move window
- Sort nums: O(nlogn)
- Set min value at i
- set 2nd value at j=i+1; and 3rd value at k=i+2
- find max of k that fits into triangle
- count all possible k candidates from [j+1, k]
- then move j to a new candidate
- O(n^2)
- 主要是题意很难理解, 非常misleading, 等到看明白题目, 其实没有什么算法要求.
- Count duplicates and print
- enumerate all candidates and filter to keep the correct ones
- String.compareTo(string) -> gives lexicographical comparision
- [3,4,7] -> cannot rotate, failures. Must NOT have. set1
- 2,5,6,9 -> good candidates. Must have 1. set2
- [0,1,8] -> goes back to itself. can have
- loop over [1, N], count=int[10] appearance.
- set1 meet 0
- set2 meet at least 1
- start/end point, 然后比较大小记录dist
- 注意1: 如果第一个座位没有人, 特殊处理, dist = [0 ~ end]
- 注意2: �如果最后一个座位没有人, 特殊处理: dist = [n - 1 - start];
- 其余:
dist = Math.max(dist, (end - start) / 2)
- 相关题目: 几乎同样概念
Binary Gap
, 升级复杂版Exam Room
- Use i, j to process from end of 2 strings
- handle edge case for i, j
- if i < 0, its num = 0 (since we are doing sum, blindly setting 0 is okay)
- Note:
sb.insert(0, x)
is much slower than doing a finalsb.reverse()
- when converting to int[], remember to reverse string.
- when converting to int[], remember to reserve extra space for carry
- preserve max
- 清零count
- 注意
list = Arrays.asList(x, y, z ...)
给fixed-size list, 不能直接 list.add(). - Use
new ArrayList<>(Arrays.asList(...))
to wrap it up. - Parse each digit based on rules
-
- parse: analyze the situations
- put 3 elements into array, sort and follow below rules:
- min:
- if 3 elements consecutive, 0 move.
- if only 1 pair of the two elemnets consecutive or if they have 1 slot in between, it needs exactly 1 move
- otherwise, at most 2 moves
- max: # of open slots between them (high - low + 1) - n, where n = 3
- Follow up:
1040. Moving Stones Until Consecutive
is more interesting with special rulese (cannot move toending spot
), and it uses sliding window concept - when counter != 2 , it will execute hydrogen() two times so that 'H' will reach 2
- when count == 2, it will execute oxygen() once so that 'O' will reach 2
- to start: H is at count 2 and O is at count 0. They need both be at 0 to be unlocked
- hydrogen():
h.acquire()
will execute 2 times until H.count is reduced to 0o.release
will add O.count by 1 for 2 times
- oxygen():
o.acquire(2)
can only occur when O.count == 2 due to the 2 calls inhydrogen(..)
h.release(2)
will restore the H.count back to 2
- semaphore: https://www.geeksforgeeks.org/semaphore-in-java/
- dp[i] 表示: # of ways to fill weight i
- 1维: dp[w]: fill weigth w 有多少种方法. 前面有多少种可能性, 就sum多少个:
- dp[w] = sum{dp[w - nums[i]]}, i = 0~n
- 拼背包时, 可以有重复item, 所以考虑'最后被放入的哪个unique item' 就没有意义了.
- 背包问题, 永远和weight分不开关系.
- 这里很像coin chagne: 考虑最后被放入的东西的value/weigth, 而不考虑是哪个.
- 与背包1不同: 这里不是check可能性(OR)或者最多能装的size是多少; 而是计算有多少种正好fill的可能性.
- dp[i][w]: 用前i本书, 正好fill到 w weight的可能性.
- 对于末尾, 还是两种情况:
-
- i-1位置没有加bag
-
- i-1位置加了bag
- 两种情况可以fill满w的情况加起来, 就是我们要的结果.
- 如常: dp[n + 1][w + 1]
- 重点: dp[0][0] 表示0本书装满weight=0的包, 这里我们必须 dp[0][0] = 1, 给后面的 dp function 做base
- Space, time: O(MN)
- Rolling array, 空间优化, 滚动数组. Space: O(M)
- 分析row(i-1)的规律, 发现所有row(i)的值, 都跟row(i-1)的左边element相关, 而右边element是没用的.
- 所以可以被override.
- Space: O(M), 真*一维啊!
- Time: O(MN)
- 做了Backpack I, 这个就如出一辙, 只不过: dp存的不是max weight, 而是 value的最大值.
- 想法还是,选了A[i - 1] 或者没选A[i - 1]时候不同的value值.
- 时间空间O(mn)
- Rolling Array, 空间O(m)
- 如果无法达到的w, 应该mark as impossible. 一种简单做法是mark as -1 in dp.
- 如果有负数value, 就不能这样, 而是要开一个can[i][w]数组, 也就是backpack I 的原型.
- 这样做似乎要多一些代码, 好像并不是非常需要
- 简单直白的思考 dp[i][m]: 前i本书, 背包大小为M的时候, 最多能装多种的书?
- 注意: 背包问题, 重量weight一定要是一维.
- dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
- 每一步都track 最大值
- 最后return dp[n][m]
- 时间空间 O(mn)
- Rolling array, 空间O(m)
- true/false求解, 稍微曲线救国: 重点是, 最后, 按照weight从大到小遍历, 第一个遇到true的, index就是最大值.
- 考虑: 用i个item (可跳过地取), 是否能装到weight w?
- 需要从'可能性'的角度考虑, 不要搞成单一的最大值问题.
-
- 背包可装的物品大小和总承重有关.
-
- 不要去找dp[i]前i个物品的最大总重, 找的不是这个. dp[i]及时找到可放的最大sum, 但是i+1可能有更好的值, 把dp[i+1]变得更大更合适.
- boolean[][] dp[i][j]表示: 有前i个item, 用他们可否组成size为j的背包? true/false.
- (反过来考虑了,不是想是否超过size j, 而是考虑是否能拼出exact size == j)
- 注意: 虽然dp里面一直存在i的位置, 实际上考虑的是在i位置的时候, 看前i-1个item.
-
- picked A[i-1]: 就是A[i-1]被用过, weight j 应该减去A[i-1]. 那么dp[i][j]就取决于dp[i-1][j-A[i-1]]的结果.
-
- did not pick A[i-1]: 那就是说, 没用过A[i-1], 那么dp[i][j]就取决于上一行d[i-1][j]
- dp[i][j] = dp[i - 1][j] || dp[i - 1][j - A[i - 1]]
- 跑一遍dp 最下面一个row. 从末尾开始找, 最末尾的一个j (能让dp[i][j] == true)的, 就是最多能装的大小 :)
- 时间,空间都是:O(mn)
- 可以无限使用物品, 就失去了last i, last unique item的意义: 因为可以重复使用.
- 所以可以转换一个角度:
-
- 用i 种 物品, 拼出w, 并且满足题目条件(max value). 这里因为item i可以无限次使用, 所以考虑使用了多少次K.
-
- K虽然可以无限, 但是也被 k*A[i]所限制: 最大不能超过背包大小.
- dp[i][w]: 前i种物品, fill weight w 的背包, 最大价值是多少.
- dp[i][w] = max {dp[i - 1][w - k*A[i-1]] + kV[i-1]}, k >= 0
- Time O(nmk)
- 如果k = 0 或者 1, 其实就是 Backpack II: 拿或者不拿
- 优化时间复杂度, 画图发现:
- 所计算的 (dp[i - 1][j - k*A[i - 1]] + k * V[i - 1])
- 其实跟同一行的 dp[i][j-A[i-1]] 那个格子, 就多出了 V[i-1]
- 所以没必要每次都 loop over k times
- 简化: dp[i][j] 其中一个可能就是: dp[i][j - A[i - 1]] + V[i - 1]
- Time O(mn)
- 根据上一个优化的情况, 画出 2 rows 网格
- 发现 dp[i][j] 取决于: 1. dp[i - 1][j], 2. dp[i][j - A[i - 1]]
- 其中: dp[i - 1][j] 是上一轮 (i-1) 的结算结果, 一定是已经算好, ready to be used 的
- 然而, 当我们 i++,j++ 之后, 在之前 row = i - 1, col < j的格子, 全部不需要.
- 降维简化: 只需要留着 weigth 这个 dimension, 而i这个dimension 可以省略:
- (i - 1) row 不过是需要用到之前算出的旧value: 每一轮, j = [0 ~ m], 那么dp[j]本身就有记录旧值的功能.
- 变成1个一位数组
- 降维优化的重点: 看双行的左右计算方向
- Time(mn). Space(m)
- 计数问题, 可以想到DP. 其实就是Backpack VI.
- 从x个数字里面找candidate(可以重复用同一个数字), 来sum up to target. 找: # of ways to form the sequence.
- Backpack VI: 给一个数组nums, 全正数, 无重复数字; 找: # of 拼出m的方法
- dp[i]: # of ways to build up to target i
- consider last step: 如果上一步取的是 candidate A, 那么就该加到dp[i]:
- dp[i] += dp[i - A]
- 要找overall dp[i], 就做一个for loop: dp[i] = sum{dp[i - num]}, where for (num: nums)
- Time: O(mn). m = size of nums, n = target
- If we optimize dp for loop, 需要Sort nums. O(mlogm). will efficient 如果m是constant或者relatively small. Overall: O(n)
- 尽管思考方式是对的, 但是 times out
- 可以重复使用数字的时候, 比如用1 来拼出 999, 这里用1就可以走999 dfs level, 不efficient
- O(MN): M, total target amount; N: size of coins
- 类似于: 网格dp, unique path 里面的2种走法: 从上到下, 从左到右
- 状态: dp[i]: sum of ways that coins can add up to i.
- Function: dp[j] += dp[j - coins[i]];
- Init: dp[0] = 1 for ease of calculation; other dp[i] = 0 by default
- note: 避免重复count, 所以 j = coins[i] as start
- 注意 coins 需要放在for loop 外面, 主导换coin的流程, 每个coin可以用无数次, 所以在每一个sum value上都尝试用一次每个coin
- define dp[x], 积累到amount x, 最少用多少个coin
- function:
dp[x] = Math.min(dp[x], dp[x - coinValue] + 1)
. two branches based on choosing coinValue or not - initialization
- dp[0], zero amount uses 0 coin. so dp[0] = 0
- Utilize
Integer.MAX_VALUE
as default val for initialize dp[x]: 1) alert error stage; 2) easy comparison
- create subproblem: (coins, amount - pickedCoin)
- memo[i] 依然表示: min # of coints to make amount i
- initialize memo[i] = Integer.MAX_VALUE
- 先选最后一步(遍历coins), 然后dfs做同样的操作
- 记录memo[amount] 如果已经给过value, 不要重复计算, 直接return.
- time: O(n * S), worst case it runs n coins for S(amount) iterations
- space: O(S)
- New interface to understand: object contains integer or object
- Visit all && sum, consider dfs.
- 简单的处理nested structure, dfs增加depth.
- time: visit all nodes eventually, O(n), space O(n)
- bfs, queue, 处理queue.size() for a level
- use a level variable to track levels
- slower since it uses extra space, worst case O(n) of all items
- Build a list of NestedInt
- DFS:
- sum up integers in the list are integers
- dfs on nested list
- overallSum = sum * (depth+1)
- End state: if no nested list (no more child dfs), return depth 1
- Parent level: sum up all ints and times the (depth+1)
- Using stack to flatten all nestedList, and process in the end
- Can actually use list, does not need to be stack.
- uses more memory
- Option1: always set integer on top of the stack everywhere
- if not, poping stack until the top is integer
- code is easy
- Option2: in hasNext(), faltten the list in stack
- 用queue to store all items. Kinda hack. Defeat the purpose of the problem.
- Super fast to query next(), however, needs to holds everything in memory
- O(n)
- Median还是用min-heap 和 max-heap. Time(logN)
- 加/减: prioirtyQueue, log(n)
- findMedian: O(1)
- 加一个数, 减一个数。
- 加减时看好,是从前面的maxheap里面抽,还是从后面的minHeap里面抽。
- 抽完balance一下
- 用maxHeap, minHeap时候, 习惯选择让maxHeap多一个数字:
- 左边的maxHeap总有 x+1或者x个数字
- 后边minHeap应该一直有x个数字
- Use HashMap to store <num, freq>
- Bucket
List<Integer>[]
: stores <count, list unique element with that count>- Size of the data structure will be uniqe item size.
- The bucket[i] stores item at frequency i
- Simply loop from bucket.length -> 0, when bucket[i] not null, add to result.
- Solid O(n)
- Use regualr priorityQueue to sort by frequency ascendingly
- the queue.peek() record has lowest frequency, which is replacable
- Always only maintain k elements in the queue, so sorting is O(logk)
- IMPORTANT: remember to
rst.add(0, x)
for desired ordering - time faster than maxHeap: O(nlogk)
- option1: just use
map<num, freq>
; option2: useclass Record {int num; int freq}
- 题目有提醒: 必须beetter than O(nLog(n)).
- max heap approach stores all nodes: it is wrong
- even though freq count size m < n, but it can be m == n. ALL unique.
- then it is O(nlogN) again.
- therefore, storing all items into pq is INCORRECT.
- 把Input stream想成向上的山坡. 山坡中间那点,自然就是median.
- 前半段,作为maxHeap,关注点是PriorityQueue的峰点,也就是实际上的median.
- 后半段,作为minHeap,正常的PriorityQueue。 开头是最小的。
- 这里要首先定好, 哪一个queue是多存一个element的. 这里选maxHeap: maxHeap.size() == minHeap.size() + 1 || minHeap.size()
- 必须先维护maxHeap里面有个元素, 否则null了会在比较大小时出问题.
-
- Build frequency map, 2) use frequency map to build freq bucket
- Loop from largest bucket freq -> 0, and output.
- Time: Solid O(n)
- Space: O(n)
- O(n) space of map, O(nlogk) to build queue.
- limit minHeap queue size to k: add to queue if found suitable item; always reduce queue if size > k
- 用HashMap存frequency, 用ArrayList存lists of words
- create一个Node class, 然后用PriorityQueue.
- PriorityQueue里面用到了 String.compareTo(another String).巧妙。
- time: PQ uses O(nlogn), overall O(nlogn)
- slower, because the maxHeap needs to add all candidates
- 用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
- 注意排序时Collection.sort()的cost是O(nLogk)
- not efficient
- This approach follows the pattern of finding min pair:
-
- only need to store k pairs
-
- always start from min of A list and min of B list
-
- pre-build k pairs honoring A list, and then pick the min pair, and start swapping with min of list B
-
- First attemp all first k pairs from nums1[i] against nums2[0] <=k : O(k)
- Use queue to pull min node and save results
- Use the nums1 val from the min node, pair up with nums2[j], add back to queue to sort
- overall runtime: O(klogk)
- space: O(k)
- Brutle: build all pairs time O(mn), sort with maxHeap pq with k size, and find top k
- overall time: O(mnLogK)
- space: O(k)
- a^b 显示出bit format里面有不同binary code的数位.
- 每次 (a^b)>>i 移动i位之后, 再 & 1时其实是指留下这一位的数字.
- count
- 其实用到了 ^ 找不同的bit, >> 移位, &1 mask
- 跟First Missing Positive 非常像, 只有一行代码的区别.
- swap 所有的数字, 到自己的correct position
- 最后一个for loop找到错位的index, 也就是缺的数字.
- XOR will only retain bits that are different 1 ^ 0 = 1, but 0^0, 1^1 == 0
- Use that feature, 把所有value都和index XOR了
- 剩下的多余的数字, 其实是那个index无法被XOR消掉, 也就是那个缺的number value.
- 注意: 题目告诉数字是 [0 ~ n], 然而缺一个数字, 那么在[0 ~ n - 1] 里面, 最大的数字(不管缺没缺), 一定是 n = nums.length.
- 全存, 找missing
- O(n) space, 不合题意
- sort, 找1st missing
- O(n log n) 太慢, 不合题意
- Bit题: 考验 bit >>, mask & 1, 还有对题目的理解能力
- Put integers in binary, and compare each column:
- for each
1
, ask: how many are different from me? all the0
# of diffs at each bit-column = #ofZero * #ofOne
-
- countZero[], countOne[]; 2. loop over nums and populate the two array
- 问清楚: 10^9 < 2^31, we are okay with 32 bits
- �
最终的hamming distance �要从 [1 ~ 32] 哪个bit开始算起
? 取决于最长
的那个binary format: 但不用先去找bit length - 在做countZero, countOne时候, 都做32-bit; 最终做乘积的时候, 如果
1
或者0
个数为零, 乘积自然为0. - shift >> i
- apply mask & 1
- 首先要分两半解决,断点是'.': str.split("\.");
- Integer那一半好弄,whie loop里: num%2, num/2. 做一个
parseInteger()
function - Decimal那边复杂点. 做一个
parseDecimal()
function: - bit == 1的数学条件: 当下num * 2 >= 1。 更新: num = num * 2 - 1;
- bit == 0的数学条件: num * 2 < 1. 更新: num = num * 2
- num是 double, 小数在
num = num * 2 - 1
的公式下可能无限循环 - 因此check: num重复性,以及binary code < 32 bit.
- 所以题目也才有了32BIT的要求!
- 理解Binary Gap的描述
- 简单的
>>
,&1
, track start and end point 就好了 - ~0 = -1 = 111111...11111111 (32-bit)
- Create mask by shifting right >>>, and shifting left
- Reverse to get 0000...11110000 format mask
- & 0000 = clean up; | ABC = assign ABC
- Hex 0xaaaaaaaa 是1010101....1010; 0x55555555 是01010101....0101
- 可以用这两个hex取单数和负数. 如果需要取其他的pattern, 也可以做.
- x很可能是negative number, 所以right-shift 要用logic shift, >>> 避免leading负数补位.
- 对于每一个数字, 其实很简单就能算出来: 每次 >>1, 然后 & 1 就可以count 1s. Time: 一个数字可以 >>1 O(logN) 次
- 现在要对[0 ~ num] 都计算, 也就是N个数字, 时间复杂度: O(nLogN).
- 用DP来优化, 查找过的number的1s count, 存下来在 dp[number]里面.
- 计算你顺序从 0 -> num, count过的数字就可以重复利用.
- Bit题目 用num的数值本身表示DP的状态.
- 这里, dp[i] 并不是和 dp[i-1]有逻辑关系; 而是dp[i] 和dp[i>>1], 从binary representation看出有直接关系.
-
枚举可能的A, 2. 然后一个个猜.
-
枚举A: 因为求MAX肯定是找leading-1最多的数字, 那么枚举A从(1000000...000)开始, 每次多一位取1或者0
-
因为枚举A的时候是按照每个bit来, 那么B和C也要以同样数位出现. 这里吧B和C变成了prefix的形式, 放在了set里面. 跟2sum用hashmap的思想类似, 每次用枚举的 A^B=C, 看看结果C是否已经在set里面. 如果在, 证明枚举的A可能被B^C得出, 那么就找到了一种情况.
- Move pointer: move digit after process 4 bits.
>>>
Unsigned right shift- always fills 0 irrespective of the sign of the number
- Mas:
num & 0xf
=num & 15
- Time, Space: O(n)
- 前提: input必须valid, 比较罕见少用. Moore Voting Algorithm: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
- 与当下candidate相同, vote++. 与之不同, vote--.
- Majority Number是指超半数, 多1个就行: 消减至最后, 会至少有vote>=1.
- 那么: vote++, vote--到最后剩下的就是winner.
- 这个方法比较greedy, 前提是: valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。
- time: O(n), space O(1)
- sort entire nums array
- assume there is a solution, then nums[n/2] must be that majority num
- time O(nlogn)
- Recursive approach
- For ange rangeA & rangeB, rangeA has majorElementA and rangeB has majorElementB
- majorElementA = majorElementB, of course this element will be the major number for whole range
- if majorElementA != majorElementB, then need to count both elements in whole range
- of course the larger occurance will be the major num
- TODO
- Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。
- Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n)
, 3ms - space: O(2^n) results
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- BFS, 注意考虑如果让one level to generate next level
-
- maintain a list of Indexe to store candidate indexes.
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
-
- should be same O(2^n), but actual run time 7ms, slower
- O(n) space
- 跟
kth largest element in an Array
的 template一样. - quickSelect 可以找到 kth 最小的元素
- 利用这个原理, 找这个kth最小值, 然后如果 == target index, 就找到了我们的median
- 主要步骤:
-
- partition
-
- check end state
pivot index ?= target index
- check end state
-
- recursive call one part of the array
-
- time: 与quickSort不同在于, 每次只要在一半list里面recurring, 所以把O(logn)的时间复杂度降到O(n)
- n + n/2 + n/4 + n/8 + ....+ 1 = O(2n) = O(n)
- space: O(logn), based on recursive stacks
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- 根据给定的性质, 其实点选的极端一点: x = 最下面的row, y = 当下一行里面最小的left position.
- (x,y)在左下角
- 在此情况下, 只能往一个方向运行: 如果小于target, y++; 如果大于target, 那么只能x--
- 每次操作, 都是删掉一行, 或者一列, 再也不需要回头看
while (x >= 0 && y < col) {}
确保不会跑脱- 同样的方式: 可以从右上角(0, col - 1) 开始, 代码稍微改一改
- TODO
- a^n可以被拆解成(aaa*a....*a), 是乘机形式,而%是可以把每一项都mod一下的。所以就拆开来take mod.
- 这里用个二分的方法,recursively二分下去,直到n/2为0或者1,然后分别对待.
- 注意1: 二分后要conquer,乘积可能大于Integer.MAX_VALUE, 所以用个long.
- 注意2: 要处理n%2==1的情况,二分时候自动省掉了一份 a,要乘一下。
- SegtmentTree, methods: Build, Query. 这题是在SegmentTreeNode里面存min.
- 类似的有存:max, sum, min
- Similar to Binary Tree Longest Consecutive Sequence I
- 只不过可以递增递减, 还有连接上parent的方向.
- 对于任何一个节点, 都可能:
-
- 自己跟两个child链接, 成为一个sequence
-
- 左边孩子, 右边孩子各自是一个consecutive sequence, 但是不跟root相连
- main function 一开始就divide成这三份, 然后dfs
- dfs take diff == 1, diff == -1, 来做递增递减的校对.
- dfs rules:
-
- if node == null, leaf depth = 0
-
- if not consecutive, reset the depth = 0 (same for both left child, and right child)
-
- compare the leftDepth && rightDepth to find the maximum
-
- diff is the same in the same dfs loop to maintain consistant increase/decrease
- dfs的结果很可能是0, 如果没有任何结果, 那么上一层的caller depth = dfs() + 1 = 1
- 那么回归到root, dfs的结果很可能就是1.
- 可能会问: 那么在tree里面的partial sequence (不连接到root)的被忽略了?
- 这里
longestConsecutive(root.left)
就很重要了 - 这一步特地忽略掉了root, 然后走下去一层: 因为是recursive, 所以还会继续divde && conquer
- 最后, 任何一层的孩子都会被照顾到.
- 用两种recursive的方式handle skip root node的情况
- Recursive using dfs(), basically build child + parent
- Recursive using main function, but with value of child node: skipping root
- Traverse tree: left, right
- Concept of partial compare vs. whole compare
- Process inner item first: last come, first serve, use stack.
- Record number globally and only use it when '[' is met.
- Stack存 [ ] 里面的内容, detect 括号开头结尾: 结尾时process inner string
- 有很多需要注意的细节才能做对:
- Stack 也可以用, 每个地方要注意 cast. 存进去的需要是Object: String, Integer
- 几个 type check: instanceof String, Character.isDigit(x), Integer.valueOf(int num)
- 出结果时候:
sb.insert(0, stack.pop())
- Bottom->up: find deepest inner string first and expand from inside of
[ ]
- 与Stack时需要考虑的一些function类似. 特别之处: 检查
[ ]
的结尾 - 因为DFS时候, 括号里的substring会被保留着进入下一个level, 所以我们在base level要keep track of substring.
- 用int paren 来track 括号的开合, 当paren再次==0的时候 找到closure ']'
- 其他时候, 都要继续 append to substring
- 看到log(m+n), 就想到binary search, 或者是recursive 每次砍一半
- 两个sorted array 参差不齐, 肯定不能做简单的binary search
- 这里有个数学排除思想: 考虑A, B各自的中间点.
- 如果A[mid] < B[mid], 那么 A[0 ~ mid - 1] 就不在 median的range里面, 可以排除. divide/conquer就这么来的.
- 具体逻辑看代码, 大致意思就是: 每次都取比较A 和 B [x + k / 2 - 1] 的位置, 然后做range 排除法
- end cases:
-
- 如果我们发现dfs()里面A或者B的start index溢出了, 那么就是最简单的case: midian一定在另外那个array里面
-
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
Math.max(A[startA], B[startB])
就可以
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
- 总共的数字长度是 (m + n) 而且每次都有一般的内容被删除, 那么time就是 O(log(m + n))
- 写个Inorder和Postorder的例子。利用他们分left/right subtree的规律解题。
- Postorder array 的末尾, 就是当下层的root.
- 在Inorder array 里面找到这个root,就刚好把左右两边分割成left/right tree。
- 这题比较tricky地用了一个helper做recursive。 特别要注意处理index的变化, precisely考虑开头结尾
- runtime: O(n), visit && build all nodes
findMid(arr)
can be replaced with a map<value, index>, no need execute O(n) search at runtime-
- find middle. 快慢指针
-
- Sort: 切开两半,先sort前半, 如果先sort了mid.next~end, sort后,中间点mid.next == null,再sort前半段
-
- Merge: 假设given list A, B 已经是sorted, 然后按照大小,混合。
- 要recursively call sortList() on partial list.
- 想做可以看讲义:http://www.jiuzhang.com/solutions/sort-list/
- 但是quick sort不建议用在list上面。
- 排列list, merge sort可能更可行和合理。原因分析在下面, 以及: http://www.geeksforgeeks.org/why-quick-sort-preferred-for-arrays-and-merge-sort-for-linked-lists/
- Binary Search Tree特点: 左边的node都比右边的node小.
- height balance, subtree height 相差<1, 必须左右sub tree均分. 做DFS(num, start, end)
- 在每一个level, 找到中间点, 然后分割2半, 继续dfs
- Divide and Conquer
- time/space: O(n), visit all nodes, no redundant visits.
- 题目要求DFS. 想清楚了如何在DFS level把几种情况都考虑了, 写起来很简单. NOT BFS, because requires O(1) space
- 对于一个root来说, 只有几个点可以顾忌到: root.left, root.right, root.next.
- 想办法把这三个方向的点, 能连起来的都连起来:
-
node.left.next = node.right
-
- If
node.next != null
, linknode.right.next = node.next.left
;
- If
- 然后在dfs(root.left), dfs(root.right)
- Time: visit && connect all nodes, O(n)
- 不和题意,用了queue space,与Input成正比。太大。
- BFS over Tree。 用Queue 和 queue.size(),老规矩。
- process每层queue时, 注意把next pointer加上去就好.
- 我们不可能一口气准确定位(x,y), 但是我们可以再一个row/col里面, 找到1D array的 peak.
- 根据这个点, 再往剩下两个方向移动
-
- 在中间的一行i=midX, 找到peak所在的y.
-
- 在中间的一列j=midY, 找到peak所在的x. (有可能强势override之前找到的y, 也就是放弃那一行的peak, 在midY上找peak)
-
- 根据 (x,y) 的4个neighbor check (x,y)是不是 peak, 如果不是, 像更高的位置移动一格
-
- 根据之前算的 midX, midY 把board分成4个象限, 在每一份里面再继续找
- 这个题目LintCode不给做了, 所以思路对的, 但是解答还没有再次验证.
- 每次只是找到一个row/col里面的peak而已!
- 找到这个点, 就等于把board切成了两半.
- 然后, 再跟剩下的相邻的两个位置比较, 就知道了哪里更大, 就去哪里找peak, 也就是又切了第二刀.
- 切第二刀的时候, 也要把(x, y) 移到需要取的象限. 进行DFS
- 根据mid row 切割:
- http://www.jiuzhang.com/solution/find-peak-element-ii/#tag-highlight-lang-java
- http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf
- 每一个level都减一半
- T(n) = n + T(n/2) = n + n/2 + n/4 + ... + 1 = n(1 + 1/2 + .... + 1/n) = 2n = O(n)
- TODO
- O(nLogN)
- Recursively 在segment tree里面找index, update it with value.
- 每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下
- 最后轮回到头顶,头顶一下包括头顶,就全部都是max了
- 根据BST规则, divide and conquer
- 取一个value, 然后分两半(start, value - 1), (value + 1, end) 分别dfs
- 然后左右两边的结果cross match
- 跟string相关, 写起来可能稍微繁琐一点
- 数字有 dfs([1,2,3...]) 组合方法
- operator有[
+
,-
,*
] 3种组合方法
- 注意1: 乘号要特殊处理, pass along 连乘的数字, 计算下一步乘积的时候, 要 sum - preProduct + product
- 注意2: '01' 这种数字要skip
- 注意3: 第一个选中数字不需要加操作符, 直接加进去
- Time: O(4^n), Space: O(4^n)
- T(n) = 3 * T(n-1) + 3 * T(n-2) + 3 * T(n-3) + ... + 3 *T(1);
- T(n-1) = 3 * T(n-2) + 3 * T(n-3) + ... 3 * T(1);
- Thus T(n) = 4T(n-1) = 4^2 * T(n - 1) = .... O(4^n)
- 逻辑一样, 代码更短, 只不过不做list, 直接pass
buffer + "+" + curr
- 因为每次都创建新string, 所以速度稍微慢一点. Time complexity 一样
- 和 Segment Tree Query I 以及其他Segment Tree类似: 这个SegmentTreeNode return count of elements in range
- 这个题目考了validate input source:input 的start,end可能超出root[start,end]。
- 那么第一步就要先clear一下: 1. 完全不在range就return 0. 2. 有range重合就规整到root的range.
- Divide and Conquer
- 找到mid node
- 然后分割两半, 分别dfs做各自两个subtree: node.left,node.right
- 用长度来定位mid, 每次找中间点做root, 然后前半段, 后半段分别dfs with length.
- 用快慢pointer 找到mid. Better: 不用traverse entire linked list
- slowPointer = node;
- fastPointer = node.next;
- 然后把root = mid.next
- 然后开始sortedListToBST(mid.next.next); //后半段
- mid.next = null;//非常重要,要把后面拍过序的断掉
- sortedListToBST(head); //从头开始的前半段
- 最后root.left, root.right merge一下。
- 因为数组规律会变, 所以很难找'第一个burst的球'. 反之, 想哪一个是最后burst?
- 最后burst的那个变成一堵墙: 分开两边, 分开考虑, 加法原理; 最后再把中间的加上.
- dp[i][j] represent max value on range [i, j)
- Need to calculate dp[i][j] incrementally, starting from range size == 3 ---> n
- Use k to divide the range [i, j) and conquer each side.
- 中间劈开
- 砍断首或尾
- Range区间作为iteration的根本
- use pi[i][j] and print recursively.
- Print k, using pi[i][j]: max value taken at k
- 其实会做之后挺好想的一个DP
- dp[i][j] = balloons i~j 之间的 max.
- 然后找哪个点开始burst? 设为x。
- For loop 所有的点作为x, 去burst。
- 每次burst都切成了三份:左边可以recusive 求左边剩下的部分的最大值 + 中间3项相乘 + 右边递归下去求最大值。
- Note: 这个是Memoization, 而不纯是DP
- 因为recursive了,其实还是搜索,但是memorize了求过的值,节省了Processing
- Need to maintain k large elements, where the smallest will be compared and dropped if applicable:
- Maintain k elements with min value: consider using minHeap
- add k base elements first
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分
- sort结束后是ascending的, 那么 n - k 就是第k大.
- partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
- 没找到继续partion recursively.
- sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)
- Steps:
- 每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
- 找到一个low>pivot, high<pivot, 也就可以swap了。
- 得到的low就是当下的partion point了
- Overall O(nlogN), average O(n) for this problem.
- �这个node的subtree涵盖最深level的所有leaves.
- 这个node必须是能找到的最deep那个 � 条件2的需求是因为: root本身就是满足条件1的node, 还有很多Higher-level node也是如此, 所以要找那个deepest.
- 分析题目, 思想是: 看到tree里面所有的leaves, 找到他们最deep的 common ancestor
- Maintain a map <Node, maxChildDepth>
- Recursively dfs: return deepest node that has all leaves by these comparisons:
-
- If left,right child same depth, return root: they need common ancestor
-
- If not same depth, return the one with larger depth
- 被传送去上一个level的, 永远都是subtree里面符合题意的: the node containing all leaf nodes
- Visit all nodes once O(n), space O(n)
- Find all leaves at deepest level
- Use map to track each node-parent
- Backtrack all nodes to find common ancestor
- Divide and Conquer. dfs
- 分开 看左边/右边
- 如果左边满足连续递增的规则, dfs (depth + 1), 如果不满足规则, dfs(depth = 1)
- 右边也是一样
- 对结果跟max作比较, return
-
给的是Array. 注意找区间内的max, assign给区间. 其余和普通的segment tree build一样
-
注意, segment tree是根据array index range 排位: 根据index in [0, array.length - 1]割开区间, break到底
-
最终start==end做结尾
-
这道题要trackmax, 那么在leaf node assign max=A[start] or A[end]
-
往上,parent一层的max:就是比较左右孩子,其实都是在两个sub-tree里面比较sub-tree的max。
-
Devide and Conquer
-
先分,找到left/right,比较max,在create current node,再append到当前node上面。
-
实际上是depth-first, 自底向上建立起的。
- 根据[start,end]跟 mid of (root.start, root.end) 做比较:
-
- [start,end] on LEFT of mid
-
- [start, end] on RIGHT of mid
-
- [start, end] includes mid: break into 2 queries
- query [leftNode, start, node.left.end]
- query [rightNode, node.right.start, end]
-
- Usage
- which of these intervals contain a given point
- which of these points are in a given interval
- Recursively build the binary tree
- 左孩子:(A.left, (A.left+A.rigth)/2)
- 右孩子:((A.left+A.rigth)/2+1, A.right)
- https://leetcode.com/problems/count-of-range-sum/discuss/77990/Share-my-solution
-
- build preSum[n+1]: then sum range [i,j]= preSum[j+1] - preSum[i]
-
- Divide and Conquer:
- 先考虑[start, mid] range里的 ran sum result
- 再考虑[mid, end] range里面的结果
- 最后考虑[low, high]总体的结果
- NOTE: should write merge() function, but that is minor, just use
Arrays.sort(nums, start, end)
, OJ passed - Every mergeSort() has a for loop => O(n log n)
- 如何 count range?
- 这里比较特别的一个做法: 找一个 [low, mid]里面的i, mid 之后的preSum作比较 (解释源自: https://blog.csdn.net/qq508618087/article/details/51435944)
- 即在右边数组找到两个边界, 设为
m, n
, - 其中m是在右边数组中第一个使得
sum[m] - sum[i] >= lower
的位置, - n是第一个使得
sum[n] - sum[i] > upper
的位置, - 这样
n-m
就是与左边元素i所构成的位于[lower, upper]
范围的区间个数.
- 边界[lower, higher] 在 sorted array 好作比较, 一旦过界, 就可以停止计算, 减少不必要计算.
- 上面这个n,m的做法可行的前提: preSum[]里面前后两个 range[low, mid], [mid, high]已经sorted了
- 也就是说, 在recursively mergeSort()的时候, 真的需要merge sorted 2 partitions
- 也许会问: 能不能sort呢, sort不久打乱了顺序? 对,打乱的是preSum[]的顺序.
- 但是不要紧: 很巧妙的, 分治的时候, 前半段/后半段 都在原顺序保留的情况下 分开process完了, 最后才merge
- 在做m,n 的range的时候, 原理如下, 比如preSum被分成这么两段:
[A,B,C]
,[D,E,F]
- 每一个preSum value
A
在跟 preSum[i] 作比较的时候A - preSum < lower
, 都是单一作比较, 不牵扯到 B, C - 因此,
[A, B, C]
是否保留一开始 preSum的顺序在此时不重要
- 每一个preSum value
- 此时最重要的是,
[A,B,C]
以及排序好, 那么在于lower
boundary 作比较的时候, 一旦过界, 就可以停止计算(减少不必要的计算) - TODO?
- This segment tree approach(https://leetcode.com/problems/count-of-range-sum/discuss/77987/Java-SegmentTree-Solution-36ms)
- does not build segment tree based on given nums index
- it is built on sorted preSum array.
- regular segment tree based on nums array does not work:
- segment tree based on input array is good for: search/query by index
- is NOT good at: given range sum/value, find indexes
- why? segment tree is built based on index division, not by range value division.
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- dfs function: have toDelete set, and a result list
- dive deep into child node FIRST, and test if a removal is needed at bottom of tree
- if remove, add orphan and return null; otherwise, return itself
- time: O(n), visit all nodes
- space: O(logn), height of the tree
- traverse tree and create
map <val, parent>
to fast O(1) removal. O(n) - set root into a rootSet
- after deleting a node A, the children of the node becomes 2 forests root
- children should be marked in rootSet
- also remove node A from rootSet (if appears)
- output: find all root in root set, traverse and output.
- This approach requires a dfs build of parentMap
- it is same amount of efforts to do the regular dfs removal.
- not a good solution
- time: O(n)
- space: O(n)
- Using merge sort concept (NOT merge sort impl).
- One very simply desire: if we want to know # elements between [i, j] such that
nums[i] > 2*nums[j]
, it would be so great if array is sorted!- If sorted, fix index i, keep j++ for all
nums[i]/2.0 > nums[j]
- We CANNOT just sort entire array. WHY? Because it distrupts the value of curr index i, and the restriction is:
find matching elements on right side of curr index i
- BUT, what about just sort
right side of i
, and make sure the subproblem (i+1, end) is solved first?
- If sorted, fix index i, keep j++ for all
- 灵感: use merge sort concept.divide and conquer [i ~ n] into 2 sections:
-
- solve subProblem(start,mid) & subProblem(mid+1, end). sort the sub array so that it can be used recursively at parent level.
-
- solve the curr pblem: for all [i, mid], check against [mid+1, end].
-
- Question1: does it cover all use cases?
- First, subProblem(start,mid) & subProblem(mid+1, end) recursively solves its own range
- Last, the only range is the current level problem check
[i, mid]
against its entire right side range:[mid+1, end]
. DONE. all covered.
- Question2: what it is okay for
subProblem(start,mid) & subProblem(mid+1, end)
partially sort the array?- that is the goal: 1) we want the right side range to be sorted; 2) left range is sorted but it does not matter since we treat [start, mid] as 1 group
- use classic while loop
while(j<=e && nums[i]/2.0 > nums[j])
to count pairs - TODO
- split the array into index-based segment tree, where each element is at leaf
- store min of range: use min to determine if certain range is needed for further query
- query for each element right side range (i + 1, end), where it recursively query&aggregate sub-range if meeting requirement
nums[i] > 2*nums[j]
- only when target > subRange.min * 2: there are possible candidates, query further
- worst case O(n^2) when all tailing elements are meeting requirement.
- TODO
- Build the BST based on node value. It will be not applicable if we search after entire tree is built (our goal is right range), so we need to build right elements, and search/count right after the elements is added
- Worst case is still O(n^2), if all added nodes are meeting requirement
- search(tree, curr / 2.0)
- check each one of them
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
- Create customized Point{} class
- Sort by distance
- Maintain queue size <= K
- ?, select sort?
- What if
the processed list is sorted
, so that I can BinarySeach for curr target?- process from end
- binary search for
index to insert new element
in sorted ascending list - that index = # of smaller numbers; record it for final result
- time: O(nlogn)
- space: O(n)
- Segment Tree functions:
Build
: construct segment tree based on min/max range: at leaf node, update count of numbers in rangemodify(SegmentTreeNode root, int value, int count)
: find leaft at with value, and update count for leaf & all parent nodesquery(SegmentTreeNode root, int start, int end)
: return count # of numbers in range [start, end]
- Very similar to
Count of Smaller Number
, where segment tree is built on actual value!! - IMPORTANT to drop processed number from left-hand-side:
- only find on remaining numbers.
- Utilize
modify(root, target, -1)
to erase element count & update the tree.
- time:
n * log(m)
, where m = Math.abs(max-min). log(m) is used to modify() the leaf element - space: O(m)
Define the positive range
- negative nubmer division
rounds up towards 0
(this is a problem). (i.e.(-2 - 1) / 2 = -1.5 = -1
), which causes range error. - We want the entire segment tree range to be ascending, and we want the mid = (start+end)/2 to round down.
- Solution:
- build entire segment tree based on [min, max], where min must be >= 0.
- we can do this by adding Math.abs(min) onto both min/max, as well as +offset during accessing nums[i]
- negative nubmer division
- https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
- Assume we have a BST, where each node has smallerCount and a val, for any new target, how to find smaller items?
-
- add the # of smaller count to current node
-
- compare:
- if target < node.val, keep searching
countVisit(node.left, target)
- if target > node.val: 1) add currNode.smallerCount, 2) minus node.right.smallertCount (reduce double-counting), 3) plus
countVisit(node.right, target)
- remember to create left/right node before dfs countVisit into the sides.
-
- Time, Space: O(n)
- 前提: input必须valid, 比较罕见少用. Moore Voting Algorithm: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
- 与当下candidate相同, vote++. 与之不同, vote--.
- Majority Number是指超半数, 多1个就行: 消减至最后, 会至少有vote>=1.
- 那么: vote++, vote--到最后剩下的就是winner.
- 这个方法比较greedy, 前提是: valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。
- time: O(n), space O(1)
- sort entire nums array
- assume there is a solution, then nums[n/2] must be that majority num
- time O(nlogn)
- Recursive approach
- For ange rangeA & rangeB, rangeA has majorElementA and rangeB has majorElementB
- majorElementA = majorElementB, of course this element will be the major number for whole range
- if majorElementA != majorElementB, then need to count both elements in whole range
- of course the larger occurance will be the major num
- TODO
- Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。
- Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。
- By Definition, merge sort: divide the list into 2 parts
- recursively merge them together.
- time complexity: O(nlogk) divide by log(k) times, each recursive call can work on n nodes.
- space: O(logk) stacks
- Iterative, PQ来排列所有list的leading node.
- Note: k lists need to be sorted (luckily, already given)
- 时间:n*O(logk), where n = total node number, and PriorityQueue: logk,
- Note:
-
- 不要忘记customized priority需要一个customized new Comparator()
-
- Given list 里面也可能有null node, 不要忘记查.
-
- 如果k很大,一个机器上放不下所有的k list怎么办?
- 如果Merge起来的很长,一个机器上放不下怎么办?
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
- use preorder to find root, one index at a time (global index)
- use the root to divide and conquer inorder int[] to 2 sides;
- root.left = dfs(left); root.right = dfs(right)
- end stage: start == end index, create a node
- can use a map to store inorder <val, index> for O(1) find
- 和Construct from Inorder && Postorder 想法一样。
- 写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。
- 跟Convert Sorted Array to Binary Tree类似, 找到对应的index, 然后:
- node.left = dfs(...), node.right = dfs(...)
- Divide and Conquer
- optimize on finding
mid node
: given value, find mid of inorder:
- optimize on finding
- Instead of searching linearly, just store inorder sequence in
map <value -> index>
, O(1) - IMPORATANT: the mid from inorder sequence will become the main baseline to tell range:
range of subTree = (mid - inStart)
- sapce: O(n), time: O(n) access
- Regular convert BST to sored list: in-order traversal
- Carefully doubly link node head, tail
- traverse all nodes, DFS 好做:
left, curr, right
- Tail:
- Assume head is found for sub tree, then
tail = head.left
- Link
LeftTail <-> Curr Root <-> RightHead
- Link
RightTail <-> LeftHead
- Assume head is found for sub tree, then
- In place:
- 同
Node {val, left, right}
, w/o new doubley linked list class - different from
Convert Binary Search Tree to Sorted Doubly Linked List (extra space)
- 同
- 查看每个parent-child关系:
- leftchild < root < rightChild
- all of left child < curr < all of right child
- 方法: 把root.val 传下来作为 max 或者 min, valid child in (min, max)
- BST 有两个极端: left-most-leaf is the smallest element, and right-most-leaf is largest
- imagine we know the two extreme border: Long.MIN_VALUE, Long.MAX_VALUE
- min/max: long type to meet edge case: node.val = Integer.MAX_VALUE
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- 判断当下的node是否被采用,用一个boolean来表示.
- 如果curr node被采用,那么下面的child一定不能被采用.
- 如果curr node不被采用,那么下面的children有可能被采用,但也可能略过,所以这里用Math.max() 比较一下两种可能有的dfs结果。
- dfs重复计算:每个root都有4种dive in的可能性, 假设level高度是h, 那么时间O(4^(h)), where h = logN, 也就是O(n^2)
- 并不是单纯的DP, 是在发现DFS很费劲后, 想能不能代替一些重复计算?
- 基本思想是dfs解法一致: 取root找最大值, 或者不取root找最大值
- 在root上DFS, 不在dfs进入前分叉; 每一个level按照状态来存相应的值: dp[0] root not picked, dp[1] root picked.
- Optimization: DP里面, 一口气找leftDP[]会dfs到最底层, 然后自下向上做计算
- 这个过程里面, 因为没有在外面给dfs()分叉, 计算就不会重叠, 再也不用回去visit most-left-leaf了, 算过一遍就完事.
- 然而, 普通没有dp的dfs, 在算完visited的情况下的dfs, 还要重新dfs一遍!visited的情况.
- Space O(h), time O(n), 或者说是O(2^h), where h = log(n)
- 不为状态而分叉dfs
- 把不同状态model成dp
- 每一个dfs都return一个based on status的 dp array.
- 等于一次性dfs计算到底, 然后back track, 计算顶部的每一层.
- DP 并不一定要是以n为base的. 也可以是局部的去memorize状态->value.
- dp[i][status]: 在 status=[0,1] 情况下, 前i个 房子拿到的 max rob gain. status=0, 1st house robbed; status=1, 1st house skipped
- 根据dp[i-1]是否被rob来讨论dp[i]: dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i - 1]);
- 特别的是,末尾的last house 和 first house相连. 这里就需要分别讨论两种情况: 第一个房子被搜刮, 或者第一个房子没被搜刮
- be careful with edge case nums = [0], only with 1 element.
- Time,space: O(n)
- 是否搜刮了第一个房子, 分出两个branch, 可以看做两种状态.
- 可以考虑用两个DP array; 也可以加一dp维度, 补充这个状态.
- 连个维度表示的是2种状态(1st house being robbed or not); 这两种状态是平行世界的两种状态, 互不相关.
- 与House Robber I一样, 可以用%2 来操作rolling array, space reduced to O(1)
- 与StockII一样, dp[i]: represents 前i天的最大profit.
- sell 的时候, 才完成了一次transaction, 需要扣fee; 而买入不扣fee.
- model sell on dp[i] day (which depends on dp[i-1]) and each day can be sell/buy => add status to dp[i][status]
- status[0] buy on this day, status[1] sell on this day
- dp[i][0] = Math.max(dp[i-1][0], dp[i - 1][0] - prices[i]);
- dp[i][1] = Math.max(dp[i-1][1], dp[i - 1][1] + prices[i] - fee);
- init: dp[0][0,1] = 0; dp[1][1] = 0; dp[1][0] = - prices;
- return dp[n][1]
- 特点: 上一步可能是swaped也可能是fixed
- 考虑A,B之间的现状:
A[i] > A[i - 1] && B[i] > B[i - 1]
或者A[i] > B[i - 1] && B[i] > A[i - 1]
- 问题: 如何把这个状态变成合理的strick-increasing状态?
A[i] > A[i - 1] && B[i] > B[i - 1]
: 1. 已经合理, 也不动. 2. [i], [i-1] 全部都swapA[i] > B[i - 1] && B[i] > A[i - 1]
, 交错开来, 所以调换[i], 或者[i-1]: 1. 换[i-1]. 2. 换[i]- 注意因为求min, 所以init value应该是 Integer.MAX_VALUE;
- dp[i]: 前i个房子拿到的max gain
- 看最后结尾状态的前一个或前两个的情况,再综合考虑当下的
- 搞清楚当下[i]的和之前[i-x]的情况的关系: 不可以连着house, 那么就直接考虑 dp[i-2]的情况
- Sequence DP, new dp[n + 1];
- Rolling Array
- [i]'只和前两个位子 [i-1], [i - 2]'相关
- 用%2来标记 [i], [i - 1], [i - 2]三个位置.
- 其他滚动时惯用curr/prev来表示坐标, 这里%2虽然抽象, 但是更加实用.
- dp[i] depends on nums[i-1] or nums[i-2] based on the state at (i-1)
- use dp[n][2] to store dp[i] and stages
- dp[0][0] = 0; dp[0][1] = nums[0]
- calculation
- dp[i][0] = Math.max(dp[i - 1][1], dp[i - 1][0]). The prior house can be either state.
- dp[i][1] = dp[i - 1][0] + nums[i]. The prior house must be
NOT ROBBED
.
- return
Math.max(dp[n - 1][0], dp[n - 1][1])
- Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
- 从低谷找peek, sell.
- DP. (old dp solution BuyOn[], SellOn[])
- DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)
- 画图, 因为可以无限买卖, 所以只要有上升, 就有profit
- 所有卖掉的, 平移加起来, 其实就是overall best profit
- 当天卖光, 再买进.
- O(n) time
- 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。
- 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.
- profit += prices[peek - 1] - prices[start]; 挺特别的。
- 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.
- 想知道前i天的最大profit, 那么用sequence DP:
- dp[i]: represents 前i天的最大profit
- 当天的是否能卖, 取决于昨天是否买进, 也就是
昨天买了或者卖了的状态
: 加状态, dp[i][0], dp[i][1] 买
的状态dp[i][0]
=-
- 今天买入, 昨天sell后的dp[i-1][1]值 - 今天的价格price[i];
-
- 今天不买, compare with 昨天buy的dp[i-1][0]值.
-
卖
的状态dp[i][1]
=-
- 今天卖出, 昨天buy的 dp[i-1][0]值 + price[i];
-
- 今天不卖, compare with 昨天sell后的 dp[i-1][1]值.
-
- 注意init:
- dp[0][0] = dp[0][1] = 0; // day 0 buy/sell: no profit regardless of buy/sell status
- dp[1][1] = 0; // day 1 sell: haven't bought, so just 0 profit.
- dp[1][0] = - prices[0]; // day 1 buy: just cost of prices[0]
- Note:
int[][] dp = new int[n+1][2]
以后, index是 1-based. for loop 注意使用prices[i-1]
- O(n) time, O(n) space
- [i] 和 [i - 1] 相关联, roll
- 求dp[i]的min cost, depends on the color of dp[n-1]
- 选中最后一个房子的颜色同时, 根据dp[i - 1]的颜色/cost + cost[i-1], 来找出最低的cost
- Need to have status with dp array: int[index][color status]
- dp[i][j]: 前i个house 刷成 j 号颜色的最小cost.
- dp[0][j] = 0: 0th house, no cost
- 计算顺序: 从每一个house开始算起 [0 ~ n], first for loop
- time: O(nm), m = # of colors
- space: O(nm)
- 观察发现 index[i] 只跟 [i-1] 相关, 所以2位就足够, %2
- space:O(1)
- 跟Paint House I 几乎一模一样, 只不过paint color更多了: k colors.
- 先考虑单纯地用dp[i]表示涂前 i 个房子的最小cost
- 但是 dp[i] 和 dp[i-1] 两个index选什么颜色会互相影响, 难讨论, 于是加状态: 序列DP被加了状态变成2D.
- 考 虑最后位, 而前一位i-1又被i位的颜色限制, 于是在考虑 min dp[i] 时候, 又多了一层iteration.
- 做dp[i][j]: # cost for 前 i 个房子, 所以要先pick (i-1) 房子的cost, 然后在找出 (i-2)房子的cost
- K种颜色 => O(NK^2)
- 如果不优化, 跟Paint House I 几乎是一模一样的代码
- Time O(NK^2), space(NK)
- Rolling array: reduce space to O(K)
- 序列型dp[i]表示'前i-1个'的结果. 所以dp最好设定为 int[n + 1] size.
- 然而, 颜色在这里是状态, 所以保留在 j: [ 0~k)
- [[8]] 这样的edge case. 跑不进for loop, 所以特殊handle.
- Time: O(NK)
- 如果已知每次都要从cost里面选两个不同的最小cost,那么先把最小两个挑出来, 就不必有第三个for loop 找 min
- 每次在数列里面找: 除去自己之外的最小值, 利用最小值/次小值的思想
- 维持2个最值: 最小值/次小值.
- 计算的时候, 如果除掉的不是最小值的index, 就给出最小值; 如果除掉的是最小值的index, 就给出次小值.
- Every loop: 1. calculate the two min vlaues for each i; 2. calcualte dp[i][j]
- 如何想到优化: 把表达式写出来, 然后看哪里可以优化
- 另外, 还是可以rolling array, reduce space complexity to O(K)
- Median还是用min-heap 和 max-heap. Time(logN)
- 加/减: prioirtyQueue, log(n)
- findMedian: O(1)
- 加一个数, 减一个数。
- 加减时看好,是从前面的maxheap里面抽,还是从后面的minHeap里面抽。
- 抽完balance一下
- 用maxHeap, minHeap时候, 习惯选择让maxHeap多一个数字:
- 左边的maxHeap总有 x+1或者x个数字
- 后边minHeap应该一直有x个数字
- 用个char[]存string的frequency.
- 2 pointer:
- move
end
to find a valid window; - once valid inwindow found: now move
start
to narrow down to minimum window. - once window invalid, continue moving
end
and repeat last 2 steps
- move
- HashMap的做法比char[]写起来要复杂一点, 但是更generic
- Almost identical approach as in
438. Find All Anagrams in a String
- use sliding window template:
-
- extend right pointer and reduce char count
-
- process when count == 0
-
- contract/shrink left side
-
- special on the
3) step
:- there is no hard length limit in this problem: in fact, the goal is to find the shortest length
3) step
now apperas in thewhile(counter == 0)
loop- shrink the left side of the window as long as counter == 0, until we break the
counter==0
balance.
- time: O(n) one pass
- space: O(1), freq[256] can be ignored.
- Two Pointers, use 1 char freq map + counter to determine valid state
- Inspired by: https://leetcode.com/problems/minimum-window-substring/discuss/26808/Here-is-a-10-line-template-that-can-solve-most-'substring'-problems
- Idea: use freqMap and counter to maintain a valid substring range, use two pointers to iterate; reduce to
counter==0
which is the valid substring state. - Steps:
-
- build valid freq count map based on target string
-
- use end index [0~n) to find valid char and reduce counter to find valid range
-
- count==0 gives valid range: process; then
map[s.charAt(start++)]++ == 0
to break the peace
- count==0 gives valid range: process; then
-
- Explain
if (map[s.charAt(start++)]++ == 0) counter++
:- when
count != 0
,map[s.charAt(end++)]--
reduces freq regardless of what char it visits (it can be ANY char, rather than T characters) - when
count == 0
,map[s.charAt(start++)]++
increases freq regardless of what char that is.- if
map[s.charAt(start)] == 0
: it is a T character being reduced to 0 previously (so we can break the balance on this char) - YES, map has other index that has 0 freq: however,
start
ONLY covers indexes thatend
has stepped through :)
- if
- when
- time: O(n)
- space: O(1)
- much faster than method2: skip the O(256*n) comparison logic.
- Note: from the concept, it is the reversed thinking of method2.
- Use 2 char freq maps: source/target.
- target map: fixed freq map, used for comparision
- source map: attempt to build a valid freq map on the fly
- two pointers:
- use index
start=[0, n)
as start index of source candidate - have a end pointer that will attempt to as far as possible to find 1st valid sequence
- use index
- time: have double while loop, but still O(n), why?
- end pointer will at most reach full length n, only once
- start pointer iterate source strichtly once O(n)
- overall, it will be O(n)
- space: O(1), only used a constant char[256]
- Option2: use map, a bit more generic
- update the window start index;
left
in sliding window- update when we need to start a new range:
nums[i-1] >= nums[i]
- calculate the max distance
i - widowStart + 1
- O(n) time and O(1) space
- size++ when meeting condition
nums[i] > nums[i - 1]
- otherwise, reset size = 1
- track max all the way
- 1D coordinate, dp 的角标, 就是代表 index i 的状态
- 求最值, dp[i] = 在index i位置的最长子序列
- 如果 nums[i] > nums[i - 1], dp[i] = dp[i - 1] + 1
- 如果没有持续上升, 那么dp[i] = 1, 重头来过
- maintain max
- Sliding window template:
-
- Check right pointer and move right
-
- Move left when necessary
-
- Verify count == 0 & end state
- Note: normally 2) and 3) are in reversed order; this problem is a bit different
-
- This is efficient when the number of characters is not limited to 26, the runtime is still O(m + n)
- time: O(m + n), m = s1 length, n = s2 length
- space: O(k), k = # of possible chars, 26 in this case
-
如果做s1的permudation, 时间复杂度是O(n!) 肯定不可以
-
这里用HashTable的做法 (因为26字母, 所以用int[26]简化) 来记录window内的 character count
-
如果window内的character count 相等, 那么就是permudation
-
更进一步优化: 找两个map相互对应, 不如用一个 int[26]: s1对遇到的character做加法, s2对遇到的character做减法
-
two pointer 运用在 n1, n2 的把控; 以及 s2.charAt(i - n1) 这一步
-
time: (m + n)
- However, if # of possible chars is more than 26
- For example,
k unique characters
, then the runtime will become: O(m + nk)
-
space: O(k), k = # of possible chars, 26 in this case
- DIFFERENT from sliding window for substring (
76. Minimum Window Substring
)- because this problem rquries keeping the order of characters from the target string
- Use a
backtrack mechanism
based on target matching to find closest left starting point to right
- Simple two pointers:
-
- move sIndex and tIndex: find all T chars in S, in order.
-
- backtrack tIndex to 0; backtrack sIndex to initial char match
-
- record potential min result
-
- Be VERY careful about pointer and index.
- time: O(n^2), backtrack n steps
- Since it requires order of substring,
freqMap+counter+twoPointers
approach is NOT applicable - TODO
- 维持monotonuous queue:
front is always at max
and thetail end is min
. Always need to return the max end of queue. - when adding new elements x:
-
- start from small-end of the queue
-
- drop all smaller elements
-
- append to the ending element that is larger than x.
- This is to maintain a front->tail decreasing queue
-
- when sliding window: queue curr window 里面 最大的已经在max-end, remove it if needed.
- 妙:用deque数据结构(实际上采用LinkedList的形式)来做一个
递减的queue
: better than using arraylist, since DeQueue(linked list) removes at O(1) cost - 每次把小于当前node的,全部剔除,剩下的,自然就是:最大的>第二大的>第三大的...ETC.
- 我们只在乎最大值的存在;而任何小于当前(正要新就加进去的)值的,反正以后也成不了最大值,于是扔掉!
- Option1: sliding window template using right/left + while loop
-
- tailing the new number to max queue, if applicable
-
- process: record max
-
- contract/shrink left: remove top max if the topMax is the left-most val: rst[i - k + 1]
-
- Option2: same concept, but use index
i
to mark right, andi - k + 1
to mark left. - time: O(n), one pass
- space: O(k), store the deque
- can always build a
class Node{index, val}
; and sort them with PQ of size k - time: O(nlogK)
- space: O(k)
- this is not linear time, not as good as method1
- Make sure to sort array: we need to use the actual number range
A[j] - A[i]
, which requires the array to be sorted - we want to form a new array where A[n-1] - A[0] + 1 == n; order does not matter but all slots need to be filled consecutivly
- max moves: https://leetcode.com/problems/moving-stones-until-consecutive-ii/discuss/289357/c%2B%2B-with-picture
- A interval will be automatically dropped between A[0] and A[1], if moving A[0] first
- Same, a interval between A[n-2] and A[n-1] will be dropped when moving A[n-1] first
- so largest possible move = firstItem + remaining range size - n items = 1 + (A[n-1] - A[1] + 1) - n = A[n-1] - A[1] -n + 2
- or A[n-2] - A[0] - n + 2
- min moves:
Sliding Window
- use slinding window to assume a right pointer, to make sure A[right] - A[left] + 1 < n; otherwise, move left++
- check # of included stones
- calculate remaining, which is remaining moves
- Handle min move edge case:
- Consecutive Array up to right = n - 1; need 2 moves to finish
- 读懂题目, 注意average 和 window 的处理.
- 简单的queue.size() comparison
- Note: if we it is calculate moving-window-product, better to use deque :)
- Sliding window?
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
if (queue.size() > size)
- Though, the solution must use a data structure to store data; it is not the traditional sliding window type of
left/right
pointer problem
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
- Method1 and Method2 are identical to
159. Longest Substring with At Most Two Distinct Characters
. - However, time complexity for Method2 in increases to O(nk). https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- we want to do better than that (Method3)
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == k, process and record max len
-
- if map.size() > k, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(k)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(nk) to find the left-most element
- space: O(k)
- https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/solution/
- as mentioned above, Method2 uses O(nk), because it takes O(k) to find head item that was inserted first
- meanwhile, we still need the hash map feature to get/put/remove last occurance of a char with O(1)
- Solution: use a LinkedHashMap:
map.entrySet().iterator()
maintains the insertion order!
- Special handling:
- since we want the
lastOccurMap
to preserve laset insertion order - we need to
remove
the char every time before put.
- since we want the
- time: O(n)
- space: O(k)
- https://leetcode.com/problems/max-consecutive-ones-iii/solution/
- Start with DFS thought, but realize redundant calculations:
- we never need to flip 2 indexes [A], [C] from 0 -> 1, if there is a [B] in middle that is 0 too
- the flipped k zeroes must be consecutive too
- we can utilize two pointers to establish a window that captures k zeroes
- always expend right pointer; if seeing an zero, k--
- note:
len = right - left + 1
is the ongoing max length
- note:
- when k < 0 (too many zeros), we need to slide the left side of the window to make sure:
- keep window len
- potentially do k++ when A[left]==0
- always expend right pointer; if seeing an zero, k--
- goal: matain a max size of the window, until right == n
- return (right - left). at this moment, right == n, so no need to (right - left + 1)
- A creative way of using anagram char count
hash[c] >= 0
to determine if the curr c is a target char of the deesired anagram.- because we always reduce hash[c]-- for all characters
- so only the anagram chars would be
hash[c] >= 0
after reducing.
- https://leetcode.com/problems/minimum-window-substring/discuss/26808/here-is-a-10-line-template-that-can-solve-most-substring-problems
- Slinding window always has left/right pointer:
-
- at any given time move 1 index at a time: expand right window, process rsult, shrink left window
-
- one of the basic goal is to maintain fixed window size
-
- algo:
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
hash[c] >= 0
indicates a anagram char - expand right window: move right to expand the window; ONLY when meeting a anagram char, count--
- process result: if count reduces to 0, one anagram is found
- shrink left window: if (right - left) == p.length(), drop curr left char, and move forward
- calc char freq of the target p, and store in a hash[256]; it will be used to distinguish anagram chars:
- how could we rely on only just
count == 0
?- the hidden pre-condition is
right - left must already be p.length()
, which is validaterd in prev iteration
- the hidden pre-condition is
- time: O(n)
- space: O(1)
- count character apperance -> hash table, here just a int[26]
- use a window to record count++ and count--, in order to compare with countP
- prep the countP takes O(m) time
- time: O(n) + O(m)
- space: O(n)
- First sort all of the items together by actual val using
Node {int val, int row}
- Slinding window goal:
-
- use right to find range that touches all rows,
-
- use left to shrink the range
-
- Sliding Window Template
- move right pointer
- Counts[i] = # of elements used in left/right range
- when counts[i] == 0, countUnique++; the number of row/list being included
- when count == row size:
- processing & save shorter range by using left/right Pointers
- move left pointer; when counts[i] == 0, countUnique--
- time: O(nlogn) for initial sort and then O(n) to process
- space: O(n)
- What is hard here? To think of the idea of counting one usage of each row:
- when each all rows are used at least 1 time
- calculate the min dist
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104893/Java-Code-using-PriorityQueue.-similar-to-merge-k-array
- https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/solution/
- Typical slinding window: the goal is to keep a distinct char size/window of size 2.
- use a map<char, count> to track; map.size() is the window size. Follow the template
-
- move right pointer, and update freq count map
-
- if map.size() == 2, process and record max len
-
- if map.size() > 2, maintain window size: drop curr left char, update map
-
- return max
- time: O(n)
- space: O(1)
- record last occurance index in map<char, index>
- when size goes over limit, find last occurance of left-most element
- set left = leftMostIndex + 1.
- This truncates entire block before the last occurance of left-most element
- sliding window 的切割: 用hashmap 存 last occurrance of char index;
- map.remove(c) 之后, 就等于彻底切掉了那一段; 那么 map.get(c) + 1 也就是新的 left window border
- time: O(n)
- space: O(1)
- 接成圈是不行的, 所以visit过得 (x,y)就不能再去了.
- 斜角方向不能走, 只能走上下左右
- 无法按照坐标DP来做, 因为计算顺序4个方向都可以走.
- 最终要visit所有node, 所以用DFS搜索比较合适.
- 简单版: longest path, only allow right/down direction:
dp[x][y] = Math.max(dp[prevUpX][prevUpY], or dp[prevUpX][prevUpY] + 1)
; and compare the other direction as well- This problem, just compare the direction from dfs result
- DFS太多重复计算; memoization (dp[][], visited[][]) 省去了重复计算
- initialize dp[x][y] = 1, (x,y) 自己也算path里的一格
- dfs(matrix, x, y): 每次检查(x,y)的4个neighbor (nx, ny), 如果他们到(x,y)是递增, 那么就考虑和比较:
- Maht.max(dp[x][y], dp[nx][ny] + 1); where dp[n][ny] = dfs(matrix, nx, ny)
- top level: O(mn), 尝试从每一个 (x,y) 出发
- O(m * n * k), where k is the longest path
- indegree tracking: Track all neighbors/childrens. 把所有的children都存在 inDegree<label, indegree count>里面
- Process with a queue: 先把所有的root加一遍(indegree == 0),可能多个root。并且全部加到queue里面。
- BFS with Queue:
- Only when map.get(label) == 0, add into queue && rst. (indegree剪完了, 就是root啦)
- inDegree在这里就 count down indegree, 确保在后面出现的node, 一定最后process.
- 几个graph的condition:
-
- 可能有多个root
-
- directed node, 可以direct backwards.
- build
Map<DirectedGraphNode, Integer> inDegree = new HashMap<>();
and include the root itself - that is more traditional indegree building
Build graph
:- 上下两行string, 相对应的相同的index上, 如果字母不同, 就说明排在第一行的字母在字母表里更领先: form sequence between chars
- form graph Map<Char, List of Chars>, for topological sort usage.
- 也可以
List[26] edges
(Course Schedule problem)
Build InDegreeCountMap<Char, Count>
: based on the char diff of 2 words- 注意: indegree 是反向的 (跟 node to neighbors 相反的方式建立)
Topological Sort
, BFS:-
- use queue to find
inDegree == 0
node. It is the letter that points to others, 排在字母表前面.
- use queue to find
-
- reduce edges using Graph
map<Character, List<Character>>
(more generic than List[26], 26个字母的dictionary)
- reduce edges using Graph
-
- Edge Case:
inDegreeCountMap.size() != result.length()
: some nodes did not make it into result sequencecycle
: when inDegree of a one node would never reduce to 0, and will not be added to result- In this case, it will be treated as invalid input, and return ""
- space: O(n), n = # of graph edges
- time: O(n)
- TODO
- 跟BFS建立 grpah 的过程一模一样
- DFS的不同在于: 用visited map 来标记走过的地方
- 走到leaf的时候, add to result: 但因为走到了底才add, 最终的顺序应该颠倒 (或者, sb.insert(0, x) 直接用颠倒的顺序add)
- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目问是否能把所有的课排了
- input是 numOfCourses, 还有这个prerequisites [[]]
- Concept of Indegree:
# of incoming node that depends on me
. It is a graph ofarrows pointing inward to me
structure. - Remember: indegree是周围的node到我这里的次数count.
- Note: 如果有cycle, 这个node上面会多一些inDegree, 也就无法清0, 它也无法进入 queue && sorted list.
- 如果周围所有node的连线, 都意义切除后, 我的indegree还不等于0, 那么肯定有某些node间接地有重复连线, 也就是有cycle
- Topological problem: almost always care about cycle case (if detecting cycle is not goal)
- Two structures:
-
- build inDegreeEdges:
List[] inDegreeEdges
: list of incoming nodes that depends onnode i
,
- build inDegreeEdges:
-
- build dependencyCount:
int[] dependencyCount
, count # of braches that curr node depends on
- build dependencyCount:
-
- any dependencyCount[node]==0, means this node is now a leaf, add to queue
- Topological Sort Process, Kahn algorithem:
- topologically process:
- add leaf node to queue, get ready to process;
- process leafNode, like cutting of leaf
- if any child node dependencyCount == 0, it is a leaf node now: add this node to queue.
- this problem aims for deteching cycle, not output final list. Simply: visit all nodes and verify cycle
- Option1: array of indegree lists, List[]
- 用 visited int[] 来确认是否有cycle. 1 means
visited
, -1 meansvisted from last dfs level
- Deteching
-1
: 说明这个node在上一级或者以上的同一个dfs path里面已经走过, 那么证明有cycle, return false. - dfs on curr node indegree dependencies; if all passes w/o failing, set visited[i] = 1
- Deteching
- Similarly, can use
HashMap<Integer, List<Integer>> map
to replace List[], but exact same idea.
- 用 visited int[] 来确认是否有cycle. 1 means
- Optoin2: use a struct
class Node {Boolean visiting; Map<Integer, Node> inDegreeMap}
to be more generic - topo sort may output the sort order: 1) at DFS bottom level, put record to a
stack
, 2) rst.insert(0, curr record) - 还有 List[] arrayOfList = new ArrayList[]; 这样的操作啊, 代替了map<integer, integerList>. Though: map may be more flexible
- 是topological sort的题目。一般都是给有dependency的东西排序。
- 最终都会到一个sink/leaf node,no further dependency, 在那个点截止
- 画个图的话, prerequisite都是指向那个sink/leaf node
- when building the inDegreeMap/inDegreeEdge: we use sink/leaf node as key/index, which pionts back to inDegree/parent nodes
- BFS: when all braches/dependency count are reduced to 0, then it is now a leaf node, ready to be used.
- DFS Insert Order: rst.insert(0, node); Assume we want leaf/node at index 0 in final output:
- the very bottom-node depends on everybody
- any visited node should be added to 0 index of the list, so it will be at tail later
- Realize we need to: 1) topo sort group, 2) topo sort items in the group.
- Luckily, the candidates to be sorted are all integers: groupIds, or item ids. We can have 1 generic topo sort function
- Overall workflow
-
- group items to map <GroupId, List>
-
- build group graph
-
- topo sort group -> return sorted group id list
-
- for each group: build item graph, topo sort items -> return sorted item list
-
- flatten and return results
-
207. Course Schedule
has more notes- 一堆课用int[2] pair 来表示. [1, 0] 表示要上课1的话, 必须先把课0上了.
- 每一个数字都是一个ndoe, 题目求这个最后排好的课的list
- 如果排不好, 就给个空就好
- input是 numOfCourses, 还有这个prerequisites [[]]
- 用
List[] edges; edges[i] = new ArrayList<>();
来表示graph: 就是每个node, to all its neighbors - 每个没有 inDegree==0 node, 都是可以加进 final list里面的. 比如一开始找到的那些 inDegree = 0的 node
- 注意, 如果 prerequisites = [], 那么就是说这些课都independent, 开个int[0 ~ n-1]的数组并赋值就好.
- 如果有cycle, 严格意义上就做不了topological sort, 也无法涵盖所有nodes, 那么return [ ]
- 根据 Course Schedule 里面的DFS 修改
- 维持visited int[]全局变量
- 维持sortedList int[] 全局变量, 注意加进去的时候是 add(0, node) 加在开头这样
- 每次到一个node的children全部DFS走完之后, 就可以把他加进final list里面
- 如果有cycle, 也就是dfs return false的时候, 这个题目判定排课失败, return new int[] { }
- Given assumption: 1)
only 1 celebrity
, 2) person k, who knows nobody ahead of him or after him. - if first pass finds candidate,
person k
, it means:- person [0, k-1] are not celebrity: they know a previous or current candidate
- person k knows no one between [k + 1, n): k+1 to n-1 can not be the celebrity either.
- person k is just the last standing possible celebrity
- second pass validation: we do not know if
knows(celeb, [0~k-1] )
. Do a final O(n) check - time:O(n), space O(1)
- DO NOT: Brutle compare all -> all: O(n^2) handshakes.
- 先写出来[0 ~ n - 1], 最简单的方式 O(n^2) 检查, 记录每个人的状态.
- 逐渐发现, 因为 celeb 谁都不会认识, 那么当任何candidate knows anyone, 他自身就不是celeb.
- 我们可以greedy地, 一旦fail一个, 就立刻假设下一个是celeb candidate
- 最终还是要检查一遍, 避免错漏.
- 想一下happy case: 如果 celeb=0, 那么 know(celeb, i) 永远都是false, 然后 celeb一直保持0, 坚持到verify所有人.
- 首先partition. 返还一个partition的那个中间点的位置: 这个时候, 所有小于nums[partitionIndex] 都应该在 partitionIndex左边
- 然后劈开两半
- 前后各自 quick sort, recursively
- 注意:在partition里面, 比较的时候nums[start] < pivot, nums[end]>pivot, 如果写成了 <= 会 stack overflow.
- Time O(nlogn), Space: O(1)
- 跟
kth largest element in an Array
的 template一样. - quickSelect 可以找到 kth 最小的元素
- 利用这个原理, 找这个kth最小值, 然后如果 == target index, 就找到了我们的median
- 主要步骤:
-
- partition
-
- check end state
pivot index ?= target index
- check end state
-
- recursive call one part of the array
-
- time: 与quickSort不同在于, 每次只要在一半list里面recurring, 所以把O(logn)的时间复杂度降到O(n)
- n + n/2 + n/4 + n/8 + ....+ 1 = O(2n) = O(n)
- space: O(logn), based on recursive stacks
- O(nk)
- Need to maintain k large elements, where the smallest will be compared and dropped if applicable:
- Maintain k elements with min value: consider using minHeap
- add k base elements first
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分
- sort结束后是ascending的, 那么 n - k 就是第k大.
- partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
- 没找到继续partion recursively.
- sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)
- Steps:
- 每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
- 找到一个low>pivot, high<pivot, 也就可以swap了。
- 得到的low就是当下的partion point了
- Overall O(nlogN), average O(n) for this problem.
- partition the array by pivot k = {0, 1, 2}
- 每一次partition都return starting point of the current partition
- 然后根据下一个 color, 去还没有sort 干净的那个部分, 再sort一下就好
- time O(kn), where k = 0 => O(n)
- 这里只是partion, 并不需要recursively quick sort, 所以结果是简单的O(n)
- have two pointers, left/right
- start tracks red, end tracks blue. Swap red/blue to right position, and left++ or right--.
- leave white as is and it will be sorted automatically
- be very careful with index i: when swapping with index right, we do not know what is nums[right], so need to re-calculate index i .
- O(n)
- Note: this one pass solution does not work if there are more than 3 colors. Need to use the regular quick sorty.
- TODO: count occurance and reassign array
- Quick sort的基础.
- Partition Array根据pivot把array分成两半。
- 从array两边开始缩进。while loop到遍历完。非常直白的implement。
- 注意low/high,或者叫start/end不要越边界
- O(n)
- 注意: 这里第二个inner while
while(low <= high && nums[high] >= pivot) {..}
采用了nums[high] >= pivot
- 原因是题目要找第一个nums[i] >= k, 也就是说, 即便是nums[i]==k也应该swap到前面去
- 这个跟quick sort 原题有一点点不一样.
- Use minHeap to maintain PQ of k size and return PQ.peek()
- Maintain MinHeap: only allow larger elements (which will squzze out the min value)
- Remove peek() of queue if over size
- O(nlogk)
- 用Quick Sort 里面partion的一部分: sort结束后是ascending的.
- kth largest = (n - k)th smallest
- in partioned array (quick sort), the portion before pivot are less than pivot
- that is, the
pivot value
is the divider: anything after pivot is larger than it. - after
swap(nums, low, pivot)
: index low has the (n-k)th smallest, iflow = n-k
- Steps:
- each iteration: pick pivot,然后从low,和high都和pivot作比较
- Find
low>pivot, high<pivot
to swap - The new low is the next partion point
- Time: average O(n), worst case O(n^2)
- space: O(1) extra spaces besides recursive stack
- Given: values are [1,n], so val can represent index. Therefore, set val to its correct position
- 小心handle i:
- value是 1-based
- 每次换位, 需要
i--
, 重新省察nums[i]
- Option1: use negative number to mark visited:
- 很巧妙地运用了标记的方法, 标记成负数,证明visit过。
- Preserve原数的负数,这样可以继续用此负数的绝对值来寻找原数所该被定的位置。非常巧妙!
- Option2: use large number (larger than n)
- 跟方法2类似,也是做标记,这一次是加上一个大于n的数(因为题目给了n的border),最后check一下就好。为不超Integer.MAX_VALUE, 每次加n前,取余数。
- 做标记的方法固然快,但是相对来说比较hacky,在常规的代码中,估计不会用到.
- store
Map<Word, Longest Chain Length>
- sort all words, try from short to long: short word will be calculated first to serve later words as candidate
- time: O(nlogn)
- space: O(n)
- store
Bucket: List[17] of words
, given word size limit [0 ~ 16] - time: O(n)
- space: O(n)
- PQ can be used to sort on multiple attributes
- follow the specified rules, and build all possible pairs of visits vs. bike. Pair {int dist, workerIndex, bikeIndex}
- PQ to sort them
- first by dist
- if same dist, sort by workerIndex
- if same workderIndex, sort by bikeIndex
- process all candidates, and skip the ones (workers/bikes) visited
- Similar to using PQ: the goal is to find: 1) min dist; 2) closer worker index, 3) closer bike index
- can use bucket sort to hold all possible distances [0 ~ 2000]: bucket[List of pairs]
- do a hard iteration (ordered access from min dist).
- time: O(mn), no need to sort
- space: O(mn)
- 例子写出来,发现可以sort以后按照定义搜索一遍。 nlogn.
- 搜索一遍时候可以优化,用binary search. 但是没意义,因为array.sort已经用了nlogn
- 题目给的规则, 从小到大排序后: 剩下的 paper
n-h
, 全部要 <= h 个 citation. - time O(nlogn), search O(n)
- 从i = 0 开始找第一个
citations[i] >= h
, 就是第一个符合 h-index 规则的paper, return h - 如果从 h = n, 每次h--; 那么
x = n - h
就是从[0 ~ n)
开始找第一个dictations[x] >= h
, 就是结果 - 同时,
dictations[x-1]
就是最后一个(dictation最多的)其余的paper. - O(n)
- Bucket sort的思想(更像是counting sort?): 过一遍 input, 把dictation value 作为 index, 分布在bucket[index]上++
- bucket[x] 是 count when # of citation == x.
- 如果 x 大于 n的时候, 就超出了index范围, 但是刚好这个问题可以包容, 把这样的情况记位在bucket[n]就可以
- 巧妙:
sum += bucket[h]
whereh = [n ~ 0]
利用了h-index的definition: - #of papers (sum of bucket[n]...bucket[0]) has more than h cidations
- 这里运用到了bucket sort的思想, 但是并不是sorting, 而h-index的定义运用的很巧妙.
- Read more about actual bucket sort: https://en.wikipedia.org/wiki/Bucket_sort
- Application: for number/character range
- Steps:
- Find range, define countArray
- Count element and record in the array
- PreSum the countArray
- Start from beginning of the array,
print & decrese count
to produce the sorted elements
- maintain the
farest can go
, and use it the target for i increse towards. Why?-
- when I know the
farest can go
, in fact it is just currently 1 step away.
- when I know the
-
- why to iterate from curr
i to farset
? In range [i, farest], we will calc the newmaxRange
- why to iterate from curr
-
- once
i
reachesfarset
, updatefarest = maxRange
- once
-
- greedy concept: once we know the farest we can reach at the moment, it is just 1 step away :)
- On average should be jumpping through the array without looking back
- time: average O(n)
- Impl:
- 图解 http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html
- track the farest point
- whenver curr index reachest the farest point, that means we are making a nother move, so count++
- there seems to have one assumption: must have a solution. Otherwise, count will be wrong number.
- 其实跟第一个greedy的思维模式是一模一样的.
- DP[i]: 在i点记录,走到i点上的最少jump次数
- dp[i] = Math.min(dp[i], dp[j] + 1);
- condition (j + nums[j] >= i)
- 注意使用 dp[i] = Integer.MAX_VALUE做起始值, 来找min
- time: O(n^2), slow, and timesout
- 分三份:a b c考虑
- 若a: countA++; 或b: countB++
- 或c:countA--, countB--
- 注意: 按照if statement的顺序, valA&&countA 比valB&&countB有优先性
- 最后出现的两个count>0的a和b,自然是potentially大于1/3的。其中有一个大于1/3.
- 比较countA和countB哪个大,就return哪一个。
- 看到 min value, 至少考虑dp:
- Consider last num: min sum will be (preMinSum + curr, or curr)
- Use preMinSum to cache previouly calcualted min sum, also compare with +curr.
- Have a global min to track: because the preMinSum can be dis-continuous.
- 也可以写成 dp[i] 但是没什么必要
- PriorityQueue排序+分布排列的一个经典用法.
- Count frequency and store in pq.
- Consume element of pq for k rounds, each time pick one element from queue.
- Exception: if k still has content but queue is consumed: cannot complete valid string, return "";
- space, O(n) extra space in sb, O(26) constant space with pq.
- time: O(n) to add all items
- count[] = int[256], 不需要
c-'a'
- boolean visited[]: 一旦一个字母固定了位置后, 再次遇到时候, 直接跳过用过的character
- 如果tail字母可以变小, 那就delete掉tail, 重新接上新字母 (前提条件: 去掉的字母后面还会再出现, set visited[tail] = false)
- Space: O(1) count[], visited[].
- Time: Go through all letters O(n)
- Use stack instead of stringBuffer: keep append/remove last added item
- However, stringBuffer appears to be faster than stack.
- 分析字符 ?, * 所代表的真正意义, 然后写出表达式.
- 搞清楚initialization 的时候 dp[i][0] 应该always false. 当p为empty string, 无论如何都match不了 (除非s="" as well)
- 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.
- A. p[j] != '*'
- last index match => dp[i - 1][j - 1]
- last index == ? => dp[i - 1][j - 1]
- B. p[j] == "*"
-
- is empty => dp[i][j - 1]
-
- match 1 or more chars => dp[i - 1][j]
-
- For 2 passwords, the shortest situation is both passwords overlap for n-1 chars.
- We can use a window to cut out last (n-1) substring and append with new candidate char from [k-1 ~ 0]
- Track the newly formed string; if new, add the new char to overall result
- Note: this operation will run for k^n times: for all spots of [0 ~ n - 1] each spot tries all k values [k-1 ~ 0]
- Same concept as dfs
- Same concept: use window to cut out tail, and append with new candidate
- do this for k^n = Math.pow(k, n) times
- 与StockII一样, dp[i]: represents 前i天的最大profit.
- sell 的时候, 才完成了一次transaction, 需要扣fee; 而买入不扣fee.
- model sell on dp[i] day (which depends on dp[i-1]) and each day can be sell/buy => add status to dp[i][status]
- status[0] buy on this day, status[1] sell on this day
- dp[i][0] = Math.max(dp[i-1][0], dp[i - 1][0] - prices[i]);
- dp[i][1] = Math.max(dp[i-1][1], dp[i - 1][1] + prices[i] - fee);
- init: dp[0][0,1] = 0; dp[1][1] = 0; dp[1][0] = - prices;
- return dp[n][1]
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
- 要赢, 必须保证对手拿到棋盘时, 在所有他可走的情况中, '有可能败', 那就足够.
- 设计dp[i]:表示我面对i个coins的局面时是否能赢, 取决于我拿掉1个,或者2个时, 对手是不是会可能输?
- dp[i] = !dp[i - 1] || !dp[i-2]
- 时间: O(n), 空间O(n)
- 博弈问题, 常从'我的第一步'角度分析, 因为此时局面最简单.
- concept similar to
31. Next Permutation
-
- first pass: find the one that is in incorrect order
-
- second pass: find the right spot to swap
- when there is a value that can cover entire row of size n
- it must be:
n = countA[i] + countB[i] - overlap[i]
- it must be:
- Code easy to write and read
- time: O(n)
- space: O(1)
- Observation: if A[0] works, no need to check B[0].
- Because if both A[0] and B[0] exist in all dominoes,
- when you swap A[0] in a whole row,
- you will swap B[0] in a whole at the same time.
- The result of trying A[0] and B[0] will be the same.
- time: O(n)
- space: O(1)
- there should exist 1 numbers, that can appear in (A[i], B[i]).
- failure case: there exist at least 1 index, that does not have the common number
- maximum case: there can be 2 numbers, that both will make it work.
- findCommon2, and count them:
- set.add(A[0], A[B]),
- if any new one does not exist in set, remove it from set
- if set is empty() , return -1
- use the 2 numbers from set to do a sweep and count in A, O(n), return the less appearance one.
- time: O(n)
- space: O(1)
- Sort start times, and end times in 2 different arrays
- Loop over start time
- when start[i] < end[endIndex], Count++, need more room
- start[i] >= end[endIndex], done using some room, move to next end time, endIndex++ (like vacating a room)
- Note: we never decrese count because:
- what ever count reaches, it is the max
- since we keep moving endIndex, when start[i] >= end[endIndex], we will just reuse meeting room w/o count++
- time: O(nlogn)
- space: O(n)
- somehow, super fast, over 100%
- inspired by: https://leetcode.com/problems/meeting-rooms-ii/discuss/67855/Explanation-of-%22Super-Easy-Java-Solution-Beats-98.8%22-from-%40pinkfloyda
- Use sweep line to process, track max count as max # of rooms needed
- 跟 Number of Airpline in the sky是同一道题
- time: O(nlogn)
- space: O(n)
- start from index = 0
- Keep track of farest can go
- 一旦 farest >= nums.length - 1, 也就是到了头, 就可以停止, return true.
- 一旦 farest <= i, 也就是说, 在i点上, 已经走过了步数, 不能再往前跳, 于是 return false
- start from index = n - 1
- greedy: start from end, and mark last index
- loop from i = [n - 2 -> 0], where i + nums[i] should always >= last index
- check if last == 0 when returning. It means: can we jump from index=0 to the end?
- time: O(n)
- space: O(1)
- DP[i]: 在i点记录,i点之前的步数是否可以走到i点? True of false.
- 其实j in [0~i)中间只需要一个能到达i 就好了
- Function: DP[i] = DP[j] && (j + A[j] >= i), for all j in [0 ~ i)
- Return: DP[dp.length - 1];
- time: O(n^2)
- space: O(n)
- 不论从哪一个点开始, 都可以记录总油耗,
total = {gas[i] - cost[i]}
. 最后如果total < 0, 无论从哪开始, 必然都不能走回来 - 可以记录每一步的油耗积累,
remain += gas[i] - cost[i]
- 一旦 remain < 0, 说明之前的starting point 不合适, 也就是说, 初始点肯定在后面的index. 重设: start = i + 1
- single for loop. Time: O(n)
- 看似有点像 House Robber II, 但是问题要求的是: 一个起始点的index
- 而不是求: 最后点可否走完/最值/计数
- Given assumption: 1)
only 1 celebrity
, 2) person k, who knows nobody ahead of him or after him. - if first pass finds candidate,
person k
, it means:- person [0, k-1] are not celebrity: they know a previous or current candidate
- person k knows no one between [k + 1, n): k+1 to n-1 can not be the celebrity either.
- person k is just the last standing possible celebrity
- second pass validation: we do not know if
knows(celeb, [0~k-1] )
. Do a final O(n) check - time:O(n), space O(1)
- DO NOT: Brutle compare all -> all: O(n^2) handshakes.
- 先写出来[0 ~ n - 1], 最简单的方式 O(n^2) 检查, 记录每个人的状态.
- 逐渐发现, 因为 celeb 谁都不会认识, 那么当任何candidate knows anyone, 他自身就不是celeb.
- 我们可以greedy地, 一旦fail一个, 就立刻假设下一个是celeb candidate
- 最终还是要检查一遍, 避免错漏.
- 想一下happy case: 如果 celeb=0, 那么 know(celeb, i) 永远都是false, 然后 celeb一直保持0, 坚持到verify所有人.
- define the bucket by index: the total distance is fixed [0, 1000]
- +/- capacities for each pos and save into the bucket
- go over the bucket and see if the total cap goes over input capacity
- O(n), trips size
- space: O(1), bucket size 1000 is constant
IMPORTANT
: before using PQ to sort, consider bucket sort:- if the boundary set and seems resonable? i.e., max size =
1000
- is the sorted items index based?
- if the boundary set and seems resonable? i.e., max size =
- Like meeting room, merge interval
- process items on same index
- 正面去做:
- count task出现的次数
- 然后PQ sort Task object in descending order
- 每个section: k slots = n + 1. Same task being n slots apart, meaning one section has n + 1 slots.
- 目标是穷尽 k, or 穷尽 pq (poll k times, but will save it back to queue if Task # > 0)
- 如果qp 真的穷尽, break, return count
- 不然, count += k, where k are just # of idle intervals
- time O(n) + constant time O(xlogx), where x = 26
- extra space O(x) ~ O(1)
- Enumerate to understand:
- 1.module tasks in module/section;
- 2.Only need sum the intervals/slots, not return actual layout
- Perfect case: all letters appear identical # times: just line them up separate in order.
- Real case: task appears different times
-
- Place maxCount task as header followed with n slots: define (maxCount-1) sections
-
- For tasks with less # than maxCount# can fill the (maxCount-1) sections; what about the tail section?
-
- Any task with same maxTask#, of if prior sections all filled, will fill the tail section
- To count overall slots/intervals, come up with this equation:
-
- Fixed sections:
(maxCount - 1) * (n + 1)
- Fixed sections:
-
- Plus all repeating maxCount tasks: calculate by couting identical maxCount of them
-
- Exception: if the first (max - 1) sections are all filled completely, and we still have extra task (ex: when n is not large enough), then just return tasks.length
-
- time O(n), space O(1)
- ??? Need to study
- Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
- 从低谷找peek, sell.
- DP. (old dp solution BuyOn[], SellOn[])
- DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)
- 画图, 因为可以无限买卖, 所以只要有上升, 就有profit
- 所有卖掉的, 平移加起来, 其实就是overall best profit
- 当天卖光, 再买进.
- O(n) time
- 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。
- 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.
- profit += prices[peek - 1] - prices[start]; 挺特别的。
- 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.
- 想知道前i天的最大profit, 那么用sequence DP:
- dp[i]: represents 前i天的最大profit
- 当天的是否能卖, 取决于昨天是否买进, 也就是
昨天买了或者卖了的状态
: 加状态, dp[i][0], dp[i][1] 买
的状态dp[i][0]
=-
- 今天买入, 昨天sell后的dp[i-1][1]值 - 今天的价格price[i];
-
- 今天不买, compare with 昨天buy的dp[i-1][0]值.
-
卖
的状态dp[i][1]
=-
- 今天卖出, 昨天buy的 dp[i-1][0]值 + price[i];
-
- 今天不卖, compare with 昨天sell后的 dp[i-1][1]值.
-
- 注意init:
- dp[0][0] = dp[0][1] = 0; // day 0 buy/sell: no profit regardless of buy/sell status
- dp[1][1] = 0; // day 1 sell: haven't bought, so just 0 profit.
- dp[1][0] = - prices[0]; // day 1 buy: just cost of prices[0]
- Note:
int[][] dp = new int[n+1][2]
以后, index是 1-based. for loop 注意使用prices[i-1]
- O(n) time, O(n) space
- [i] 和 [i - 1] 相关联, roll
- PQ can be used to sort on multiple attributes
- follow the specified rules, and build all possible pairs of visits vs. bike. Pair {int dist, workerIndex, bikeIndex}
- PQ to sort them
- first by dist
- if same dist, sort by workerIndex
- if same workderIndex, sort by bikeIndex
- process all candidates, and skip the ones (workers/bikes) visited
- Similar to using PQ: the goal is to find: 1) min dist; 2) closer worker index, 3) closer bike index
- can use bucket sort to hold all possible distances [0 ~ 2000]: bucket[List of pairs]
- do a hard iteration (ordered access from min dist).
- time: O(mn), no need to sort
- space: O(mn)
- just check flowerbed[i-1] & flowerbed[i+1] and count
- Greedy: Remove 1) earlier digits(数位靠前权值大), 2) large digits
- Keep a increasing stack that:
- use stack.peek() to guard incoming digit
- if peek is larger than incoming digit, continue
stack.pop()
- Result: monotonous increasing stack. Print it in correct order.
- process and check at the end, or, 2) sanitize first and process assume correct input
- Aggregate map<char, count>, and sort the entry with priority queue.(Optionally, can use object
Letter {char c, int count}
) - Naturally: we want to prioritize the largest population and exhaust it first, so we want to keep it in the a buffer queue
- it is a queue, first in first out
- monitor queue size k = 2, so that it holds off the just last-processed letter for 1 unit of time
- the buffer then sends the last-process item to the main priority queue (pq will sort it again)
- Error handling: largest population may have extra letter
- the main PQ has already exhausted
- but the largest-population-letter will end up stuck in the buffer queue
- it will never be picked up again so the final result sb will be shorter than orignal string: that is the error case
- Option0. Similar to
621. Task Scheduler
:- use a buffer to hold potential letter to add back, but NOT ADD BACK YET, until k slots have been filled.
- time: O(m), m = # of unique letters
- space: O(nmLogm), n = length, pq sorting requires mlogm, we will visit all n nodes.
- put all in map<char, count>
- Sanitize the input: if certain popular char count is over (n + 1)/2, then it should fail right away, just return empty map.
- Once the input is sanitized, when building results, we can be greedy and consume most popular char and then the rest
- Int[2] can be used store char and count
- PriorityQueue can sort int[]. Okay to not specific length of int[] when defining pq.
- Alternatively, can use a Letter {char c, int count} to represent
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
- 正面去做:
- count task出现的次数
- 然后PQ sort Task object in descending order
- 每个section: k slots = n + 1. Same task being n slots apart, meaning one section has n + 1 slots.
- 目标是穷尽 k, or 穷尽 pq (poll k times, but will save it back to queue if Task # > 0)
- 如果qp 真的穷尽, break, return count
- 不然, count += k, where k are just # of idle intervals
- time O(n) + constant time O(xlogx), where x = 26
- extra space O(x) ~ O(1)
- Enumerate to understand:
- 1.module tasks in module/section;
- 2.Only need sum the intervals/slots, not return actual layout
- Perfect case: all letters appear identical # times: just line them up separate in order.
- Real case: task appears different times
-
- Place maxCount task as header followed with n slots: define (maxCount-1) sections
-
- For tasks with less # than maxCount# can fill the (maxCount-1) sections; what about the tail section?
-
- Any task with same maxTask#, of if prior sections all filled, will fill the tail section
- To count overall slots/intervals, come up with this equation:
-
- Fixed sections:
(maxCount - 1) * (n + 1)
- Fixed sections:
-
- Plus all repeating maxCount tasks: calculate by couting identical maxCount of them
-
- Exception: if the first (max - 1) sections are all filled completely, and we still have extra task (ex: when n is not large enough), then just return tasks.length
-
- time O(n), space O(1)
- ??? Need to study
- 读懂题目, 注意average 和 window 的处理.
- 简单的queue.size() comparison
- Note: if we it is calculate moving-window-product, better to use deque :)
- Sliding window?
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
if (queue.size() > size)
- Though, the solution must use a data structure to store data; it is not the traditional sliding window type of
left/right
pointer problem
- It has the spirit of slinding window: 1) maintain a range; 2) check range size
- maintain the
farest can go
, and use it the target for i increse towards. Why?-
- when I know the
farest can go
, in fact it is just currently 1 step away.
- when I know the
-
- why to iterate from curr
i to farset
? In range [i, farest], we will calc the newmaxRange
- why to iterate from curr
-
- once
i
reachesfarset
, updatefarest = maxRange
- once
-
- greedy concept: once we know the farest we can reach at the moment, it is just 1 step away :)
- On average should be jumpping through the array without looking back
- time: average O(n)
- Impl:
- 图解 http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html
- track the farest point
- whenver curr index reachest the farest point, that means we are making a nother move, so count++
- there seems to have one assumption: must have a solution. Otherwise, count will be wrong number.
- 其实跟第一个greedy的思维模式是一模一样的.
- DP[i]: 在i点记录,走到i点上的最少jump次数
- dp[i] = Math.min(dp[i], dp[j] + 1);
- condition (j + nums[j] >= i)
- 注意使用 dp[i] = Integer.MAX_VALUE做起始值, 来找min
- time: O(n^2), slow, and timesout
- 其实跟给一个2D matrix没有什么区别, 可以做dfs, memoization.
- initialize memo: pathSum[i][j] = MAX_VALUE; 计算过的path省略
- Bottom-top: 先dfs到最深的path, 然后逐步网上返回
OR 原理: min(pathA, pathB) + currNode
- 浪费一点空间, pathSum[n][n]. space: O(n^2), where n = triangle height
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- 跟dfs的原理很像,
OR 原理: min(pathA, pathB) + currNode
- init dp[n-1][j] = node values
- build from bottom -> top: dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
- 跟传统的coordinate dp有所不同, inner for loop 是需要计算 j <= i, 原因是triangle的性质.
- 空间: dp[n][n]. space: O(n^2)
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- Based on the DP solution: the calculation always depend on
next row
for col atj
andj + 1
- 既然只depend on next row, 可以用rolling array来处理: reduce to O(n) space.
- Further: 可以降维, 把第一维彻底去掉, 变成 dp[n]
- 同样是double for loop, 但是只在乎column changes:
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
- due to access permission, not test
- dp[i][j]: longest continuous subsequence length at coordinate (i, j)
- dp[i][j] should come from (i-1,j) and (i, j-1).
- dp[0][0] = 1
- condition: from up/left, must be increasing
- return dp[m-1][n-1]
- O(mn) space for dp and flag.
- O(mn) runtime because each spot will be marked once visited.
- 这个题目的简单版本一个array的例子:从简单题目开始想DP会简单一点。每个位置,都是从其他位置(上下左右)来的dpValue + 1. 如果啥也没有的时候,init state 其实都是1, 就一个数字,不增不减嘛。
- envelopes没有顺序, 先排序 (主要根据第一个index排序)
- 然后观察: 排序过后, 就变成了1D的坐标动态规划.
- max number 取决于上一个成功Russian doll的 max value + 1
- 上一个index不知道, 所以遍历找上一个index.
- 当下index i 的状态, 取决于前面index j 的状态, 所以遍历两个index.
- O(n^2)的DP, n = envelopes.length;
- 这个方法是自己想出来的, 但是时间复杂度太大, timeout
- 把envelop标记在2D grid上面, 然后像走机器人一样, 求到最右下角的最大 count max.
- count 当下能存在多少Russian doll
- 两种情况: 当下coordinate 没有target, 当下coordinate有target
- 当下coordinate 没有target: 如同机器人走法, Math.max(dp[i - 1][j], dp[i][j - 1])
- 当下coordinate 有target: dp[i - 1][j - 1] + dp[i][j]
- timeout: O(n^2), n = largest coordinate.
- 需要能够判断综合题, 分清楚情况和套路: combination of
longest subsequence
andways to do
, as well as global variable. - len[i] (我们平时的dp[i]): 在前i个元素中, 最长的 increasing subsequence length;
- count[i]: 在前i个元素中, 并且以 len[i]这个长度为准的 subsequence的 count. 或者: 在前i个元素中, ways to reach longest increasing subsequence.
len[i] == len[j] + 1
: same length, but different sequence, so add allcount[i] += count[j]
len[i] < len[j] + 1
: 这就是更长的情况找到了, 那么有多少次 count[j] 有多少, count[i] 就有多少. 仔细想sequence: 长度增长了, 但是ways to reach i 没有增长.- 同样的判断需要用在 maxLen 和 maxFreq上:
- 如果没有增长 maxLen 不变, maxFreq上面需要 +=count[i] (同一种长度, 多了更多的做法)
- 如果maxLen 变长, maxFreq 也就是采用了 count[i] = count[j]
- TODO: Is rolling array possible?
- 都是 Coordiate DP, DP的鼻祖家族:
- Longest Increasing Subsequence (跟这道题的一部分一模一样)
- Longest Continuous Increasing Subsequence (连续, 只check dp[i - 1])
- Longest Increasing Continuous Subsequence I, II (Lintcode, II 是matrix)
- 找subsequence: 不需要continous, 可以skip candidate
- 考虑nums[i]结尾的时候, 在[0, i), dp[i - 1] 里count有多少小于nums[i]
- dp[i]: 到i为止 (对于所有 j in [0, i], 记录max length of increasing subsequence
- max需要在全局维护: nums是无序的, nums[i]也可能是一个很小的值, 所以末尾dp[i]并不是全局的max, 而只是对于nums[i]的max.
- 正因此, 每个nums[i]都要和每个nums[j] 作比较, j < i.
- dp[i] = Maht.max(dp[i], dp[j] + 1); j = [0 , i - 1]
- 时间复杂度 O(n^2)
- 维持一个list of increasing sequence
- 这个list其实是一个base-line, 记录着最低的increasing sequence.
- 当我们go through all nums的时候, 如果刚好都是上升, 直接append
- 如果不上升, 应该去list里面, 找到最小的那个刚好大于new num的数字, 把它换成num
- 这样就完成了baseline. 举个例子, 比如替换的刚好是在list最后一个element, 等于就是把peak下降了, 那么后面其他的数字就可能继续上升.
- '维护baseline就是一个递增的数列' 的证明, 还没有仔细想.
- use dp[i] track local max, maintain global max
- int[] dp. dp[i]: longest valid string that ends on i.
- 结尾是 ')', 2种情况: 1. 刚好s[i-1]是'('; 2. s[i]的')'更前面的一个起始'(' 对应
- 注意, 结尾如果是'('属于不合理情况, 忽略.
- init: dp[0] = 0, 单个char不可能成型.
- 计算顺序: 从左到右, 找local max, maintain global max
- O(n) space, O(n) runtime
- Stack 里面存所有的open/close parentheses.
- 如果遇到stack.top()刚好开合结掉, 就stack.pop().
- 剩下的都是不合理的elements.
- 有点像negatively找 solution:
endIndex - 最后一个failedIndex(stack.pop()) - 1
, 应该就是最后一个succeeded string的长度 - 每次更新 endIndex 为stack.top(), 然后从stack继续找下一个failedIndex
- 所有的length作比较, 就可以找出最长length
- O(n) stack space, O(n) runtime. 应该比dp慢一点, 因为做了2遍O(n)
- 接成圈是不行的, 所以visit过得 (x,y)就不能再去了.
- 斜角方向不能走, 只能走上下左右
- 无法按照坐标DP来做, 因为计算顺序4个方向都可以走.
- 最终要visit所有node, 所以用DFS搜索比较合适.
- 简单版: longest path, only allow right/down direction:
dp[x][y] = Math.max(dp[prevUpX][prevUpY], or dp[prevUpX][prevUpY] + 1)
; and compare the other direction as well- This problem, just compare the direction from dfs result
- DFS太多重复计算; memoization (dp[][], visited[][]) 省去了重复计算
- initialize dp[x][y] = 1, (x,y) 自己也算path里的一格
- dfs(matrix, x, y): 每次检查(x,y)的4个neighbor (nx, ny), 如果他们到(x,y)是递增, 那么就考虑和比较:
- Maht.max(dp[x][y], dp[nx][ny] + 1); where dp[n][ny] = dfs(matrix, nx, ny)
- top level: O(mn), 尝试从每一个 (x,y) 出发
- O(m * n * k), where k is the longest path
- 特点: 上一步可能是swaped也可能是fixed
- 考虑A,B之间的现状:
A[i] > A[i - 1] && B[i] > B[i - 1]
或者A[i] > B[i - 1] && B[i] > A[i - 1]
- 问题: 如何把这个状态变成合理的strick-increasing状态?
A[i] > A[i - 1] && B[i] > B[i - 1]
: 1. 已经合理, 也不动. 2. [i], [i-1] 全部都swapA[i] > B[i - 1] && B[i] > A[i - 1]
, 交错开来, 所以调换[i], 或者[i-1]: 1. 换[i-1]. 2. 换[i]- 注意因为求min, 所以init value应该是 Integer.MAX_VALUE;
- update the window start index;
left
in sliding window- update when we need to start a new range:
nums[i-1] >= nums[i]
- calculate the max distance
i - widowStart + 1
- O(n) time and O(1) space
- size++ when meeting condition
nums[i] > nums[i - 1]
- otherwise, reset size = 1
- track max all the way
- 1D coordinate, dp 的角标, 就是代表 index i 的状态
- 求最值, dp[i] = 在index i位置的最长子序列
- 如果 nums[i] > nums[i - 1], dp[i] = dp[i - 1] + 1
- 如果没有持续上升, 那么dp[i] = 1, 重头来过
- maintain max
- 正方形, 需要每条边都是
一样长度
.- 以右下角为考虑点, 必须满足条件: left/up/diagonal的点都是1
- 并且, 如果三个点分别都衍生向三个方向, 那么最长的 square 边就是他们之中的最短边 (受最短边限制)
- dp[i][j]: max square length when reached at (i, j), from the 3 possible directions
- dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
- init: 每个点都可能是边长1, 如果 matrix[i][j] == '1'
- Space, time O(mn)
- Rolling array: [i] 和 [i - 1] 之间的关系, 想到滚动数组优化 space, O(n) sapce.
- 计数DP: 2 ways to reach (i,j): dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- non-overlapping:
dp[i - 1][j]
,dp[i][j - 1]
- covers the only 2 possible way to reach (i,j)
- non-overlapping:
- initialization: dp[i][0] = 1, dp[0][i] = 1
- Of course, row i = 0, or col j = 0, there is only 1 way to reach
- time O(mn), space O(mn)
- [i] 只跟 [i - 1] 有关系, 用 curr/prev 建立滚动数组.
- space O(n) 优化空间
- move from (0,0) towards (m, n)
- use Map<coordinate, steps> as memoization technique
- Time, Space O(MN)
- 往右下角走, 计算最短的 path sum. 典型的坐标型.
- 注意: init 第一行的时候, 要accumulate dp[0][j - 1] + grid[i][j], 而不是单纯assign grid[i][j]
- Rolling Array
- Time O(MN), Space O(N)
-
- 需要在同一个for loop里面完成initialization, 2) reuse dp[i][j]
- Reason: dp[i % 2][j] 在被计算出来的时候, 马上在下一轮会被用. 被覆盖前不用,就白算
- Option3 below initialize dp outside of loop: 看起来固然简单, 但是不方便空间优化
- Enumerate the possible 2 paths: go right, go down
- sub problem: dfs(i+1,j), dfs(i,j+1); pick the min of the two
- memoization: after the path from a point (i,j) to end is computed, record memo[i][j] to avoid repatative calculation
- time: O(mn), only visite and record memo[i][j] once.
- space: O(mn) extrem case of m=100000, n = 2; the stack height is O(mn)
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
- Check if mod result exist in earlier preSum
- Utilize
Pigeonhole principle
to optimize:-
- put positive integers into k slots
-
- when # of integers > 2*k, then there must be a range sum that is multipler of k
- more illustration here: https://leetcode.com/problems/continuous-subarray-sum/solution/
-
- Draw the presum and try to take mod of each presum and save to set, we realize
-
- assume a mod result = 7, and we mark it in the set
-
- some time later, after summing up more values, (7 + x + y ...+ z) % k == 7
- it means
(x + y ...+ z) % k == 0
- There is a
整除
exist; return true
-
- Meanwhile, if we want to record the list of indexes, we can use a Map rather than set.
- Note: if all we do to the presum is to % k, therefore
preSum % k
can representpresum
in some cases. - time: O(n)
- space: O(k), size restrited by mod result of
%k
- PreSum[]:
-
- cal preSum array
-
- preSum(i, j) = continuous range sum
-
- determine if
preSum(i, j) % k == 0
- determine if
-
- time: O(n^2)
- DP (坐标型. specifically, preSum[])
- 记录在0 ~ i点(包括nums[i], 以nums[i]结尾)的sum, 坐标型动态规划.
- dp[i] = dp[i - 1] + nums[i];
- 从sum = 每次[i ~ j]的所有情况
- time: O(n^2)
- space: O(1)
- Space, Time: O(MN)
- dp[i][j] 就是(i, j)上最多能炸掉的enemy数量
- dp[i][j] 需要从4个方向加起来, 也就是4个方向都要走一遍, 所以分割成 UP/Down/Left/Right 4个 int[][]
- 最后一步的时候求max
- 分方向考虑的方法很容易想到,但是四个方向移动的代码比较繁琐.
- 往上炸: 要从顶向下考虑
- 往下炸: 要从下向上考虑
- 熟练写2D array index 的变换.
- Inspired by: https://leetcode.com/problems/bomb-enemy/discuss/83387/Short-O(mn)-time-O(n)-space-solution
- Analyize the problem: need to add up 2 directions at any given point.
- Notice 1: if I traverse row by row, each colSum at a specific col j is likely to be the same
- Unless there is a Wall in last row, so we have to calclate the col sum starting from current row, below the Wall
- Notice 2: for row it is the same:
- If I in a new spot row[i][j], where (i-1) is Wall, i need to sum row from 0
- Notice 1: if I traverse row by row, each colSum at a specific col j is likely to be the same
- we will process each point:
- process row by row and add up row sum
- buffer col[j] in an array vertically so we can resue
- make sure to recalculate row sum or col sum if last row index or last col index is Wall
- time: O(mn) traversal
- space: O(n) only use a column array
- 重点是根据找Histogram里面rectangle的性质, 维持一个单调递增的Stack
- 在loop over indexes的时候:
- 如果高度>= previous peek(), 那么对于那个peek, 就意味着, 往下走, 一直走高嘛, 之前的peek总可以继续抄底
- 什么时候不能抄底了呢? 就是有一个下降趋势的时候
- 这时候并不是calculate所有前面的peek, 而是考虑 大于 current height的之前所有的peek.
- 把这些peek到 current height 前一格的rectangle全部找出来: stack.pop()
- 这个stack.pop()的过程里面, 其实没有算上 current height, 因为需要留到下一轮, 把current index加进stack 再说
- 为什么用stack? 因为需要知道连续递增的peek, stack.peek() O(1), 好用 而其实不用stack, 也可以用其他方式记录所有height, 只不过要 O(n)去找peek不方便
- 理解monotonous stack 是如何被维护的
- 维护monotonous stack 是题目需要, 而不是stack本身性质, 是一种借助 stack.peek() O(1)的巧妙用法.
- Greedy: Remove 1) earlier digits(数位靠前权值大), 2) large digits
- Keep a increasing stack that:
- use stack.peek() to guard incoming digit
- if peek is larger than incoming digit, continue
stack.pop()
- Result: monotonous increasing stack. Print it in correct order.
- Goal: given a index i, want right-side closest & higer number
- Draw example: right-most number at base, and builds up monotonous stack (mountain shape)
- add smaller item on top of stack
- keep popping if peek is higher than incoming
- space: O(n), time:O(n)
- Refernece: https://leetcode.com/problems/daily-temperatures/solution/
- From right side:
-
- record tempIndex[currTemp] = i;
-
- Brutle find smallest temp index in range [currTemp + 1, 100] and record as result
-
- O(nk)
- 其实就是quick sort里面的partition function的简化版
- Two pointers, 找一个 pivot 'a' 来区分大写小写字母
- ASCII code 里面 大写字母在小写字母前面, 数字更小
- 然后 while, move start++, end--,
- 每一轮都swap
- 直接用两个 pointer left/right 标记开头结尾
- 每次遇到
>= 'a'
就是小写字母, swap(chars, i, left); - 每次遇到
< 'a'
就是大写字母, swap(chars, i, right); - 注意: 每次处理完left swap, 任由for loop i++, 因为确定 [0 left] 都是准确的
- 每次处理完 right swap, 我们不确定从 right index 换过来的是不是正确的, 所以 i--, 跟for loop 的 i++抵消.
- 写 while loop 的 solution看起来更容易理解.
- partition the array by pivot k = {0, 1, 2}
- 每一次partition都return starting point of the current partition
- 然后根据下一个 color, 去还没有sort 干净的那个部分, 再sort一下就好
- time O(kn), where k = 0 => O(n)
- 这里只是partion, 并不需要recursively quick sort, 所以结果是简单的O(n)
- have two pointers, left/right
- start tracks red, end tracks blue. Swap red/blue to right position, and left++ or right--.
- leave white as is and it will be sorted automatically
- be very careful with index i: when swapping with index right, we do not know what is nums[right], so need to re-calculate index i .
- O(n)
- Note: this one pass solution does not work if there are more than 3 colors. Need to use the regular quick sorty.
- TODO: count occurance and reassign array
- 分三份:a b c考虑
- 若a: countA++; 或b: countB++
- 或c:countA--, countB--
- 注意: 按照if statement的顺序, valA&&countA 比valB&&countB有优先性
- 最后出现的两个count>0的a和b,自然是potentially大于1/3的。其中有一个大于1/3.
- 比较countA和countB哪个大,就return哪一个。
- 找到个转角度的规律公式: r = c; c = (w - r)
- 用temp variable, swap in place.
- curr index is based on previous calculation: the min of all 3 previous factors
- O(n)
- 非常brutle的。
- 每次把dp[i-1]拿出来,不管三七二十一,分别乘以2,3,5. 出来的结果放进priority queue做比较。
- 最后时间是nlog(n3)
- 注意:use long, use HashSet确保没有重复
- O(nlogn)
- use list, iterative? keep candidates and populating
- clean up the dfs code, a bit messy
- edge case of "0001000" is invalid, right?
- A bit like BFS solution: find inner list, and then combine with outter left/right sides.
- find all solutions, DFS will be easier to write than iterative/BFS
- when n = 1, there can be list of candidates at bottom of the tree, so bottom->up is better
- bottom->up, dfs till leaf level, and return candidates.
- each level, pair with all the candidates
- 其实就是剥皮,一层一层,是一个central-depth-first的,钻到底时候,return n=1,或者n=2的case,然后开始backtracking。
- 难的case先不handle.到底之后来一次overall scan.
- every level have 5 choices of digital pairs to add on sides. Need to do for n-2 times.
- Time complexity: O(5^n)
- Basic implementation, array, enumeration
- 写一下position前进的方向: RIGHT->DOWN->LEFT->UP
- 用一个direction status 确定方向
- 写一个compute direction function 改变方向
(direction + 1) % 4
boolean[][] visited
来track走过的地方- enumerate all candidates and filter to keep the correct ones
- String.compareTo(string) -> gives lexicographical comparision
- 用HashSet存visited row/col/block.
- 在nest for loop里面validate row,col,and block.
- Special: validate block要利用i 和 j 增长的规律
- i, j are [0~n) can build block boundary in a for loop:
int c = 3 * (i % 3) + j % 3;
//make use of how i and j increasesint r = 3 * (i / 3) + j / 3;
- 单独做block validation: validate block的时候虽然看到了4层for. 其实也就是n^2
- 可能代码稍微复杂一点
- 注意String的index handle, 慢慢写edge case
- 理解题目意思:
read4(char[] buf)
这样的populate input object
的function稍微少一点. - 遇到时候, 仔细理解function用法, 不要慌乱. 其实思考方式很简单, 仔细handle string 还有 edge case就好了.
- alaternatively: use queue to hold so we do not need to worry about size
- 正面去做:
- count task出现的次数
- 然后PQ sort Task object in descending order
- 每个section: k slots = n + 1. Same task being n slots apart, meaning one section has n + 1 slots.
- 目标是穷尽 k, or 穷尽 pq (poll k times, but will save it back to queue if Task # > 0)
- 如果qp 真的穷尽, break, return count
- 不然, count += k, where k are just # of idle intervals
- time O(n) + constant time O(xlogx), where x = 26
- extra space O(x) ~ O(1)
- Enumerate to understand:
- 1.module tasks in module/section;
- 2.Only need sum the intervals/slots, not return actual layout
- Perfect case: all letters appear identical # times: just line them up separate in order.
- Real case: task appears different times
-
- Place maxCount task as header followed with n slots: define (maxCount-1) sections
-
- For tasks with less # than maxCount# can fill the (maxCount-1) sections; what about the tail section?
-
- Any task with same maxTask#, of if prior sections all filled, will fill the tail section
- To count overall slots/intervals, come up with this equation:
-
- Fixed sections:
(maxCount - 1) * (n + 1)
- Fixed sections:
-
- Plus all repeating maxCount tasks: calculate by couting identical maxCount of them
-
- Exception: if the first (max - 1) sections are all filled completely, and we still have extra task (ex: when n is not large enough), then just return tasks.length
-
- time O(n), space O(1)
- ??? Need to study
- compare left/right
- flip number (6 and 9), and then reverse the string, see if the string is the same.
- takes more
- 乘法原理, 加法原理
- 跟decode way I 一样, 加法原理, 切割点时: 当下是取了 1 digit 还是 2 digits 来decode
- 定义dp[i] = 前i个digits最多有多少种decode的方法. new dp[n + 1].
- 不同的情况是: 每一个partition里面, 如果有"*", 就会在自身延伸出很多不同的可能
- 那么: dp[i] = dp[i - 1] * (#variations of ss[i]) + dp[i - 2] * (#variations of ss[i,i+1])
- Enumeration:
- 具体分析 '*' 出现的位置, 枚举出数字, 基本功.
- 注意!!题目说 * in [1, 9]. (如果 0 ~ 9 会更难一些)
- 枚举好以后, 其实这个题目的写法和思考过程都不难
- Mode: 数字太大, 取mod来给最终结果: 其实在 10^9 + 7 这么大的 mod 下, 大部分例子是能通过的.
- DFS top-down approach is used to analyze the problem. The logic flow:
-
- consider the case of 1 letter or 2 letters.
-
- one letter:
- [*]: + 9 * dfs(s, i + 1)
- [0~9]: + dfs(s, i + 1)
-
- two letters:
- [*, *]: + 15 * dfs(s, i + 2)
- memo[i] records # of ways to decode from [i ~ n]
- space: O(n), Size of recursion tree can go upto n
- time: O(n),
memo array is filled exactly once
!!! - greedy approach: line up as many words as possible; once exceed the MaxLength, justify the list of words
- Steps
-
- Split & group
-
- Juststify a row of words
-
- clean up last row
-
- Calcualte bounded row length =
width + (list.size() - 1)
.list.size()-1
= Minimum amount of slot/space used. - Calculate max ave spaces ot insert into each slot =
totalExtraSpace/slot
Juststify a row of words
:-
- take a list of words and assume minimum 1 space in-between words
-
- distribute the remaining spaces evenly to each slot
-
- Overall runtime: O(n) to go over all space
- Overall space O(maxWidth) for maxWidth amount of strings
- 理解题目: 其实就是track
可以read多少bytes by read4() response
- 另外一个有用的function
System.arraycopy(src, srcIndex, dest, destIndex, length)
- Edge Case:
- When there is not enough space to the result buffer,
i + 3 > n
, then only copy what we can:Math.min(n - i, count)
count < 4
meaning there is not enough content to read, break
- When there is not enough space to the result buffer,
- 基本implementation
分类讨论
: thounsand, million, billion.3个数字一格
.- 用array枚举 token
- 运用 % 和 / 来找到每个分段的英语翻译
- 3-digit 的部分, 可以用一个helper funtion来找到结果, 每段的处理方法都是一样的
- Note:
- StringBuffer 更有效率!
sb.insert(0, xxx)
append在sb前面 - 注意加 " " 的时候, 如果多余, 要
trim()
- 注意,
小于20的数字, 有自己的特殊写法, 需要额外handle
- 这道题目就是要细致
耐心
, 几乎么有什么算法, 就是想要写的efficient并且正确, 需要很小心
- StringBuffer 更有效率!
- Thinking process:
1 ~ 19
: [one, two ... nine, ten, eleven, ...., ninteen]20 ~ x0
: [twenty, thirty, fourty, ... ninety]x00
: hundred: 100- thousand: 10^3
- million: 10^6
- billion: 10^9
- trillian: 10^12 way over 2^31, not needed
- plan:
- parse 3 digits at a time
- convert the 3 digit to [xx hundred xx-ty x]
- come up with a string[]
- insert the thousands/million/billion to the string[]
- 遇到
.
,e
,+/-
,int
的几种不同情况 - 分别遇到的顺序不同时候, 结果也不同.
- 这道题更多是分析情况, 然后把edge case enumerate出来, 算法的意义比较少.
- Continuous product can be positive/negative/zero
- If nums[i] > 0, want prior largest product[i-1] * nums[i]
- If nums[i] < 0, want prior smallest product[i-1] * nums[i]
- If nums[i] == 0, product = 0
- `prior product[i-1]: 想到DP
-
- 正负数情况, 需要用两个
PreProduct
array: minProduct[], maxProduct[]
- 正负数情况, 需要用两个
-
- continuous prodct: it has to utilize curr nums[i]
- 是跟nums[x]当下值比较的, 如果当下值更适合, 会舍去之前的continous product, 然后重新开始.
- Use a global variable to hold overall result.
-
- Time/Space O (n)
- Space optimization, rolling array
- maxProduct && minProduct 里面的 index i, 都只能 i - 1相关, 所以可以省去redundant operatoins
- Time: O(n)
- space: O(1)
- same concept as method1, but simplified: given that we always have to use nums[i], so only 1 result can be passed on
- FAST, simple to write and read
- time: O(n)
- space: O(1)
- working solution, BUT Time/Space complexity O(n^2) are too much
- 分析普通做法, 了结到用从左到右一遍O(n), 从右到左一遍 O(n) 就可以
- 注意carry的维护
- 第一轮:PreProduct (跟preSum的感觉有点像)
- PreProduct[i] stores product from num[0] -> num[i-1] (skipping current num[i])
- init preProduct[i] = 1, as base for product
- 错过一位操作: always
preProduct[i] *= carry;
andcarry *= nums[i]
- 第二轮: 从右边乘起, 每次在index i, 收到的carry都是
nums[i+1] *....* nums[end]
- 第一轮的结果 * 第二轮的结果, 刚好在index i 缺少掉 nums[i]. 如题所愿.
- Time: O(n)
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Pre-order-traversal 就能记录下 Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- Note: label需要是String. 虽然 Operator是长度为1的char, 但是数字可为多位
- 计算 expression 的值: 1. 建造 expression tree. 2. DFS计算结果
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- build好Min Tree以后,做PostTraversal.
- Divde and Conquer: 先recursively找到 left和right的大小, 然后evaluate中间的符号
- Time, Space O(n), n = # expression nodes
-
- Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
-
- 若有个child是null,那就return另外一个node。
-
- prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.
- 等于dfs遍历了所有k1<= x <= k2的x node。
- dfs left, process root, then dfs right
- 这里, 把 left/right/match的情况全部cover了,然后把k1,k2的边框限制好,中间就全部遍历了。
- Recursively 在segment tree里面找index, update it with value.
- 每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下
- 最后轮回到头顶,头顶一下包括头顶,就全部都是max了
- 和Max-tree一样,https://leetcode.com/problems/maximum-binary-tree
- 用到bottom->top递增的stack: 最底下的root维持成最小的element.
- 这个题目是Min-tree, 头上最小,Logic 和max-tree如出一辙
- Space: O(n)
- Time on average: O(n).
- TreeNode: 用一个并不是最终结果的TreeNode, 存weight, 用来排序
- 用base weight的概念权衡同一个层面的 符号, 数字 顺序
- 每一个character都是一个节点, 都有自己的weight. 用一个TreeNode来存weight value, 利用用weight来判断:
-
- (while loop) 如果node.val <= stack.peek().nodeValue, 把当前stack.peek() 变成 left child.
-
- (if condition) 如果stack有残余, 把当前node变成 stack.peek().rightChild
- 和 Segment Tree Query I 以及其他Segment Tree类似: 这个SegmentTreeNode return count of elements in range
- 这个题目考了validate input source:input 的start,end可能超出root[start,end]。
- 那么第一步就要先clear一下: 1. 完全不在range就return 0. 2. 有range重合就规整到root的range.
- Expression Tree是一个 weight-based的 min-tree
- 基于 运算符号 + 数字的 tree: 数字永远在leaf, 然后符号是tree node, 括号不出现在tree里面
- 用 monotonuous stack 来构建这个tree
- Understand Expression Tree
- Use stack to build the expression tree + understand the weight system
- Use post-order traversal to evaluate the tree
- 注意, input里面的数字不会是single digit, 所以需要一个buffer存number string
- 整个题目的做法, 可以参照
Expression Evaluation
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Post-order-traversal 就能记录下 Reverse Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
-
给的是Array. 注意找区间内的max, assign给区间. 其余和普通的segment tree build一样
-
注意, segment tree是根据array index range 排位: 根据index in [0, array.length - 1]割开区间, break到底
-
最终start==end做结尾
-
这道题要trackmax, 那么在leaf node assign max=A[start] or A[end]
-
往上,parent一层的max:就是比较左右孩子,其实都是在两个sub-tree里面比较sub-tree的max。
-
Devide and Conquer
-
先分,找到left/right,比较max,在create current node,再append到当前node上面。
-
实际上是depth-first, 自底向上建立起的。
- 根据[start,end]跟 mid of (root.start, root.end) 做比较:
-
- [start,end] on LEFT of mid
-
- [start, end] on RIGHT of mid
-
- [start, end] includes mid: break into 2 queries
- query [leftNode, start, node.left.end]
- query [rightNode, node.right.start, end]
-
- Usage
- which of these intervals contain a given point
- which of these points are in a given interval
- Recursively build the binary tree
- 左孩子:(A.left, (A.left+A.rigth)/2)
- 右孩子:((A.left+A.rigth)/2+1, A.right)
- the special requirement causes: we have to track exact position of nodes
- Using
Node {int offset, level, val}
andMap<offset, Map<level, List<Val>>>
:- set all nodes to its correct position
- output all together
- the
max/min
offset allows us to loop over the map in a ordered manner (save efforts of sorting) - time: O(n) to mark all nodes at correct spot, but
O(nlogn)
to sort the vertical array - space: O(n), mark all nodes in the nested map
- A (offset, level) has 2 nodes: use nested
Map<offset, Map<level, List<Val>>>
to track nodes - Also need a
class Node{int offset; TreeNode node}
to build queue:- need
offset
: queue at each level cannot derive level index - need
TreeNode
:Node
extends originalTreeNode
so we can queue it.
- need
- lots code to write due to the
class Node
for BFS - Find all paths, bfs/dfs all works. dfs will be simplier to write
- Recursive:分叉. dfs.
- top->bottom: enumerate current node into the list, carry to next level, and backtrack
- top->bottom is trivial to consider: path flows from top->bottom
- time: visit all n nodes
- space: to hold all paths, O(nlogn)
- O((n-1)/2) = O(n) nodes at leaf
- O(logn) depth
- We can also take current node.left or node.right to generate list of results from the subproblem
- let dfs return list of string candidates, and we can run pair the list with currenet node, once they come back.
- TODO: can write code to practice
- Iterative, 非递归练习了一下
- 因为要每次切短list, 所以再加了一个Stack 来存level
- 单这道题用dfs更简单, 因为找的就是从头到尾的path, 是dfs的pattern
- DFS to return the tail of the flattened list
- Careful handling the null child. Choose one and both works:
- Option1: put non-null as right child and continue dfs
- Option2: put non-null as left child and continue dfs
- latten the tree, no extra space.
-
- Set right node in buffer, and connect:
root.right = root.left
, DFS flatten(root.right)
- Set right node in buffer, and connect:
-
- 移花接木: traverse to tail of the current root.right and attach the buffer node.
-
- flatten the remaining of buffer
-
- 顺序一定要清楚, 不能写错, 写几个example可以看出来
- 移动的那些node, 要把node.left = null, 清扫干净
- Random choose: think about reservoir sampling. https://www.youtube.com/watch?v=A1iwzSew5QY
- Use random generator rd.nextInt(x) pick integer between [0, x)
- try all numbers, when target is met, we want to model reservoir sampling:
- item was chosen out of i samples, and all other samples are failed.
- where we can use 'count' to represent the denominator/base to choose.
**HAVE TO finish all samples** to make sure equal opportunity
- we can pick that last matched item as result
rd.nextInt(count++) == 0
make sure we are always picking num == 0 to meet definition of reservoir sampling.- probability theory:
- If multiply these probablities together to get the probability of one item being chosen with reservior sampling:
- probability = 1/i * (1 - 1/i+1) * (1 - 1/i+2) ....(1 - 1/n) = 1/n
- find slow/fast to detect the meeting point
- find begin node of the cycle: traverse from head, also move slow; utill head/slow meets slow
- Imagine two runners running on a track at different speed. What happens when the track is actually a circle?
- https://leetcode.com/problems/linked-list-cycle/solution/
- O(1) sapce: 用快慢指针,
start=head.next
,end=head.next.next
- Fast pointer will eventually catch up to slow pointer
- O(n) space: 用HashMap,一直add elements. 如果有重复,那么很显然是有Cycle
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Pre-order-traversal 就能记录下 Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- Note: label需要是String. 虽然 Operator是长度为1的char, 但是数字可为多位
- 计算 expression 的值: 1. 建造 expression tree. 2. DFS计算结果
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- build好Min Tree以后,做PostTraversal.
- Divde and Conquer: 先recursively找到 left和right的大小, 然后evaluate中间的符号
- Time, Space O(n), n = # expression nodes
-
- Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
-
- 若有个child是null,那就return另外一个node。
-
- prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.
- 和Max-tree一样,https://leetcode.com/problems/maximum-binary-tree
- 用到bottom->top递增的stack: 最底下的root维持成最小的element.
- 这个题目是Min-tree, 头上最小,Logic 和max-tree如出一辙
- Space: O(n)
- Time on average: O(n).
- TreeNode: 用一个并不是最终结果的TreeNode, 存weight, 用来排序
- 用base weight的概念权衡同一个层面的 符号, 数字 顺序
- 每一个character都是一个节点, 都有自己的weight. 用一个TreeNode来存weight value, 利用用weight来判断:
-
- (while loop) 如果node.val <= stack.peek().nodeValue, 把当前stack.peek() 变成 left child.
-
- (if condition) 如果stack有残余, 把当前node变成 stack.peek().rightChild
- Expression Tree是一个 weight-based的 min-tree
- 基于 运算符号 + 数字的 tree: 数字永远在leaf, 然后符号是tree node, 括号不出现在tree里面
- 用 monotonuous stack 来构建这个tree
- Understand Expression Tree
- Use stack to build the expression tree + understand the weight system
- Use post-order traversal to evaluate the tree
- 注意, input里面的数字不会是single digit, 所以需要一个buffer存number string
- 整个题目的做法, 可以参照
Expression Evaluation
- Expression Tree: Minimum Binary Tree (https://lintcode.com/en/problem/expression-tree-build/)
- 根据题意做出Expression Tree出来以后: 来个Post-order-traversal 就能记录下 Reverse Polish Notation
- 本题没有给'ExpressionTreeNode', 所以把TreeNode就当做成我们需要的node, 里面扩展成有left/right child就可以了.
- 根据给定的性质, 其实点选的极端一点: x = 最下面的row, y = 当下一行里面最小的left position.
- (x,y)在左下角
- 在此情况下, 只能往一个方向运行: 如果小于target, y++; 如果大于target, 那么只能x--
- 每次操作, 都是删掉一行, 或者一列, 再也不需要回头看
while (x >= 0 && y < col) {}
确保不会跑脱- 同样的方式: 可以从右上角(0, col - 1) 开始, 代码稍微改一改
- TODO
- Binary Search 的一种变型, LintCode无法再跑一边.
- 考虑mid-1, mid+1.
- 一旦没有mid = target.index。 那么target最终就narrow down在(mid-1,mid) 或者(mid,mid+1)
- SegtmentTree, methods: Build, Query. 这题是在SegmentTreeNode里面存min.
- 类似的有存:max, sum, min
- 和平时的segment tree问题不同。 [0 ~ n] 代表实际数字: based on real value的segment tree.
- Modify时,把array里面的value带进去,找到特定的位子, 然后count + 1.
- 最终在SegmentTree leaf上面全是array里面实际的数字。
- node.count: 在node range里面的有多少个数字
- build() 只是 empty segment tree, 没有property
- modify() 需要: 1. 找到left, update count+=1; 2. aggregate all parent when after returning
- 所以每一个modify 都是在整个path上所有的node上 + count
- 在query前,给进去的start和end是: 0 ~ value-1.
value-1
: 找比自己所在range小1的range(那么自然而然地就不包括自己了),这样就找到了smaller number.- [那么其他做过的SegmentTree是怎么样呢?]
- 那些构成好的SegmentTree(找min,max,sum)也有一个Array。但是构成Tree时候,随Array的index而构架。
- 也就是说,假如有Array[x,y,....]:在leaf,会有[0,0] with value = x. [1,1] with value = y.
- [但是这题]
- 构成时,是用actual value.也就是比如Array[x,y,....]会产生leaf:[x,x]with value = ..; [y,y]with value =...
- 其实很容易看穿:
- 若给出一个固定的array构成 SegmentTree,那估计很简单:按照index从0~array.lengh,leaf上就是[0,0] with value = x.
- 若题目让构造一个空心SegmentTree,
based on value 0 ~ n-1 (n <= 10000)
, 然后把一个Array的value modify 进去。 - 这样八成是另外一种咯。
- 看到log(m+n), 就想到binary search, 或者是recursive 每次砍一半
- 两个sorted array 参差不齐, 肯定不能做简单的binary search
- 这里有个数学排除思想: 考虑A, B各自的中间点.
- 如果A[mid] < B[mid], 那么 A[0 ~ mid - 1] 就不在 median的range里面, 可以排除. divide/conquer就这么来的.
- 具体逻辑看代码, 大致意思就是: 每次都取比较A 和 B [x + k / 2 - 1] 的位置, 然后做range 排除法
- end cases:
-
- 如果我们发现dfs()里面A或者B的start index溢出了, 那么就是最简单的case: midian一定在另外那个array里面
-
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
Math.max(A[startA], B[startB])
就可以
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
- 总共的数字长度是 (m + n) 而且每次都有一般的内容被删除, 那么time就是 O(log(m + n))
- 全部是positive integer, 那么preSum一定是增长的.
- 那其实就用two pointer:
start=0, end=0
不断往前移动. 策略: -
- end++ until a solution where sum >= s is reached
-
- 然后移动start; 记录每个solution, Math.min(min, end - start);
-
- 然后再移动end,往下找
- Note: 虽然一眼看上去是nested loop.但是分析后,发现其实就是按照end pointer移动的Loop。start每次移动一格。总体上,还是O(n)
- O(nlogn) NOT DONE.
- O(n^2), inefficient
- 最左端: 当start=0, end = 2 => mid = 1, mid-1 = 0;
- 最右端: 当end = n - 1, start = n - 3; mid = (start+end)/2 = n - 2; 那么mid + 1 = n - 2 + 1 = n - 1 < n 是理所当然的
- envelopes没有顺序, 先排序 (主要根据第一个index排序)
- 然后观察: 排序过后, 就变成了1D的坐标动态规划.
- max number 取决于上一个成功Russian doll的 max value + 1
- 上一个index不知道, 所以遍历找上一个index.
- 当下index i 的状态, 取决于前面index j 的状态, 所以遍历两个index.
- O(n^2)的DP, n = envelopes.length;
- 这个方法是自己想出来的, 但是时间复杂度太大, timeout
- 把envelop标记在2D grid上面, 然后像走机器人一样, 求到最右下角的最大 count max.
- count 当下能存在多少Russian doll
- 两种情况: 当下coordinate 没有target, 当下coordinate有target
- 当下coordinate 没有target: 如同机器人走法, Math.max(dp[i - 1][j], dp[i][j - 1])
- 当下coordinate 有target: dp[i - 1][j - 1] + dp[i][j]
- timeout: O(n^2), n = largest coordinate.
- 我们不可能一口气准确定位(x,y), 但是我们可以再一个row/col里面, 找到1D array的 peak.
- 根据这个点, 再往剩下两个方向移动
-
- 在中间的一行i=midX, 找到peak所在的y.
-
- 在中间的一列j=midY, 找到peak所在的x. (有可能强势override之前找到的y, 也就是放弃那一行的peak, 在midY上找peak)
-
- 根据 (x,y) 的4个neighbor check (x,y)是不是 peak, 如果不是, 像更高的位置移动一格
-
- 根据之前算的 midX, midY 把board分成4个象限, 在每一份里面再继续找
- 这个题目LintCode不给做了, 所以思路对的, 但是解答还没有再次验证.
- 每次只是找到一个row/col里面的peak而已!
- 找到这个点, 就等于把board切成了两半.
- 然后, 再跟剩下的相邻的两个位置比较, 就知道了哪里更大, 就去哪里找peak, 也就是又切了第二刀.
- 切第二刀的时候, 也要把(x, y) 移到需要取的象限. 进行DFS
- 根据mid row 切割:
- http://www.jiuzhang.com/solution/find-peak-element-ii/#tag-highlight-lang-java
- http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf
- 每一个level都减一半
- T(n) = n + T(n/2) = n + n/2 + n/4 + ... + 1 = n(1 + 1/2 + .... + 1/n) = 2n = O(n)
- TODO
- O(nLogN)
- 找subsequence: 不需要continous, 可以skip candidate
- 考虑nums[i]结尾的时候, 在[0, i), dp[i - 1] 里count有多少小于nums[i]
- dp[i]: 到i为止 (对于所有 j in [0, i], 记录max length of increasing subsequence
- max需要在全局维护: nums是无序的, nums[i]也可能是一个很小的值, 所以末尾dp[i]并不是全局的max, 而只是对于nums[i]的max.
- 正因此, 每个nums[i]都要和每个nums[j] 作比较, j < i.
- dp[i] = Maht.max(dp[i], dp[j] + 1); j = [0 , i - 1]
- 时间复杂度 O(n^2)
- 维持一个list of increasing sequence
- 这个list其实是一个base-line, 记录着最低的increasing sequence.
- 当我们go through all nums的时候, 如果刚好都是上升, 直接append
- 如果不上升, 应该去list里面, 找到最小的那个刚好大于new num的数字, 把它换成num
- 这样就完成了baseline. 举个例子, 比如替换的刚好是在list最后一个element, 等于就是把peak下降了, 那么后面其他的数字就可能继续上升.
- '维护baseline就是一个递增的数列' 的证明, 还没有仔细想.
- while: start + 1 < end
- mid = start + (end - start) / 2;
- 根据mid作比较
- 末尾double check start, end.
- Binary Search的思想, 用在所要找的这个 average sum 上面. 大小是在[min, max]之中
- 找k的时候, 是>=k都可以, 巧用一个 min(preSum)的概念.
- 找k的时候, 画图, 可以看出来, 其实要的是 k window 里面的sum [x, i], 所以要用 sum[0, i] - sum[0, x]
- 第一步, 理解题目要求的问题: 前k个人copy完n本书, 找到最少的用时; 也可以翻译成:
n本书, 让k个人来copy, 也就是分割成k段
. - 最后需要求出 dp[n][k]. 开: int[n+1][k+1].
- 原理:
-
- 考虑最后一步: 在[0 ~ n - 1]本书里, 最后一个人可以选择copy 1 本, 2 本....n本, 每一种切割的方法的结果都不一样
-
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
Math.max(dp[j][k - 1], sum)
.
- 讨论第k个人的情况, 在 j = [0 ~ i] 循环. 而循环j时候最慢的情况决定 第k个人的结果(木桶原理):
-
- 其中:
dp[j][k-1]
是 [k-1]个人读完j本书的结果, 也就是著名的上一步
. 这里循环考虑的是第k个人不同的j种上一步 : )
- 其中:
-
- 循环的结果, 是要存在 dp[i][k] = Math.min(Math.max(dp[j][k - 1], sum[j, i]), loop over i, k, j = [i ~ 0])
- Time: O(kn^2), space O(nk)
- Init: dp[0][0] = 0, 0个人0本书
- Integer.MAX_VALUE的运用:
- 当 i = 1, k = 1, 表达式: dp[i][k] = Math.min(dp[i][k], Math.max(dp[j][k - 1], sum));
- 唯一可行的情况就只有一种: i=0, k=0, 刚好 0 个人 copy 0 本书, dp[0][0] = 0.
- 其他情况, i = 1, k = 0, 0 个人读 1本书, 不可能发生: 所以用Integer.MAX_VALUE来冲破 Math.max, 维持荒谬值.
- 当 i=0, k=0 的情况被讨论时候, 上面的方程式才会按照实际情况计算出 dp[i][k]
- 这道题的init是非常重要而tricky的
- k个人, 需要一个for loop;
- k个人, 从copy1本书开始, 2, 3, ... n-1,所以 i=[1, n], 需要第二个for loop
- 在每一个i上, 切割的方式可以有[0 ~ i] 中, 我们要计算每一种的worst time
- [k] 只有和 [k - 1] 相关
- Space: O(n)
- 根据: 每个人花的多少时间(time)来做binary search: 每个人花多久时间, 可以在K个人之内, 用最少的时间完成?
- time variable的范围不是index, 也不是page大小. 而是[minPage, pageSum]
- validation 的时候注意3种情况: 人够用 k>=0, 人不够所以结尾减成k<0, 还有一种是time(每个人最多花的时间)小于当下的页面, return -1
- O(nLogM). n = pages.length; m = sum of pages.
- 排序好的array. Two pointer移动start和end,核查sum.
- 注意sum用long.
- O(n) time
- 定住一个valueA, 然后在剩下的里面 binary serach 找 (target - valueB)
- for loop O(n), binary search O(logn)
- overall time: O(nLogN), 就不写了
- 其实是segment tree 每个node上面加个sum
- 记得Segment Tree methods: Build, Query
- Note: 存在SegmentTreeNode里面的是sum. 其他题目可能是min,max,count ... or something else.
- build: recursively build children based on index-mid and link to curr node
- query: recursively try to find
node.start == targetStart && node.end == targetEnd
. Compare with node.mid - modify: recursively try to find
node.start == targetStart && node.end == targetEnd
; modify and recursively assign upper interval with updated interval property. - 和Merge K sorted Array/ List 类似:使用PriorityQueue。
- 因为Array的element无法直接找到next,所以用一个class Node 存value, x,y positions.
- Initial O(n) time, also find k O(k), sort O(logn) => O(n + klogn)
- 变型: Kth Largest in N Arrays
- we know where the boundary is start/end are the min/max value.
- locate the kth smallest item (x, y) by cutt off partition in binary fasion:
- find mid-value, and count # of items < mid-value based on the ascending matrix
- O(nlogn)
- n/2的奇数偶数
- n的正负
- n == 0的情况, x == 0, x == 1 的情况.
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
- 2个while loop
- 找first/last occurance
- TODO: Can the code be simplified?
- 一行一行是从小到大, sorted, 连续的, 可以看做1D sorted array
- Binary Search
- H-index的一个简单版, 已经sorted(从小到大), 找target value
- 按定义, 找最后一个
dictations[mid] >= h
, whereh = n - mid
- O(logn)
- 每次看最左left depth和最右leaf depth 是不是一样
- 如果一样, 直接 2 ^ h - 1就好
- 不一样的话, 再DFS
- calculate
2^(h)
: 位运算, Math.pow(2, h) = 2 << (h - 1). 神奇!- 2 << 1就是把所有bits往左移动一位, 也就是 * 2
- time: O(n) visit all nodes on 1 side
- space: O(h) visit all nodes on 1 side
- See details in comments inline. 要对tree非常理解
- binary tree one child tree nodes # = 2 ^ h - 1; 所以一个child tree + root = 2 ^ h
- total missing nums = nums[curr] - nums[0] - curr
- edge case: if k > total missing nums, then just add the diff from nums[end]
- otherwise, find this
missing count == k
in the nums using binary search - After binary search:
start + 1 == end
:- re-calculate
count = nums[start] - nums[0] - start;
- output final num:
nums[start] + k - count;
- re-calculate
- Option1: always compare total missing nums count
- Option2: compare partial missing nums count (inspired by: https://leetcode.com/problems/missing-element-in-sorted-array/discuss/303444/Java-O(logN)-solution-Binary-Search)
- Bianry serach on the min/max value range
- The mid value is calcualted with helper function
calc(piples, k)
- find celing:
count += (i - 1) / k + 1
, faster thanMath.ceil(i / k)
- time: O(logm) to find the best velocity, assume total range is m; O(n) for each
calc
call - 用到hashset找unique && duplicate: O(m+n)
- 可以用binary search 找数字.
- Note:binary search一定需要array sorted: nLog(m)
- What if
the processed list is sorted
, so that I can BinarySeach for curr target?- process from end
- binary search for
index to insert new element
in sorted ascending list - that index = # of smaller numbers; record it for final result
- time: O(nlogn)
- space: O(n)
- Segment Tree functions:
Build
: construct segment tree based on min/max range: at leaf node, update count of numbers in rangemodify(SegmentTreeNode root, int value, int count)
: find leaft at with value, and update count for leaf & all parent nodesquery(SegmentTreeNode root, int start, int end)
: return count # of numbers in range [start, end]
- Very similar to
Count of Smaller Number
, where segment tree is built on actual value!! - IMPORTANT to drop processed number from left-hand-side:
- only find on remaining numbers.
- Utilize
modify(root, target, -1)
to erase element count & update the tree.
- time:
n * log(m)
, where m = Math.abs(max-min). log(m) is used to modify() the leaf element - space: O(m)
Define the positive range
- negative nubmer division
rounds up towards 0
(this is a problem). (i.e.(-2 - 1) / 2 = -1.5 = -1
), which causes range error. - We want the entire segment tree range to be ascending, and we want the mid = (start+end)/2 to round down.
- Solution:
- build entire segment tree based on [min, max], where min must be >= 0.
- we can do this by adding Math.abs(min) onto both min/max, as well as +offset during accessing nums[i]
- negative nubmer division
- https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
- Assume we have a BST, where each node has smallerCount and a val, for any new target, how to find smaller items?
-
- add the # of smaller count to current node
-
- compare:
- if target < node.val, keep searching
countVisit(node.left, target)
- if target > node.val: 1) add currNode.smallerCount, 2) minus node.right.smallertCount (reduce double-counting), 3) plus
countVisit(node.right, target)
- remember to create left/right node before dfs countVisit into the sides.
-
- Also most identical to
33. Search in Rotated Sorted Array
:- find where nums[mid] lands by comparing to nums[start]. i.e., if nums[mid] < nums[start], on right half of the array
- when
nums[mid] == nums[start]
: duplicate. Shift by start++
- the worst case of
nums[mid] == nums[start]
willl cause O(n), - but if duplicate is not entire array, should be O(logn)
- binary search template: mid+1, mid-1,
start <= end
- define index as long.
- 记录找到过的closest
- Binary Search, 根据current node走位, until null leaf
- time: O(logn), space O(1) since no extra space used
- when less than curr val, consider left
- when greater than curr val, consider right
- dfs到底, 然后每一层比较, 再return
- time: O(logn), space: O(logn)
- binary search to find A[i-1] < A[i] < A[i+1]
- if [mid-1] < [mid+1], on left slope, start = mid
- if [mid-1] > [mid+1], on right slope, end = mid
- init: start == 1, end = n - 2;
- use hash to store <key, list of values>
- binary serach on list of values
- use hash to store <key, TreeMap<Timestamp, Value>>
- treemap.floorKey(timestamp) finds the top item below certain timestamp
- 理解题意, 从[0, x]找一个可以m*m=x的值.
- 注意, 如果找不到, 最后问考官该return一个什么值:按道理,因为return int, 会取整,那么return一个平方最close to x就可以.
- 注意 mid 用 long, 因为很可能超过最大int.
- 二分float number, 应该用精度来定义结尾.
- 还是二分, 但是判断条件变成: while ( end - start > eps)
- eps = 1e-12,也就是精度到1e-12
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- Map of nums1: <num, # appearance>
- check nums2 against nums1 map
- time:O(n + m)
- space:O(n + m)
- 关键点, 是找到 [mid]是在左边/还是右边的continous increasing subarray: 比较
A[start] < A[mid]
- 在两个section 里面分别讨论 target 的位置
-
nums[start] < nums[mid]
: start是从index=0开始的, 那就说明mid在前半段
-
nums[start] > nums[mid]
: start是从index=0开始的, 那就说明mid在后半段
-
- Binary search template:
-
start + 1 < end
(adjacent indexes)
-
- start/end = mid,
-
- compare start and end individually
-
-
- binay search break point
-
- binary search target
- 注意等号,在判断target在前半段还是后半段:if (A[p1] <= target && target <= A[breakPoint])
- need search left bound & right bound.
- use input parameter
direction
to binary search function - Option0: simplification, inspired by
278. First Bad Version - Method1: Check is-NOT-BadVersion
-
- if found match, but NOT sure it is desired boundary, just leave it and keep going
-
- check the final results after
binary search while loop
completes
- check the final results after
- WHY? code is easier to read in this way.
-
- simply binary Search: if not bad, assign
start = mid+1
- 根据isBadVersion的性质,判断还如何end=mid or start=mid.
- A bit more code to handle
- number of test cases is small, like 1000, so less concern about the time complexity
- simply loop over the list of intervals, and check if any overlapping.
- where to insert does not really matter: every time we are just checking for overlaopping, not merging any range
- IMPORTANT: if interval over lapping, they will have this property
Math.max(s1, s2) < Math.min(e1, e2)
. This will help detect the overlapping very easily. - O(n^2) runtime, with simple code. But somehow this approach is faster than the TreeMap solution: maybe the test cause causes avg O(n)?
- One constraint from the simply array solution: it always cost O(n) to find the potential overlapping interval
- We can manually sort and always manually try to find the correct element via binary search, or we could store the interval in a treeMap<startKey, endValue>, where the intervals are sorted by
start
. - As result, all we need to do for book(start, end) is to find the next element ceiling(start), last element floor(start), and check for overlapping
- This approach also saves the custom data structure
- Overall cost O(nlogn)
- always with key sorted ascendingly
- more costly than regular HashMap because of the sorting. Building treemap of n items: O(nlogn)
- use
Point{int start, end; boolean start}
to mark start/end of class. Add to pq. - Adding new item to pq, sort, and check if overlapping occurs by counting started classes
- If started classes > 1, that means we overlapped.
- Every time it could consume all classes to find the overlap, O(n^2).
- Not quite need to sort or insert at correct point, and this solution requires longer code. Not quite worthy it for a simple problem.
- Use priority queue to sort by customized class interval{int dist; int x, y;}.
- Sort by larger distance and then sort by start index
- seat(): pq.poll() to find interval of largest distance. Split and add new intervals back to queue.
- leave(x): one seat will be in 2 intervals: remove both from pq, and merge to a new interval.
- 主方程写出来其实很好写, 就是 split + add interval, 然后 find + delete interval 而已. 最难的是构建data structure
- seat(): O(logn), leave(): O(n)
Trick: 构建虚拟 boundary
- 如果是开头的seat, 或者是结尾的seat, 比较难handle: 一开始坐在seat=0的时候, 没有interval啊!
- Trick就是, 我们自己定义个虚拟的座位
seat=-1
,seat=N
- 一开始有一个 interval[-1, N] 然后就建立了boundary.
- 从此以后, 每次split成小interval的时候:
- 遇到
interval[-1, y]
, distance就是(y - 0)
- 遇到
interval[x, N]
, distance就是(N - 1 - x)
- 当然正常的interval dist 就是
(y - x) / 2
- distance 中间点
- Interval.dist 我们其实做的是 distance的中间点
(y - x) / 2
- 这里的dist是
距离两边的距离
而不是 x, y 之间的距离. 这里要特别注意.
- Interval.dist 我们其实做的是 distance的中间点
- TreeSet
- TreeMap<starting Pos, Interval>
- seat(): O(logn)
- find largest dist with TreeSet.first()
- break into 2 intervals; save to set and save to map
- leave(x): O(logn)
- find the interval before starting point x using TreeMap.floorEntry()
- merge and store back to set/map
- for test case it is slower than PQ, because it saves to 2 data structure
- use hash to store <key, list of values>
- binary serach on list of values
- use hash to store <key, TreeMap<Timestamp, Value>>
- treemap.floorKey(timestamp) finds the top item below certain timestamp
- store efficiently: use List<Map<snapId, val>>. only preserve changed itemd
- if no match, find last modifed item based on snapId, use TreeMap.floorEntry
- map.floorEntry(id) return the item.key lower or equal to id
- Utilize a
buffer: Map<Integer, Integer>
and perform atomic save - one to keep regular elements
- one to repat the max at current stack level
- time: O(n) for popMax() and O(1) for the rest operations
- space: O(n)
- Reference: https://leetcode.com/problems/max-stack/solution/
- Use TreeMap to store <Int, List of Nodes>, which gives: O(logN) insert, delete and find MAX
- Key reason to use
DoubleLinkedList
is to perform O(1) removal forpopMax()
- The problem becomes finding the target value & remove from DoubleLinkedList
- time: O(1) for popMax() and O(logN) for the rest
- space: O(n)
- https://leetcode.com/problems/count-of-range-sum/discuss/77990/Share-my-solution
-
- build preSum[n+1]: then sum range [i,j]= preSum[j+1] - preSum[i]
-
- Divide and Conquer:
- 先考虑[start, mid] range里的 ran sum result
- 再考虑[mid, end] range里面的结果
- 最后考虑[low, high]总体的结果
- NOTE: should write merge() function, but that is minor, just use
Arrays.sort(nums, start, end)
, OJ passed - Every mergeSort() has a for loop => O(n log n)
- 如何 count range?
- 这里比较特别的一个做法: 找一个 [low, mid]里面的i, mid 之后的preSum作比较 (解释源自: https://blog.csdn.net/qq508618087/article/details/51435944)
- 即在右边数组找到两个边界, 设为
m, n
, - 其中m是在右边数组中第一个使得
sum[m] - sum[i] >= lower
的位置, - n是第一个使得
sum[n] - sum[i] > upper
的位置, - 这样
n-m
就是与左边元素i所构成的位于[lower, upper]
范围的区间个数.
- 边界[lower, higher] 在 sorted array 好作比较, 一旦过界, 就可以停止计算, 减少不必要计算.
- 上面这个n,m的做法可行的前提: preSum[]里面前后两个 range[low, mid], [mid, high]已经sorted了
- 也就是说, 在recursively mergeSort()的时候, 真的需要merge sorted 2 partitions
- 也许会问: 能不能sort呢, sort不久打乱了顺序? 对,打乱的是preSum[]的顺序.
- 但是不要紧: 很巧妙的, 分治的时候, 前半段/后半段 都在原顺序保留的情况下 分开process完了, 最后才merge
- 在做m,n 的range的时候, 原理如下, 比如preSum被分成这么两段:
[A,B,C]
,[D,E,F]
- 每一个preSum value
A
在跟 preSum[i] 作比较的时候A - preSum < lower
, 都是单一作比较, 不牵扯到 B, C - 因此,
[A, B, C]
是否保留一开始 preSum的顺序在此时不重要
- 每一个preSum value
- 此时最重要的是,
[A,B,C]
以及排序好, 那么在于lower
boundary 作比较的时候, 一旦过界, 就可以停止计算(减少不必要的计算) - TODO?
- This segment tree approach(https://leetcode.com/problems/count-of-range-sum/discuss/77987/Java-SegmentTree-Solution-36ms)
- does not build segment tree based on given nums index
- it is built on sorted preSum array.
- regular segment tree based on nums array does not work:
- segment tree based on input array is good for: search/query by index
- is NOT good at: given range sum/value, find indexes
- why? segment tree is built based on index division, not by range value division.
- Analysis (inspired by, but not same solution: https://leetcode.com/problems/the-skyline-problem/solution/)
- If there are just 2 overlapping building (totally 4 points on x-axis), here is the outline process:
- Process x coordinate from left->right, one at a time.
-
- compare all
on-going heights
and find max, add as new outline point
- compare all
-
- Handling building end: if the position ends a building, need to remove this height from the list of
on-going heights
- Handling building end: if the position ends a building, need to remove this height from the list of
-
- Requires 2 heap:
- sort by x coordinates
on-going heights
: maintain a pq of ongoing heights
- Steps:
- original reference http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt
- 画图分析: 需要找到 non-overlaping height point at current index; also height needs to be different than prev height peek to be visible.
on-going heights
: 用max-heap (reversed priorityqueue),再iterate heightPoints 来存最大的height- NOTE: heightQueue里面加一个0, 用来在结尾的时候做closure
- time: initial sort O(nlogn) + calculate n * O(nlogn) [maxQueue sort]
- space: O(n)
- 看了一些做法, segment tree写法很复杂, 估计在面试中难以用segment tree来写: https://www.cnblogs.com/tiezhibieek/p/5021202.html
- HashHeap template 可以考虑: https://www.jiuzhang.com/solution/building-outline/#tag-highlight-lang-java
- rotate all
- rotate 2 sides: < k or >=
- Given assumption: 1)
only 1 celebrity
, 2) person k, who knows nobody ahead of him or after him. - if first pass finds candidate,
person k
, it means:- person [0, k-1] are not celebrity: they know a previous or current candidate
- person k knows no one between [k + 1, n): k+1 to n-1 can not be the celebrity either.
- person k is just the last standing possible celebrity
- second pass validation: we do not know if
knows(celeb, [0~k-1] )
. Do a final O(n) check - time:O(n), space O(1)
- DO NOT: Brutle compare all -> all: O(n^2) handshakes.
- 先写出来[0 ~ n - 1], 最简单的方式 O(n^2) 检查, 记录每个人的状态.
- 逐渐发现, 因为 celeb 谁都不会认识, 那么当任何candidate knows anyone, 他自身就不是celeb.
- 我们可以greedy地, 一旦fail一个, 就立刻假设下一个是celeb candidate
- 最终还是要检查一遍, 避免错漏.
- 想一下happy case: 如果 celeb=0, 那么 know(celeb, i) 永远都是false, 然后 celeb一直保持0, 坚持到verify所有人.
- inorder and postorder does NOT work: it is hard to find mid point, since the tree is not balanced or complete
- Serilize: Divide and conquer, Pre-order traversal to link all nodes together
- build the string data: use '#' to represent null child.
- the preorder string, can be parsed apart by
split(',')
- Deserialize
- Use a queue to process 1 node at a time. dfs on remaining of the queue
- first node from the list is always the head
- '#' will be a null child: this should break & return dfs
- queue is shared, so dfs(right child) will happen after dfs(left child) completes
- Note:
- Append multiple stirngs with
sb.append(x).append(y)
- If want to process 1 item at a time from head of the list: make it a queue and poll()
- Append multiple stirngs with
- serialize: preorder using queue:
- start with root
- process curr node, then: queue.offer(leftNode),queue.offer(rightNode)
- while(!queue.isEmpty())
- deserialize:
- split into str[] to process
- since serialization ensures 2 children added (including null), we assume:
- the sequence of parent, left child, right child.
- use queue to reproduce the preorder sequence as we process each index of str[]
- Queue will not be empty until all index reaches end of str[], so no need to worry about queue emptiness
- 维持monotonuous queue:
front is always at max
and thetail end is min
. Always need to return the max end of queue. - when adding new elements x:
-
- start from small-end of the queue
-
- drop all smaller elements
-
- append to the ending element that is larger than x.
- This is to maintain a front->tail decreasing queue
-
- when sliding window: queue curr window 里面 最大的已经在max-end, remove it if needed.
- 妙:用deque数据结构(实际上采用LinkedList的形式)来做一个
递减的queue
: better than using arraylist, since DeQueue(linked list) removes at O(1) cost - 每次把小于当前node的,全部剔除,剩下的,自然就是:最大的>第二大的>第三大的...ETC.
- 我们只在乎最大值的存在;而任何小于当前(正要新就加进去的)值的,反正以后也成不了最大值,于是扔掉!
- Option1: sliding window template using right/left + while loop
-
- tailing the new number to max queue, if applicable
-
- process: record max
-
- contract/shrink left: remove top max if the topMax is the left-most val: rst[i - k + 1]
-
- Option2: same concept, but use index
i
to mark right, andi - k + 1
to mark left. - time: O(n), one pass
- space: O(k), store the deque
- can always build a
class Node{index, val}
; and sort them with PQ of size k - time: O(nlogK)
- space: O(k)
- this is not linear time, not as good as method1
- when counter != 2 , it will execute hydrogen() two times so that 'H' will reach 2
- when count == 2, it will execute oxygen() once so that 'O' will reach 2
- to start: H is at count 2 and O is at count 0. They need both be at 0 to be unlocked
- hydrogen():
h.acquire()
will execute 2 times until H.count is reduced to 0o.release
will add O.count by 1 for 2 times
- oxygen():
o.acquire(2)
can only occur when O.count == 2 due to the 2 calls inhydrogen(..)
h.release(2)
will restore the H.count back to 2
- semaphore: https://www.geeksforgeeks.org/semaphore-in-java/
- when counter != 2 , it will execute hydrogen() two times so that 'H' will reach 2
- when count == 2, it will execute oxygen() once so that 'O' will reach 2
- to start: H is at count 2 and O is at count 0. They need both be at 0 to be unlocked
- hydrogen():
h.acquire()
will execute 2 times until H.count is reduced to 0o.release
will add O.count by 1 for 2 times
- oxygen():
o.acquire(2)
can only occur when O.count == 2 due to the 2 calls inhydrogen(..)
h.release(2)
will restore the H.count back to 2
- semaphore: https://www.geeksforgeeks.org/semaphore-in-java/
- maintain the
farest can go
, and use it the target for i increse towards. Why?-
- when I know the
farest can go
, in fact it is just currently 1 step away.
- when I know the
-
- why to iterate from curr
i to farset
? In range [i, farest], we will calc the newmaxRange
- why to iterate from curr
-
- once
i
reachesfarset
, updatefarest = maxRange
- once
-
- greedy concept: once we know the farest we can reach at the moment, it is just 1 step away :)
- On average should be jumpping through the array without looking back
- time: average O(n)
- Impl:
- 图解 http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html
- track the farest point
- whenver curr index reachest the farest point, that means we are making a nother move, so count++
- there seems to have one assumption: must have a solution. Otherwise, count will be wrong number.
- 其实跟第一个greedy的思维模式是一模一样的.
- DP[i]: 在i点记录,走到i点上的最少jump次数
- dp[i] = Math.min(dp[i], dp[j] + 1);
- condition (j + nums[j] >= i)
- 注意使用 dp[i] = Integer.MAX_VALUE做起始值, 来找min
- time: O(n^2), slow, and timesout
- O(n)
- 两个pointer, 每次计较prev和curr之间的部分.
- 然后prev = curr,向前移动一格
- TODO: check the edge case and make sure max/min of int are checked
- 跟First Missing Positive 非常像, 只有一行代码的区别.
- swap 所有的数字, 到自己的correct position
- 最后一个for loop找到错位的index, 也就是缺的数字.
- XOR will only retain bits that are different 1 ^ 0 = 1, but 0^0, 1^1 == 0
- Use that feature, 把所有value都和index XOR了
- 剩下的多余的数字, 其实是那个index无法被XOR消掉, 也就是那个缺的number value.
- 注意: 题目告诉数字是 [0 ~ n], 然而缺一个数字, 那么在[0 ~ n - 1] 里面, 最大的数字(不管缺没缺), 一定是 n = nums.length.
- 全存, 找missing
- O(n) space, 不合题意
- sort, 找1st missing
- O(n log n) 太慢, 不合题意
- 其实跟给一个2D matrix没有什么区别, 可以做dfs, memoization.
- initialize memo: pathSum[i][j] = MAX_VALUE; 计算过的path省略
- Bottom-top: 先dfs到最深的path, 然后逐步网上返回
OR 原理: min(pathA, pathB) + currNode
- 浪费一点空间, pathSum[n][n]. space: O(n^2), where n = triangle height
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- 跟dfs的原理很像,
OR 原理: min(pathA, pathB) + currNode
- init dp[n-1][j] = node values
- build from bottom -> top: dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
- 跟传统的coordinate dp有所不同, inner for loop 是需要计算 j <= i, 原因是triangle的性质.
- 空间: dp[n][n]. space: O(n^2)
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- Based on the DP solution: the calculation always depend on
next row
for col atj
andj + 1
- 既然只depend on next row, 可以用rolling array来处理: reduce to O(n) space.
- Further: 可以降维, 把第一维彻底去掉, 变成 dp[n]
- 同样是double for loop, 但是只在乎column changes:
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
- 用一个list as the buffer to store candidates
- when: 1. end of nums; 2. not continuous integer => convert list to result
- due to access permission, not test
- dp[i][j]: longest continuous subsequence length at coordinate (i, j)
- dp[i][j] should come from (i-1,j) and (i, j-1).
- dp[0][0] = 1
- condition: from up/left, must be increasing
- return dp[m-1][n-1]
- O(mn) space for dp and flag.
- O(mn) runtime because each spot will be marked once visited.
- 这个题目的简单版本一个array的例子:从简单题目开始想DP会简单一点。每个位置,都是从其他位置(上下左右)来的dpValue + 1. 如果啥也没有的时候,init state 其实都是1, 就一个数字,不增不减嘛。
- 要想看连续元素, 必须要num++, num--这样搜索
-
- 需要O(1)找到元素
-
- 需要简单快速找到 num - 1, num + 1.
- 如果用min,max开array, 耗费空间
- 用HashSet来存, 用set.contains() 来查找 num - 1, num + 1 存在与否
- for loop. O(n)
- 里面的while loop 一般不会有O(n); 一旦O(n), 也意味着set 清零, for loop也不会有更多 inner while 的衍生.
- overall O(n) 时间复杂度
- 最终是要把相连的元素算一下总长, 其实也就是把元素group起来, 相连的group在一起, 于是想到UnionFind
- 这里用到了一个
int[] size
来帮助处理合并的时候parent是哪个
的问题: 永远往group大的union里去 - main function 里面, 有一个map来track, 每个元素, 只处理1遍.
- union的内容: current number - 1, current number + 1
- https://www.jianshu.com/p/e6b955ca208f
- Union Find 在index上做好像更加容易
- 其他union find function:
boolean connected(a,b){return find(a) == find(b)}
- 看到 min value, 至少考虑dp:
- Consider last num: min sum will be (preMinSum + curr, or curr)
- Use preMinSum to cache previouly calcualted min sum, also compare with +curr.
- Have a global min to track: because the preMinSum can be dis-continuous.
- 也可以写成 dp[i] 但是没什么必要
- 看到log(m+n), 就想到binary search, 或者是recursive 每次砍一半
- 两个sorted array 参差不齐, 肯定不能做简单的binary search
- 这里有个数学排除思想: 考虑A, B各自的中间点.
- 如果A[mid] < B[mid], 那么 A[0 ~ mid - 1] 就不在 median的range里面, 可以排除. divide/conquer就这么来的.
- 具体逻辑看代码, 大致意思就是: 每次都取比较A 和 B [x + k / 2 - 1] 的位置, 然后做range 排除法
- end cases:
-
- 如果我们发现dfs()里面A或者B的start index溢出了, 那么就是最简单的case: midian一定在另外那个array里面
-
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
Math.max(A[startA], B[startB])
就可以
- 如果 k == 1: 就是找A/B 里面的1st item, 那么做个
- 总共的数字长度是 (m + n) 而且每次都有一般的内容被删除, 那么time就是 O(log(m + n))
- 找到开头的字母, 然后recursively DFS 去把word串到底:
- 每到一个字母, 朝四个方向走, 之中一个true就可以.
- Note:每次到一个字母,mark一下'#'. 4个path recurse回来后,mark it back.
- 用一个boolean visited[][]
- Use hash map, key = x@y
- 全部是positive integer, 那么preSum一定是增长的.
- 那其实就用two pointer:
start=0, end=0
不断往前移动. 策略: -
- end++ until a solution where sum >= s is reached
-
- 然后移动start; 记录每个solution, Math.min(min, end - start);
-
- 然后再移动end,往下找
- Note: 虽然一眼看上去是nested loop.但是分析后,发现其实就是按照end pointer移动的Loop。start每次移动一格。总体上,还是O(n)
- O(nlogn) NOT DONE.
- O(n^2), inefficient
- 写个Inorder和Postorder的例子。利用他们分left/right subtree的规律解题。
- Postorder array 的末尾, 就是当下层的root.
- 在Inorder array 里面找到这个root,就刚好把左右两边分割成left/right tree。
- 这题比较tricky地用了一个helper做recursive。 特别要注意处理index的变化, precisely考虑开头结尾
- runtime: O(n), visit && build all nodes
findMid(arr)
can be replaced with a map<value, index>, no need execute O(n) search at runtime- 如果底边上的一个value==0, 那么算作没有height(以这个底边做rectangle, value==0的位置是空中楼阁, 不能用)
- 如果底边上的value==1, 那么就把上面的height加下来, 做成histogram
-
虽然Easy, 但用到DFS最基本的想法.
-
- dive deep
-
- mark VISITED
-
- sum it up
-
Time: worst O(mn), traverse all possible nodes
-
更要注意, 要从符合条件value==1的地方开始dfs.
-
对于什么island都没有的情况,area应该位0, 而不是Integer.MIN_VALUE, 问清楚考官那小伙, 别写顺手。
- 最左端: 当start=0, end = 2 => mid = 1, mid-1 = 0;
- 最右端: 当end = n - 1, start = n - 3; mid = (start+end)/2 = n - 2; 那么mid + 1 = n - 2 + 1 = n - 1 < n 是理所当然的
- BST structure not given, use TreeSet to build BST with each node
- Every time find last/next inorder element
treeSet.lower(x)
,treeSet.higher(x)
- 一旦位置相隔(k + 1), 就满足题目条件
- O(nlogn), good enough
- Reverse the array, save days index into days[], where the new index is slot.
- days[i]: at slot i, which day a flower will be planted
- O(n)
- Needs to understand: http://www.cnblogs.com/grandyang/p/8415880.html
- 简单的implementation, 把count function写清楚就好.
- time: O(mn), extra space: O(mn)
- 注意结尾要一个个board[i][j]copy
- time: O(mn), extra space: O(1)
- bit manipulation: 用第二个bit来存previous value.
- 因为我们只考虑 0 和 1 而已, 所以可以这样取巧. 但是并不scalable.
- 如果需要存multiple state, 可能需要移动更多位, 或者用一个 state map
- 注意 bit manipulation 的细节: <<1, >>1, 还有 mast的用法: |, &
- 找到个转角度的规律公式: r = c; c = (w - r)
- 用temp variable, swap in place.
- 计数问题, 可以想到DP. 其实就是Backpack VI.
- 从x个数字里面找candidate(可以重复用同一个数字), 来sum up to target. 找: # of ways to form the sequence.
- Backpack VI: 给一个数组nums, 全正数, 无重复数字; 找: # of 拼出m的方法
- dp[i]: # of ways to build up to target i
- consider last step: 如果上一步取的是 candidate A, 那么就该加到dp[i]:
- dp[i] += dp[i - A]
- 要找overall dp[i], 就做一个for loop: dp[i] = sum{dp[i - num]}, where for (num: nums)
- Time: O(mn). m = size of nums, n = target
- If we optimize dp for loop, 需要Sort nums. O(mlogm). will efficient 如果m是constant或者relatively small. Overall: O(n)
- 尽管思考方式是对的, 但是 times out
- 可以重复使用数字的时候, 比如用1 来拼出 999, 这里用1就可以走999 dfs level, 不efficient
- 跟
kth largest element in an Array
的 template一样. - quickSelect 可以找到 kth 最小的元素
- 利用这个原理, 找这个kth最小值, 然后如果 == target index, 就找到了我们的median
- 主要步骤:
-
- partition
-
- check end state
pivot index ?= target index
- check end state
-
- recursive call one part of the array
-
- time: 与quickSort不同在于, 每次只要在一半list里面recurring, 所以把O(logn)的时间复杂度降到O(n)
- n + n/2 + n/4 + n/8 + ....+ 1 = O(2n) = O(n)
- space: O(logn), based on recursive stacks
- 这个问题更在于问题本身的分析 (而且还有多余条件); 最终的for loop 也比较不standard.
- People younger than 15 cannot make requests due to the first rule.
- From the age of 15, people can make requests to the same age: a[i] * (a[i] - 1) requests.
- People can make requests to younger people older than 0.5 * i + 7: a[j] * a[i] requests.
- The third rule is redundant as the condition is already covered by the second rule.
- TODO: the approach.
- 只卖2次, 把买卖分割成5个状态模块.
- 在状态index 0, 2, 4: 没有持有股票. 1. 一直在此状态, max profit不变; 2. 刚卖掉, dp[i][前状态] + profit
- 在状态index 1, 3: 持有股票. 1. 一直在此状态, daily profit. 2. 刚刚买进, 状态改变, 但是没有profit yet: dp[i][前状态]
- 把每天的partial profit (diff)加在一起, 最终的overall profit是一样的. 唯一更好的是, 不需要记录中间买入的时间点.
- 什么时候会积累profit呢?
-
- 原本就持有股票的, 如果毫无动作, 那么状态不变, 积累profit diff.
-
- 卖出了股票, 状态改变, 积累profit diff.
- 注意: 只有在状态index: 0, 2, 4, 也就是卖掉股票的时候, 才可以积累profit
- [i] 只有和 [i-1] 打交道, reduce space
- O(1) space, O(n) time
- 找峰头;然后往下再找一个峰头。
- 怎么样在才能Optimize两次巅峰呢?从两边同时开始找Max!(棒棒的想法)
- leftProfit是从左往右,每个i点上的最大Profit。
- rightProfit是从i点开始到结尾,每个点上的最大profit.
- 那么在i点上,就是leftProfit,和右边rightProfit的分割点。在i点,leftProfit+rightProfit相加,找最大值。
- 三个O(n),还是O(n)
- DFS, 找准需要pass along的几个数据结构. 先
sort input
, 然后DFS - Using for loop approach: 每个dfs call是一种可能性,直接add into result.
- 为了除去duplicated result, skip used item at current level:
if (i > depth && nums[i] == nums[i - 1]) continue;
- sort O(nlogn), subset: O(2^n)
- space O(2^n), save results
- Regular BFS, 注意考虑如果让one level to generate next level
- skip duplicate:
if (i > endIndex && nums[i] == nums[i - 1]) continue;
-
- 用queue来存每一次的candidate indexes
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
- srot O(nlogn), subset: O(2^n)
- should be same O(2^n). slower than dfs
- 在DFS种skip duplicate candidates, 基于sorted array的技巧:
- 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],
- 说明x=nums[i-1]已经在curr level 用过,不需要再用一次: [a,x1,x2],x1==x2
- i == index -> [a,x1]
- i == index + 1 -> [a,x2]. 我们要skip这一种
- 如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。
- 不能去用result.contains(), 这本身非常costly O(nlogn)
- 几遍是用 list.toString() 其实也是O(n) iteration, 其实也是增加了check的时间, 不建议
- 木桶理论。盛水的最高取决于最低的那面墙。
- 左右两墙,往中间跑动。
- 另:若一面墙已经小于另外一面,就要移动,换掉矮墙(可能下一面更高,或更低)
- 但决不能换掉当下的高墙,因为低墙已经limit的盛水的上限,若高墙移动,导致两墙之间距离减少,就注定水量更少了。(弄啥来,不能缺心眼啊)
- 跟Coins in a line I 不一样: 每个coin的value不同.
- 用到MiniMax的思想, 这里其实是MaxiMin. Reference: http://www.cnblogs.com/grandyang/p/5864323.html
- Goal: 使得player拿到的coins value 最大化.
- 设定dp[i]: 从index i 到 index n的最大值. 所以dp[0]就是我们先手在[0 ~ n]的最大取值
- 于此同时, 你的对手playerB也想最大化, 而你的选择又不得不被对手的选择所牵制.
- 用MaxiMin的思想, 我们假设一个当下的状态, 假想对手playerB会做什么反应(从对手角度, 如何让我输)
- 在劣势中(对手让我输的目标下)找到最大的coins value sum
- Reference里面详细介绍了表达式如何推到出来, 简而言之:
- 如果我选了i, 那么对手就只能选(i+1), (i+2) 两个位置, 而我在对方掌控时的局面就是min(dp[i+2], dp[i+3])
- 如果我选了i和(i+1), 那么对手就只能选(i+2), (i+3) 两个位置, 而我在对方掌控时的局面就是min(dp[i+3], dp[i+4])
- 大家都是可选1个或者2个coins
- 目标是maximize上面两个最坏情况中的最好结果
- 更加简化一点: 如果我是先手, dp[i]代表我的最大值.
- 取决于我拿了[i], 还是[i] + [i+1], 对手可能是dp[i + 1], 或者是dp[i+2]
- 其实dp[i] = Math.max(sum - dp[i + 1], sum - dp[i + 2]);
- 这里的sum[i] = [i ~ n] 的sum, 减去dp[i+1], 剩下就是dp[i]的值没错了
- 这个做法是从最后往前推的, 注意initialize dp末尾的值.
- dp = new int[n + 1]; dp[n] = 0; // [n ~ n]啥也不选的时候, 为0.
- sum = new int[n + 1]; sum[n] = 0; // 啥也不选的时候, 自然等于0
- 然后记得initialize (n-1), (n-2)
- 算出一个右下角点(i,j)到(0,0)的大小: 上一块 + 左一块 + curr node - overlap area
- preSum[i][j]: sum from (0,0) to (i-1,j-1)
- same approach as
subarray sum
: use hashmap to store diff->index; if diff re-appears, that means sum of 0 has occurred - sequence of calculation: 1. iterate over start row. 2. iterate over end row. 3. iterate over col number (this is where hashmap is stored based on)
- the iteration over col is like a screening: find previous sum and determine result
- Note: 其实并没有真的去找
== 0
的解答,而是根据特性来判断剩下的/后来加上的一定是0
- 把Interval拆分成数轴上的Point
- 起飞mark 1
- 降落mark -1
- 用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。
- 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
- 当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
- 这避免了错误多count,或者少count
- Binary Search的思想, 用在所要找的这个 average sum 上面. 大小是在[min, max]之中
- 找k的时候, 是>=k都可以, 巧用一个 min(preSum)的概念.
- 找k的时候, 画图, 可以看出来, 其实要的是 k window 里面的sum [x, i], 所以要用 sum[0, i] - sum[0, x]
- number of test cases is small, like 1000, so less concern about the time complexity
- simply loop over the list of intervals, and check if any overlapping.
- where to insert does not really matter: every time we are just checking for overlaopping, not merging any range
- IMPORTANT: if interval over lapping, they will have this property
Math.max(s1, s2) < Math.min(e1, e2)
. This will help detect the overlapping very easily. - O(n^2) runtime, with simple code. But somehow this approach is faster than the TreeMap solution: maybe the test cause causes avg O(n)?
- One constraint from the simply array solution: it always cost O(n) to find the potential overlapping interval
- We can manually sort and always manually try to find the correct element via binary search, or we could store the interval in a treeMap<startKey, endValue>, where the intervals are sorted by
start
. - As result, all we need to do for book(start, end) is to find the next element ceiling(start), last element floor(start), and check for overlapping
- This approach also saves the custom data structure
- Overall cost O(nlogn)
- always with key sorted ascendingly
- more costly than regular HashMap because of the sorting. Building treemap of n items: O(nlogn)
- use
Point{int start, end; boolean start}
to mark start/end of class. Add to pq. - Adding new item to pq, sort, and check if overlapping occurs by counting started classes
- If started classes > 1, that means we overlapped.
- Every time it could consume all classes to find the overlap, O(n^2).
- Not quite need to sort or insert at correct point, and this solution requires longer code. Not quite worthy it for a simple problem.
- 排序好的array. Two pointer移动start和end,核查sum.
- 注意sum用long.
- O(n) time
- 定住一个valueA, 然后在剩下的里面 binary serach 找 (target - valueB)
- for loop O(n), binary search O(logn)
- overall time: O(nLogN), 就不写了
- 跟Coins in a Line II 一样, MaxiMin的思想: 找到我的劣势中的最大值
dp[i][j] 代表在[i,j]区间上 选手最多能取的value 总和
- 同样, sum[i][j]表示[i] 到 [j]间的value总和
- 对手的最差情况, 也就是先手的最好情况:
- dp[i][j] = sum[i][j] - Math.min(dp[i][j - 1], dp[i + 1][j]);
- 这里需要search, 画出tree可以看明白是如何根据取前后而分段的.
- 因为是看区间[i,j]的情况, 所以可以想到是区间 DP
- 这个方法需要复习, 跟数学表达式的推断相关联: S(x) = - S(y) + m. 参考下面的公式推导.
- dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x).
- 其中一个S(x) = dp[i][j] = a[i] - dp[i + 1][j]
- m 取在开头, m 取在末尾的两种情况:
- dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}
- len = 1, 积分就是values[i]
- 最后判断 dp[0][n] >= 0, 最大数字和之差大于0, 就赢.
- 时间/空间 O(n^2)
- S(x) = X - Y, 找最大数字和之差, 这里X和Y是选手X的总分, 选手Y的总分.
- 对于选手X而言: 如果S(x)最大值大于0, 就是赢了; 如果最大值都小于0, 就一定是输了.
- 选手Y: S(y)来表示 对于Y, 最大数字和之差. S(y) = Y - X
- 根据S(x) 来看, 如果从 数字和X里面, 拿出一个数字 m, 也就是 X = m + Xwithout(m)
- S(x) = m + Xwithout(m) - Y = m + (Xwithout(m) - Y).
- 如果我们从全局里面索性去掉m, 那么 S(y'') = Y - Xwithout(m)
- 那么推算下来: S(x) = m + (Xwithout(m) - Y) = m - (Y - Xwithout(m)) = m - S(y'')
- 在这个问题里面, 我们model X 和 Y的时候, 其实都是 dp[i][j], 而区别在于先手/后手.
- 将公式套用, 某一个S(x) = a[i] - dp[i + 1][j], 也就是m=a[i], 而 S(y'') = dp[i + 1][j]
- 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维.
- 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.
- Trick: 利用差值公式, 推导有点难想到.
- 找出[i, j]区间内的性质: dp[i][j]下标表示区间范围 [i, j]
- 子问题: 砍头, 砍尾, 砍头砍尾
- loop应该基于区间的length
- template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;
- 更正常的start/end partition pointer类似: when condition meet, swap
- Clean up TODO
- 跟Combination Sum I, II 没什么太大区别, 只不过, 一定要用k个数字, 也就是一个for loop里面的特别条件
- 考虑input: 没有重复数字 [1 ~ 9]
- 考虑candidate重复利用: 不可以重复利用, next level dfs 时候, curr index + 1
- the result is trivial, save success list into result.
- Which one?
- worst case: tried all numbers and cannot find: O(m!), m = 9, all possible integers in [1~9]
- C(n,k), n choose k problem :
n! / (k! * (n-k)!)
=> ends up beingO(min(n^k, n^(n-k)))
- 与StockII一样, dp[i]: represents 前i天的最大profit.
- sell 的时候, 才完成了一次transaction, 需要扣fee; 而买入不扣fee.
- model sell on dp[i] day (which depends on dp[i-1]) and each day can be sell/buy => add status to dp[i][status]
- status[0] buy on this day, status[1] sell on this day
- dp[i][0] = Math.max(dp[i-1][0], dp[i - 1][0] - prices[i]);
- dp[i][1] = Math.max(dp[i-1][1], dp[i - 1][1] + prices[i] - fee);
- init: dp[0][0,1] = 0; dp[1][1] = 0; dp[1][0] = - prices;
- return dp[n][1]
- 考虑两个方向的dp[i]: 包括i在内的subarray max sum.
- dp[i] 的特点是: 如果上一个 dp[i - 1] + nums[i - 1] 小于 nums[i-1], 那么就舍弃之前, 从头再来:
- dp[i] = Math.max(dp[i - 1] + nums.get(i - 1), nums.get(i - 1));
- 缺点: 无法track全局max, 需要记录max.
- 因为我们现在要考虑从左边/右边来的所有max, 所以要记录maxLeft[] 和 maxRight[]
- maxLeft[i]: 前i个元素的最大sum是多少 (不断递增); maxRight反之, 从右边向左边
- 最后比较maxLeft[i] + maxRight[i] 最大值
- Space, Time O(n)
- Rolling array, reduce some space, but can not reduce maxLeft/maxRight
- preSum是[0, i] 每个数字一次加起来的值
- 如果维持一个minPreSum, 就是记录[0, i]sum的最小值(因为有可能有负数)
- preSum - minPreSum 就是在 [0, i]里, subarray的最大sum值
- 把这个最大subarray sum 记录在array, left[] 里面
- right[] 是一样的道理
- enumerate一下元素的排列顺位, 最后 max = Math.max(max, left[i] + right[i + 1])
- partition the array by pivot k = {0, 1, 2}
- 每一次partition都return starting point of the current partition
- 然后根据下一个 color, 去还没有sort 干净的那个部分, 再sort一下就好
- time O(kn), where k = 0 => O(n)
- 这里只是partion, 并不需要recursively quick sort, 所以结果是简单的O(n)
- have two pointers, left/right
- start tracks red, end tracks blue. Swap red/blue to right position, and left++ or right--.
- leave white as is and it will be sorted automatically
- be very careful with index i: when swapping with index right, we do not know what is nums[right], so need to re-calculate index i .
- O(n)
- Note: this one pass solution does not work if there are more than 3 colors. Need to use the regular quick sorty.
- TODO: count occurance and reassign array
- 用BFS找最短路径.
- 问题: how to effectively store the path, if the number of paths are really large?
- If we store Queue<List>: all possibilities will very large and not maintainable
- 用BFS做出一个反向structure, 然后再reverse search
- BFS 找到所有start string 可以走到的地方 s, 放在一个overall structure里面: 注意, 存的方式 Map<s, list of sources>
- BFS时候每次都变化1step, 所以记录一次distance, 其实就是最短路径candidate (止步于此)
-
- 反向mutation map:
destination/end string -> all source candidates
using queue:Mutation Map
- 反向mutation map:
- Mutation Map<s, List>: list possible source strings to mutate into target key string.
-
- 反向distance map:
destination/end string -> shortest distance to reach dest
- 反向distance map:
- Distance Map<s, possible/shortest distance>: shortest distance from to mutate into target key string.
- BFS prep step 并没解决问题, 甚至都没有用到end string. 我们要用BFS建成的反向mapping structure, 做search
- 从结尾end string 开始扫, 找所有可以reach的candidate && only visit candidate that is 1 step away
- dfs 直到找到start string.
- reversed structure 已经做好了, 现在做search 就可以: 也可以选用bfs.
Queue<List<String>>
to store candidates, searching from end-> start- Basic implementation, array, enumeration
- 写一下position前进的方向: RIGHT->DOWN->LEFT->UP
- 用一个direction status 确定方向
- 写一个compute direction function 改变方向
(direction + 1) % 4
boolean[][] visited
来track走过的地方- Similar concept as
The Maze
: keep walking until hit wall, turn back - fix direction
dx[direction % 4]
- Quick sort的基础.
- Partition Array根据pivot把array分成两半。
- 从array两边开始缩进。while loop到遍历完。非常直白的implement。
- 注意low/high,或者叫start/end不要越边界
- O(n)
- 注意: 这里第二个inner while
while(low <= high && nums[high] >= pivot) {..}
采用了nums[high] >= pivot
- 原因是题目要找第一个nums[i] >= k, 也就是说, 即便是nums[i]==k也应该swap到前面去
- 这个跟quick sort 原题有一点点不一样.
- 将问题reduce到: row of values, find 1st value >= target.
-
- loop over startingRow; 2. loop over [startingRow, m - 1]; 3. Use TreeSet to track areas and find boundary defined by k.
- When building more rows/cols the rectangle, total sum could be over k:
- when it happens, just need to find a new starting row or col,
- where the rectangle area can reduce/remain <= k
- 找多余area的起始点: extraArea = treeSet.ceiling(totalSum - k). 也就是找 减去k 后 起始的/左边的area.
- 去掉这些左边的起始area, 剩下的就 <=k. (num - extraArea)
- 为什么用TreeSet: area的大小无规律, 并且要找 >= 任意值 的第一个value. 给一串non-sorted数字, 找 >= target的数, 如果不写binary search, 那么用BST最合适
- O(m^2*nlogn)
- 从最基本的O(m^2*n^2) 考虑: 遍历 startingRow/startingCol
- rectangle? layer by layer? 可以想到Presum的思想, 大于需要的sum的时候, 减掉多余的部分
- 如何找到多余的area? 那么就是search: 把需要search的内容存起来, 可以想到用BST(TreeSet), 或者自己写Binary Search.
- 2个while loop
- 找first/last occurance
- TODO: Can the code be simplified?
- 一行一行是从小到大, sorted, 连续的, 可以看做1D sorted array
- Binary Search
- 重点是根据找Histogram里面rectangle的性质, 维持一个单调递增的Stack
- 在loop over indexes的时候:
- 如果高度>= previous peek(), 那么对于那个peek, 就意味着, 往下走, 一直走高嘛, 之前的peek总可以继续抄底
- 什么时候不能抄底了呢? 就是有一个下降趋势的时候
- 这时候并不是calculate所有前面的peek, 而是考虑 大于 current height的之前所有的peek.
- 把这些peek到 current height 前一格的rectangle全部找出来: stack.pop()
- 这个stack.pop()的过程里面, 其实没有算上 current height, 因为需要留到下一轮, 把current index加进stack 再说
- 为什么用stack? 因为需要知道连续递增的peek, stack.peek() O(1), 好用 而其实不用stack, 也可以用其他方式记录所有height, 只不过要 O(n)去找peek不方便
- 理解monotonous stack 是如何被维护的
- 维护monotonous stack 是题目需要, 而不是stack本身性质, 是一种借助 stack.peek() O(1)的巧妙用法.
- 存在int[26], Arrays.toString(arr) 就是 string key: character frequency map
- anagram都有一样的key, 存进hashmap<string, list of anagrams>
- output anagrams
- HashMap 的做法. sort每个string, 存进HashMap, 重复的就是anagrams,最后输出。
- toCharArray
- Arrays.sort
- Stirng.valueOf(char[])
- 时间nLO(logL),L是最长string的长度。
- Arrays.toString(arr)的做法。arr是int[26], assuming only have 26 lowercase letters.
- Count occurrance, 然后convert to String,作为map的key.
- Time complexity: nO(L)
- 另一种做法:http://www.jiuzhang.com/solutions/anagrams/
-
- take each string, count the occurrance of the 26 letters. save in int[]count.
-
- hash the int[] count and output a unique hash value; hash = hash * a + num; a = a * b.
-
- save to hashmap in the same way as we do.
- 这一步把for s: strs 里面的时间复杂度降到了O(L). L = s.length().
- Need to work on the getHash() function.
- 时间变成n*O(L). Better.
- space O(n), time O(n)
- space O(n), time O(nlogn)
- double for loop: O(n^2)
subarray sum equals k
的简单版: k = 0- 求preSum, 然后不断check
map.containsKey(preSum - k)
. - 如果
priorSum = preSum - k == 0
, 说明 [priorSum.index + 1, curr index] 就是我们要找的这一段
- 求preSum, 然后不断check
- 分析出,如果sum[0
a]=x, 然后sum[0b]=x, 说明sum[a+1 ~ b] == 0 - 用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组.
- Array: Left Max Wall vs Right Max Wall.
- 对于每个index而言, vertically 能存放的最大水柱, 就是靠 左 右 最高墙决定的:
- min(leftHighestWall, rightHighestWall) - currHeight.
- time: O(n)
- space: O(n)
- Optimization from Method1: two pointer, 还是找左边最高和右边最高. O(1) space.
- 利用到了方法3里面的想法一样: 整个structure是被中间的最高bar 二分天下:
- always limited by the shorter wall: 左边按照maxLeft来计算, 右边按照maxRight来计算.
- time: O(n)
- space: O(1)
-
- 找中间最高bar的index
-
- 两面往中心扫:每次加上(topBarIndex - currIndex)* (elevation from previous index).也就是每次加一个横条
-
- 每次还要减去block自身的height
- time: O(n)
- space: O(1)
- 主要想法和方法3一致: 在山坡下坡的基础上, 一直用stack堆积bottom.
- 最后遇到上升之前, 此时bottom可以用来跟stack之前堆积的所有下坡index做比较, 算跟他们高度相差的积水.
- 用了stack记录下坡, 然后用个while loop一挖到底的想法非常棒.
- time: O(n)
- space: O(n)
- Given: values are [1,n], so val can represent index. Therefore, set val to its correct position
- 小心handle i:
- value是 1-based
- 每次换位, 需要
i--
, 重新省察nums[i]
- Option1: use negative number to mark visited:
- 很巧妙地运用了标记的方法, 标记成负数,证明visit过。
- Preserve原数的负数,这样可以继续用此负数的绝对值来寻找原数所该被定的位置。非常巧妙!
- Option2: use large number (larger than n)
- 跟方法2类似,也是做标记,这一次是加上一个大于n的数(因为题目给了n的border),最后check一下就好。为不超Integer.MAX_VALUE, 每次加n前,取余数。
- 做标记的方法固然快,但是相对来说比较hacky,在常规的代码中,估计不会用到.
- start/end point, 然后比较大小记录dist
- 注意1: 如果第一个座位没有人, 特殊处理, dist = [0 ~ end]
- 注意2: �如果最后一个座位没有人, 特殊处理: dist = [n - 1 - start];
- 其余:
dist = Math.max(dist, (end - start) / 2)
- 相关题目: 几乎同样概念
Binary Gap
, 升级复杂版Exam Room
- 似乎没什么算法特点, 就是array基本运算, 然后分割成一个helper function去做重复计算, 剪短代码.
- 注意check MxN 的分界线.
- the goal is to check [i][j] == [i+1][j+1] for every i and j.
- concept similar to
31. Next Permutation
-
- first pass: find the one that is in incorrect order
-
- second pass: find the right spot to swap
- O(nlogn) time (PriorityQueue), O(n) space
-
- 扫描线+Count: when
count==0
, startFlags==endFlags. 是interval的开头/结尾 (write an example)
- 扫描线+Count: when
-
- Note: remember to merge points on same sweep line position
-
- Comparator:
new PriorityQueue<>(Comparator.comparing(p -> p.val))
; - Sort intervals: O(nlogn), extra space O(n) when creating rst list
Arrays.sort(intervals, Comparator.comparing(i -> i[0]));
- 找到结尾 interval, 满足条件就可以save
- 如果不到return的条件, 就继续延伸 interval.end
- Less applicable when input is
int[][] intervals
, but more applicable when we haveList<int[]> intervals
- Related example: Insert Interval
- Sort fist, loop over and merge, cut off overlapped interval.
- sort by Interval.start:
intervals.sort(Comparator.comparing(interval -> interval.start)); // O(nlogn)
- 用两个相连的Interval: curr, next
- 如果 curr.end覆盖了 next.start: 需要merge. 那么比较一下 curr.end vs. next.end
- 一旦merge, 需要remove被覆盖的 next interval:
list.remove(i+1)
- 若没有重合,就继续iteration
- sort by Interval.start:
- time O(nlogn), space O(1)
- 比较升序的时候, 必须要估计到
i-1, i, i+1
三个数位. - 写出来
i-1, i, i+1
之间的关系, 然后做合理的fix.- reduce nums[i+1] to fix
- raise nums[i+1] to fix
- 需要真的fix数组, 因为loop through做比较时会用到fix后的数字.
- Prep: 存Map<word, index list>
- Process: 相继从两个 index list 里面拿出 p1,p2
- 根据index的大小, 移动双指针: try to move the pointers closer; always calculate diff
- Optionally: if one list is much larger, do binary search on the larger list
- sorted array, 重复元素都在一起
- 跟
Remove Duplicates from Sorted Array
几乎一模一样, 只不过unique index现在可以 validate 2 位 - 其余一模一样, use index to track unique item; skip if duplicated for more than 2 times
- O(n) time, O(1) space
- 这里也可以真的用2个pointers 写while loop, 但是没有必要, 只是单纯地走一个for loop其实就足够.
- when index i and i-1 are diff, use count=1 to start
- in while loop, keep count++ until count==k
- reset when next diff comes in
- update the window start index;
left
in sliding window- update when we need to start a new range:
nums[i-1] >= nums[i]
- calculate the max distance
i - widowStart + 1
- O(n) time and O(1) space
- size++ when meeting condition
nums[i] > nums[i - 1]
- otherwise, reset size = 1
- track max all the way
- 1D coordinate, dp 的角标, 就是代表 index i 的状态
- 求最值, dp[i] = 在index i位置的最长子序列
- 如果 nums[i] > nums[i - 1], dp[i] = dp[i - 1] + 1
- 如果没有持续上升, 那么dp[i] = 1, 重头来过
- maintain max
- when there is a value that can cover entire row of size n
- it must be:
n = countA[i] + countB[i] - overlap[i]
- it must be:
- Code easy to write and read
- time: O(n)
- space: O(1)
- Observation: if A[0] works, no need to check B[0].
- Because if both A[0] and B[0] exist in all dominoes,
- when you swap A[0] in a whole row,
- you will swap B[0] in a whole at the same time.
- The result of trying A[0] and B[0] will be the same.
- time: O(n)
- space: O(1)
- there should exist 1 numbers, that can appear in (A[i], B[i]).
- failure case: there exist at least 1 index, that does not have the common number
- maximum case: there can be 2 numbers, that both will make it work.
- findCommon2, and count them:
- set.add(A[0], A[B]),
- if any new one does not exist in set, remove it from set
- if set is empty() , return -1
- use the 2 numbers from set to do a sweep and count in A, O(n), return the less appearance one.
- time: O(n)
- space: O(1)
- preserve max
- 清零count
- sorted array, 重复元素都在一起
- Two pointers 其实也可以是一个 for loop pointer, 另一个 dynamic variable.
- track unique index
- skip duplicated items
- O(n)
- Remove Duplicate from Array 不同于remove from linked list.
- LinkedList里面我们是最好不要动node.val的,直接把node去掉。
- 而array我们很难直接把node去掉,又不能用新array,那么就要:
- 把不重复的element一个个放到最前面。
- 这个思想跟merge two sorted array (其中一个后续非常长的array可以放下arr1,arr2) 类似。
- 就是找个不会事后mess up,不会去动得index,把满足条件的element 填进去。这样保证了in place.
- 反向思维:remove duplicate, 实际上也是找unique elements, and insert into original array
- 用while loop, 不断地尝试把 number 送到该放的地方
- 如果 index = nums[i] 超过了nums.length, 当然就不移动了
- 注意: 检查 val != nums[val], avoid infinitely loop
- 检验: nums[i] 是否等于 i, 如果不对, 就找到了结果
- 如果nums==null, 其实missing positive integer 自然而然是 1
- 有可能这串数字里没有断开的integer, 但是最大的integer在首位 (因为index超标, 无法被放到正确的地方)
- 这种时候, n被放在 index 0, 其实就是说, 下一个integer应该是 n + 1
- 最终, 如果array本来就是完全sorted, 也不缺, 还符合角标的条件, 那么唯一下一个就是array范围外的第一个positive number: n
- single-bit always starts with '0', two-bits always start with '1'.
- Therefore there is ONLY 1 way to reach end.
- 如果i位是0, 那么前面dp[i-1]或者dp[i-2] true就够了.
- 如果i位是1, 那么i-1位必须是1才满足规则, 并且 dp[i-2]需要true.
- 想着用一用prefix sum. 把值一个个叠加
- 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和
- O(n^2), not as sufficient
- dp[i]: last element(或包括前i个element), 可能组成的 subarray 的最大sum.
- dp[i] = Math.max(dp[i-1]+lastElement, lastElement(drop dp[i-1]))
- init:
- dp = int[n + 1],
- dp[0]: first 0 items, does not have any sum
- 因为continous sequence, 所以不满足条件的时候, 会断.
- need to take curr num regardless => can drop prev max in dp[i]
- track overall max
- init dp[0] = 0; max = MIN_VALUE 因为有负数
- Time, space O(n)
- Rolling array, space O(1)
- 找一个mid piont, 考虑3种情况: 1) 只要左边, 2) 只要右边, 3) cross-mid
- left/rigth case: 直接 dfs
- corss-mid case: continuous sum max from left + continous sum max from right + mid
- continuous sum max from one direction:
- Worst case O(n^2): visit all nodes O(n); in dfs: calculates continuous sum (including mid), which is also O(n)
- Continuous product can be positive/negative/zero
- If nums[i] > 0, want prior largest product[i-1] * nums[i]
- If nums[i] < 0, want prior smallest product[i-1] * nums[i]
- If nums[i] == 0, product = 0
- `prior product[i-1]: 想到DP
-
- 正负数情况, 需要用两个
PreProduct
array: minProduct[], maxProduct[]
- 正负数情况, 需要用两个
-
- continuous prodct: it has to utilize curr nums[i]
- 是跟nums[x]当下值比较的, 如果当下值更适合, 会舍去之前的continous product, 然后重新开始.
- Use a global variable to hold overall result.
-
- Time/Space O (n)
- Space optimization, rolling array
- maxProduct && minProduct 里面的 index i, 都只能 i - 1相关, 所以可以省去redundant operatoins
- Time: O(n)
- space: O(1)
- same concept as method1, but simplified: given that we always have to use nums[i], so only 1 result can be passed on
- FAST, simple to write and read
- time: O(n)
- space: O(1)
- working solution, BUT Time/Space complexity O(n^2) are too much
- Similar to 15. 3Sum, but simpler.
- 只需要count triplet, 但是不需要save triplet, 而且还不需要handle duplicated triplets
- 发现start, end满足条件时候,(end - start)就是所有 sum <target的情况了。
- 而一旦 > target, 那么就end--
- 两层循环, O(n2)
- negative index i, positive index j
- Great write up: https://leetcode.com/problems/next-permutation/solution/
- next lexicographically permutation:
smallest
butlarger than curr
permutation:- find first low point from right [low]
- find the slight larger [high] to swap with [low]
- make sure right side of low is eventually the smallest
- Analyze the use cases, to find next low permutation, 2 major steps:
-
- Find
first low/drop candidate
from right
- Find
-
- Find
first high where nums[high] > nums[low]
from right
- Find
-
- swap(low, high).
- By now, [low, n-1] forms a greater permutation
- but it is not the smallest, because right side [low + 1, n - 1] is descending
-
- reverse(low + 1, n-1) to create ascending slopt on right of low (smallest next lexicographically permutation)
-
- Corner case: if input array is decending (1st low not found), reverse it all together O(n)
- time: O(n) visit all indexes
- space: O(1) not using additional
- Similar question:
1053. Previous Permutation With One Swap
- 分析普通做法, 了结到用从左到右一遍O(n), 从右到左一遍 O(n) 就可以
- 注意carry的维护
- 第一轮:PreProduct (跟preSum的感觉有点像)
- PreProduct[i] stores product from num[0] -> num[i-1] (skipping current num[i])
- init preProduct[i] = 1, as base for product
- 错过一位操作: always
preProduct[i] *= carry;
andcarry *= nums[i]
- 第二轮: 从右边乘起, 每次在index i, 收到的carry都是
nums[i+1] *....* nums[end]
- 第一轮的结果 * 第二轮的结果, 刚好在index i 缺少掉 nums[i]. 如题所愿.
- Time: O(n)
- 计数DP: 2 ways to reach (i,j): dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- non-overlapping:
dp[i - 1][j]
,dp[i][j - 1]
- covers the only 2 possible way to reach (i,j)
- non-overlapping:
- initialization: dp[i][0] = 1, dp[0][i] = 1
- Of course, row i = 0, or col j = 0, there is only 1 way to reach
- time O(mn), space O(mn)
- [i] 只跟 [i - 1] 有关系, 用 curr/prev 建立滚动数组.
- space O(n) 优化空间
- move from (0,0) towards (m, n)
- use Map<coordinate, steps> as memoization technique
- 处理duplicate wthin triplets:
- 如果最外圈的移动点i重复, 一直顺到结尾的最后一个再用.
- 如果是triplet内有重复, 用完start point, 移动到结尾.
- Note:
-
- 找 value triplets, 多个结果。注意,并非找index。
-
- 要升序, 第一层for loop 从最后一个元素挑起, 保证了顺序。
-
- 去掉duplicate: check用过的同样的数字,都跳掉。不需要用同样的数字再计算一边已有结果。
-
- 时间 O(n^2), 两个nested loop
- HashMap 2Sum. Remember to handle duplicates
-
- For loop 挑个数字A
-
- 2Sum 出一堆2个数字的结果
-
- Cross match 步骤1里面的A
-
- start from index = 0
- Keep track of farest can go
- 一旦 farest >= nums.length - 1, 也就是到了头, 就可以停止, return true.
- 一旦 farest <= i, 也就是说, 在i点上, 已经走过了步数, 不能再往前跳, 于是 return false
- start from index = n - 1
- greedy: start from end, and mark last index
- loop from i = [n - 2 -> 0], where i + nums[i] should always >= last index
- check if last == 0 when returning. It means: can we jump from index=0 to the end?
- time: O(n)
- space: O(1)
- DP[i]: 在i点记录,i点之前的步数是否可以走到i点? True of false.
- 其实j in [0~i)中间只需要一个能到达i 就好了
- Function: DP[i] = DP[j] && (j + A[j] >= i), for all j in [0 ~ i)
- Return: DP[dp.length - 1];
- time: O(n^2)
- space: O(n)
- rotate all
- rotate 2 sides: < k or >=
- 注意
list = Arrays.asList(x, y, z ...)
给fixed-size list, 不能直接 list.add(). - Use
new ArrayList<>(Arrays.asList(...))
to wrap it up. - Given assumption: 1)
only 1 celebrity
, 2) person k, who knows nobody ahead of him or after him. - if first pass finds candidate,
person k
, it means:- person [0, k-1] are not celebrity: they know a previous or current candidate
- person k knows no one between [k + 1, n): k+1 to n-1 can not be the celebrity either.
- person k is just the last standing possible celebrity
- second pass validation: we do not know if
knows(celeb, [0~k-1] )
. Do a final O(n) check - time:O(n), space O(1)
- DO NOT: Brutle compare all -> all: O(n^2) handshakes.
- 先写出来[0 ~ n - 1], 最简单的方式 O(n^2) 检查, 记录每个人的状态.
- 逐渐发现, 因为 celeb 谁都不会认识, 那么当任何candidate knows anyone, 他自身就不是celeb.
- 我们可以greedy地, 一旦fail一个, 就立刻假设下一个是celeb candidate
- 最终还是要检查一遍, 避免错漏.
- 想一下happy case: 如果 celeb=0, 那么 know(celeb, i) 永远都是false, 然后 celeb一直保持0, 坚持到verify所有人.
- Follow up of 243. Shortested Word Distance
- 特别handle
word == word1 == word2
case:- p1 and p2 will always be the same
- when
word == word1 == word2
, simply calculate distance using theold p1 or p2
withcurr index i
- The rest impl aligns with 243.
- when
word1==word2
, make usre to skipp1==p2
by increasing i or j - The rest impl aligns with 244
- Time: still O(n), but slower than Method1: 2 passes
- Space: uses extra space O(n) to hold all indexes
- 正面去做:
- count task出现的次数
- 然后PQ sort Task object in descending order
- 每个section: k slots = n + 1. Same task being n slots apart, meaning one section has n + 1 slots.
- 目标是穷尽 k, or 穷尽 pq (poll k times, but will save it back to queue if Task # > 0)
- 如果qp 真的穷尽, break, return count
- 不然, count += k, where k are just # of idle intervals
- time O(n) + constant time O(xlogx), where x = 26
- extra space O(x) ~ O(1)
- Enumerate to understand:
- 1.module tasks in module/section;
- 2.Only need sum the intervals/slots, not return actual layout
- Perfect case: all letters appear identical # times: just line them up separate in order.
- Real case: task appears different times
-
- Place maxCount task as header followed with n slots: define (maxCount-1) sections
-
- For tasks with less # than maxCount# can fill the (maxCount-1) sections; what about the tail section?
-
- Any task with same maxTask#, of if prior sections all filled, will fill the tail section
- To count overall slots/intervals, come up with this equation:
-
- Fixed sections:
(maxCount - 1) * (n + 1)
- Fixed sections:
-
- Plus all repeating maxCount tasks: calculate by couting identical maxCount of them
-
- Exception: if the first (max - 1) sections are all filled completely, and we still have extra task (ex: when n is not large enough), then just return tasks.length
-
- time O(n), space O(1)
- ??? Need to study
- O(n) solution: 找最大值, 和第二大的值, 看是否符合题意, 就行了.
- O(2n) 最简单方法: 可以loop 两遍: 找最值; 作比较.
- O(2n) 举反例: 有一个不满足, 就够反对这个'at least twice of alllll others'.
- Also most identical to
33. Search in Rotated Sorted Array
:- find where nums[mid] lands by comparing to nums[start]. i.e., if nums[mid] < nums[start], on right half of the array
- when
nums[mid] == nums[start]
: duplicate. Shift by start++
- the worst case of
nums[mid] == nums[start]
willl cause O(n), - but if duplicate is not entire array, should be O(logn)
- 从结果出发, 只需要找到加法的结果,而不强调具体配对.
- 写一写example发现规律: 升序排列会让
高位的min(pair)
最大化, 于是一言不合先排列
- 找到排列取单数位的规律,再考虑负数和正数的相同规律,即可找到排列求解的方法。
- sort, O(nlogn)
- 考虑input: 没有duplicate, 不需要sort
- 考虑重复使用的规则: 可以重复使用, 那么for loop里面dfs的时候, 使用curr index i
- the result is trivial, save success list into result.
- Time and Space complexity:
- transform the analysis as for
40. Combination Sum II
- Since this problem allows reuse of elemenets, assume they exist in original input as duplicates
- time: O(k * 2^n), k = avg rst length
- space: O(k) stack depth, if not counting result size
- transform the analysis as for
- Make sure to sort array: we need to use the actual number range
A[j] - A[i]
, which requires the array to be sorted - we want to form a new array where A[n-1] - A[0] + 1 == n; order does not matter but all slots need to be filled consecutivly
- max moves: https://leetcode.com/problems/moving-stones-until-consecutive-ii/discuss/289357/c%2B%2B-with-picture
- A interval will be automatically dropped between A[0] and A[1], if moving A[0] first
- Same, a interval between A[n-2] and A[n-1] will be dropped when moving A[n-1] first
- so largest possible move = firstItem + remaining range size - n items = 1 + (A[n-1] - A[1] + 1) - n = A[n-1] - A[1] -n + 2
- or A[n-2] - A[0] - n + 2
- min moves:
Sliding Window
- use slinding window to assume a right pointer, to make sure A[right] - A[left] + 1 < n; otherwise, move left++
- check # of included stones
- calculate remaining, which is remaining moves
- Handle min move edge case:
- Consecutive Array up to right = n - 1; need 2 moves to finish
- A够长,那么可以从A的尾部开始加新元素: 从尾部,是大数字优先排末尾的.
- Deal with remaining:
- When A values are used up, put remian of B into it
- When B values are finished, there is nothing todo. The remain of A is already in place.
- Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
- 从低谷找peek, sell.
- DP. (old dp solution BuyOn[], SellOn[])
- DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)
- 画图, 因为可以无限买卖, 所以只要有上升, 就有profit
- 所有卖掉的, 平移加起来, 其实就是overall best profit
- 当天卖光, 再买进.
- O(n) time
- 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。
- 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.
- profit += prices[peek - 1] - prices[start]; 挺特别的。
- 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.
- 想知道前i天的最大profit, 那么用sequence DP:
- dp[i]: represents 前i天的最大profit
- 当天的是否能卖, 取决于昨天是否买进, 也就是
昨天买了或者卖了的状态
: 加状态, dp[i][0], dp[i][1] 买
的状态dp[i][0]
=-
- 今天买入, 昨天sell后的dp[i-1][1]值 - 今天的价格price[i];
-
- 今天不买, compare with 昨天buy的dp[i-1][0]值.
-
卖
的状态dp[i][1]
=-
- 今天卖出, 昨天buy的 dp[i-1][0]值 + price[i];
-
- 今天不卖, compare with 昨天sell后的 dp[i-1][1]值.
-
- 注意init:
- dp[0][0] = dp[0][1] = 0; // day 0 buy/sell: no profit regardless of buy/sell status
- dp[1][1] = 0; // day 1 sell: haven't bought, so just 0 profit.
- dp[1][0] = - prices[0]; // day 1 buy: just cost of prices[0]
- Note:
int[][] dp = new int[n+1][2]
以后, index是 1-based. for loop 注意使用prices[i-1]
- O(n) time, O(n) space
- [i] 和 [i - 1] 相关联, roll
- Use 2 pointers to record most recent pos of word1 and word2
- move pointer i [0 ~ n) and keep refreshing pos1 and pos2
- both pos1 and pos2 will be as adjacent as possible since they both moving towards same direction
- keep recalculating best distance when either word is matched
- 而同一时间,只需要计算一个最近的curr distance: greedy不断变更A/B index, 做比较
- 注意special case:
when less than 3 items, return maximum
- PQ是natural order: remain peek() will be the 3rd maximum
- analyze problem, and realize we want to eliminate
isolated servers
- count row[], count col[]
- cross-reference row[] and col[]:
row[i]==1 & col[j]==1
indicates a isolated server - Unfortunately, this problems checks unconnected items, so dfs needs to brutlely check entire row or column
- Only add if
vertical + horizontal count
> 1 - time: O(mn) * O(m + n)
- Time, Space: O(n)
- 前提: input必须valid, 比较罕见少用. Moore Voting Algorithm: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
- 与当下candidate相同, vote++. 与之不同, vote--.
- Majority Number是指超半数, 多1个就行: 消减至最后, 会至少有vote>=1.
- 那么: vote++, vote--到最后剩下的就是winner.
- 这个方法比较greedy, 前提是: valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。
- time: O(n), space O(1)
- sort entire nums array
- assume there is a solution, then nums[n/2] must be that majority num
- time O(nlogn)
- Recursive approach
- For ange rangeA & rangeB, rangeA has majorElementA and rangeB has majorElementB
- majorElementA = majorElementB, of course this element will be the major number for whole range
- if majorElementA != majorElementB, then need to count both elements in whole range
- of course the larger occurance will be the major num
- TODO
- Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。
- Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。
- dfs的两种路子: 1. pick&&skip dfs, 2. for loop dfs
-
- pick&&skip dfs: 取或者不取 + backtracking. 当level/index到底,return 一个list. Bottom-up, reach底部, 才生产第一个solution.
-
- for loop dfs: for loop + backtracking. 记得:做subset的时候, 每个dfs recursive call是一种独特可能,先加进rst. top-bottom: 有一个solution, 就先加上.
- Time&&space: subset means independent choice of either pick&¬ pick. You pick n times:
O(2^n)
, 3ms - space: O(2^n) results
- n = nums.length, 那么在每一个index, 都是 pick / not pick: 0/1
- 考虑subset index 0/1的bit map: range 的就是 [0000...00 ~ 2^n-1]
- 每一个bitmap就能展现出一个subset的内容: all the 1 represents picked indexes
- 做法:
-
- 找出Range
-
- 遍历每一个bitmap candidate
-
- 对每一个integer 的 bit representation 遍历, 如果是1, add to list
- time: O(2^n * 2^n) = O(4^n), still 3ms, fast.
- BFS, 注意考虑如果让one level to generate next level
-
- maintain a list of Indexe to store candidate indexes.
-
- 每一次打开一层candiates, add them all to result
-
- 并且用每一轮的candidates, populate next level, back into queue.
-
- should be same O(2^n), but actual run time 7ms, slower
- O(n) space
- Map:
map<val, index>, Lis: tracks
index->value` - list maintain 用来 insert/remove/random operations.
- Remove: swap input valueIndex & tialIndex = list.size() -1.
- list.remove(object) 应该是要O(logn) 做一个search的.
- list.remove(list.size() - 1) is cheapter
- Approach#4 of https://leetcode.com/problems/subarray-sum-equals-k/solution/
- Hash Table two sum 思想, but to save frequency of current sum:
preSumCount<sum, count>
- for loop 从左开始积累
preSumCount<sum, count>
- derive
priorSum = sum - k
: 看看前面有多少此种sum,preSumCount.get(priorSum)
# ways to reach priorSum
gives # of ways for thatpriorSum + k = curr Sum
- therefore, count += preSumCount.get(priorSum)
- for loop 从左开始积累
- O(n) time, O(n) space
- Note: 如果需要实际index, 可以存
Map<Integer, List<Index>>
- presum: socalled
cummulative sum
- move from starting point i = [0 ~ n -1] and test each
range = [i ~ j]
- use presum to verify k:
preSum[j + 1] - preSum[i]
- time: O(n^2):
1 + 2 + 3 + 4 ... + n ~= O(n^2)
- 很巧妙地根据k range地条件
- 把HashSet里面的值控制在[i - k, i]
- 每次不断地往set里面加新元素, 从set里减去末尾index的元素
- 而set.add(x)如果遇到重复, 会return false.
- 一旦在这个length k 的 range里面, 有重复, 就符合条件.
- Time O(n)
- Time O(nm), m = # of duplicates. 太慢
- 记录每个element value的index in the list
- 一旦有重复element重复, 就把整个list of indexes 端出来, 查看有没有符合条件的: (index - i) <= k
- 方法1是限定选拔的candidate, 不合格就去掉, 那么一旦有符合条件的(duplicates), 那么一定中, 剩下的就不看了.
- 方法2是把符合条件的index找出来, 集中处理, 但是所有candidate都会选出来
- 就好像招人一样: 一种是遇到好的就停止; 第二种是看过所有人, 从中选拔最好的. 显然第一种更快.
- Use LinkedList Cycle Concept:
- Each element the array is like a
Node {int currIndex; int val;}
, where theval
is also pointer to next Node - A node is like a portal; a pointer can: 1) visit a node by currIndex, 2) pick up newIndex =
nums[currIndex]
, then keep repeating step 1 and 2. - Important: since nums is immutable, the pointer footprint is unique/linear
- Just like linked list. Therefore, use slow/fast pointer to detect cycle.
- Each element the array is like a
- https://leetcode.com/problems/find-the-duplicate-number/solution/
- it is now the same as
142. Linked List Cycle II
- 注意不要思维定式: binary search
NOT on index
binary search on value
: [1, n]- O(logN)
- validate(nums, candidate): for loop to count number of
value <= candidate
count == candidate
: no duplicate from [1 ~ candidate].count < candidate
: missing element in [1~ candidate], so duplicates are in later range. start = mid;count > candidate
: there are duplicates in [1~ candidate]. end = mid;
- Time: O(nLogN)
- Space: O(1)
- No brain: HashSet.
- Time O(n), Space O(n)
- Arrays.sort(x): Time O(nLogN), Space O(1)
- 排序后, 重复数会排在一起, 然后 binary search
- Time, Space O(MN)
- 往右下角走, 计算最短的 path sum. 典型的坐标型.
- 注意: init 第一行的时候, 要accumulate dp[0][j - 1] + grid[i][j], 而不是单纯assign grid[i][j]
- Rolling Array
- Time O(MN), Space O(N)
-
- 需要在同一个for loop里面完成initialization, 2) reuse dp[i][j]
- Reason: dp[i % 2][j] 在被计算出来的时候, 马上在下一轮会被用. 被覆盖前不用,就白算
- Option3 below initialize dp outside of loop: 看起来固然简单, 但是不方便空间优化
- Enumerate the possible 2 paths: go right, go down
- sub problem: dfs(i+1,j), dfs(i,j+1); pick the min of the two
- memoization: after the path from a point (i,j) to end is computed, record memo[i][j] to avoid repatative calculation
- time: O(mn), only visite and record memo[i][j] once.
- space: O(mn) extrem case of m=100000, n = 2; the stack height is O(mn)
- Moore voting: vote可以加减, 一旦为零, 换下一个candidate, 之前抵消掉的算作清零.
- 一个array里面, 最多也只有2个数字 出现次数大于2次, 所以用A/B表示.
- count overall apperance at the end for the two items: countA, countB
- save to result as valA, valB
- 有点 moore voting的意思:
- 当count == 0的时候, reset
- 两个candidate A/B都不等, 那么countA--, countB--
- 最终重新计数, 然后比较出结局.
- 注意: 按照if statement的顺序, valA&&countA 比valB&&countB有优先性
- O(nlogN)
- 每天都就交易价格,n天只让买卖一次,那就找个最低价买进,找个最高价卖出
- 记录每天最小值Min是多少. O(n)
- 每天都算和当下的Min买卖,profit最大多少.
- Find min value for first i items, new dp[n + 1].
- dp[i]: 前i天, prices最小的price是多少: min cost of first i days
- 然后用当天的price做减法dp[i]算max profit.
- Time, Space: O(n)
- 更进一步, 用一个min来表示min[i], 因为计算中只需要当下的min.
- index i only depend on [i - 2]
- Space O(n)
- 每天都试着买进,然后之后的每一天尝试卖出. double for loop, O(n^2). timeout.
- 其中很多都是没必要的计算:[7, 1, 5, 3, 6, 4]
- if we know buyin with 1 is cheapest, we dont need to buyin at 5, 3, 6, 4 later on;
- only need to sell on higher prices.
- store efficiently: use List<Map<snapId, val>>. only preserve changed itemd
- if no match, find last modifed item based on snapId, use TreeMap.floorEntry
- map.floorEntry(id) return the item.key lower or equal to id
- Utilize a
buffer: Map<Integer, Integer>
and perform atomic save - just check flowerbed[i-1] & flowerbed[i+1] and count
- 相对暴力简洁: 找到一个value, 存一个index
- 若在HashMap里面 match 到结果, 就return HashMap里存的index.
- O(n) space && time.
- 前后++, --搜索. Sort 用时O(nlogn).
-
- 第一步 two pointer 找 value.
-
- 注意,要利用额外的空间保留original array, 用来时候找index. (此处不能用HashMap,因为以value 为key,但value可能重复)
- O(n) space, O(nlogn) time.
- Outside pointer that moves in certain condition.
- Save appropirate elements
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
- dp[i][j]: # of paths to reach grid[i][j]
- Bottom-up: at end, there are 2 ways to reach dp[i][j]
- dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- Handle obstacle (cause dp[i][j] to be 0).
- use preorder to find root, one index at a time (global index)
- use the root to divide and conquer inorder int[] to 2 sides;
- root.left = dfs(left); root.right = dfs(right)
- end stage: start == end index, create a node
- can use a map to store inorder <val, index> for O(1) find
- 和Construct from Inorder && Postorder 想法一样。
- 写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。
- 跟Convert Sorted Array to Binary Tree类似, 找到对应的index, 然后:
- node.left = dfs(...), node.right = dfs(...)
- Divide and Conquer
- optimize on finding
mid node
: given value, find mid of inorder:
- optimize on finding
- Instead of searching linearly, just store inorder sequence in
map <value -> index>
, O(1) - IMPORATANT: the mid from inorder sequence will become the main baseline to tell range:
range of subTree = (mid - inStart)
- sapce: O(n), time: O(n) access
- when the input has duplicates, and want to skip redundant items? 考虑重复使用的规则: 不可以重复使用
-
- sort. 考虑input: 有duplicate, 必须sort
-
- in for loop, skip same neighbor:
if (i > index && candidates[i] == candidates[i - 1]) continue;
- 因为在同一个level里面重复的数字在下一个dfs level里面是会被考虑到的, 这里必须skip
- in for loop, skip same neighbor:
-
- the result is trivial, save success list into result.
- Time complexity: O(k * 2^n), k = average result length
-
- Assume on average, there are k elements in result
-
- Since element can be used ONLY once, so the total # of solutions can be
C(n, k)
:pick k out of n
- Since element can be used ONLY once, so the total # of solutions can be
-
- Now let k be any number [0, n], so total # of solutions can be:
C(n,0) + C(n,1) + C(n,2) + ... C(n,n) = 2^n
- Now let k be any number [0, n], so total # of solutions can be:
-
- Now:
the new ArrayList<>(list)
takes average O(k) time
- Now:
- Total: O(k * 2^n)
-
- Space: O(n), stack depth, if not counting results size
- want to find
nums[i - 1] == nums[n - 1] - nums[i]
, given:- preSum[i], sum from [0, i] inclusive
- preSum[j] - preSum[i] = [i+1, j] inclusive
- O(n) to build preSum
- O(n) to find pivot
- 关键点, 是找到 [mid]是在左边/还是右边的continous increasing subarray: 比较
A[start] < A[mid]
- 在两个section 里面分别讨论 target 的位置
-
nums[start] < nums[mid]
: start是从index=0开始的, 那就说明mid在前半段
-
nums[start] > nums[mid]
: start是从index=0开始的, 那就说明mid在后半段
-
- Binary search template:
-
start + 1 < end
(adjacent indexes)
-
- start/end = mid,
-
- compare start and end individually
-
-
- binay search break point
-
- binary search target
- 注意等号,在判断target在前半段还是后半段:if (A[p1] <= target && target <= A[breakPoint])
- Goal: find word count that fits into
f(queries[i]) < f(W)
- What if: we can store the f(W) as preSum, then goal:
rst[i] = preSum[end] - preSum[queryWordCount]
- count(W) and store in count[i]
- calc preSum
- processs queries array
- kinda bucket sort:
-
- we know the boundary of the word length, so we can create
bucket
- we know the boundary of the word length, so we can create
-
function count(w)
can produce a value that sort a word into a specific bucket slot
- extend: the bucket can store keys that links back to the word (if there are follow up questions)
-
- time: O(m + n)
- space: O(m + n)
- time:
- O(nm) to count all words, O(nlogn) to sort the wordCount
- O(nm) to to count all queries
- O(n^2) to perform the match
- space: O(n)
- need search left bound & right bound.
- use input parameter
direction
to binary search function - Option0: simplification, inspired by
278. First Bad Version - Method1: Check is-NOT-BadVersion
-
- if found match, but NOT sure it is desired boundary, just leave it and keep going
-
- check the final results after
binary search while loop
completes
- check the final results after
- WHY? code is easier to read in this way.
-
- split into 3 parts [0, i -1], [i, i + k -1]. [i + k, n - 1]
- NOTE: be very careful about index handling:
presum[i + 1] - presum[0]
gives inclusive range of[0, i]
- Use DP to record the starting position of max sum,
- inspired by: https://leetcode.com/problems/maximum-sum-of-3-non-overlapping-subarrays/discuss/108231/C%2B%2BJava-DP-with-explanation-O(n)
-
- calculate preSum with range [0, n]
-
- calculate leftMaxIndex[], rightMaxIndex[]
-
- test middle range to find max solution
-
- Note: the test range for 1, 2, 3 always start with assumption that k has been consumed from one side
- Note: When need to record at max/min value change, we can check/assign it manually (rather than use a object to carry & sort)
- If given n points, we can calculate all possible slopes. O(n^2) times
- For the two dots that generates the same slope, these dots could be on parallel slopes
- figure out how to prune the parallel dots
- GCD: greatest common divider
- Devide the x and y by their greatest common divider, such that x and y can be reduced to minimum value
- All other x and y can be reduced to such condition as well
- track the final reduced (x,y) in a map: they are the key to the count
- No need to use Map<Integer, Map<Integer, Integer>> to perform 2 level mapping; just
map<String, Integer>
, where the key is "x@y" - 这里已经给了 sorted intervals by start point;
-
- 直接找到可以insert newInterval的位子. Insert and convert to list
-
- Merge: Use
pre, curr
to iterate over list, and remove curr after merging
- remove之前都会重新assgin
pre.end
, 确保被remove的node.end 被capture
- Merge: Use
-
- Convert back to int[][]
-
- time/space: O(n)
- code is slightly better to read
- handle edge cases:
- new interval is non-overlapping
-
- head
-
- tail
-
- in middle
-
- new interval is overlapping:
-
- end index in existing interval; reuse the existing interval end to close new range
-
- end index in the gap of 2 intervals, use new interval.end to close the new range
-
- new interval is non-overlapping
- time, space: O(n)
- Interval 拆点,PriorityQueue排点
- Merge时用count==0作判断点
- 注意, 一定要compare curr
p.x == queue.peek().x
确保重合的点全部被process:count+=p.x
- PriorityQueue: O(logN). 扫n点, 总共:O(nLogn). SLOW.
- 因为interval已经sort, 本想用Binary Search O(logn).
- 但是找到interval insert position 最后 merge还是要用 O(n), 所以不必要 binary Search
- Sort nums: O(nlogn)
- Set max value fixed on right side at k
- set 2nd value from right index j
- set last value at min index i
- if
nums[i] + nums[j] > nums[k]
: with fixed j, i can pick from [i, j-1] combinations- then j--, to pick another j candidate
- maintain a window [i,j]; if invalid, move i++
- time: O(n^2)
- Note: very similar to 3-sum, fixing 1 index and use 2 pointers to move window
- Sort nums: O(nlogn)
- Set min value at i
- set 2nd value at j=i+1; and 3rd value at k=i+2
- find max of k that fits into triangle
- count all possible k candidates from [j+1, k]
- then move j to a new candidate
- O(n^2)
- 特点1: 所有给出的点(再找出没有specify的对角线点), 如果最后组成perfect rectangle, 都应该互相消除, 最后剩下4个corner
- 特点2: 找到所有点里面的min/max (x,y), 最后组成的 maxArea, 应该跟过程中accumulate的area相等
- 特点1确保中间没有空心的部分, 保证所有的重合点都会互相消除, 最后剩下4个顶点
- 特点2确保没有重合: 重合的area会最终超出maxArea
- If given n points, we can calculate all possible slopes. O(n^2) times
- For the two dots that generates the same slope, these dots could be on parallel slopes
- figure out how to prune the parallel dots
- GCD: greatest common divider
- Devide the x and y by their greatest common divider, such that x and y can be reduced to minimum value
- All other x and y can be reduced to such condition as well
- track the final reduced (x,y) in a map: they are the key to the count
- No need to use Map<Integer, Map<Integer, Integer>> to perform 2 level mapping; just
map<String, Integer>
, where the key is "x@y" - the problem turns into: can we find a subset of items that sum up to target sum?
- create
boolean dp[j]
to represent if we can sum up to j, where j = sum value- want to try out all items in num,
- use dfs to find a subset of items that sum up to target sum?
- 用while loop, 不断地尝试把 number 送到该放的地方
- 如果 index = nums[i] 超过了nums.length, 当然就不移动了
- 注意: 检查 val != nums[val], avoid infinitely loop
- 检验: nums[i] 是否等于 i, 如果不对, 就找到了结果
- 如果nums==null, 其实missing positive integer 自然而然是 1
- 有可能这串数字里没有断开的integer, 但是最大的integer在首位 (因为index超标, 无法被放到正确的地方)
- 这种时候, n被放在 index 0, 其实就是说, 下一个integer应该是 n + 1
- 最终, 如果array本来就是完全sorted, 也不缺, 还符合角标的条件, 那么唯一下一个就是array范围外的第一个positive number: n
- 其实跟给一个2D matrix没有什么区别, 可以做dfs, memoization.
- initialize memo: pathSum[i][j] = MAX_VALUE; 计算过的path省略
- Bottom-top: 先dfs到最深的path, 然后逐步网上返回
OR 原理: min(pathA, pathB) + currNode
- 浪费一点空间, pathSum[n][n]. space: O(n^2), where n = triangle height
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- 跟dfs的原理很像,
OR 原理: min(pathA, pathB) + currNode
- init dp[n-1][j] = node values
- build from bottom -> top: dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
- 跟传统的coordinate dp有所不同, inner for loop 是需要计算 j <= i, 原因是triangle的性质.
- 空间: dp[n][n]. space: O(n^2)
- 时间:O(n^2). Visit all nodes once: 1 + 2 + 3 + .... n = n^2
- Based on the DP solution: the calculation always depend on
next row
for col atj
andj + 1
- 既然只depend on next row, 可以用rolling array来处理: reduce to O(n) space.
- Further: 可以降维, 把第一维彻底去掉, 变成 dp[n]
- 同样是double for loop, 但是只在乎column changes:
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
- due to access permission, not test
- dp[i][j]: longest continuous subsequence length at coordinate (i, j)
- dp[i][j] should come from (i-1,j) and (i, j-1).
- dp[0][0] = 1
- condition: from up/left, must be increasing
- return dp[m-1][n-1]
- O(mn) space for dp and flag.
- O(mn) runtime because each spot will be marked once visited.
- 这个题目的简单版本一个array的例子:从简单题目开始想DP会简单一点。每个位置,都是从其他位置(上下左右)来的dpValue + 1. 如果啥也没有的时候,init state 其实都是1, 就一个数字,不增不减嘛。
- 找subsequence: 不需要continous, 可以skip candidate
- 考虑nums[i]结尾的时候, 在[0, i), dp[i - 1] 里count有多少小于nums[i]
- dp[i]: 到i为止 (对于所有 j in [0, i], 记录max length of increasing subsequence
- max需要在全局维护: nums是无序的, nums[i]也可能是一个很小的值, 所以末尾dp[i]并不是全局的max, 而只是对于nums[i]的max.
- 正因此, 每个nums[i]都要和每个nums[j] 作比较, j < i.
- dp[i] = Maht.max(dp[i], dp[j] + 1); j = [0 , i - 1]
- 时间复杂度 O(n^2)
- 维持一个list of increasing sequence
- 这个list其实是一个base-line, 记录着最低的increasing sequence.
- 当我们go through all nums的时候, 如果刚好都是上升, 直接append
- 如果不上升, 应该去list里面, 找到最小的那个刚好大于new num的数字, 把它换成num
- 这样就完成了baseline. 举个例子, 比如替换的刚好是在list最后一个element, 等于就是把peak下降了, 那么后面其他的数字就可能继续上升.
- '维护baseline就是一个递增的数列' 的证明, 还没有仔细想.
- 跟Coins in a line I 不一样: 每个coin的value不同.
- 用到MiniMax的思想, 这里其实是MaxiMin. Reference: http://www.cnblogs.com/grandyang/p/5864323.html
- Goal: 使得player拿到的coins value 最大化.
- 设定dp[i]: 从index i 到 index n的最大值. 所以dp[0]就是我们先手在[0 ~ n]的最大取值
- 于此同时, 你的对手playerB也想最大化, 而你的选择又不得不被对手的选择所牵制.
- 用MaxiMin的思想, 我们假设一个当下的状态, 假想对手playerB会做什么反应(从对手角度, 如何让我输)
- 在劣势中(对手让我输的目标下)找到最大的coins value sum
- Reference里面详细介绍了表达式如何推到出来, 简而言之:
- 如果我选了i, 那么对手就只能选(i+1), (i+2) 两个位置, 而我在对方掌控时的局面就是min(dp[i+2], dp[i+3])
- 如果我选了i和(i+1), 那么对手就只能选(i+2), (i+3) 两个位置, 而我在对方掌控时的局面就是min(dp[i+3], dp[i+4])
- 大家都是可选1个或者2个coins
- 目标是maximize上面两个最坏情况中的最好结果
- 更加简化一点: 如果我是先手, dp[i]代表我的最大值.
- 取决于我拿了[i], 还是[i] + [i+1], 对手可能是dp[i + 1], 或者是dp[i+2]
- 其实dp[i] = Math.max(sum - dp[i + 1], sum - dp[i + 2]);
- 这里的sum[i] = [i ~ n] 的sum, 减去dp[i+1], 剩下就是dp[i]的值没错了
- 这个做法是从最后往前推的, 注意initialize dp末尾的值.
- dp = new int[n + 1]; dp[n] = 0; // [n ~ n]啥也不选的时候, 为0.
- sum = new int[n + 1]; sum[n] = 0; // 啥也不选的时候, 自然等于0
- 然后记得initialize (n-1), (n-2)
- 接成圈是不行的, 所以visit过得 (x,y)就不能再去了.
- 斜角方向不能走, 只能走上下左右
- 无法按照坐标DP来做, 因为计算顺序4个方向都可以走.
- 最终要visit所有node, 所以用DFS搜索比较合适.
- 简单版: longest path, only allow right/down direction:
dp[x][y] = Math.max(dp[prevUpX][prevUpY], or dp[prevUpX][prevUpY] + 1)
; and compare the other direction as well- This problem, just compare the direction from dfs result
- DFS太多重复计算; memoization (dp[][], visited[][]) 省去了重复计算
- initialize dp[x][y] = 1, (x,y) 自己也算path里的一格
- dfs(matrix, x, y): 每次检查(x,y)的4个neighbor (nx, ny), 如果他们到(x,y)是递增, 那么就考虑和比较:
- Maht.max(dp[x][y], dp[nx][ny] + 1); where dp[n][ny] = dfs(matrix, nx, ny)
- top level: O(mn), 尝试从每一个 (x,y) 出发
- O(m * n * k), where k is the longest path
- 跟Coins in a Line II 一样, MaxiMin的思想: 找到我的劣势中的最大值
dp[i][j] 代表在[i,j]区间上 选手最多能取的value 总和
- 同样, sum[i][j]表示[i] 到 [j]间的value总和
- 对手的最差情况, 也就是先手的最好情况:
- dp[i][j] = sum[i][j] - Math.min(dp[i][j - 1], dp[i + 1][j]);
- 这里需要search, 画出tree可以看明白是如何根据取前后而分段的.
- 因为是看区间[i,j]的情况, 所以可以想到是区间 DP
- 这个方法需要复习, 跟数学表达式的推断相关联: S(x) = - S(y) + m. 参考下面的公式推导.
- dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x).
- 其中一个S(x) = dp[i][j] = a[i] - dp[i + 1][j]
- m 取在开头, m 取在末尾的两种情况:
- dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}
- len = 1, 积分就是values[i]
- 最后判断 dp[0][n] >= 0, 最大数字和之差大于0, 就赢.
- 时间/空间 O(n^2)
- S(x) = X - Y, 找最大数字和之差, 这里X和Y是选手X的总分, 选手Y的总分.
- 对于选手X而言: 如果S(x)最大值大于0, 就是赢了; 如果最大值都小于0, 就一定是输了.
- 选手Y: S(y)来表示 对于Y, 最大数字和之差. S(y) = Y - X
- 根据S(x) 来看, 如果从 数字和X里面, 拿出一个数字 m, 也就是 X = m + Xwithout(m)
- S(x) = m + Xwithout(m) - Y = m + (Xwithout(m) - Y).
- 如果我们从全局里面索性去掉m, 那么 S(y'') = Y - Xwithout(m)
- 那么推算下来: S(x) = m + (Xwithout(m) - Y) = m - (Y - Xwithout(m)) = m - S(y'')
- 在这个问题里面, 我们model X 和 Y的时候, 其实都是 dp[i][j], 而区别在于先手/后手.
- 将公式套用, 某一个S(x) = a[i] - dp[i + 1][j], 也就是m=a[i], 而 S(y'') = dp[i + 1][j]
- 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维.
- 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.
- Trick: 利用差值公式, 推导有点难想到.
- 找出[i, j]区间内的性质: dp[i][j]下标表示区间范围 [i, j]
- 子问题: 砍头, 砍尾, 砍头砍尾
- loop应该基于区间的length
- template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;
- 因为数组规律会变, 所以很难找'第一个burst的球'. 反之, 想哪一个是最后burst?
- 最后burst的那个变成一堵墙: 分开两边, 分开考虑, 加法原理; 最后再把中间的加上.
- dp[i][j] represent max value on range [i, j)
- Need to calculate dp[i][j] incrementally, starting from range size == 3 ---> n
- Use k to divide the range [i, j) and conquer each side.
- 中间劈开
- 砍断首或尾
- Range区间作为iteration的根本
- use pi[i][j] and print recursively.
- Print k, using pi[i][j]: max value taken at k
- 其实会做之后挺好想的一个DP
- dp[i][j] = balloons i~j 之间的 max.
- 然后找哪个点开始burst? 设为x。
- For loop 所有的点作为x, 去burst。
- 每次burst都切成了三份:左边可以recusive 求左边剩下的部分的最大值 + 中间3项相乘 + 右边递归下去求最大值。
- Note: 这个是Memoization, 而不纯是DP
- 因为recursive了,其实还是搜索,但是memorize了求过的值,节省了Processing
- Transform the problem:
removing at most k items to make it a palindrome
- that is: find the longest palindrome subsequence with length x, such that
n - x <= k
516. Longest Palindromic Subsequence
utilizes Interval DP to find LPS length x- at the end, perform n - x <= k?
- time: O(n^2) to find LPS
- space: O(n^2) for dp
- Either times out or too much space used
- time: O(n^2)
- space: O(n^2) or O(k*n^2)
- fib[n] = fibonacci(n - 1) + fibonacci(n - 2);
- 滚动数组, 简化DP
- recursively calculate fib(n - 1) + fib(n - 2). 公式没问题, 但是时间太长, timeout.
- define dp[x], 积累到amount x, 最少用多少个coin
- function:
dp[x] = Math.min(dp[x], dp[x - coinValue] + 1)
. two branches based on choosing coinValue or not - initialization
- dp[0], zero amount uses 0 coin. so dp[0] = 0
- Utilize
Integer.MAX_VALUE
as default val for initialize dp[x]: 1) alert error stage; 2) easy comparison
- create subproblem: (coins, amount - pickedCoin)
- memo[i] 依然表示: min # of coints to make amount i
- initialize memo[i] = Integer.MAX_VALUE
- 先选最后一步(遍历coins), 然后dfs做同样的操作
- 记录memo[amount] 如果已经给过value, 不要重复计算, 直接return.
- time: O(n * S), worst case it runs n coins for S(amount) iterations
- space: O(S)
- IMPORANT, Memoization:
Map<prefix, List<suffix variations>>
to build substring segments. Reduces repeated calculation if the substring has been tried. - Realize the input s expands into a tree of possible prefixes.
- Find list of candidates from subproblem, and cross-match
- DFS returns List segments of target s: every for loop takes a prefix substring, and append with all suffix (result of dfs)
- Time O(n!). Worst case, permutation of unique letters:
s= 'abcdef....'
, anddict=[a,b,c,d,e,f...]
- DFS on string: find a valid prefix, dfs on the suffix, building individual candidate in list till substring exhaust.
- improvement:
- use memo to record failed case (solved the timeout issue explained below)
- use min/max to as boundary for dict check.
- core code is short; helper code is slightly longer
- 两个DP一起用, 解决了timeout的问题: when a invalid case 'aaaaaaaaa' occurs, isValid[] stops dfs from occuring
-
- isWord[i][j], subString(i,j)是否存在dict中?
-
- 用isWord加快 isValid[i]: [i ~ end]是否可以从dict中找到合理的解?
- 从末尾开始查看i:因为我们需要测试isWord[i][j]时候,j>i, 而我们观察的是[i,j]这区间;
- j>i的部分同样需要考虑,我们还需要知道isValid[0~j+1]。 所以isValid[x]这次是表示[x, end]是否valid的DP。
- i 从 末尾到0, 可能是因为考虑到isWord[i][j]都是在[0~n]之内,所以倒过来数,坐标比较容易搞清楚。
- (回头看Word Break I, 也有坐标反转的做法)
-
- dfs 利用 isValid 和isWord做普通的DFS。
- Regarding regular solution: 如果不做memoization或者dp, 'aaaaa....aaa' will repeatedly calculate same substring
- Regarding double DP solution: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始. 但是, contains()本身是O(n); intead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary
- 递归很好写, 但是重复计算, timeout. time: O(2^n)
- O(2^n): each n can spawn 2 dfs child, at next level, it will keep spawn. Total 2^n nodes will spawn.
- 用全局变量int[] memo 帮助减少重复计算
- O(n) time, space
- 加法原理, 最后一步被前两种走法决定: dp[i] = dp[i - 1] + dp[i - 2]
- 基础sequence DP, int[] dp = int[n + 1];
- DP[]存的是以 1-based index的状态
- dp[i]: count # of ways to finish 前i个 台阶
- 需要知道dp[n] 的状态, 但是最大坐标是[n-1], 所以int[n+1]
- dp[0]往往是有特殊状态的. 这里, dp[0]: 1种方式可以原地不动
- dp[1]=1, 1种方式到达index=1,
- 之后的
dp[2] = dp[0]+dp[1]
: 就是dp[0]的走法 or dp[1]的走法
- O(n) space, time
- [i] only associates with [i-2], [i-1].
- %2
- O(1) space
- Use Map<number, count > to store the inputs
- Iterate over map to find the pair
- Use Set memo to store the success cases for fast return
- time: O(n), loop over all elements in map
- space: O(n), store all elements in map & memoization set
- Use example to understand: for any given ending char, 3 cases of palindromes
- a. ss[i, j] is a palindrome. dp[i+1][j-1] + 2 since the two indexes are counted, assume dp[i + 1][j - 1] is calculated
- b. ss[i + 1, j] is a palindrome
- c. ss[i, j - 1] is a palindrome
- time/space: O(n^2)
- Option1: start processing substring from tail
- set:
i = [n-1 towards 0]
,j = i + 1
- consider ss[i, j], ss[i + 1, j], ss[i, j - 1]
- since i starts from n - 1 -> 0 and j = i + 1, these are calculated and ready to go: dp[i+1][j-1], dp[i+1][j] and dp[i][j-1]
- FAST: skipped the initialization
- set:
- Option2: start processing substring from head
- 用[i][j]表示区间的首尾: 考虑3个情况: 砍头, 砍尾, 砍头并砍尾 (考虑首尾关系)
- Iteration on len:
- len = j - i + 1; 那么反推, 如果len已知,
j = len + i - 1
; - 注意考虑len == 1, len == 2是的特殊情况.
- consider sub problems with 3 major cases
- a. ss[i, j] is a palindrome: dfs check ss[i + 1, j - 1]
- b. ss[i + 1, j] maybe a palindrome: dfs check ss[i + 1, j]
- c. ss[i, j - 1] maybe a palindrome: dfs check ss[i, j - 1]
- memo[i][j]: max palindrome length in range [i, j], if calculated, return directly
- Init memo[i][j] = -1 to track the progress, memoization
- 注意: init dp[i][j]=-1, dfs的时候查dp[i][j] 是否算过
- more about dfs: bottom-up, first dive deep into dfs(i+1,j-1) till the base cases.
- Space: O(n^2)
- Time: O(n^2)
- prepare dp[n][n]: O(n^2); dfs: visit all combinations of [i,j]: O(n^2)
- Pick a subset (max-size k), and produce sub problem to solve by dfs
- NOTE: no need to change actual index value. That makes this problem easier (no need to record the choice path)
- time: O(n), calc memo[n]
- space: O(n), memo + stack depth
- trivial, 先加left recursively, 再加right recursively, 然后组成头部.
- Option1 w/o helper; option2 with dfs helper.
- Option1: reversely add to list
- 双stack的思想, 需要在图纸上画一画
- 原本需要的顺序是: 先leftChild, rightChild, currNode.
- 营造一个stack, reversely process: 先currNode, 再rightChild, 再leftChild
- 这样出来的结果是reverse的, 那么翻转一下就可以了.
- reverse add:
list.add(0, x)
; - 利用stack的特点
- 每次加element进stack的时候, 想要在 bottom/后process的, 先加
- 想要下一轮立刻process的, 最后push进stack.
- Option2: regular sequence add to stack: add curr, right, left
- Use set to contain the processed children
- only process curr if its children is processed
- IMPORTANT: DO NOT ASSUME positive integers
- Overall idea: write example and realize 2 cases at each node:
-
combo sum
: left + right + root
-
single path sum WITH curr node
: left/right + root
-
- DFS returns the path over curr node: a path needs to be continuous, so we cannot skip curr node.
- IMPORTANT, key discovery: if left/right single path over curr node is less than 0: reutrn 0.
- Parent path will simply drop this path, since we want maximize the path sum.
- It is so IMPORTANT: when left or right becomes 0, when comparing with global combo path:
- it automatically covers a special case:
single left/right path + node
, since one of left/right == 0!!!
- it automatically covers a special case:
- With the above understanding: what if I want to skip curr node and just want left/right path w/o curr node:
- it is handled and compared with global in dfs(node.left) or dfs(node.right) automatically!
- time: O(n), go over whole tree
- space: O(logn), tree height.
- tree给我们2条branch, 每条branch就类似于 dp[i - 1], 这里类似于dp[left], dp[right] 这样
- 找到 dp[left], dp[right] 以后, 跟 curr node结合.
- 因为是找max sum, 并且可以skip nodes, 所以需要全局变量max
- 每次dfs() return的一定是可以继续
continuously link 的 path
, 所以returnone single path sum + curr value
. - 用 PathSum 比较特别. 没有 data structure的时候, 写起来比较繁琐.
- 第一次做有点难理解,复杂原因是:因为可能有负值啊。不能乱assume正数。
- single path max 的计算是为了给后面的comboMax用的。
- 如果single path max小于0,那没有什么加到parent上面的意义,所以就被再次刷为0.
- combo的三种情况:(root可能小于0)
-
- 只有left
-
- 只有right
-
- root大于0,那么就left,right,curr全部加起来。
- 情况1和情况2取一个最大值,然后和情况三比较。做了两个Math.max(). 然后就有了这一层的comboMax
11. The Maze.java Level: Medium Tags: [BFS, DFS]
12. Median of Two Sorted Arrays.java Level: Hard Tags: [Array, Binary Search, DFS, Divide and Conquer]
著名的找两个sorted array的中位数. 中位数定义: 如果两个array总长为偶数, 取平均值. 题目要求在 log(m + n) 时间内解决
13. Word Search.java Level: Medium Tags: [Array, Backtracking, DFS]
14. Flood Fill.java Level: Easy Tags: [DFS]
Same as MS Paint
15. Construct Binary Tree from Inorder and Postorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Tree]
16. Expression Evaluation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Minimum Binary Tree, Stack]
给一个公式 expression, array of strings, 然后evaluate expression 结果.
17. Subtree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree s, 和一个binary tree t, 检查t是不是s的subtree.
18. Redundant Connection.java Level: Medium Tags: [BFS, DFS, Graph, Tree, Union Find]
19. Combinations.java Level: Medium Tags: [Backtracking, Combination, DFS]
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
20. Max Area of Island.java Level: Easy Tags: [Array, DFS]
21. Word Search II.java Level: Hard Tags: [Backtracking, DFS, Trie]
给一串words, 还有一个2D character matrix. 找到所有可以形成的words. 条件: 2D matrix 只可以相邻走位.
22. Convert Sorted Array to Binary Search Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
如题, build balanced BST from sorted array
23. Populating Next Right Pointers in Each Node.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个特殊的binary tree, treeNode里面有一个 next pointer.
写一个function, 把所有node都更同level的node 连在一起. 最右边的node.next = NULL
24. Find Peak Element II.java Level: Hard Tags: [Binary Search, DFS, Divide and Conquer]
2Dmatrix, 里面的value有一些递增, 递减的特点(细节比较长, 看原题). 目标是找到peak element
peak: 比周围4个方向的点value大
25. Redundant Connection II.java Level: Hard Tags: [DFS, Graph, Tree, Union Find]
26. Tweaked Identical Binary Tree.java Level: Easy Tags: [DFS, Tree]
检查binary tree是否 identical.
特点: subtree如果是有旋转的, 只要tree node value相等, 就可以算是identical
27. Subsets II.java Level: Medium Tags: [Array, BFS, Backtracking, DFS]
给一串integers(may have duplicates), 找到所有可能的subset. result里面不能有重复.
28. Segment Tree Modify.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给一个segmentTree, node里面存max. 写一个modify function: modify(node, index, value).
29. Kth Smallest Element in a BST.java Level: Medium Tags: [BST, DFS, Stack, Tree]
30. Robot Room Cleaner.java Level: Hard Tags: [Backtracking, DFS]
31. Invert Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
32. Number of Connected Components in an Undirected Graph.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
count这个graph里面有多少个独立的component.
33. Find the Connected Component in the Undirected Graph.java Level: Medium Tags: [BFS, DFS]
给一个undirected graph, return 所有的component. (这道题找不到了)
34. Surrounded Regions.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2D board, 里面是 'X' 和 'O'. 把所有被X包围的area都涂成'X'.
从四个边的edge出发, 像感染僵尸病毒一样扩散, 把靠边的node全部mark, 然后将还是'O'的改成X, 最后回复marks -> 'O'
35. Expression Add Operators.java Level: Hard Tags: [Backtracking, DFS, Divide and Conquer, String]
给一个数字String, 数字来自0-9
, 给3个操作符 +
,-
,*
, 看如何拼凑, 可以做出结果target.
output 所有 expression
36. Cracking the Safe.java Level: Hard Tags: [DFS, Greedy, Math]
37. Merge Two Binary Trees.java Level: Easy Tags: [DFS, Tree]
38. Path Sum II.java Level: Easy Tags: [Backtracking, DFS, Tree]
给一个inputSum, 然后dfs, 找到所有path, 满足: path sum 跟 inputSum 一样.
39. Segment Tree Query II.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
40. Longest Increasing Path in a Matrix.java Level: Hard Tags: [Coordinate DP, DFS, DP, Memoization, Topological Sort]
m x n 的matrix, 找最长增序的序列长度. 这里默认连续的序列.
还没有做
41. Recover Binary Search Tree.java Level: Hard Tags: [BST, DFS, Tree]
BST里面有2个node misplace, 要归为. 要求: O(1) extra space
42. Strobogrammatic Number II.java Level: Medium Tags: [DFS, Enumeration, Math, Sequence DFS]
TODO:
43. The Maze II.java Level: Medium Tags: [BFS, DFS, PriorityQueue]
44. Convert Sorted List to Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List]
如题, 把一个sorted singly linked list 转换成一个 height balanced BST
45. Smallest Subtree with all the Deepest Nodes.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个tree, �按照题意找最一个node�满足:
46. Combination Sum III.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一个integer k, 和一个target n.
从positive数字[1 ~ 9], 找到所有unique的 组合(combination) int[], size = k, 要求每个combination的和 = n.
(隐藏条件, 需要clarify): 同一个candidate integer [1 ~ 9], 只可以用一次.
47. Path Sum III.java Level: Easy Tags: [DFS, Double Recursive, Tree]
count所有存在的 path sum == target sum. 可以从任意点开始. 但是只能parent -> child .
48. Convert Expression to Reverse Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Reverse Polish Notation (RPN).
49. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
50. The Maze III.java Level: Hard Tags: [BFS, DFS, PriorityQueue]
51. Binary Tree Longest Consecutive Sequence.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
找到binary tree 里的最长 consecutive sequence.
52. Path Sum IV.java Level: Medium Tags: [DFS, Hash Table, Tree]
给一串3-digit 的数组. 每个数字的表达一个TreeNode, 3 digit分别代表: depth.position.value
这串数字已经从小到大排列. 求: 所有可能的 root->leaf path 的所有可能的 path sum 总和.
53. Target Sum.java Level: Medium Tags: [DFS, DP]
// 如何想到从中间initialize
54. Populating Next Right Pointers in Each Node II.java Level: Medium Tags: [DFS, Tree]
给一个binary tree, 用constant space link 所有所有node.next to same level next node.
55. [lint]. Segment Tree Query.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给了segment Tree, node里面有Max value, 找[start,end]里面的max
56. [tool]. Topological Sorting.java Level: Medium Tags: [BFS, DFS, Lint, Topological Sort]
TODO:
57. 102. Binary Tree Level Order Traversal.java Level: Medium Tags: [BFS, DFS, Tree]
如题.
58. 269. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
59. 22. Generate Parentheses.java Level: Medium Tags: [Backtracking, DFS, Sequence DFS, String]
60. 236. Lowest Common Ancestor of a Binary Tree.java Level: Medium Tags: [DFS, Tree]
给一个Binary Tree root, 以及两个node p, q. 找 p 和 q 的 lowest common ancestor
61. 301. Remove Invalid Parentheses.java Level: Hard Tags: [BFS, DFS, DP]
给一个string, 里面有括号和其他字符. 以最少刀 剪出 valid string, 求所有这样的string.
这个题目有多种解法, 最强就是O(n) space and time
62. 111. Minimum Depth of Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
63. 1216. Valid Palindrome III.java Level: Hard Tags: [DFS, DP, Memoization, String]
64. 207. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
65. 987. Vertical Order Traversal of a Binary Tree.java Level: Medium Tags: [BFS, Binary Tree, DFS, Hash Table, Tree]
space: O(n)
Very similar to 314. Binary Tree Vertical Order Traversal
with 1 special condition: if 2 nodes at same (offset, level):
sort it by its value
66. 694. Number of Distinct Islands.java Level: Medium Tags: [DFS, Hash Table]
67. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
68. 199. Binary Tree Right Side View.java Level: Medium Tags: [BFS, DFS, Tree]
给一个binary tree, 从右边看过来, return all visible nodes
69. 1203. Sort Items by Groups Respecting Dependencies.java Level: Hard Tags: [BFS, DFS, Graph, Topological Sort]
70. 1008. Construct Binary Search Tree from Preorder Traversal.java Level: Medium Tags: [DFS, Tree]
71. 515. Find Largest Value in Each Tree Row.java Level: Medium Tags: [BFS, DFS, Tree]
72. 1161. Maximum Level Sum of a Binary Tree.java Level: Medium Tags: [BFS, DFS, Graph]
73. 131. Palindrome Partitioning.java Level: Medium Tags: [Backtracking, DFS]
给个string s, partition(分段)后, 要确保每个partition都是palindrome.
求所有partition palindrome组合. list<list<string>>
74. 222. Count Complete Tree Nodes.java Level: Medium Tags: [Binary Search, DFS, Tree]
Complete Tree就是说, 最后一个level可能是缺node的(不是说最右下角缺node, 别忘了!)
75. 257. Binary Tree Paths.java Level: Easy Tags: [Backtracking, Binary Tree, DFS]
给一个binary tree, 返回所有root-to-leaf path
76. 1219. Path with Maximum Gold.java Level: Medium Tags: [Backtracking, DFS]
77. 1110. Delete Nodes And Return Forest.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
78. 339. Nested List Weight Sum.java Level: Easy Tags: [BFS, DFS, NestedInteger]
给一串integers, list里面可能有nest list. 算总的sum. 规则, 如果是nested list, 每深一个depth, sum要乘以depth.
79. 322. Coin Change.java Level: Medium Tags: [Backpack DP, DFS, DP, Memoization]
给一串不同数额的coins, 和total amount to spent. 求 最少 用多少个coin可以组合到这个amount. 每种coins个数不限量.
80. 140. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary. (Word Break I
only checks possibility)
利用 memoization: Map<prefix, List<suffix variations>>
81. 741. Cherry Pickup.java Level: Hard Tags: [DFS, DP]
special hint: r1 + c1 = constant t = r2 + c2
, if the two points are moving at same time.
82. 104. Maximum Depth of Binary Tree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree, 找最深depth
83. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
84. 46. Permutations.java Level: Medium Tags: [BFS, Backtracking, DFS, Permutation]
Method1-Option2: Recursive Backtracking, with list.contains()
to avoid reuse of index
Method1-Option3: Recursive Backtracking, with visited[]
to avoid reuse of index
85. 200. Number of Islands.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2Dmatrix, 里面是1和0, 找#of island.
86. 47. Permutations II.java Level: Medium Tags: [Backtracking, DFS]
space: O(n!)
给一串数组, 找出所有permutation数组. 注意: 给出的nums里面有重复数字, 而permutation的结果需要无重复.
Similar to 46. Permutations, but with dedup
87. 332. Reconstruct Itinerary.java Level: Medium Tags: [Backtracking, DFS, Graph]
88. 39. Combination Sum.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
89. 1106. Parsing A Boolean Expression.java Level: Hard Tags: [DFS, Stack, String]
90. 144. Binary Tree Preorder Traversal.java Level: Medium Tags: [BFS, DFS, Stack, Tree]
91. 110. Balanced Binary Tree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree, 看是否是height-balanced
92. 100. Same Tree.java Level: Easy Tags: [BFS, DFS, Tree]
给两个 binary tree, 看两个tree是否identical.
93. 112. Path Sum.java Level: Easy Tags: [DFS, Tree]
给一个inputSum, 然后dfs, 找到是否有一条path, 得出的path sum 跟 inputSum 一样.
94. 1026. Maximum Difference Between Node and Ancestor.java Level: Medium Tags: [DFS, Tree]
95. 78. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
96. 210. Course Schedule II.java Level: Medium Tags: [BFS, DFS, Graph, Topological Sort]
97. 314. Binary Tree Vertical Order Traversal.java Level: Medium Tags: [BFS, DFS, Hash Table, Tree]
给一个Binary Tree, traverse所有node, 按照vertial order 排列成output: List
重点是: col里面有排序, lower level的排在前面; 如果node遇到collision在同一个位置: 根据他们的相对位置 先放left, 再放right
98. 261. Graph Valid Tree.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
检查这些edge是否能合成一个 valid tree
99. 114. Flatten Binary Tree to Linked List.java Level: Medium Tags: [Binary Tree, DFS]
给一个binary tree, 把tree做成 linked list的形式, in-place.
100. 516. Longest Palindromic Subsequence.java Level: Medium Tags: [DFS, DP, Interval DP, Memoization]
给一个string s, 找最长的sub-sequence which is also palindrome.
注意!subsequence并不是substring, 是可以skip letter / non-continuous character sequence
101. 430. Flatten a Multilevel Doubly Linked List.java Level: Medium Tags: [DFS, Linked List]
102. 105. Construct Binary Tree from Preorder and Inorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Hash Table, Tree]
如题
103. 40. Combination Sum II.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (can have duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 只可以用一次.
104. 364. Nested List Weight Sum II.java Level: Medium Tags: [DFS, NestedInteger]
105. 1043. Partition Array for Maximum Sum.java Level: Medium Tags: [DFS, DP, Graph, Memoization]
106. 133. Clone Graph.java Level: Medium Tags: [BFS, DFS, Graph]
给一个graph node, 每个node有list of neighbors. 复制整个graph, return new head node.
实现起来就好像在crawl urls.
107. 743. Network Delay Time.java Level: Medium Tags: [BFS, DFS, Graph, Heap, PQ]
quesiton: sorting by travel delay/time will find better answer earlier?
108. 426. Convert Binary Search Tree to Sorted Doubly Linked List.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List, Tree]
把 BST 转换成一个 sorted doubly linked list. (in-place)
109. 98. Validate Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Tree]
验证是否是BST by definition
110. 1123. Lowest Common Ancestor of Deepest Leaves.java Level: Medium Tags: [BFS, DFS, Tree]
111. 399. Evaluate Division.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
112. 785. Is Graph Bipartite.java Level: Medium Tags: [BFS, DFS, Garph]
113. 124. Binary Tree Maximum Path Sum.java Level: Hard Tags: [DFS, DP, Tree, Tree DP]
找max path sum, 可以从任意treeNode 到任意 treeNode.
114. 721. Accounts Merge.java Level: Medium Tags: [DFS, Hash Table, Union Find]
给一串account in format [[name, email1, email2, email3], [name2, email,..]]
.
要求把所有account merge起来 (可能多个record记录了同一个人, �by common email)
115. 101. Symmetric Tree.java Level: Easy Tags: [BFS, DFS, Tree]
检查tree是否symmetric
注意Symmetric Binary Tree的例子和定义: 是镜面一样的对称. 并不是说左右两个sub-tree相等。
116. 698. Partition to K Equal Sum Subsets.java Level: Medium Tags: [DFS, DP, Recursion]
117. 366. Find Leaves of Binary Tree.java Level: Medium Tags: [DFS, Tree]
118. 235. Lowest Common Ancestor of a Binary Search Tree.java Level: Easy Tags: [BST, DFS, Tree]
给 binary search tree root, q node, p node. 找到p q 的lowest common ancestor
119. 156. Binary Tree Upside Down.java Level: Medium Tags: [DFS, Tree]
120. 254. Factor Combinations.java Level: Medium Tags: [BFS, Backtracking, DFS]
0. Sliding Window Median.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap, Sliding Window]
Data Stream Median 的同理题目: 不只是不断增加的Sequence, 而且要remove item (保持一个window size)
1. Perfect Rectangle.java Level: Hard Tags: [Design, Geometry, Hash Table]
看的list of coordinates 是否能组成perfect rectangle, 并且不允许overlap area.
2. Flatten 2D Vector.java Level: Medium Tags: [Design]
Implement an iterator to flatten a 2d vector.
Just move pointers carefully with next(), hashNext()
3. Implement Stack using Queues.java Level: Easy Tags: [Design, Stack]
如题.
4. ColorGrid.java Level: Medium Tags: [Design, Hash Table]
5. Peeking Iterator.java Level: Medium Tags: [Design]
6. LFU Cache.java Level: Hard Tags: [Design, Hash Table]
7. Singleton.java Level: Easy Tags: [Design]
让一个class 是 singleton
8. Min Stack.java Level: Easy Tags: [Design, Stack]
双Stack:一个正常stack,另一个minStack存当下level最小值. 注意维护minStack的变化
另外. 如果要maxStack,也是类似做法
9. Design Search Autocomplete System.java Level: Hard Tags: [Design, Hash Table, MinHeap, PriorityQueue, Trie]
Description is long, but in short: 做 search auto complete.
Best problem to review Trie (prefix search), Top K frequent elements (Hash Map), and MinHeap (PriorityQueue)
Easier to revisit https://leetcode.com/problems/design-search-autocomplete-system/description/
10. Unique Word Abbreviation.java Level: Medium Tags: [Design, Hash Table]
给一个string[] dict, 和一个word.
每个word都可以缩写成固定的abbreviation <first letter><number><last letter>
(详细看原题)
检查input word是否满足unique
11. Implement Queue using Stacks.java Level: Easy Tags: [Design, Stack]
画图, 知道最后maintain的stack是那个 reverseStack: pop(), peek(), empty() 都在这个stack上, 无需变换. push()里面做stack和reverseStack的来回倾倒. 相比老的code, 在PUSH里面做倾倒, 更容易读.
双Stack. 一个是等于是queue,一个是backfillStack. Tricky: 是在pop()和peek()的时候backfill, 并且要等到stack用完再backfill. 写一下例子就知道,如果提早backfill,stack.peek()就不是queue的head了.
12. 359. Logger Rate Limiter.java Level: Easy Tags: [Design, Hash Table]
13. 244. Shortest Word Distance II.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
14. 173. Binary Search Tree Iterator.java Level: Medium Tags: [BST, Design, Stack, Tree]
15. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
16. 245. Shortest Word Distance III.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
跟243/244不同: 这里允许list里面有重复的word.
17. 211. Add and Search Word - Data structure design.java Level: Medium Tags: [Backtracking, Design, Trie]
18. 295. Find Median from Data Stream.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap]
19. 146. LRU Cache.java Level: Medium Tags: [Design, Doubly Linked List, Hash Table, Linked List]
20. 170. Two Sum III - Data structure design.java Level: Easy Tags: [Design, Hash Table, Memoization]
21. 432. All One Data Structure.java Level: Hard Tags: [Design, Doubly Linked List]
22. 380. Insert Delete GetRandom O(1).java Level: Medium Tags: [Array, Design, Hash Table]
23. 346. Moving Average from Data Stream.java Level: Easy Tags: [Design, Queue, Sliding Window]
给一个interface, design一个structure, 能够计算moving window average.
24. 208. Implement Trie (Prefix Tree).java Level: Medium Tags: [Design, Trie]
Implement Tire, 也即是 Prefix Tree. 做三个function: insert, search, startWith
25. 716. Max Stack.java Level: Medium Tags: [Design, Doubly Linked List, Stack, TreeMap]
26. 341. Flatten Nested List Iterator.java Level: Medium Tags: [Design, NestedInteger, Stack]
0. Coins in a Line II.java Level: Medium Tags: [Array, DP, Game Theory, Memoization, MiniMax]
给一串coins, 用values[]表示; 每个coin有自己的value. 先手/后手博弈, 每次只能 按照从左到右的顺序, 拿1个或者2个棋子, 最后看谁拿的总值最大.
MiniMax的思考方法很神奇, 最后写出来的表达式很简单
Time O(n) Space O(n): dp[], sum[]
1. Coins in a Line III.java Level: Hard Tags: [Array, DP, Game Theory, Interval DP, Memoization]
LeetCode: Predict the Winner
还是2个人拿n个coin, coin可以有不同的value.
只不过这次选手可以从任意的一头拿, 而不限制从一头拿. 算先手会不会赢?
2. Coins in a Line.java Level: Medium Tags: [DP, Game Theory, Greedy]
拿棋子游戏, 每个人可以拿1个或者2个, 拿走最后一个子儿的输. 问: 根据给的棋子输, 是否能确定先手的输赢?
Game Theory: 如果我要赢, 后手得到的局面一定要'有输的可能'.
空间优化O(1). Rolling array, %2
3. Nim Game.java Level: Easy Tags: [Brainteaser, DP, Game Theory]
0. [lint]. HashHeap.java Level: Hard Tags: [HashHeap, Heap, Lint]
非题.是从九章找来的HashHeap implementation.
1. [lint]. Heapify.java Level: Medium Tags: [HashHeap, Heap, Lint, MinHeap]
Turn unsorted array into a min-heap array, where for each A[i],
A[i * 2 + 1] is the left child of A[i] and A[i * 2 + 2] is the right child of A[i].
2. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
0. Restore IP Addresses.java Level: Medium Tags: [Backtracking, DFS, String]
给一串数字, 检查是否是valid IP, 如果合理, 给出所有valid 的IP组合方式.
1. Flip Game II.java Level: Medium Tags: [Backtracking, DFS, DP]
String 只包含 + , - 两个符号. 两个人轮流把consecutive连续的++
, 翻转成 --
.
如果其中一个人再无法翻转了, 另一个人就赢. 求: 给出string, 先手是否能赢.
2. Word Search.java Level: Medium Tags: [Array, Backtracking, DFS]
3. Permutation Sequence.java Level: Medium Tags: [Backtracking, Math]
TODO: what about regular DFS/backtracking to compute the kth? dfs(rst, list, candiate list, k)
k是permutation的一个数位。而permutation是有规律的。
也就是说,可以根据k的大小来判断每一个数位的字符(从最大数位开始,因为默认factorio从最大数位开始变化)。
于是先求出n!, 然后 k/n!就可以推算出当下这一个数位的字符。然后分别把factorio 和 k减小。
另外, 用一个boolean[] visited来确保每个数字只出现一次。
这个方法比计算出每个permutation要efficient许多。
4. Letter Combinations of a Phone Number.java Level: Medium Tags: [Backtracking, String]
方法1: Iterative with BFS using queue.
方法2: Recursively adding chars per digit
5. Combinations.java Level: Medium Tags: [Backtracking, Combination, DFS]
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
6. Word Search II.java Level: Hard Tags: [Backtracking, DFS, Trie]
给一串words, 还有一个2D character matrix. 找到所有可以形成的words. 条件: 2D matrix 只可以相邻走位.
7. Gray Code.java Level: Medium Tags: [Backtracking]
TODO:
题目蛋疼,目前只接受一种结果。
BackTracking + DFS:
Recursive helper里每次flip一个 自己/左边/右边. Flip过后还要恢复原样.遍历所有.
曾用法(未仔细验证):
基本想法就是从一个点开始往一个方向走,每次flip一个bit, 碰壁的时候就回头走。
8. Subsets II.java Level: Medium Tags: [Array, BFS, Backtracking, DFS]
给一串integers(may have duplicates), 找到所有可能的subset. result里面不能有重复.
9. Robot Room Cleaner.java Level: Hard Tags: [Backtracking, DFS]
10. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
11. Expression Add Operators.java Level: Hard Tags: [Backtracking, DFS, Divide and Conquer, String]
给一个数字String, 数字来自0-9
, 给3个操作符 +
,-
,*
, 看如何拼凑, 可以做出结果target.
output 所有 expression
12. Path Sum II.java Level: Easy Tags: [Backtracking, DFS, Tree]
给一个inputSum, 然后dfs, 找到所有path, 满足: path sum 跟 inputSum 一样.
13. Word Squares.java Level: Hard Tags: [Backtracking, Trie]
可以开Trie class, 里面用到TrieNode. 开Trie(words) 可以直接initalize with for loop TrieNode 里面可以有一个 List startWith: 记录可以到达这个点的所有string: 有点像树形, ancestor形状的存储.
神操作: 根据square的性质, 如果选中了list of words, 设定 int prefixIndex = list.size(). 取出list里面的所有word[prefixedIndex], 并且加在一起, 就是下一个word candidate的 prefix.
形象一点: list = ["ball", "area"]; prefixIndex = list.size(); ball[prefixIndex] = 'l'; area[prefixIndex] = 'e'; //then candidatePrefix = ball[prefixIndex] + area[prefixIndex] = "le"; 这里就可以用到Trie的那个 findByPrefix function, 在每个点, 都存有所有这个点能产生的candidate. 这时, 试一试所有candidate: dfs
能想到这种倒转的结构来存prefix candidates 在 Trie里面, 这个想法非常值得思考.
14. Combination Sum III.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一个integer k, 和一个target n.
从positive数字[1 ~ 9], 找到所有unique的 组合(combination) int[], size = k, 要求每个combination的和 = n.
(隐藏条件, 需要clarify): 同一个candidate integer [1 ~ 9], 只可以用一次.
15. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
16. Palindrome Permutation II.java Level: Medium Tags: [Backtracking, Permutation]
TODO: need to review permutation
permutation的综合题:
17. 269. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
18. 22. Generate Parentheses.java Level: Medium Tags: [Backtracking, DFS, Sequence DFS, String]
19. 207. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
20. 131. Palindrome Partitioning.java Level: Medium Tags: [Backtracking, DFS]
给个string s, partition(分段)后, 要确保每个partition都是palindrome.
求所有partition palindrome组合. list<list<string>>
21. 257. Binary Tree Paths.java Level: Easy Tags: [Backtracking, Binary Tree, DFS]
给一个binary tree, 返回所有root-to-leaf path
22. 1219. Path with Maximum Gold.java Level: Medium Tags: [Backtracking, DFS]
23. 140. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary. (Word Break I
only checks possibility)
利用 memoization: Map<prefix, List<suffix variations>>
24. 51. N-Queens.java Level: Hard Tags: [Backtracking]
N-Queen 问题, 给数字n, 和 nxn board, 找到所有N-queens的答案.
25. 46. Permutations.java Level: Medium Tags: [BFS, Backtracking, DFS, Permutation]
Method1-Option2: Recursive Backtracking, with list.contains()
to avoid reuse of index
Method1-Option3: Recursive Backtracking, with visited[]
to avoid reuse of index
26. 211. Add and Search Word - Data structure design.java Level: Medium Tags: [Backtracking, Design, Trie]
27. 47. Permutations II.java Level: Medium Tags: [Backtracking, DFS]
space: O(n!)
给一串数组, 找出所有permutation数组. 注意: 给出的nums里面有重复数字, 而permutation的结果需要无重复.
Similar to 46. Permutations, but with dedup
28. 332. Reconstruct Itinerary.java Level: Medium Tags: [Backtracking, DFS, Graph]
29. 39. Combination Sum.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
30. 10. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
IMPORTANT: '*' 需要有一个 prefix element [elm], so it becomes [elm]*
. There 2 possible cases:
31. 78. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
32. 52. N-Queens II.java Level: Hard Tags: [Backtracking]
跟 N-Queens 一样, 不是找所有结果, 而是count多少结果.
33. 40. Combination Sum II.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (can have duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 只可以用一次.
34. 254. Factor Combinations.java Level: Medium Tags: [BFS, Backtracking, DFS]
0. Frog Jump.java Level: Hard Tags: [DP, Hash Table]
Frog jump 的题目稍微需要理解: 每个格子可以 jump k-1, k, k+1 steps, 而k取决于上一步所跳的步数. 默认 0->1 一定是跳了1步.
注意: int[] stones 里面是stone所在的unit (不是可以跳的步数, 不要理解错).
1. Perfect Rectangle.java Level: Hard Tags: [Design, Geometry, Hash Table]
看的list of coordinates 是否能组成perfect rectangle, 并且不允许overlap area.
2. Line Reflection.java Level: Medium Tags: [Hash Table, Math]
给一串点, 找是否有一个所有点中间的, 跟y-axis平行的中线.
3. Longest Consecutive Sequence.java Level: Hard Tags: [Array, Hash Table, Union Find]
给一串数字, unsorted, 找这串数字里面的连续元素序列长度 (consecutive序列, 是数字连续, 并不是说要按照原order)
4. Rearrange String k Distance Apart.java Level: Hard Tags: [Greedy, Hash Table, Heap]
给一个string, 全是lowercase letter, 要求重新排列: 然后每个unique的character要有k distance apart.
跟Task Scheduler有点像, 只不过Task那道题里面还可以用其他方法求count, 这道题要求出排列结果
5. Remove Duplicate Letters.java Level: Hard Tags: [Greedy, Hash Table, Stack]
6. ColorGrid.java Level: Medium Tags: [Design, Hash Table]
7. Maximal Rectangle.java Level: Hard Tags: [Array, DP, Hash Table, Stack]
分解开来, 其实是'Largest Rectangle in Histogram', 只不过这里要自己model heights. 一个2D array里面的rectangle, 最终也是用height * width做出来的. 巧妙在于, 把每一行当做底边, 算出这个底边, 到顶部的height:
如果看具体实例, 有些row似乎是白算的, 但是没有办法, 这是一个搜索的过程, 最终会比较出最优解.
Coordinate DP?
8. LFU Cache.java Level: Hard Tags: [Design, Hash Table]
9. Encode and Decode TinyURL.java Level: Medium Tags: [Hash Table, Math]
其实想到了切入点, 是个可难可简单的题目. 这里的encode就是想办法把url存起来, 然后给个 key. 那么具体怎么做这个key, 简单就可以用一个map, 然后counting. 复杂一点就可以做random letter/number组成key.
10. Rehashing.java Level: Medium Tags: [Hash Table]
给一个Hash Table, 是用 linked list 做的. 问题是: capacity太小, collision太多的情况下, 需要double capacity 然后rehash.
11. 4Sum.java Level: Medium Tags: [Hash Table]
12. Contiguous Array.java Level: Medium Tags: [Hash Table]
TODO: how aout without chaning the input nums?
13. Palindrome Pairs.java Level: Hard Tags: [Hash Table, String, Trie]
Obvious的做法是全部试一遍, 判断, 变成 O(n^2) * O(m) = O(mn^2). O(m): isPalindrome() time.
当然不行了, 那就看是O(nlogN), 还是O(n)?
O(mn)
还要做一下那.
14. Longest Substring Without Repeating Characters.java Level: Medium Tags: [Hash Table, String, Two Pointers]
方法1: Two Pointers 双指针: 从start开始遍历, 但是第一步是while loop来推进end; 直到推不动end, 然后start++ 巧妙点: 因为end是外围variable, 在start的loop上, end不会重置;[star ~ end] 中间不需要重复计算. 最终可以O(n);
Previous verison of two pointers:
用两个pointer, head和i.
注意:head很可能被退回到很早的地方,比如abbbbbba,当遇到第二个a,head竟然变成了 head = 0+1 = 1.
当然这是不对的,所以head要确保一直增长,不回溯
方法2: HashMap<Char, Integer>: <character, last occurance index> 一旦有重复, rest map. 没有重复时候, 不断map.put(), 然后求max值
问题: 每次reset map之后就开始从新从一个最早的index计算, 最坏情况是O(n^2): 'abcdef....xyza'
15. Fraction to Recurring Decimal.java Level: Medium Tags: [Hash Table, Math]
TODO: no need of hashMap, just use set to store the existing
不难想到处理除法:考虑正负,考虑小数点前后。主要是小数点以后的要着重考虑。 很容易忽略的是integer的益处。
16. Majority Number III.java Level: Medium Tags: [Hash Table, Linked List]
TODO:
17. Design Search Autocomplete System.java Level: Hard Tags: [Design, Hash Table, MinHeap, PriorityQueue, Trie]
Description is long, but in short: 做 search auto complete.
Best problem to review Trie (prefix search), Top K frequent elements (Hash Map), and MinHeap (PriorityQueue)
Easier to revisit https://leetcode.com/problems/design-search-autocomplete-system/description/
18. Maximum Size Subarray Sum Equals k.java Level: Medium Tags: [Hash Table, PreSum, Subarray]
19. Submatrix Sum.java Level: Medium Tags: [Array, Hash Table, PreSum]
给一个int[][] matrix, 找一个sub matrix, where the sum == 0.
20. Unique Word Abbreviation.java Level: Medium Tags: [Design, Hash Table]
给一个string[] dict, 和一个word.
每个word都可以缩写成固定的abbreviation <first letter><number><last letter>
(详细看原题)
检查input word是否满足unique
21. Brick Wall.java Level: Medium Tags: [Hash Table]
给一面墙, 每一行是一行bricks. 用一条vertical line 扫描, 会vertically割开brink. 找到割开最少brick的那条线的x index.
22. HashWithCustomizedClass(LinkedList).java Level: Medium Tags: [Hash Table]
练习HashMap with customized class. functions: get(), put(), getRandom()
23. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
24. Group Shifted Strings.java Level: Medium Tags: [Hash Table, String]
25. Path Sum IV.java Level: Medium Tags: [DFS, Hash Table, Tree]
给一串3-digit 的数组. 每个数字的表达一个TreeNode, 3 digit分别代表: depth.position.value
这串数字已经从小到大排列. 求: 所有可能的 root->leaf path 的所有可能的 path sum 总和.
26. [lint]. Longest Words.java Level: Easy Tags: [Hash Table, Lint, String]
给一串String, 找到最长的长度, 把最长的String全都return
27. [lint]. Anagrams.java Level: Medium Tags: [Array, Hash Table, Lint]
把anagram找到并output
28. [lint]. Lowest Common Ancestor II.java Level: Easy Tags: [Hash Table, Lint, Tree]
给一个Binary Tree root, 以及两个node A, B. 特点: node里面存了parent pointer. 找 lowest common ancestor
29. [lint]. Subarray Sum.java Level: Easy Tags: [Array, Hash Table, Lint, PreSum, Subarray]
给一串数字, 找其中的一个subarray的 [start, end] index, 条件: subarary sum == 0.
30. [tool]. Hash Function.java Level: Easy Tags: [Hash Table, Lint]
In general, there is no universal recipe to stick to when it comes to implementing hashCode(). https://www.baeldung.com/java-hashcode
31. 36. Valid Sudoku.java Level: Easy Tags: [Enumeration, Hash Table]
32. 359. Logger Rate Limiter.java Level: Easy Tags: [Design, Hash Table]
33. 347. Top K Frequent Elements.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue]
给一串数字, 找到top k frequent element, 并且time complexity 要比nLogN要好
34. 953. Verifying an Alien Dictionary.java Level: Easy Tags: [Hash Table]
35. 1213. Intersection of Three Sorted Arrays.java Level: Easy Tags: [Hash Table, Two Pointers]
Very similar to 349.Intersection of Two Arrays.
36. 76. Minimum Window Substring.java Level: Hard Tags: [Hash Table, Sliding Window, String, Two Pointers]
基本思想:
37. 244. Shortest Word Distance II.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
38. 987. Vertical Order Traversal of a Binary Tree.java Level: Medium Tags: [BFS, Binary Tree, DFS, Hash Table, Tree]
space: O(n)
Very similar to 314. Binary Tree Vertical Order Traversal
with 1 special condition: if 2 nodes at same (offset, level):
sort it by its value
39. 204. Count Primes.java Level: Easy Tags: [Hash Table, Math]
计数: 所有小于n的prime number.
40. 496. Next Greater Element I.java Level: Easy Tags: [Hash Table, Stack]
41. 694. Number of Distinct Islands.java Level: Medium Tags: [DFS, Hash Table]
42. 136. Single Number.java Level: Easy Tags: [Bit Manipulation, Hash Table]
Bit XOR: 当两个bit不同时,return 1. 题目正要消光所有重复出现的数儿留下出现一次的那个.
43. 1048. Longest String Chain.java Level: Medium Tags: [Bucket Sort, DP, Hash Table, Sort]
44. 299. Bulls and Cows.java Level: Medium Tags: [Hash Table]
45. 266. Palindrome Permutation.java Level: Easy Tags: [Hash Table]
给String, 看permutation是否能是palindrome
46. 311. Sparse Matrix Multiplication.java Level: Medium Tags: [Hash Table]
给两个matrics, 做乘积. 注意, 是sparse matrix (特点: 很多0).
47. 140. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary. (Word Break I
only checks possibility)
利用 memoization: Map<prefix, List<suffix variations>>
48. 349. Intersection of Two Arrays.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
49. 245. Shortest Word Distance III.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
跟243/244不同: 这里允许list里面有重复的word.
50. 771. Jewels and Stones.java Level: Easy Tags: [Hash Table]
51. 727. Minimum Window Subsequence.java Level: Hard Tags: [DP, Hash Table, Sliding Window, String, Two Pointers]
52. 387. First Unique Character in a String.java Level: Easy Tags: [Hash Table, String]
53. 146. LRU Cache.java Level: Medium Tags: [Design, Doubly Linked List, Hash Table, Linked List]
54. 246. Strobogrammatic Number.java Level: Easy Tags: [Enumeration, Hash Table, Math, Two Pointers]
根据题意枚举, 再根据规则basic implementation
55. 463. Island Perimeter.java Level: Easy Tags: [Hash Table]
56. 170. Two Sum III - Data structure design.java Level: Easy Tags: [Design, Hash Table, Memoization]
57. 981. Time Based Key-Value Store.java Level: Medium Tags: [Binary Search, Hash Table, TreeMap]
58. 202. Happy Number.java Level: Easy Tags: [Hash Table, Math]
Basic Implementation of the requirements.
用HashSet存查看过的数值。若重复,return false.
59. 380. Insert Delete GetRandom O(1).java Level: Medium Tags: [Array, Design, Hash Table]
60. 560. Subarray Sum Equals K.java Level: Medium Tags: [Array, Hash Table, PreSum, Subarray]
给一串数字, 找其中的 # of subarray的 where subararySum == k.
61. 219. Contains Duplicate II.java Level: Easy Tags: [Array, Hash Table]
Unsorted array, 找出是否有duplicate elemenets: 必要条件是, 这两个element的index i,j 的大小最多相差k.
62. 205. Isomorphic Strings.java Level: Easy Tags: [Hash Table]
63. 314. Binary Tree Vertical Order Traversal.java Level: Medium Tags: [BFS, DFS, Hash Table, Tree]
给一个Binary Tree, traverse所有node, 按照vertial order 排列成output: List
重点是: col里面有排序, lower level的排在前面; 如果node遇到collision在同一个位置: 根据他们的相对位置 先放left, 再放right
64. 242. Valid Anagram.java Level: Easy Tags: [Hash Table, Sort]
65. 340. Longest Substring with At Most K Distinct Characters.java Level: Hard Tags: [Hash Table, LinkedHashMap, Sliding Window, String, Two Pointers]
66. 217. Contains Duplicate.java Level: Easy Tags: [Array, Hash Table]
无序数组, 找是否有重复element, return true/false.
67. 1146. Snapshot Array.java Level: Medium Tags: [Array, Hash Table, TreeMap]
68. 1. Two Sum.java Level: Easy Tags: [Array, Hash Table]
69. 139. Word Break.java Level: Medium Tags: [DP, Hash Table, Sequence DP]
给一个String word, 和一个字典, 检查是否word可以被劈开, 而所有substring都应该是dictionary里面的words.
70. 105. Construct Binary Tree from Preorder and Inorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Hash Table, Tree]
如题
71. 274.H-Index.java Level: Medium Tags: [Bucket Sort, Hash Table, Sort]
找到h-index, 给的citation int[] 并不是sorted. h-index 的definition 具体看题目.
72. 350. Intersection of Two Arrays II.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
73. 49. Group Anagrams.java Level: Medium Tags: [Hash Table, String]
给一串string, return list of list, 把anagram 放在一起.
74. 720. Longest Word in Dictionary.java Level: Easy Tags: [Hash Table, Trie]
给串word[], 找最长的Word, 满足条件: 这个Word可以从 word[] 里面一个字母一个字母被build出来.
如果多种答案, respect smallest lexicographical order.
75. 438. Find All Anagrams in a String.java Level: Medium Tags: [Hash Table, Sliding Window, Two Pointers]
跟 Permutation in String 很像. 给短string p, 长string s.
找所有p的 anagram (permutation) 在s 里面的起始index.
76. 632. Smallest Range Covering Elements from K Lists.java Level: Hard Tags: [Hash Table, Sliding Window, Two Pointers]
77. 138. Copy List with Random Pointer.java Level: Medium Tags: [Hash Table, Linked List]
deep copy linked list. linked list 上有random pointer to other nodes.
78. 159. Longest Substring with At Most Two Distinct Characters.java Level: Medium Tags: [Hash Table, Sliding Window, String, Two Pointers]
如题.
79. 760. Find Anagram Mappings.java Level: Easy Tags: [Hash Table]
80. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
81. 94. Binary Tree Inorder Traversal.java Level: Easy Tags: [Hash Table, Stack, Tree]
Inorder traverse Binary Tree
82. 767. Reorganize String.java Level: Medium Tags: [Greedy, Hash Table, Heap, Sort, String]
We want to exhaust largest population and merge like merging k list. Problem: largest population may result in them being adjacent. How to resolve?
83. 721. Accounts Merge.java Level: Medium Tags: [DFS, Hash Table, Union Find]
给一串account in format [[name, email1, email2, email3], [name2, email,..]]
.
要求把所有account merge起来 (可能多个record记录了同一个人, �by common email)
84. 149. Max Points on a Line.java Level: Hard Tags: [Array, Geometry, Hash Table, Math]
给list of (x,y) coordinates. Determine # of points on the same line
85. 739. Daily Temperatures.java Level: Medium Tags: [Hash Table, Monotonous Stack, Stack]
0. Binary Tree Longest Consecutive Sequence II.java Level: Medium Tags: [DFS, Divide and Conquer, Double Recursive, Tree]
找到binary tree 里的最长 consecutive sequence. Sequence可以递增递减, Sequence顺序可以回溯parent.
1. Path Sum III.java Level: Easy Tags: [DFS, Double Recursive, Tree]
count所有存在的 path sum == target sum. 可以从任意点开始. 但是只能parent -> child .
0. Combinations.java Level: Medium Tags: [Backtracking, Combination, DFS]
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
1. Combination Sum III.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一个integer k, 和一个target n.
从positive数字[1 ~ 9], 找到所有unique的 组合(combination) int[], size = k, 要求每个combination的和 = n.
(隐藏条件, 需要clarify): 同一个candidate integer [1 ~ 9], 只可以用一次.
2. 39. Combination Sum.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
3. 40. Combination Sum II.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (can have duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 只可以用一次.
0. K Empty Slots.java Level: Hard Tags: [Array, BST, TreeSet]
题目解析后: find 2 number, that: 1. k slots between the 2 number, 2. no slots taken between the two number.
1. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
2. 855. Exam Room.java Level: Medium Tags: [PriorityQueue, Sort, TreeMap, TreeSet]
3. 715. Range Module.java Level: Hard Tags: [Segment Tree, TreeSet]
0. Inorder Successor in BST.java Level: Medium Tags: [BST, Tree]
找 Inorder traversal规则里的下一个.
主要想法是考虑: 1. 如果 node.right == null, 找上一个unprocessed node alone the inorder traversal path 2. 如果 node.right != null, successor 一定在这个node.right那个subtree里面 最后竟然可以简化成几行, 非常全面的BST问题: 有search, 有对inorder traversal的理解, 还有坑.
1. House Robber III.java Level: Medium Tags: [DFS, DP, Status DP, Tree]
Houses被arrange成了binary tree, 规则还是一样, 连续相连的房子不能同时抄.
求Binary Tree neighbor max 能抄多少.
2. Binary Tree Maximum Path Sum II.java Level: Medium Tags: [DFS, Tree]
找到从max path sum from root. 条件: 至少有一个node.
3. Unique Binary Search Tree.java Level: Medium Tags: [BST, DP, Tree]
Not quite clear. 根据左右分割而总结出了原理, 每次分割, 左右两边都会有一定数量的permutation, 总体上的情况数量当然是相乘. 然后每一个不同的分割点都加一遍: f(n) = f(0)*f(n-1) + f(1)*f(n-2) + ... + f(n-2)*f(1) + f(n-1)*f(0)
然后把数学公式转换成DP的方程, 有点玄学的意思啊! 不好想.
4. Two Sum IV - Input is a BST.java Level: Easy Tags: [Tree]
HashSet to store visited items. Same old 2 sum trick.
5. Binary Tree Longest Consecutive Sequence II.java Level: Medium Tags: [DFS, Divide and Conquer, Double Recursive, Tree]
找到binary tree 里的最长 consecutive sequence. Sequence可以递增递减, Sequence顺序可以回溯parent.
6. Subtree of Another Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
7. Binary Tree Level Order Traversal II.java Level: Medium Tags: [BFS, Tree]
如题, 但是output要倒序.
8. Maximum Binary Tree.java Level: Medium Tags: [Stack, Tree]
给一串数字, 做一个 maximum binary tree: 最顶上的root最大; 左child也是一个max tree, 右child也必须是max tree.
9. Construct Binary Tree from Inorder and Postorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Tree]
10. Subtree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree s, 和一个binary tree t, 检查t是不是s的subtree.
11. Redundant Connection.java Level: Medium Tags: [BFS, DFS, Graph, Tree, Union Find]
12. Convert Sorted Array to Binary Search Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
如题, build balanced BST from sorted array
13. Populating Next Right Pointers in Each Node.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个特殊的binary tree, treeNode里面有一个 next pointer.
写一个function, 把所有node都更同level的node 连在一起. 最右边的node.next = NULL
14. Redundant Connection II.java Level: Hard Tags: [DFS, Graph, Tree, Union Find]
15. Tweaked Identical Binary Tree.java Level: Easy Tags: [DFS, Tree]
检查binary tree是否 identical.
特点: subtree如果是有旋转的, 只要tree node value相等, 就可以算是identical
16. Kth Smallest Element in a BST.java Level: Medium Tags: [BST, DFS, Stack, Tree]
17. Invert Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
18. Unique Binary Search Tree II.java Level: Medium Tags: [BST, DP, Divide and Conquer, Tree]
给一个数字n, 找到以(1...n)为node的所有unique BST.
19. Merge Two Binary Trees.java Level: Easy Tags: [DFS, Tree]
20. Path Sum II.java Level: Easy Tags: [Backtracking, DFS, Tree]
给一个inputSum, 然后dfs, 找到所有path, 满足: path sum 跟 inputSum 一样.
21. Recover Binary Search Tree.java Level: Hard Tags: [BST, DFS, Tree]
BST里面有2个node misplace, 要归为. 要求: O(1) extra space
22. Convert Binary Search Tree to Sorted Doubly Linked List (extra space).java Level: Medium Tags: [Linked List, Stack, Tree]
给一个BST, convert成 sorted doubly DoublyListNode.
23. Smallest Subtree with all the Deepest Nodes.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个tree, �按照题意找最一个node�满足:
24. Path Sum III.java Level: Easy Tags: [DFS, Double Recursive, Tree]
count所有存在的 path sum == target sum. 可以从任意点开始. 但是只能parent -> child .
25. Complete Binary Tree.java Level: Easy Tags: [BFS, Tree]
A complete binary tree is a binary tree in which every level, except possibly the last,
is completely filled, and all nodes are as far left as possible
26. Binary Tree Longest Consecutive Sequence.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
找到binary tree 里的最长 consecutive sequence.
27. Trim a Binary Search Tree.java Level: Easy Tags: [BST, Tree]
方法1: 适合复习BST. 用DFS对待每个node. 注意BST的特征: 所有left nodes都小于当下node, 所有right nodes都大于当下node.
根据题意用[L,R]切割.如果node.val<L, 直接连node带左边全丢掉, return node.right. 处理R也是一样. 分制是, DFS leftNode, rightNode. 然后接在node.left, node.right.
方法2: 用迭代, 还没有写.
28. Path Sum IV.java Level: Medium Tags: [DFS, Hash Table, Tree]
给一串3-digit 的数组. 每个数字的表达一个TreeNode, 3 digit分别代表: depth.position.value
这串数字已经从小到大排列. 求: 所有可能的 root->leaf path 的所有可能的 path sum 总和.
29. Populating Next Right Pointers in Each Node II.java Level: Medium Tags: [DFS, Tree]
给一个binary tree, 用constant space link 所有所有node.next to same level next node.
30. [lint]. Lowest Common Ancestor II.java Level: Easy Tags: [Hash Table, Lint, Tree]
给一个Binary Tree root, 以及两个node A, B. 特点: node里面存了parent pointer. 找 lowest common ancestor
31. 102. Binary Tree Level Order Traversal.java Level: Medium Tags: [BFS, DFS, Tree]
如题.
32. 236. Lowest Common Ancestor of a Binary Tree.java Level: Medium Tags: [DFS, Tree]
给一个Binary Tree root, 以及两个node p, q. 找 p 和 q 的 lowest common ancestor
33. 111. Minimum Depth of Binary Tree.java Level: Easy Tags: [BFS, DFS, Tree]
34. 987. Vertical Order Traversal of a Binary Tree.java Level: Medium Tags: [BFS, Binary Tree, DFS, Hash Table, Tree]
space: O(n)
Very similar to 314. Binary Tree Vertical Order Traversal
with 1 special condition: if 2 nodes at same (offset, level):
sort it by its value
35. 429. N-ary Tree Level Order Traversal.java Level: Medium Tags: [BFS, Tree]
36. 199. Binary Tree Right Side View.java Level: Medium Tags: [BFS, DFS, Tree]
给一个binary tree, 从右边看过来, return all visible nodes
37. 1008. Construct Binary Search Tree from Preorder Traversal.java Level: Medium Tags: [DFS, Tree]
38. 515. Find Largest Value in Each Tree Row.java Level: Medium Tags: [BFS, DFS, Tree]
39. 222. Count Complete Tree Nodes.java Level: Medium Tags: [Binary Search, DFS, Tree]
Complete Tree就是说, 最后一个level可能是缺node的(不是说最右下角缺node, 别忘了!)
40. 543. Diameter of Binary Tree.java Level: Easy Tags: [Tree]
找longest path (include or not include root)
跟Binary Tree Maximum Path Sum 的想法一样: 处理single path, 或者combined path (do not include curr root)
41. 1110. Delete Nodes And Return Forest.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
42. 173. Binary Search Tree Iterator.java Level: Medium Tags: [BST, Design, Stack, Tree]
43. 104. Maximum Depth of Binary Tree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree, 找最深depth
44. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
45. 270. Closest Binary Search Tree Value.java Level: Easy Tags: [BST, Binary Search, Tree]
给一个BST, 和一个double target, 走位找到最接近的number.
Concept: Iterate over all logN nodes in the BST and record the closest. Rather than finding the value at +/- 0.5 precision
46. 144. Binary Tree Preorder Traversal.java Level: Medium Tags: [BFS, DFS, Stack, Tree]
47. 110. Balanced Binary Tree.java Level: Easy Tags: [DFS, Tree]
给一个binary tree, 看是否是height-balanced
48. 100. Same Tree.java Level: Easy Tags: [BFS, DFS, Tree]
给两个 binary tree, 看两个tree是否identical.
49. 112. Path Sum.java Level: Easy Tags: [DFS, Tree]
给一个inputSum, 然后dfs, 找到是否有一条path, 得出的path sum 跟 inputSum 一样.
50. 427. Construct Quad Tree.java Level: Medium Tags: [Tree]
51. 1026. Maximum Difference Between Node and Ancestor.java Level: Medium Tags: [DFS, Tree]
52. 145. Binary Tree Postorder Traversal.java Level: Medium Tags: [Stack, Tree, Two Stacks]
如题, POST-ORDER traversal.
LeetCode给了hard, 应该是觉得stack的做法比较难想到.
53. 938. Range Sum of BST.java Level: Easy Tags: [BST, Recursion, Tree]
54. 314. Binary Tree Vertical Order Traversal.java Level: Medium Tags: [BFS, DFS, Hash Table, Tree]
给一个Binary Tree, traverse所有node, 按照vertial order 排列成output: List
重点是: col里面有排序, lower level的排在前面; 如果node遇到collision在同一个位置: 根据他们的相对位置 先放left, 再放right
55. 103. Binary Tree Zigzag Level Order Traversal.java Level: Medium Tags: [BFS, Stack, Tree]
56. 105. Construct Binary Tree from Preorder and Inorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Hash Table, Tree]
如题
57. 449. Serialize and Deserialize BST.java Level: Medium Tags: [Tree]
58. 426. Convert Binary Search Tree to Sorted Doubly Linked List.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List, Tree]
把 BST 转换成一个 sorted doubly linked list. (in-place)
59. 94. Binary Tree Inorder Traversal.java Level: Easy Tags: [Hash Table, Stack, Tree]
Inorder traverse Binary Tree
60. 98. Validate Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Tree]
验证是否是BST by definition
61. 1123. Lowest Common Ancestor of Deepest Leaves.java Level: Medium Tags: [BFS, DFS, Tree]
62. 124. Binary Tree Maximum Path Sum.java Level: Hard Tags: [DFS, DP, Tree, Tree DP]
找max path sum, 可以从任意treeNode 到任意 treeNode.
63. 101. Symmetric Tree.java Level: Easy Tags: [BFS, DFS, Tree]
检查tree是否symmetric
注意Symmetric Binary Tree的例子和定义: 是镜面一样的对称. 并不是说左右两个sub-tree相等。
64. 671. Second Minimum Node In a Binary Tree.java Level: Easy Tags: [BFS, Tree]
65. 366. Find Leaves of Binary Tree.java Level: Medium Tags: [DFS, Tree]
66. 235. Lowest Common Ancestor of a Binary Search Tree.java Level: Easy Tags: [BST, DFS, Tree]
给 binary search tree root, q node, p node. 找到p q 的lowest common ancestor
67. 156. Binary Tree Upside Down.java Level: Medium Tags: [DFS, Tree]
68. 272. Closest Binary Search Tree Value II.java Level: Hard Tags: [Stack, Tree]
0. Largest Number.java Level: Medium Tags: [Sort]
给一串数字, 非负数, 把所有数字串联起来, 组成最大数字.
因为结果很大, 所以用string表示
1. QuickSort.java Level: Medium Tags: [Quick Sort, Sort]
implement quick sort.
2. Sort List.java Level: Medium Tags: [Divide and Conquer, Linked List, Merge Sort, Sort]
3. Wiggle Sort.java Level: Medium Tags: [Array, Sort]
方法1: 排序, nLog(n). 然后把直线上坡变成层叠山峰, 需要每隔几个(题目中是每隔2位)就做个swap 造成高低不平. Note: 每隔山峰之间是相互没有关系的, 所以每次只要操心 [i], [i-1]两个位置就好了.
方法2: O(n) 看好奇数偶数位的规律, 然后根据题目给出的规律, 跑一遍, 每次只关注两个位置: 把不合适的[i], [i-1]调换位置就好了.
方法3: 跟法2一样, 只是更巧妙一点罢了: 第一遍想太多. 其实做一个fall-through就能把问题解决,原因是因为: 这样的fall-through每次在乎两个element,可以一口气搞定,无关乎再之前的elements。 特别的一点:flag来巧妙的掌控山峰和低谷的变化。又是神奇的一幕啊! 这样子的奇观,见过就要知道了,没见过的时候有点摸不着头脑。
4. The Smallest Difference.java Level: Medium Tags: [Array, Sort, Two Pointers]
5. Number of Airplane in the sky.java Level: Medium Tags: [Array, Interval, PriorityQueue, Sort, Sweep Line]
6. Sort Colors II.java Level: Medium Tags: [Partition, Quick Sort, Sort, Two Pointers]
Sort Color的普通版, sort all k colors in colors array.
Details 参见: https://github.com/awangdev/LintCode/blob/master/Java/Sort%20Color.java
7. Sort Letters by Case.java Level: Medium Tags: [Partition, Sort, String, Two Pointers]
给一串字符(ASCII 大写, 小写字母), 要求sort 小写字母, 在大写字母前面.
字母间的前后顺序无所谓, 也不需要preserve original order .
跟sort color分成相似.
8. Insertion Sort List.java Level: Medium Tags: [Linked List, Sort]
input一串数字, 需要出sorted output. 每次insert一个数字时, 都要放到正确的sorted的位置
每次insertion的时候, 都从input里面减掉这个数字
9. Subarray Sum Closest.java Level: Medium Tags: [PreSum, PriorityQueue, Sort, Subarray]
给一串数字, 找subarray的首尾index, 条件: subarray最接近0.
10. Sort Colors.java Level: Medium Tags: [Array, Partition, Quick Sort, Sort, Two Pointers]
给一串数字 nums, 数字代表颜色[0,1,2]; 要求 sort nums, 数字最终按照大小排列.
虽然叫sort color, 其实就是sort 这些 numbers, 只不过抽象了一下.
11. Partition Array.java Level: Medium Tags: [Array, Quick Sort, Sort, Two Pointers]
给一串数字, 和 int k. 根据k的值partition array, 找到第一个i, nums[i] >= k.
12. [tool]. MergeSort.java Level: Medium Tags: [Lint, Merge Sort, Sort]
13. 56. Merge Intervals.java Level: Medium Tags: [Array, PriorityQueue, Sort, Sweep Line]
给一串int[Interval] (unsorted), 把所以Interval merge起来.
14. 252. Meeting Rooms.java Level: Easy Tags: [PriorityQueue, Sort, Sweep Line]
15. 259. 3Sum Smaller.java Level: Medium Tags: [Array, Sort, Two Pointers]
16. 855. Exam Room.java Level: Medium Tags: [PriorityQueue, Sort, TreeMap, TreeSet]
17. 253. Meeting Rooms II.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort, Sweep Line]
给一串数字pair, 代表会议的开始/结束时间. 找同时又多少个会议发生(需要多少件房间)
也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。
18. 1048. Longest String Chain.java Level: Medium Tags: [Bucket Sort, DP, Hash Table, Sort]
19. 15. 3Sum.java Level: Medium Tags: [Array, Sort, Two Pointers]
20. 349. Intersection of Two Arrays.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
21. 1094. Car Pooling.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort]
22. 973. K Closest Points to Origin.java Level: Medium Tags: [Divide and Conquer, Heap, Sort]
23. 169. Majority Element.java Level: Easy Tags: [Array, Bit Manipulation, Divide and Conquer, Moore Voting, Sort]
24. 242. Valid Anagram.java Level: Easy Tags: [Hash Table, Sort]
25. 1057. Campus Bikes.java Level: Medium Tags: [Bucket Sort, Greedy, PriorityQueue, Sort]
26. 1033. Moving Stones Until Consecutive.java Level: Easy Tags: [Basic Implementation, Sort]
27. 274.H-Index.java Level: Medium Tags: [Bucket Sort, Hash Table, Sort]
找到h-index, 给的citation int[] 并不是sorted. h-index 的definition 具体看题目.
28. 350. Intersection of Two Arrays II.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
29. 767. Reorganize String.java Level: Medium Tags: [Greedy, Hash Table, Heap, Sort, String]
We want to exhaust largest population and merge like merging k list. Problem: largest population may result in them being adjacent. How to resolve?
30. 57. Insert Interval.java Level: Hard Tags: [Array, PriorityQueue, Sort, Sweep Line]
0. 118. Pascal's Triangle.java Level: Easy Tags: [Array, Basic Implementation, List]
0. 169. Majority Element.java Level: Easy Tags: [Array, Bit Manipulation, Divide and Conquer, Moore Voting, Sort]
1. 229. Majority Element II.java Level: Medium Tags: [Array, Moore Voting]
0. K Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, Trie]
给一串String, target string, int k. 找string array里面所有的candidate: 变化K次, 能变成target.
TODO
1. Word Search II.java Level: Hard Tags: [Backtracking, DFS, Trie]
给一串words, 还有一个2D character matrix. 找到所有可以形成的words. 条件: 2D matrix 只可以相邻走位.
2. Palindrome Pairs.java Level: Hard Tags: [Hash Table, String, Trie]
Obvious的做法是全部试一遍, 判断, 变成 O(n^2) * O(m) = O(mn^2). O(m): isPalindrome() time.
当然不行了, 那就看是O(nlogN), 还是O(n)?
O(mn)
还要做一下那.
3. Design Search Autocomplete System.java Level: Hard Tags: [Design, Hash Table, MinHeap, PriorityQueue, Trie]
Description is long, but in short: 做 search auto complete.
Best problem to review Trie (prefix search), Top K frequent elements (Hash Map), and MinHeap (PriorityQueue)
Easier to revisit https://leetcode.com/problems/design-search-autocomplete-system/description/
4. Word Squares.java Level: Hard Tags: [Backtracking, Trie]
可以开Trie class, 里面用到TrieNode. 开Trie(words) 可以直接initalize with for loop TrieNode 里面可以有一个 List startWith: 记录可以到达这个点的所有string: 有点像树形, ancestor形状的存储.
神操作: 根据square的性质, 如果选中了list of words, 设定 int prefixIndex = list.size(). 取出list里面的所有word[prefixedIndex], 并且加在一起, 就是下一个word candidate的 prefix.
形象一点: list = ["ball", "area"]; prefixIndex = list.size(); ball[prefixIndex] = 'l'; area[prefixIndex] = 'e'; //then candidatePrefix = ball[prefixIndex] + area[prefixIndex] = "le"; 这里就可以用到Trie的那个 findByPrefix function, 在每个点, 都存有所有这个点能产生的candidate. 这时, 试一试所有candidate: dfs
能想到这种倒转的结构来存prefix candidates 在 Trie里面, 这个想法非常值得思考.
5. Maximum XOR of Two Numbers in an Array.java Level: Medium Tags: [Bit Manipulation, Trie]
比较难想到. 利用到XOR性质A^B=C, then A=B^C.
还用到一些技巧: mask = (1 << i); // i位mask mask = mask | (1 << i); // prefix mask
6. 211. Add and Search Word - Data structure design.java Level: Medium Tags: [Backtracking, Design, Trie]
7. 208. Implement Trie (Prefix Tree).java Level: Medium Tags: [Design, Trie]
Implement Tire, 也即是 Prefix Tree. 做三个function: insert, search, startWith
8. 720. Longest Word in Dictionary.java Level: Easy Tags: [Hash Table, Trie]
给串word[], 找最长的Word, 满足条件: 这个Word可以从 word[] 里面一个字母一个字母被build出来.
如果多种答案, respect smallest lexicographical order.
9. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
10. 745. Prefix and Suffix Search.java Level: Hard Tags: [Trie]
0. Inorder Successor in BST.java Level: Medium Tags: [BST, Tree]
找 Inorder traversal规则里的下一个.
主要想法是考虑: 1. 如果 node.right == null, 找上一个unprocessed node alone the inorder traversal path 2. 如果 node.right != null, successor 一定在这个node.right那个subtree里面 最后竟然可以简化成几行, 非常全面的BST问题: 有search, 有对inorder traversal的理解, 还有坑.
1. Unique Binary Search Tree.java Level: Medium Tags: [BST, DP, Tree]
Not quite clear. 根据左右分割而总结出了原理, 每次分割, 左右两边都会有一定数量的permutation, 总体上的情况数量当然是相乘. 然后每一个不同的分割点都加一遍: f(n) = f(0)*f(n-1) + f(1)*f(n-2) + ... + f(n-2)*f(1) + f(n-1)*f(0)
然后把数学公式转换成DP的方程, 有点玄学的意思啊! 不好想.
2. Minimum Absolute Difference in BST.java Level: Easy Tags: [BST]
BST: inorder-traversal: 先left node(adding to stack till left leav), 再process stack.peek (mid node), 再 add rightNode && dive to rightNode.left leaf
3. K Empty Slots.java Level: Hard Tags: [Array, BST, TreeSet]
题目解析后: find 2 number, that: 1. k slots between the 2 number, 2. no slots taken between the two number.
4. Remove Node in Binary Search Tree.java Level: Hard Tags: [BST]
方法1: Brutle一点。找到target和target的parent.
把target remove时,把target的children nodes 重新排列组成新的BST: inorder traversal, build tree based on inorder traversal list.
方法2: 分析规律,先找到target和parent, 然后根据性质,把target remove时,移动children nodes, 保证还是BST。
5. Search Range in Binary Search Tree .java Level: Medium Tags: [BST, Binary Tree]
给一个BST, integer range (k1, k2), 找range 里面所有的integer.
6. Insert Node in a Binary Search Tree .java Level: Easy Tags: [BST]
往Binary Search Tree里面加东西,一定会找到一个合适的leaf加上去。
那么:就是说someNode.left or someNode.right是null时,就是insert node的地方。
找到那个someNode就按照正常的Binary Search Tree规律。
7. Kth Smallest Element in a BST.java Level: Medium Tags: [BST, DFS, Stack, Tree]
8. Unique Binary Search Tree II.java Level: Medium Tags: [BST, DP, Divide and Conquer, Tree]
给一个数字n, 找到以(1...n)为node的所有unique BST.
9. Zigzag Iterator.java Level: Medium Tags: [BST]
这个题目相对简单. 做的时候我先考虑起来k条怎么办. 那么用个map把index和每个listmark一下就好了。 每次next(), 相应的list的头拿下来就好。 然后就跑圈呗,每次刷一个list头。不难。只要把几个variable维护清楚就行。
10. Recover Binary Search Tree.java Level: Hard Tags: [BST, DFS, Tree]
BST里面有2个node misplace, 要归为. 要求: O(1) extra space
11. Convert Sorted List to Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List]
如题, 把一个sorted singly linked list 转换成一个 height balanced BST
12. Contains Duplicate III.java Level: Medium Tags: [BST]
给一个unsorted array, 问, 是否有两个element, value相差最大为t, 而两个element的index 相差最大为k.
Note: 虽然题目名字是Contains Duplicate, 但其实要找的两个element不是duplicate, 而是Math.abs(value1 - value2) <= t.
13. Trim a Binary Search Tree.java Level: Easy Tags: [BST, Tree]
方法1: 适合复习BST. 用DFS对待每个node. 注意BST的特征: 所有left nodes都小于当下node, 所有right nodes都大于当下node.
根据题意用[L,R]切割.如果node.val<L, 直接连node带左边全丢掉, return node.right. 处理R也是一样. 分制是, DFS leftNode, rightNode. 然后接在node.left, node.right.
方法2: 用迭代, 还没有写.
14. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
15. 173. Binary Search Tree Iterator.java Level: Medium Tags: [BST, Design, Stack, Tree]
16. 493. Reverse Pairs.java Level: Medium Tags: [BST, Binary Indexed Tree, Divide and Conquer, Merge Sort, Segment Tree]
给一串数字, count total reverse pair nums[i] > 2*nums[j]
, i < j
This problem can be solved with Merge sort concept, BST, Segment Tree and Binary Indexed Tree. Good for learning/review.
17. 315. Count of Smaller Numbers After Self.java Level: Hard Tags: [BST, Binary Indexed Tree, Binary Search, Divide and Conquer, Segment Tree]
给一串数字nums[], 求一个新数组result, where result[i] = # of smaller items on right of nums[i]
18. 270. Closest Binary Search Tree Value.java Level: Easy Tags: [BST, Binary Search, Tree]
给一个BST, 和一个double target, 走位找到最接近的number.
Concept: Iterate over all logN nodes in the BST and record the closest. Rather than finding the value at +/- 0.5 precision
19. 938. Range Sum of BST.java Level: Easy Tags: [BST, Recursion, Tree]
20. 426. Convert Binary Search Tree to Sorted Doubly Linked List.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List, Tree]
把 BST 转换成一个 sorted doubly linked list. (in-place)
21. 98. Validate Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Tree]
验证是否是BST by definition
22. 235. Lowest Common Ancestor of a Binary Search Tree.java Level: Easy Tags: [BST, DFS, Tree]
给 binary search tree root, q node, p node. 找到p q 的lowest common ancestor
0. Sliding Window Median.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap, Sliding Window]
Data Stream Median 的同理题目: 不只是不断增加的Sequence, 而且要remove item (保持一个window size)
1. Design Search Autocomplete System.java Level: Hard Tags: [Design, Hash Table, MinHeap, PriorityQueue, Trie]
Description is long, but in short: 做 search auto complete.
Best problem to review Trie (prefix search), Top K frequent elements (Hash Map), and MinHeap (PriorityQueue)
Easier to revisit https://leetcode.com/problems/design-search-autocomplete-system/description/
2. Trapping Rain Water II.java Level: Hard Tags: [BFS, Heap, MinHeap, PriorityQueue]
给一个2Dmap, 每个position 有 height. 找Trapping water sum.
3. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Sort]
kth largest in array
4. [lint]. Merge k Sorted Arrays.java Level: Medium Tags: [Heap, MinHeap, PriorityQueue]
Same as merge k sorted list, use priorityQueue
5. [lint]. Heapify.java Level: Medium Tags: [HashHeap, Heap, Lint, MinHeap]
Turn unsorted array into a min-heap array, where for each A[i],
A[i * 2 + 1] is the left child of A[i] and A[i * 2 + 2] is the right child of A[i].
6. 347. Top K Frequent Elements.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue]
给一串数字, 找到top k frequent element, 并且time complexity 要比nLogN要好
7. 295. Find Median from Data Stream.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap]
8. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
9. 373. Find K Pairs with Smallest Sums.java Level: Medium Tags: [Heap, MaxHeap, MinHeap]
10. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. Shuffle an Array.java Level: Medium Tags: [Permutation]
像shuffle music 一样, 做一套shuffle array的functions:
shuffle() 给出random的permutation
reset() 给出最初的nums
1. Palindrome Permutation II.java Level: Medium Tags: [Backtracking, Permutation]
TODO: need to review permutation
permutation的综合题:
2. 1053. Previous Permutation With One Swap.java Level: Medium Tags: [Array, Greedy, Permutation]
3. 31. Next Permutation.java Level: Medium Tags: [Array, Permutation]
4. 46. Permutations.java Level: Medium Tags: [BFS, Backtracking, DFS, Permutation]
Method1-Option2: Recursive Backtracking, with list.contains()
to avoid reuse of index
Method1-Option3: Recursive Backtracking, with visited[]
to avoid reuse of index
0. 785. Is Graph Bipartite.java Level: Medium Tags: [BFS, DFS, Garph]
0. Perfect Squares.java Level: Medium Tags: [BFS, DP, Math, Partition DP]
给一个数字n, 找到这个数字 最少能用多少个 平方数组成.
平方数比如: 1, 4, 9, 16 ... etc
1. Copy Books.java Level: Hard Tags: [Binary Search, DP, Partition DP]
给一串书pages[i], k个人, pages[i] 代表每本书的页数. k个人从不同的点同时开始抄书.
问, 最快什么时候可以抄完?
2. Palindrome Partitioning II.java Level: Hard Tags: [DP, Partition DP]
给一个String s, 找出最少用多少cut, 使致 切割出的每一个substring, 都是palindrome
3. 91. Decode Ways.java Level: Medium Tags: [DP, Partition DP, String]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
4. 639. Decode Ways II.java Level: Hard Tags: [DP, Enumeration, Partition DP]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
其中字符可能是 "*", 可以代表 [1 - 9]
0. 142. Linked List Cycle II.java Level: Medium Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
1. 141. Linked List Cycle.java Level: Easy Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
2. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
0. Design Search Autocomplete System.java Level: Hard Tags: [Design, Hash Table, MinHeap, PriorityQueue, Trie]
Description is long, but in short: 做 search auto complete.
Best problem to review Trie (prefix search), Top K frequent elements (Hash Map), and MinHeap (PriorityQueue)
Easier to revisit https://leetcode.com/problems/design-search-autocomplete-system/description/
1. Number of Airplane in the sky.java Level: Medium Tags: [Array, Interval, PriorityQueue, Sort, Sweep Line]
2. Ugly Number II.java Level: Medium Tags: [DP, Enumeration, Heap, Math, PriorityQueue]
3. The Maze II.java Level: Medium Tags: [BFS, DFS, PriorityQueue]
4. Trapping Rain Water II.java Level: Hard Tags: [BFS, Heap, MinHeap, PriorityQueue]
给一个2Dmap, 每个position 有 height. 找Trapping water sum.
5. Subarray Sum Closest.java Level: Medium Tags: [PreSum, PriorityQueue, Sort, Subarray]
给一串数字, 找subarray的首尾index, 条件: subarray最接近0.
6. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Sort]
kth largest in array
7. The Maze III.java Level: Hard Tags: [BFS, DFS, PriorityQueue]
8. [lint]. Merge k Sorted Arrays.java Level: Medium Tags: [Heap, MinHeap, PriorityQueue]
Same as merge k sorted list, use priorityQueue
9. 347. Top K Frequent Elements.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue]
给一串数字, 找到top k frequent element, 并且time complexity 要比nLogN要好
10. 56. Merge Intervals.java Level: Medium Tags: [Array, PriorityQueue, Sort, Sweep Line]
给一串int[Interval] (unsorted), 把所以Interval merge起来.
11. 252. Meeting Rooms.java Level: Easy Tags: [PriorityQueue, Sort, Sweep Line]
12. 855. Exam Room.java Level: Medium Tags: [PriorityQueue, Sort, TreeMap, TreeSet]
13. 253. Meeting Rooms II.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort, Sweep Line]
给一串数字pair, 代表会议的开始/结束时间. 找同时又多少个会议发生(需要多少件房间)
也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。
14. 1094. Car Pooling.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort]
15. 621. Task Scheduler.java Level: Medium Tags: [Array, Enumeration, Greedy, PriorityQueue, Queue]
16. 414. Third Maximum Number.java Level: Easy Tags: [Array, PriorityQueue]
17. 1057. Campus Bikes.java Level: Medium Tags: [Bucket Sort, Greedy, PriorityQueue, Sort]
18. 23. Merge k Sorted Lists.java Level: Medium Tags: [Divide and Conquer, Heap, Linked List, Merge Sort, PriorityQueue]
给一个array of ListNode, 把所有node按照大小连成一条.
19. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
20. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
21. 57. Insert Interval.java Level: Hard Tags: [Array, PriorityQueue, Sort, Sweep Line]
22. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. 938. Range Sum of BST.java Level: Easy Tags: [BST, Recursion, Tree]
1. 698. Partition to K Equal Sum Subsets.java Level: Medium Tags: [DFS, DP, Recursion]
0. Sliding Window Median.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap, Sliding Window]
Data Stream Median 的同理题目: 不只是不断增加的Sequence, 而且要remove item (保持一个window size)
1. Rearrange String k Distance Apart.java Level: Hard Tags: [Greedy, Hash Table, Heap]
给一个string, 全是lowercase letter, 要求重新排列: 然后每个unique的character要有k distance apart.
跟Task Scheduler有点像, 只不过Task那道题里面还可以用其他方法求count, 这道题要求出排列结果
2. Ugly Number II.java Level: Medium Tags: [DP, Enumeration, Heap, Math, PriorityQueue]
3. Trapping Rain Water II.java Level: Hard Tags: [BFS, Heap, MinHeap, PriorityQueue]
给一个2Dmap, 每个position 有 height. 找Trapping water sum.
4. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Sort]
kth largest in array
5. Kth Smallest Element in a Sorted Matrix.java Level: Medium Tags: [Binary Search, Heap]
给一个sorted matrix, 找kth smallest number (not distinct)
Related: Kth Largest Element in an Array
6. [lint]. Merge k Sorted Arrays.java Level: Medium Tags: [Heap, MinHeap, PriorityQueue]
Same as merge k sorted list, use priorityQueue
7. [lint]. HashHeap.java Level: Hard Tags: [HashHeap, Heap, Lint]
非题.是从九章找来的HashHeap implementation.
8. [lint]. Heapify.java Level: Medium Tags: [HashHeap, Heap, Lint, MinHeap]
Turn unsorted array into a min-heap array, where for each A[i],
A[i * 2 + 1] is the left child of A[i] and A[i * 2 + 2] is the right child of A[i].
9. 347. Top K Frequent Elements.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue]
给一串数字, 找到top k frequent element, 并且time complexity 要比nLogN要好
10. 253. Meeting Rooms II.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort, Sweep Line]
给一串数字pair, 代表会议的开始/结束时间. 找同时又多少个会议发生(需要多少件房间)
也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。
11. 1094. Car Pooling.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort]
12. 973. K Closest Points to Origin.java Level: Medium Tags: [Divide and Conquer, Heap, Sort]
13. 295. Find Median from Data Stream.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap]
14. 239. Sliding Window Maximum.java Level: Hard Tags: [Deque, Heap, Sliding Window]
15. 23. Merge k Sorted Lists.java Level: Medium Tags: [Divide and Conquer, Heap, Linked List, Merge Sort, PriorityQueue]
给一个array of ListNode, 把所有node按照大小连成一条.
16. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
17. 743. Network Delay Time.java Level: Medium Tags: [BFS, DFS, Graph, Heap, PQ]
quesiton: sorting by travel delay/time will find better answer earlier?
18. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
19. 767. Reorganize String.java Level: Medium Tags: [Greedy, Hash Table, Heap, Sort, String]
We want to exhaust largest population and merge like merging k list. Problem: largest population may result in them being adjacent. How to resolve?
20. 373. Find K Pairs with Smallest Sums.java Level: Medium Tags: [Heap, MaxHeap, MinHeap]
21. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. Scramble String.java Level: Hard Tags: [DP, Interval DP, String]
1. Coins in a Line III.java Level: Hard Tags: [Array, DP, Game Theory, Interval DP, Memoization]
LeetCode: Predict the Winner
还是2个人拿n个coin, coin可以有不同的value.
只不过这次选手可以从任意的一头拿, 而不限制从一头拿. 算先手会不会赢?
2. Burst Balloons.java Level: Hard Tags: [DP, Divide and Conquer, Interval DP, Memoization]
一排球, 每个球有value, 每次扎破一个, 就会积分: 左中间右 的值. 求, 怎么扎, 最大值?
TODO: Need more thoughts on why using dp[n + 2][n + 2] for memoization, but dp[n][n] for interval DP.
3. 516. Longest Palindromic Subsequence.java Level: Medium Tags: [DFS, DP, Interval DP, Memoization]
给一个string s, 找最长的sub-sequence which is also palindrome.
注意!subsequence并不是substring, 是可以skip letter / non-continuous character sequence
0. Convert Expression to Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Polish Notation (PN).
1. Decode String.java Level: Medium Tags: [DFS, Divide and Conquer, Stack]
给一个expression string. 里面包括数字, 字母, 括号. 其中数字代表括号里面的内容重复几次.
括号里面可以是String, 也可能是expression.
目的: 把expression展开成一个正常的String.
2. Remove Duplicate Letters.java Level: Hard Tags: [Greedy, Hash Table, Stack]
3. Backspace String Compare.java Level: Easy Tags: [Stack, Two Pointers]
4. Implement Stack using Queues.java Level: Easy Tags: [Design, Stack]
如题.
5. Maximum Binary Tree.java Level: Medium Tags: [Stack, Tree]
给一串数字, 做一个 maximum binary tree: 最顶上的root最大; 左child也是一个max tree, 右child也必须是max tree.
6. Maximal Rectangle.java Level: Hard Tags: [Array, DP, Hash Table, Stack]
分解开来, 其实是'Largest Rectangle in Histogram', 只不过这里要自己model heights. 一个2D array里面的rectangle, 最终也是用height * width做出来的. 巧妙在于, 把每一行当做底边, 算出这个底边, 到顶部的height:
如果看具体实例, 有些row似乎是白算的, 但是没有办法, 这是一个搜索的过程, 最终会比较出最优解.
Coordinate DP?
7. Expression Evaluation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Minimum Binary Tree, Stack]
给一个公式 expression, array of strings, 然后evaluate expression 结果.
8. Min Stack.java Level: Easy Tags: [Design, Stack]
双Stack:一个正常stack,另一个minStack存当下level最小值. 注意维护minStack的变化
另外. 如果要maxStack,也是类似做法
9. Kth Smallest Element in a BST.java Level: Medium Tags: [BST, DFS, Stack, Tree]
10. Implement Stack.java Level: Easy Tags: [Stack]
随便用一个data structure, implement stack.
11. Longest Valid Parentheses.java Level: Hard Tags: [Coordinate DP, Stack, String]
给一串string, 里面只有(
, )
. 找最长valid parentheses 的长度.
12. Expression Tree Build.java Level: Hard Tags: [Binary Tree, Expression Tree, Minimum Binary Tree, Stack]
给一串字符, 表示的是 公式 expression. 把公式变成expression tree
13. Evaluate Reverse Polish Notation.java Level: Medium Tags: [Stack]
给一个 RPN string list, 根据这个list, 计算结果.
14. Implement Queue using Stacks.java Level: Easy Tags: [Design, Stack]
画图, 知道最后maintain的stack是那个 reverseStack: pop(), peek(), empty() 都在这个stack上, 无需变换. push()里面做stack和reverseStack的来回倾倒. 相比老的code, 在PUSH里面做倾倒, 更容易读.
双Stack. 一个是等于是queue,一个是backfillStack. Tricky: 是在pop()和peek()的时候backfill, 并且要等到stack用完再backfill. 写一下例子就知道,如果提早backfill,stack.peek()就不是queue的head了.
15. Basic Calculator.java Level: Hard Tags: [Binary Tree, Expression Tree, Math, Minimum Binary Tree, Stack]
给一个expression String, 要evaluate这个expression的值.
Expression string 里面包括 +, -, 整数, 开合括号, 还有space.
16. Convert Binary Search Tree to Sorted Doubly Linked List (extra space).java Level: Medium Tags: [Linked List, Stack, Tree]
给一个BST, convert成 sorted doubly DoublyListNode.
17. Convert Expression to Reverse Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Reverse Polish Notation (RPN).
18. Largest Rectangle in Histogram.java Level: Hard Tags: [Array, Monotonous Stack, Stack]
给n个bar,组成柱状图histogram. 求在这一排柱状图里面可以找到的面积最大的长方形.
思考: 找长方形面积, 无非是找两个index, 然后底边长度 * height.
19. 42. Trapping Rain Water.java Level: Hard Tags: [Array, Stack, Two Pointers]
这道题目的方法比较多.
20. 1021. Remove Outermost Parentheses.java Level: Easy Tags: [Stack]
21. 496. Next Greater Element I.java Level: Easy Tags: [Hash Table, Stack]
22. 1249. Minimum Remove to Make Valid Parentheses.java Level: Medium Tags: [Stack, String]
23. 173. Binary Search Tree Iterator.java Level: Medium Tags: [BST, Design, Stack, Tree]
24. 844. Backspace String Compare.java Level: Easy Tags: [Stack, Two Pointers]
25. 1106. Parsing A Boolean Expression.java Level: Hard Tags: [DFS, Stack, String]
26. 144. Binary Tree Preorder Traversal.java Level: Medium Tags: [BFS, DFS, Stack, Tree]
27. 20. Valid Parentheses.java Level: Easy Tags: [Stack, String]
28. 145. Binary Tree Postorder Traversal.java Level: Medium Tags: [Stack, Tree, Two Stacks]
如题, POST-ORDER traversal.
LeetCode给了hard, 应该是觉得stack的做法比较难想到.
29. 103. Binary Tree Zigzag Level Order Traversal.java Level: Medium Tags: [BFS, Stack, Tree]
30. 636. Exclusive Time of Functions.java Level: Medium Tags: [Stack]
31. 94. Binary Tree Inorder Traversal.java Level: Easy Tags: [Hash Table, Stack, Tree]
Inorder traverse Binary Tree
32. 402. Remove K Digits.java Level: Medium Tags: [Greedy, Monotonous Stack, Stack]
33. 71. Simplify Path.java Level: Medium Tags: [Stack, String]
给一个path, simplify成最简单形式. 注意考虑edge case
34. 716. Max Stack.java Level: Medium Tags: [Design, Doubly Linked List, Stack, TreeMap]
35. 341. Flatten Nested List Iterator.java Level: Medium Tags: [Design, NestedInteger, Stack]
36. 739. Daily Temperatures.java Level: Medium Tags: [Hash Table, Monotonous Stack, Stack]
37. 272. Closest Binary Search Tree Value II.java Level: Hard Tags: [Stack, Tree]
0. Linked List Cycle II.java Level: Medium Tags: [Linked List, Math, Two Pointers]
LinkedList 里面有 cycle, 找到cycle的起始点(第一个重复出现的element).
1. Two Lists Sum.java Level: Medium Tags: [Linked List]
给两个Linked list, sum up and 合成新的list
2. Rotate List.java Level: Medium Tags: [Linked List, Two Pointers]
给一个single linked list, 右移k steps. k non-negative.
3. Swap Nodes in Pairs.java Level: Medium Tags: [Linked List]
基本原理, 写出来, 然后连线: pre -> A -> B -> C -> ... 需要一个虚拟 preNode做起始node, 不然无法把后面的node换到开头.
用dummy = pre作为head前一格.
用 pre.next == null && pre.next.next
来check是否为NULL.
pre.next.next 保证了至少有一次swap.
4. Sort List.java Level: Medium Tags: [Divide and Conquer, Linked List, Merge Sort, Sort]
5. Reverse Linked List II .java Level: Medium Tags: [Linked List]
reverse 一个 linked list 中 [m ~ n] 的一部分.
6. Reorder List.java Level: Medium Tags: [Linked List]
给一个Linked list, reorder: 从head/tail 两个方向 向中间进发, re-order like: one node at a time,
7. Majority Number III.java Level: Medium Tags: [Hash Table, Linked List]
TODO:
8. Partition List.java Level: Medium Tags: [Linked List, Two Pointers]
9. Remove Duplicates from Unsorted List.java Level: Medium Tags: [Linked List]
基本方法: O(n) sapce, time 遍历。 遇到duplicate(可能多个), while直到node.next不是duplicate. 接下去,既然不是duplicate,那就add 进 set
如果不用extra memory, do it in place: 那就要sort linked list. 用merge sort.
复习merge sort:
10. Remove Duplicates from Sorted List II.java Level: Medium Tags: [Linked List]
从Linked list 里面摘掉重复元素: 只要重复过, 全部都删掉; 重复出现过得元素一个不留.
11. Add Two Numbers II.java Level: Medium Tags: [Linked List]
Singly-linked list需要reverse, 用stack. 最终结果要恢复成input list 那样的sequence方向, 用stack一个个pop()刚好就可以做到.
加法都一样:
12. Insertion Sort List.java Level: Medium Tags: [Linked List, Sort]
input一串数字, 需要出sorted output. 每次insert一个数字时, 都要放到正确的sorted的位置
每次insertion的时候, 都从input里面减掉这个数字
13. Convert Sorted List to Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List]
如题, 把一个sorted singly linked list 转换成一个 height balanced BST
14. Convert Binary Search Tree to Sorted Doubly Linked List (extra space).java Level: Medium Tags: [Linked List, Stack, Tree]
给一个BST, convert成 sorted doubly DoublyListNode.
15. [lint]. Nth to Last Node in List.java Level: Easy Tags: [Linked List, Lint]
16. 21. Merge Two Sorted Lists.java Level: Easy Tags: [Linked List]
如题
17. 237. Delete Node in a Linked List.java Level: Easy Tags: [Linked List]
Given Singlely linked list, 删除一个任意node (不能是head node)
18. 142. Linked List Cycle II.java Level: Medium Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
19. 83. Remove Duplicates from Sorted List.java Level: Easy Tags: [Linked List]
从Linked list 里面摘掉重复元素, 只留下unique元素.
20. 203. Remove Linked List Elements.java Level: Easy Tags: [Linked List]
从linked list 里面去掉所有的 target
21. 19. Remove Nth Node From End of List.java Level: Medium Tags: [Linked List, Two Pointers]
22. 206. Reverse Linked List.java Level: Easy Tags: [Linked List]
23. 141. Linked List Cycle.java Level: Easy Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
24. 369. Plus One Linked List.java Level: Medium Tags: [Linked List]
25. 146. LRU Cache.java Level: Medium Tags: [Design, Doubly Linked List, Hash Table, Linked List]
26. 234. Palindrome Linked List.java Level: Easy Tags: [Linked List, Two Pointers]
27. 876. Middle of Linked List.java Level: Easy Tags: [Linked List]
找Linked List的中间node
28. 2. Add Two Numbers.java Level: Medium Tags: [Linked List, Math]
LinkedList都已经反转好了,直接做. 跟Add Binary的理解方式一模一样.
29. 23. Merge k Sorted Lists.java Level: Medium Tags: [Divide and Conquer, Heap, Linked List, Merge Sort, PriorityQueue]
给一个array of ListNode, 把所有node按照大小连成一条.
30. 430. Flatten a Multilevel Doubly Linked List.java Level: Medium Tags: [DFS, Linked List]
31. 160. Intersection of Two Linked Lists.java Level: Easy Tags: [Linked List]
给两个 linked list, 问从哪个node开始, 两个 linked list 开始有重复?
32. 138. Copy List with Random Pointer.java Level: Medium Tags: [Hash Table, Linked List]
deep copy linked list. linked list 上有random pointer to other nodes.
33. 426. Convert Binary Search Tree to Sorted Doubly Linked List.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List, Tree]
把 BST 转换成一个 sorted doubly linked list. (in-place)
0. Shortest Palindrome.java Level: Hard Tags: [KMP, String]
0. Maximum Size Subarray Sum Equals k.java Level: Medium Tags: [Hash Table, PreSum, Subarray]
1. Submatrix Sum.java Level: Medium Tags: [Array, Hash Table, PreSum]
给一个int[][] matrix, 找一个sub matrix, where the sum == 0.
2. Maximum Average Subarray II.java Level: Review Tags: [Array, Binary Search, PreSum]
给int[] nums 和 window min size k. window size可以大于K. 找最大的连续数列average value.
需要仔细去读下面的notes.
3. Subarray Sum Closest.java Level: Medium Tags: [PreSum, PriorityQueue, Sort, Subarray]
给一串数字, 找subarray的首尾index, 条件: subarray最接近0.
4. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
5. [lint]. Subarray Sum.java Level: Easy Tags: [Array, Hash Table, Lint, PreSum, Subarray]
给一串数字, 找其中的一个subarray的 [start, end] index, 条件: subarary sum == 0.
6. 303. Range Sum Query - Immutable.java Level: Easy Tags: [DP, PreSum]
给一串数字, 求sumRange.
7. 327. Count of Range Sum.java Level: Hard Tags: [BIT, Divide and Conquer, Merge Sort, PreSum, Segment Tree]
TODO: Write the code + merge function
8. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
9. 560. Subarray Sum Equals K.java Level: Medium Tags: [Array, Hash Table, PreSum, Subarray]
给一串数字, 找其中的 # of subarray的 where subararySum == k.
10. 304. Range Sum Query 2D - Immutable.java Level: Medium Tags: [DP, PreSum]
11. 724. Find Pivot Index.java Level: Easy Tags: [Array, PreSum]
12. 523. Continuous Subarray Sum.java Level: Medium Tags: [Coordinate DP, DP, Math, PreSum, Subarray]
给一个非负数的数列和数字k(可正负, 可为0). 找到连续子序列(长度超过2), 使得这个subarray的sum 是 k的倍数. 问: 是否可能?
0. 308. Range Sum Query 2D - Mutable.java Level: Hard Tags: [Binary Indexed Tree, Segment Tree]
1. 493. Reverse Pairs.java Level: Medium Tags: [BST, Binary Indexed Tree, Divide and Conquer, Merge Sort, Segment Tree]
给一串数字, count total reverse pair nums[i] > 2*nums[j]
, i < j
This problem can be solved with Merge sort concept, BST, Segment Tree and Binary Indexed Tree. Good for learning/review.
2. 315. Count of Smaller Numbers After Self.java Level: Hard Tags: [BST, Binary Indexed Tree, Binary Search, Divide and Conquer, Segment Tree]
给一串数字nums[], 求一个新数组result, where result[i] = # of smaller items on right of nums[i]
3. 307. Range Sum Query - Mutable.java Level: Medium Tags: [Binary Indexed Tree, Segment Tree]
0. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
0. Redundant Connection.java Level: Medium Tags: [BFS, DFS, Graph, Tree, Union Find]
1. Minimum Height Trees.java Level: Medium Tags: [BFS, Graph]
2. Redundant Connection II.java Level: Hard Tags: [DFS, Graph, Tree, Union Find]
3. Number of Connected Components in an Undirected Graph.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
count这个graph里面有多少个独立的component.
4. Sliding Puzzle.java Level: Hard Tags: [BFS, Graph]
5. 269. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
6. 207. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
7. 1203. Sort Items by Groups Respecting Dependencies.java Level: Hard Tags: [BFS, DFS, Graph, Topological Sort]
8. 1153. String Transforms Into Another String.java Level: Hard Tags: [Graph]
9. 1161. Maximum Level Sum of a Binary Tree.java Level: Medium Tags: [BFS, DFS, Graph]
10. 1306. Jump Game III.java Level: Medium Tags: [BFS, Graph]
11. 277. Find the Celebrity.java Level: Medium Tags: [Adjacency Matrix, Array, Graph, Greedy, Pruning]
有n个人, 其中有个人是celebrity, 注意必要条件 Celeb knows nobody; Everyone else knows the celeb
. 找到celeb
Note: the relationship graph can be presented as an adjacency matrix, but graph is not directly used in this problem.
12. 332. Reconstruct Itinerary.java Level: Medium Tags: [Backtracking, DFS, Graph]
13. 1267. Count Servers that Communicate.java Level: Medium Tags: [Array, Graph]
14. 210. Course Schedule II.java Level: Medium Tags: [BFS, DFS, Graph, Topological Sort]
15. 261. Graph Valid Tree.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
检查这些edge是否能合成一个 valid tree
16. 1043. Partition Array for Maximum Sum.java Level: Medium Tags: [DFS, DP, Graph, Memoization]
17. 133. Clone Graph.java Level: Medium Tags: [BFS, DFS, Graph]
给一个graph node, 每个node有list of neighbors. 复制整个graph, return new head node.
实现起来就好像在crawl urls.
18. 743. Network Delay Time.java Level: Medium Tags: [BFS, DFS, Graph, Heap, PQ]
quesiton: sorting by travel delay/time will find better answer earlier?
19. 399. Evaluate Division.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
0. Nim Game.java Level: Easy Tags: [Brainteaser, DP, Game Theory]
1. 319. Bulb Switcher.java Level: Medium Tags: [Brainteaser, Math]
0. 41. First Missing Positive.java Level: Hard Tags: [Analysis, Array, Edge Case]
给一串无序数字, 有负数: 找这个array里面第一个 missing的 positive integer
missing positive integer 其实是以 [1, n] 来做比较的.
0. Find the Weak Connected Component in the Directed Graph.java Level: Medium Tags: [Union Find]
遍历 weak connected graph, 将结果存在 List<List>种.
1. Longest Consecutive Sequence.java Level: Hard Tags: [Array, Hash Table, Union Find]
给一串数字, unsorted, 找这串数字里面的连续元素序列长度 (consecutive序列, 是数字连续, 并不是说要按照原order)
2. Connecting Graph.java Level: Medium Tags: [Union Find]
没有跑过这个程序, 是一个UnionFind的简单实现. Document了每个环节的计算原理/思想.
3. Redundant Connection.java Level: Medium Tags: [BFS, DFS, Graph, Tree, Union Find]
4. Redundant Connection II.java Level: Hard Tags: [DFS, Graph, Tree, Union Find]
5. Connecting Graph III.java Level: Medium Tags: [Union Find]
还是UnionFind的变形, 这次是算有剩下多少个union. 其实非常简单, 维持一个全局变量count: 一开始count=n, 因为全是散装elements; 每次union, count--.
6. Number of Connected Components in an Undirected Graph.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
count这个graph里面有多少个独立的component.
7. Surrounded Regions.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2D board, 里面是 'X' 和 'O'. 把所有被X包围的area都涂成'X'.
从四个边的edge出发, 像感染僵尸病毒一样扩散, 把靠边的node全部mark, 然后将还是'O'的改成X, 最后回复marks -> 'O'
8. Bricks Falling When Hit.java Level: Hard Tags: [Union Find]
给一个matrix of 1 and 0, 1
代表brick. 连着ceiling的brick就不会drop. 给一串coordinate hits[][], 记录每次take down 1 brick 后, 会drop多少个.
9. Connecting Graph II.java Level: Medium Tags: [Union Find]
Lint还不能跑, 全部按照题意和答案document的.
10. [tool]. UnionFind.java Level: Medium Tags: [Lint, Union Find]
11. 305. Number of Islands II.java Level: Hard Tags: [Union Find]
给一个island grid[][], and list of operations to fill a particualr (x,y) position.
count # of remaining island after each operation.
12. 200. Number of Islands.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2Dmatrix, 里面是1和0, 找#of island.
13. 261. Graph Valid Tree.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
给一个数字n代表n nodes, marked from 1 ~ n, 和一串undirected edge int[][].
检查这些edge是否能合成一个 valid tree
14. 399. Evaluate Division.java Level: Medium Tags: [BFS, DFS, Graph, Union Find]
15. 721. Accounts Merge.java Level: Medium Tags: [DFS, Hash Table, Union Find]
给一串account in format [[name, email1, email2, email3], [name2, email,..]]
.
要求把所有account merge起来 (可能多个record记录了同一个人, �by common email)
0. Number of Airplane in the sky.java Level: Medium Tags: [Array, Interval, PriorityQueue, Sort, Sweep Line]
1. 56. Merge Intervals.java Level: Medium Tags: [Array, PriorityQueue, Sort, Sweep Line]
给一串int[Interval] (unsorted), 把所以Interval merge起来.
2. 252. Meeting Rooms.java Level: Easy Tags: [PriorityQueue, Sort, Sweep Line]
3. 850. Rectangle Area II.java Level: Hard Tags: [Segment Tree, Sweep Line]
4. 253. Meeting Rooms II.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort, Sweep Line]
给一串数字pair, 代表会议的开始/结束时间. 找同时又多少个会议发生(需要多少件房间)
也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。
5. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
6. 57. Insert Interval.java Level: Hard Tags: [Array, PriorityQueue, Sort, Sweep Line]
0. Surrounded Regions.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2D board, 里面是 'X' 和 'O'. 把所有被X包围的area都涂成'X'.
从四个边的edge出发, 像感染僵尸病毒一样扩散, 把靠边的node全部mark, 然后将还是'O'的改成X, 最后回复marks -> 'O'
1. 200. Number of Islands.java Level: Medium Tags: [BFS, DFS, Matrix DFS, Union Find]
给一个2Dmatrix, 里面是1和0, 找#of island.
0. Minimum Subarray.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最小值
1. Maximum Average Subarray I.java Level: Easy Tags: [Array, Subarray]
简单的求sum of fixed window k, 同时求max avg, 结尾求余数就好.
2. Minimum Size Subarray Sum.java Level: Medium Tags: [Array, Binary Search, Subarray, Two Pointers]
给一串positive integer, 找最短的subarray sum, where the sum >= s
3. Maximum Size Subarray Sum Equals k.java Level: Medium Tags: [Hash Table, PreSum, Subarray]
4. Subarray Sum Closest.java Level: Medium Tags: [PreSum, PriorityQueue, Sort, Subarray]
给一串数字, 找subarray的首尾index, 条件: subarray最接近0.
5. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
6. [lint]. Subarray Sum.java Level: Easy Tags: [Array, Hash Table, Lint, PreSum, Subarray]
给一串数字, 找其中的一个subarray的 [start, end] index, 条件: subarary sum == 0.
7. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
8. 152. Maximum Product Subarray.java Level: Medium Tags: [Array, DP, PreProduct, Subarray]
从一组数列(正负都有)里面找一串连续的子序列, 而达到乘积product最大值.
9. 560. Subarray Sum Equals K.java Level: Medium Tags: [Array, Hash Table, PreSum, Subarray]
给一串数字, 找其中的 # of subarray的 where subararySum == k.
10. 523. Continuous Subarray Sum.java Level: Medium Tags: [Coordinate DP, DP, Math, PreSum, Subarray]
给一个非负数的数列和数字k(可正负, 可为0). 找到连续子序列(长度超过2), 使得这个subarray的sum 是 k的倍数. 问: 是否可能?
0. Number of Airplane in the sky.java Level: Medium Tags: [Array, Interval, PriorityQueue, Sort, Sweep Line]
0. Paint Fence.java Level: Easy Tags: [DP, Sequence DP]
1. Minimum Subarray.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最小值
2. House Robber II.java Level: Medium Tags: [DP, Sequence DP, Status DP]
和House Robber I 类似, 搜刮房子, 相邻不能动. 特点是: 现在nums排成了圈, 首尾相连.
3. Longest Common Subsequence.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP]
给两个string, A, B. 找这两个string里面的LCS: 最长公共字符长度 (不需要是continuous substring)
4. K Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, Trie]
给一串String, target string, int k. 找string array里面所有的candidate: 变化K次, 能变成target.
TODO
5. Longest Common Substring.java Level: Medium Tags: [DP, Double Sequence DP, Sequence DP, String]
6. Best Time to Buy and Sell Stock III.java Level: Hard Tags: [Array, DP, Sequence DP]
比stock II 多了一个限制:只有2次卖出机会.
7. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
8. Best Time to Buy and Sell Stock IV.java Level: Hard Tags: [DP, Sequence DP]
有int[] price of stock, 最多做 k transactions. 求最大profit.
9. Best Time to Buy and Sell Stock with Transaction Fee.java Level: Medium Tags: [Array, DP, Greedy, Sequence DP, Status DP]
跟Stock II 一样, 买卖无限, 需先买在卖. 附加条件: 每个sell transaction要加一笔fee.
10. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
11. 198. House Robber.java Level: Easy Tags: [DP, Sequence DP, Status DP]
搜刮房子, 相邻的不能碰. 每个房子里有value, 求max.
12. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
13. 70. Climbing Stairs.java Level: Easy Tags: [DP, Memoization, Sequence DP]
每一步可以走1步或者2步, 求总共多少种方法爬完梯子.
14. 10. Regular Expression Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Sequence DP, String]
跟WildCard Matching 一样, 分清楚情况讨论 string p last char is '' 还有并不是 ''
IMPORTANT: '*' 需要有一个 prefix element [elm], so it becomes [elm]*
. There 2 possible cases:
15. 122. Best Time to Buy and Sell Stock II.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Status DP]
和Stock I 的区别:可以买卖多次,求总和的最大盈利.
16. 121. Best Time to Buy and Sell Stock.java Level: Easy Tags: [Array, DP, Sequence DP]
给个array of stock prices, 限制能交易(买/买)一轮, 问如何找到最大profit.
17. 139. Word Break.java Level: Medium Tags: [DP, Hash Table, Sequence DP]
给一个String word, 和一个字典, 检查是否word可以被劈开, 而所有substring都应该是dictionary里面的words.
18. 256. Paint House.java Level: Easy Tags: [DP, Sequence DP, Status DP]
要paint n个房子, 还有 nx3的cost[][]. 求最少用多少cost paint 所有房子.
19. 265. Paint House II.java Level: Hard Tags: [DP, Sequence DP, Status DP]
一排n个房子, 每个房子可涂成k种颜色, 涂每个房子的价钱不一样, 用costs[][]表示.
costs[0][1]表示涂了index是0的房子, 用了color 1.
规则: 相邻的两个房子不能使同一种颜色
求: 最少的cost
20. 72. Edit Distance.java Level: Hard Tags: [DP, Double Sequence DP, Sequence DP, String]
两个字符串, A要变成B, 可以 insert/delete/replace, 找最小变化operation count
0. Expression Evaluation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Minimum Binary Tree, Stack]
给一个公式 expression, array of strings, 然后evaluate expression 结果.
1. Expression Tree Build.java Level: Hard Tags: [Binary Tree, Expression Tree, Minimum Binary Tree, Stack]
给一串字符, 表示的是 公式 expression. 把公式变成expression tree
2. Basic Calculator.java Level: Hard Tags: [Binary Tree, Expression Tree, Math, Minimum Binary Tree, Stack]
给一个expression String, 要evaluate这个expression的值.
Expression string 里面包括 +, -, 整数, 开合括号, 还有space.
0. Counting Bits.java Level: Medium Tags: [Bit Manipulation, Bitwise DP, DP]
给一个数组, 算里面有多少bit 1.
0. Coins in a Line II.java Level: Medium Tags: [Array, DP, Game Theory, Memoization, MiniMax]
给一串coins, 用values[]表示; 每个coin有自己的value. 先手/后手博弈, 每次只能 按照从左到右的顺序, 拿1个或者2个棋子, 最后看谁拿的总值最大.
MiniMax的思考方法很神奇, 最后写出来的表达式很简单
Time O(n) Space O(n): dp[], sum[]
1. Predict the Winner.java Level: Medium Tags: [DP, MiniMax]
Detailed in Coins in a Line III
2. 843. Guess the Word.java Level: Hard Tags: [MiniMax]
TODO: revist time/space complexity
0. Linked List Cycle II.java Level: Medium Tags: [Linked List, Math, Two Pointers]
LinkedList 里面有 cycle, 找到cycle的起始点(第一个重复出现的element).
1. Reverse String.java Level: Easy Tags: [String, Two Pointers]
Similar to Reverse Integer. 可以用StringBuffer, 也可以two pointer reverse head/tail
2. Backspace String Compare.java Level: Easy Tags: [Stack, Two Pointers]
3. Minimum Size Subarray Sum.java Level: Medium Tags: [Array, Binary Search, Subarray, Two Pointers]
给一串positive integer, 找最短的subarray sum, where the sum >= s
4. Rotate List.java Level: Medium Tags: [Linked List, Two Pointers]
给一个single linked list, 右移k steps. k non-negative.
5. Longest Substring Without Repeating Characters.java Level: Medium Tags: [Hash Table, String, Two Pointers]
方法1: Two Pointers 双指针: 从start开始遍历, 但是第一步是while loop来推进end; 直到推不动end, 然后start++ 巧妙点: 因为end是外围variable, 在start的loop上, end不会重置;[star ~ end] 中间不需要重复计算. 最终可以O(n);
Previous verison of two pointers:
用两个pointer, head和i.
注意:head很可能被退回到很早的地方,比如abbbbbba,当遇到第二个a,head竟然变成了 head = 0+1 = 1.
当然这是不对的,所以head要确保一直增长,不回溯
方法2: HashMap<Char, Integer>: <character, last occurance index> 一旦有重复, rest map. 没有重复时候, 不断map.put(), 然后求max值
问题: 每次reset map之后就开始从新从一个最早的index计算, 最坏情况是O(n^2): 'abcdef....xyza'
6. [tool] Quick Select - Median.java Level: Easy Tags: [Array, Lint, Quick Select, Quick Sort, Two Pointers]
给一串无序数组, 找到median(sort之后 位置在中间的数字).
7. Container With Most Water.java Level: Medium Tags: [Array, Two Pointers]
8. Partition List.java Level: Medium Tags: [Linked List, Two Pointers]
9. The Smallest Difference.java Level: Medium Tags: [Array, Sort, Two Pointers]
10. Subarray Sum II.java Level: Hard Tags: [Array, Binary Search, Two Pointers]
11. Sort Colors II.java Level: Medium Tags: [Partition, Quick Sort, Sort, Two Pointers]
Sort Color的普通版, sort all k colors in colors array.
Details 参见: https://github.com/awangdev/LintCode/blob/master/Java/Sort%20Color.java
12. Sort Letters by Case.java Level: Medium Tags: [Partition, Sort, String, Two Pointers]
给一串字符(ASCII 大写, 小写字母), 要求sort 小写字母, 在大写字母前面.
字母间的前后顺序无所谓, 也不需要preserve original order .
跟sort color分成相似.
13. Two Sum II - Input array is sorted.java Level: Medium Tags: [Array, Binary Search, Two Pointers]
升序array, 找2SUM.
14. Partition Array by Odd and Even.java Level: Easy Tags: [Array, Two Pointers]
15. Sort Colors.java Level: Medium Tags: [Array, Partition, Quick Sort, Sort, Two Pointers]
给一串数字 nums, 数字代表颜色[0,1,2]; 要求 sort nums, 数字最终按照大小排列.
虽然叫sort color, 其实就是sort 这些 numbers, 只不过抽象了一下.
16. Interleaving Positive and Negative Numbers.java Level: Medium Tags: [Two Pointers]
给一串数组 有正负数. 重新排列, 让数组里面 正数 和 负数 相隔开. 原来的order无所谓
17. Partition Array.java Level: Medium Tags: [Array, Quick Sort, Sort, Two Pointers]
给一串数字, 和 int k. 根据k的值partition array, 找到第一个i, nums[i] >= k.
18. [lint]. 3 Sum Closest.java Level: Medium Tags: [Array, Lint, Two Pointers]
3Sum 的一种简单形式, 并且都没有找index, value, 而只是找个sum罢了.
double for loop。 2Sum只能用土办法 left/right 2 pointers。 O(n^2)
注意:check closest时候用long, 以免int不够用
19. [lint]. 2 Sum II.java Level: Medium Tags: [Array, Binary Search, Lint, Two Pointers]
与 2sum II - input array is sorted类似. 都是sort array, 然后two pointer.
LintCode的题. 注意找的是greater/bigger than target。
由于给定条件允许O(nLogn):
sort
two pointer
while里面two pointer移动。每次如果num[left]+num[right] > target,那么其中所有num[left++]的加上num[right]都>target.
也就是,num[right]不动,计算加入挪动left能有多少组,那就是: right-left这么多。 全部加到count上去。
然后right--.换个right去和前面的left部分作比较。
20. 42. Trapping Rain Water.java Level: Hard Tags: [Array, Stack, Two Pointers]
这道题目的方法比较多.
21. 142. Linked List Cycle II.java Level: Medium Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
22. 360. Sort Transformed Array.java Level: Medium Tags: [Math, Two Pointers]
23. 849. Maximize Distance to Closest Person.java Level: Easy Tags: [Array, Basic Implementation, Two Pointers]
给一排座位, 一个人去坐: 找离两边�的人都最远的地方(中间点), return 跟旁边人的最大distance
是Exam Room 的同种概念, 简单化题目: 这里只考虑一个人就好了
24. 1213. Intersection of Three Sorted Arrays.java Level: Easy Tags: [Hash Table, Two Pointers]
Very similar to 349.Intersection of Two Arrays.
25. 986. Interval List Intersections.java Level: Medium Tags: [Two Pointers]
26. 76. Minimum Window Substring.java Level: Hard Tags: [Hash Table, Sliding Window, String, Two Pointers]
基本思想:
27. 244. Shortest Word Distance II.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
28. 80.Remove Duplicates from Sorted Array II.java Level: Medium Tags: [Array, Two Pointers]
给一个sorted array, 把重复的去掉: 也就是把不重复的按照顺序贴上来, array末尾多余的位置无所谓.
最多可重复出元素的数量不超过2个. return unique item 的长度.
29. 26.Remove Duplicates from Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
给一个sorted array, 把重复的去掉: 也就是把不重复的按照顺序贴上来, array末尾多余的位置无所谓.
return unique item 的长度.
30. 259. 3Sum Smaller.java Level: Medium Tags: [Array, Sort, Two Pointers]
31. 977. Squares of a Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
32. 67. Add Binary.java Level: Easy Tags: [Math, String, Two Pointers]
把binary换成数字作加法. 如果input很大,那么很可能int,long都hold不住。不保险。
33. 15. 3Sum.java Level: Medium Tags: [Array, Sort, Two Pointers]
34. 19. Remove Nth Node From End of List.java Level: Medium Tags: [Linked List, Two Pointers]
35. 349. Intersection of Two Arrays.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
36. 844. Backspace String Compare.java Level: Easy Tags: [Stack, Two Pointers]
37. 245. Shortest Word Distance III.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
跟243/244不同: 这里允许list里面有重复的word.
38. 141. Linked List Cycle.java Level: Easy Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
39. 567. Permutation in String.java Level: Medium Tags: [Sliding Window, Two Pointers]
40. 727. Minimum Window Subsequence.java Level: Hard Tags: [DP, Hash Table, Sliding Window, String, Two Pointers]
41. 345. Reverse Vowels of a String.java Level: Easy Tags: [String, Two Pointers]
vowels: 元音字母. 要求reverse所有元音字母.
拿出所有vowels, 反过来放进去. O(n)
42. 28. Implement strStr().java Level: Easy Tags: [String, Two Pointers]
给两个string A, B, 找一个 B 在 A 种的起始位置.
43. 246. Strobogrammatic Number.java Level: Easy Tags: [Enumeration, Hash Table, Math, Two Pointers]
根据题意枚举, 再根据规则basic implementation
44. 88. Merge Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
给两个排好序的数组, merge. 其中一个数组nums1有多余的位置
45. 243. Shortest Word Distance.java Level: Easy Tags: [Array, Two Pointers]
46. 234. Palindrome Linked List.java Level: Easy Tags: [Linked List, Two Pointers]
47. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
48. 340. Longest Substring with At Most K Distinct Characters.java Level: Hard Tags: [Hash Table, LinkedHashMap, Sliding Window, String, Two Pointers]
49. 1004. Max Consecutive Ones III.java Level: Medium Tags: [Sliding Window, Two Pointers]
50. 283. Move Zeroes.java Level: Easy Tags: [Array, Two Pointers]
Move non-zero elements to front of array; preseve order.
51. 125. Valid Palindrome.java Level: Easy Tags: [String, Two Pointers]
验证string是不是 palindrome. 只考虑 alphanumeric, 其他字符可以忽略
52. 350. Intersection of Two Arrays II.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
53. 438. Find All Anagrams in a String.java Level: Medium Tags: [Hash Table, Sliding Window, Two Pointers]
跟 Permutation in String 很像. 给短string p, 长string s.
找所有p的 anagram (permutation) 在s 里面的起始index.
54. 632. Smallest Range Covering Elements from K Lists.java Level: Hard Tags: [Hash Table, Sliding Window, Two Pointers]
55. 159. Longest Substring with At Most Two Distinct Characters.java Level: Medium Tags: [Hash Table, Sliding Window, String, Two Pointers]
如题.
56. 611. Valid Triangle Number.java Level: Medium Tags: [Array, Two Pointers]
0. Count and Say.java Level: Easy Tags: [Basic Implementation, String]
介绍一种count数字的方法, 然后每一行读出上一行的结果, 一行一行推算. 问nth行是啥样?
1. Cosine Similarity.java Level: Easy Tags: [Basic Implementation]
根据 Cosine Similarity 的公式, basic implementation
2. Next Closest Time.java Level: Medium Tags: [Basic Implementation, Enumeration, String]
给一个时间string"12:09", 用里面的4个integer组合成其他时间string, 目标找最小的next time.
如果组合出的time string 在input time之前, 默认 + 24 hours.
3. 788. Rotated Digits.java Level: Easy Tags: [Basic Implementation, String]
4. 849. Maximize Distance to Closest Person.java Level: Easy Tags: [Array, Basic Implementation, Two Pointers]
给一排座位, 一个人去坐: 找离两边�的人都最远的地方(中间点), return 跟旁边人的最大distance
是Exam Room 的同种概念, 简单化题目: 这里只考虑一个人就好了
5. 408. Valid Word Abbreviation.java Level: Easy Tags: [Basic Implementation, String]
tricky: find integer within a string edge case: leading '0' should not be allow in such abbr.
6. 415. Add Strings.java Level: Easy Tags: [Basic Implementation, Math, String]
7. 1108. Defanging an IP Address.java Level: Easy Tags: [Basic Implementation, String]
8. 383. Ransom Note.java Level: Easy Tags: [Basic Implementation, String]
count chars in int[256]
9. 686. Repeated String Match.java Level: Easy Tags: [Basic Implementation, Edge Case, String]
Track: 纸上分析edge case. Validation helps speed it up.
10. 485. Max Consecutive Ones.java Level: Easy Tags: [Array, Basic Implementation]
11. 824. Goat Latin.java Level: Easy Tags: [Basic Implementation, String]
12. 119. Pascal's Triangle II.java Level: Easy Tags: [Array, Basic Implementation]
简单处理 list. code is very similar to Pascal triangle I.
13. 443. String Compression.java Level: Easy Tags: [Basic Implementation, String]
14. 12. Integer to Roman.java Level: Medium Tags: [Basic Implementation, Math, String]
15. 893. Groups of Special-Equivalent Strings.java Level: Easy Tags: [Basic Implementation, String]
Mark # of characters can be useful to print string signature
16. 118. Pascal's Triangle.java Level: Easy Tags: [Array, Basic Implementation, List]
17. 1033. Moving Stones Until Consecutive.java Level: Easy Tags: [Basic Implementation, Sort]
0. 1117. Building H2O.java Level: Medium Tags: [Lock, Semaphore, Thread]
0. Backpack VI.java Level: Medium Tags: [Backpack DP, DP]
给一个数组nums, 全正数, 无重复数字; 找: # of 拼出m的方法.
nums 里的数字, 可以重复使用. 不同的order可以算作不同的拼法.
1. Backpack V.java Level: Medium Tags: [Backpack DP, DP]
2. Backpack II.java Level: Medium Tags: [Backpack DP, DP]
给i本书, 每本书有自己的重量 int[] A, 每本书有value int[] V
背包有自己的大小M, 看最多能放多少value的书?
3. Backpack.java Level: Medium Tags: [Backpack DP, DP]
给i本书, 每本书有自己的重量 int[] A, 背包有自己的大小M, 看最多能放多少重量的书?
4. Backpack III.java Level: Hard Tags: [Backpack DP, DP]
给n种不同的物品, int[] A weight, int[] V value, 每种物品可以用无限次
问最大多少value可以装进size是 m 的包?
5. Combination Sum IV.java Level: Medium Tags: [Array, Backpack DP, DP]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
6. 518. Coin Change 2.java Level: Medium Tags: [Backpack DP, DP]
给串数字, target amount, 求总共多少种方式可以reach the amount.
7. 322. Coin Change.java Level: Medium Tags: [Backpack DP, DFS, DP, Memoization]
给一串不同数额的coins, 和total amount to spent. 求 最少 用多少个coin可以组合到这个amount. 每种coins个数不限量.
0. 339. Nested List Weight Sum.java Level: Easy Tags: [BFS, DFS, NestedInteger]
给一串integers, list里面可能有nest list. 算总的sum. 规则, 如果是nested list, 每深一个depth, sum要乘以depth.
1. 364. Nested List Weight Sum II.java Level: Medium Tags: [DFS, NestedInteger]
2. 341. Flatten Nested List Iterator.java Level: Medium Tags: [Design, NestedInteger, Stack]
0. Sliding Window Median.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap, Sliding Window]
Data Stream Median 的同理题目: 不只是不断增加的Sequence, 而且要remove item (保持一个window size)
1. 347. Top K Frequent Elements.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue]
给一串数字, 找到top k frequent element, 并且time complexity 要比nLogN要好
2. 295. Find Median from Data Stream.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap]
3. 692. Top K Frequent Words.java Level: Medium Tags: [Hash Table, Heap, MaxHeap, MinHeap, PriorityQueue, Trie]
给一串String. 找到top k frequent words.
4. 373. Find K Pairs with Smallest Sums.java Level: Medium Tags: [Heap, MaxHeap, MinHeap]
0. Convert Integer A to Integer B.java Level: Easy Tags: [Bit Manipulation]
把Integer A 转换成 Integer B 需要改变多少bits?
1. Missing Number.java Level: Easy Tags: [Array, Bit Manipulation, Math]
给一串unique数字, 数字取自 [0 ~ n], 无序, 找第一个skipped的数字.
2. Single Number III.java Level: Medium Tags: [Bit Manipulation]
TODO: wut?
3. Total Hamming Distance.java Level: Medium Tags: [Bit Manipulation]
给出Hamming Distance定义(bit format时候有多少binary diff), 求一串数字的hamming distance总和.
4. Count 1 in Binary.java Level: Easy Tags: [Bit Manipulation]
count 一个 32-bit number binary format 里面有多少1
可以把integer -> string -> char array.
5. Binary Representation.java Level: Hard Tags: [Bit Manipulation, String]
6. Binary Gap.java Level: Easy Tags: [Bit Manipulation]
7. Update Bits.java Level: Medium Tags: [Bit Manipulation]
熟悉bits的一些trick:
8. O(1) Check Power of 2.java Level: Easy Tags: [Bit Manipulation]
9. Swap Bits.java Level: Easy Tags: [Bit Manipulation]
简单, 但是很多知识点:
10. Power of Two.java Level: Easy Tags: [Bit Manipulation, Math]
跟powerOfThree一样: 可以loop, check mod; 也可以用binary search找合适的数字.
11. Single Number II.java Level: Medium Tags: [Bit Manipulation]
一串数字里面, 所有数字都重复三次, 除了一个数字. 找到这个数字, linear time, without extrace space (constant space)
TODO: bits
12. Counting Bits.java Level: Medium Tags: [Bit Manipulation, Bitwise DP, DP]
给一个数组, 算里面有多少bit 1.
13. Sum of Two Integers.java Level: Easy Tags: [Bit Manipulation]
a^b 是: 不完全加法. a&b 是: 所有可能的进位. a&b<<1是向左边进位的形态.
Goal: 先a^b裸加, 算出进位; 再把结果和进位裸加, 再算出这一轮的进位; 再..裸价, 算进位....直到进位数==0.
那么就,首先记录好进位的数字:carry. 然后 a^b 不完全加法一次。然后b用来放剩下的carry, 每次移动一位,继续加,知道b循环为0为止。
在第一回 a ^ b 之后, b 的本身意义就消失. 接下去应该给parameter重新命名. sum = a ^ b; // sum without adding carries nextCarry = (a & b) << 1;
用其他variable name 取代 a, b 会更好理解一点.
Bit Operation
Steps:
a & b: 每bit可能出现的进位数
a ^ b: 每bit在此次操作可能留下的值,XOR 操作
每次左移余数1位,然后存到b, 再去跟a做第一步。loop until b == 0
(http://www.meetqun.com/thread-6580-1-1.html)
14. Maximum XOR of Two Numbers in an Array.java Level: Medium Tags: [Bit Manipulation, Trie]
比较难想到. 利用到XOR性质A^B=C, then A=B^C.
还用到一些技巧: mask = (1 << i); // i位mask mask = mask | (1 << i); // prefix mask
15. 405. Convert a Number to Hexadecimal.java Level: Easy Tags: [Bit Manipulation]
16. 136. Single Number.java Level: Easy Tags: [Bit Manipulation, Hash Table]
Bit XOR: 当两个bit不同时,return 1. 题目正要消光所有重复出现的数儿留下出现一次的那个.
17. 169. Majority Element.java Level: Easy Tags: [Array, Bit Manipulation, Divide and Conquer, Moore Voting, Sort]
18. 78. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
0. [tool] Quick Select - Median.java Level: Easy Tags: [Array, Lint, Quick Select, Quick Sort, Two Pointers]
给一串无序数组, 找到median(sort之后 位置在中间的数字).
1. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. Search a 2D Matrix II.java Level: Medium Tags: [Binary Search, Divide and Conquer]
给matrix, 每一行sorted, 每一列从上往下sorted, 找target是否存在
1. Fast Power.java Level: Medium Tags: [DFS, Divide and Conquer]
如题: Calculate the a^n % b where a, b and n are all 32bit integers.
2. Interval Minimum Number.java Level: Medium Tags: [Binary Search, Divide and Conquer, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的最小值.
3. Binary Tree Longest Consecutive Sequence II.java Level: Medium Tags: [DFS, Divide and Conquer, Double Recursive, Tree]
找到binary tree 里的最长 consecutive sequence. Sequence可以递增递减, Sequence顺序可以回溯parent.
4. Subtree of Another Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
5. Decode String.java Level: Medium Tags: [DFS, Divide and Conquer, Stack]
给一个expression string. 里面包括数字, 字母, 括号. 其中数字代表括号里面的内容重复几次.
括号里面可以是String, 也可能是expression.
目的: 把expression展开成一个正常的String.
6. Median of Two Sorted Arrays.java Level: Hard Tags: [Array, Binary Search, DFS, Divide and Conquer]
著名的找两个sorted array的中位数. 中位数定义: 如果两个array总长为偶数, 取平均值. 题目要求在 log(m + n) 时间内解决
7. Construct Binary Tree from Inorder and Postorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Tree]
8. Sort List.java Level: Medium Tags: [Divide and Conquer, Linked List, Merge Sort, Sort]
9. Convert Sorted Array to Binary Search Tree.java Level: Easy Tags: [DFS, Divide and Conquer, Tree]
如题, build balanced BST from sorted array
10. Populating Next Right Pointers in Each Node.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个特殊的binary tree, treeNode里面有一个 next pointer.
写一个function, 把所有node都更同level的node 连在一起. 最右边的node.next = NULL
11. Find Peak Element II.java Level: Hard Tags: [Binary Search, DFS, Divide and Conquer]
2Dmatrix, 里面的value有一些递增, 递减的特点(细节比较长, 看原题). 目标是找到peak element
peak: 比周围4个方向的点value大
12. Segment Tree Modify.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给一个segmentTree, node里面存max. 写一个modify function: modify(node, index, value).
13. Unique Binary Search Tree II.java Level: Medium Tags: [BST, DP, Divide and Conquer, Tree]
给一个数字n, 找到以(1...n)为node的所有unique BST.
14. Expression Add Operators.java Level: Hard Tags: [Backtracking, DFS, Divide and Conquer, String]
给一个数字String, 数字来自0-9
, 给3个操作符 +
,-
,*
, 看如何拼凑, 可以做出结果target.
output 所有 expression
15. Segment Tree Query II.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
16. Convert Sorted List to Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List]
如题, 把一个sorted singly linked list 转换成一个 height balanced BST
17. Burst Balloons.java Level: Hard Tags: [DP, Divide and Conquer, Interval DP, Memoization]
一排球, 每个球有value, 每次扎破一个, 就会积分: 左中间右 的值. 求, 怎么扎, 最大值?
TODO: Need more thoughts on why using dp[n + 2][n + 2] for memoization, but dp[n][n] for interval DP.
18. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Sort]
kth largest in array
19. Smallest Subtree with all the Deepest Nodes.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
给一个tree, �按照题意找最一个node�满足:
20. Binary Tree Longest Consecutive Sequence.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
找到binary tree 里的最长 consecutive sequence.
21. [lint]. Segment Tree Build II.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个array, 建造segment tree structure,
每个treeNode 里面存这个range里的 max value, return root node.
22. [lint]. Segment Tree Query.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给了segment Tree, node里面有Max value, 找[start,end]里面的max
23. [lint]. Segment Tree Build.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个区间[startIndex, endIndex], 建造segment tree structure, return root node.
24. 327. Count of Range Sum.java Level: Hard Tags: [BIT, Divide and Conquer, Merge Sort, PreSum, Segment Tree]
TODO: Write the code + merge function
25. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
26. 1110. Delete Nodes And Return Forest.java Level: Medium Tags: [DFS, Divide and Conquer, Tree]
27. 493. Reverse Pairs.java Level: Medium Tags: [BST, Binary Indexed Tree, Divide and Conquer, Merge Sort, Segment Tree]
给一串数字, count total reverse pair nums[i] > 2*nums[j]
, i < j
This problem can be solved with Merge sort concept, BST, Segment Tree and Binary Indexed Tree. Good for learning/review.
28. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
29. 973. K Closest Points to Origin.java Level: Medium Tags: [Divide and Conquer, Heap, Sort]
30. 315. Count of Smaller Numbers After Self.java Level: Hard Tags: [BST, Binary Indexed Tree, Binary Search, Divide and Conquer, Segment Tree]
给一串数字nums[], 求一个新数组result, where result[i] = # of smaller items on right of nums[i]
31. 169. Majority Element.java Level: Easy Tags: [Array, Bit Manipulation, Divide and Conquer, Moore Voting, Sort]
32. 23. Merge k Sorted Lists.java Level: Medium Tags: [Divide and Conquer, Heap, Linked List, Merge Sort, PriorityQueue]
给一个array of ListNode, 把所有node按照大小连成一条.
33. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
34. 105. Construct Binary Tree from Preorder and Inorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Hash Table, Tree]
如题
35. 426. Convert Binary Search Tree to Sorted Doubly Linked List.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Linked List, Tree]
把 BST 转换成一个 sorted doubly linked list. (in-place)
36. 98. Validate Binary Search Tree.java Level: Medium Tags: [BST, DFS, Divide and Conquer, Tree]
验证是否是BST by definition
37. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. House Robber III.java Level: Medium Tags: [DFS, DP, Status DP, Tree]
Houses被arrange成了binary tree, 规则还是一样, 连续相连的房子不能同时抄.
求Binary Tree neighbor max 能抄多少.
1. House Robber II.java Level: Medium Tags: [DP, Sequence DP, Status DP]
和House Robber I 类似, 搜刮房子, 相邻不能动. 特点是: 现在nums排成了圈, 首尾相连.
2. Best Time to Buy and Sell Stock with Transaction Fee.java Level: Medium Tags: [Array, DP, Greedy, Sequence DP, Status DP]
跟Stock II 一样, 买卖无限, 需先买在卖. 附加条件: 每个sell transaction要加一笔fee.
3. Minimum Swaps To Make Sequences Increasing.java Level: Medium Tags: [Coordinate DP, DP, Status DP]
4. 198. House Robber.java Level: Easy Tags: [DP, Sequence DP, Status DP]
搜刮房子, 相邻的不能碰. 每个房子里有value, 求max.
5. 122. Best Time to Buy and Sell Stock II.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Status DP]
和Stock I 的区别:可以买卖多次,求总和的最大盈利.
6. 256. Paint House.java Level: Easy Tags: [DP, Sequence DP, Status DP]
要paint n个房子, 还有 nx3的cost[][]. 求最少用多少cost paint 所有房子.
7. 265. Paint House II.java Level: Hard Tags: [DP, Sequence DP, Status DP]
一排n个房子, 每个房子可涂成k种颜色, 涂每个房子的价钱不一样, 用costs[][]表示.
costs[0][1]表示涂了index是0的房子, 用了color 1.
规则: 相邻的两个房子不能使同一种颜色
求: 最少的cost
0. Sliding Window Median.java Level: Hard Tags: [Design, Heap, MaxHeap, MinHeap, Sliding Window]
Data Stream Median 的同理题目: 不只是不断增加的Sequence, 而且要remove item (保持一个window size)
1. 76. Minimum Window Substring.java Level: Hard Tags: [Hash Table, Sliding Window, String, Two Pointers]
基本思想:
2. 674. Longest Continuous Increasing Subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP, Sliding Window]
找连续的持续上升子序列的长度.
3. 567. Permutation in String.java Level: Medium Tags: [Sliding Window, Two Pointers]
4. 727. Minimum Window Subsequence.java Level: Hard Tags: [DP, Hash Table, Sliding Window, String, Two Pointers]
5. 239. Sliding Window Maximum.java Level: Hard Tags: [Deque, Heap, Sliding Window]
6. 1040. Moving Stones Until Consecutive II.java Level: Medium Tags: [Array, Sliding Window]
7. 346. Moving Average from Data Stream.java Level: Easy Tags: [Design, Queue, Sliding Window]
给一个interface, design一个structure, 能够计算moving window average.
8. 340. Longest Substring with At Most K Distinct Characters.java Level: Hard Tags: [Hash Table, LinkedHashMap, Sliding Window, String, Two Pointers]
9. 1004. Max Consecutive Ones III.java Level: Medium Tags: [Sliding Window, Two Pointers]
10. 438. Find All Anagrams in a String.java Level: Medium Tags: [Hash Table, Sliding Window, Two Pointers]
跟 Permutation in String 很像. 给短string p, 长string s.
找所有p的 anagram (permutation) 在s 里面的起始index.
11. 632. Smallest Range Covering Elements from K Lists.java Level: Hard Tags: [Hash Table, Sliding Window, Two Pointers]
12. 159. Longest Substring with At Most Two Distinct Characters.java Level: Medium Tags: [Hash Table, Sliding Window, String, Two Pointers]
如题.
0. Longest Increasing Path in a Matrix.java Level: Hard Tags: [Coordinate DP, DFS, DP, Memoization, Topological Sort]
m x n 的matrix, 找最长增序的序列长度. 这里默认连续的序列.
还没有做
1. [tool]. Topological Sorting.java Level: Medium Tags: [BFS, DFS, Lint, Topological Sort]
TODO:
2. 269. Alien Dictionary.java Level: Hard Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
给一个 array of strings: 假如这个array是按照一个新的字母排序表(alien dictionary)排出来的, 需要找到这个字母排序.
有可能有多重排序的方法, 给出一种就可以.
3. 207. Course Schedule.java Level: Medium Tags: [BFS, Backtracking, DFS, Graph, Topological Sort]
4. 1203. Sort Items by Groups Respecting Dependencies.java Level: Hard Tags: [BFS, DFS, Graph, Topological Sort]
5. 210. Course Schedule II.java Level: Medium Tags: [BFS, DFS, Graph, Topological Sort]
0. 277. Find the Celebrity.java Level: Medium Tags: [Adjacency Matrix, Array, Graph, Greedy, Pruning]
有n个人, 其中有个人是celebrity, 注意必要条件 Celeb knows nobody; Everyone else knows the celeb
. 找到celeb
Note: the relationship graph can be presented as an adjacency matrix, but graph is not directly used in this problem.
0. QuickSort.java Level: Medium Tags: [Quick Sort, Sort]
implement quick sort.
1. [tool] Quick Select - Median.java Level: Easy Tags: [Array, Lint, Quick Select, Quick Sort, Two Pointers]
给一串无序数组, 找到median(sort之后 位置在中间的数字).
2. Sort Colors II.java Level: Medium Tags: [Partition, Quick Sort, Sort, Two Pointers]
Sort Color的普通版, sort all k colors in colors array.
Details 参见: https://github.com/awangdev/LintCode/blob/master/Java/Sort%20Color.java
3. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Sort]
kth largest in array
4. Sort Colors.java Level: Medium Tags: [Array, Partition, Quick Sort, Sort, Two Pointers]
给一串数字 nums, 数字代表颜色[0,1,2]; 要求 sort nums, 数字最终按照大小排列.
虽然叫sort color, 其实就是sort 这些 numbers, 只不过抽象了一下.
5. Partition Array.java Level: Medium Tags: [Array, Quick Sort, Sort, Two Pointers]
给一串数字, 和 int k. 根据k的值partition array, 找到第一个i, nums[i] >= k.
6. 215. Kth Largest Element in an Array.java Level: Medium Tags: [Divide and Conquer, Heap, MinHeap, PriorityQueue, Quick Select, Quick Sort]
kth largest in array
0. 448. Find All Numbers Disappeared in an Array.java Level: Easy Tags: [Array, Bucket Sort]
1. 1048. Longest String Chain.java Level: Medium Tags: [Bucket Sort, DP, Hash Table, Sort]
2. 1057. Campus Bikes.java Level: Medium Tags: [Bucket Sort, Greedy, PriorityQueue, Sort]
3. 274.H-Index.java Level: Medium Tags: [Bucket Sort, Hash Table, Sort]
找到h-index, 给的citation int[] 并不是sorted. h-index 的definition 具体看题目.
0. Jump Game II.java Level: Hard Tags: [Array, Coordinate DP, DP, Greedy]
给一串数字 是可以跳的距离. goal: 跳到最后的index 所可能用的最少次数.
1. Majority Number II.java Level: Medium Tags: [Enumeration, Greedy]
2. Minimum Subarray.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最小值
3. Rearrange String k Distance Apart.java Level: Hard Tags: [Greedy, Hash Table, Heap]
给一个string, 全是lowercase letter, 要求重新排列: 然后每个unique的character要有k distance apart.
跟Task Scheduler有点像, 只不过Task那道题里面还可以用其他方法求count, 这道题要求出排列结果
4. Remove Duplicate Letters.java Level: Hard Tags: [Greedy, Hash Table, Stack]
5. Wildcard Matching.java Level: Hard Tags: [Backtracking, DP, Double Sequence DP, Greedy, Sequence DP, String]
Double sequence DP. 与regular expression 很像.
6. Cracking the Safe.java Level: Hard Tags: [DFS, Greedy, Math]
7. Best Time to Buy and Sell Stock with Transaction Fee.java Level: Medium Tags: [Array, DP, Greedy, Sequence DP, Status DP]
跟Stock II 一样, 买卖无限, 需先买在卖. 附加条件: 每个sell transaction要加一笔fee.
8. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
9. Coins in a Line.java Level: Medium Tags: [DP, Game Theory, Greedy]
拿棋子游戏, 每个人可以拿1个或者2个, 拿走最后一个子儿的输. 问: 根据给的棋子输, 是否能确定先手的输赢?
Game Theory: 如果我要赢, 后手得到的局面一定要'有输的可能'.
空间优化O(1). Rolling array, %2
10. Queue Reconstruction by Height.java Level: Medium Tags: [Greedy]
别无他法, 只能写一遍例子, 找规律,然后greedy. 需要写一遍发现的规律比如: 从h大的开始排列, 先放入k小的. 写comparator的时候要注意正确性. 如果要sort, 并且灵活insert:用arrayList. 自己做一个object. 最后做那个'matchCount'的地方要思路清晰, 找到最正确的spot, 然后greedy insert.
O(n) space, O(nLog(n)) time, because of sorting.
可能有简化的余地, 代码有点太长. 比如试一试不用额外空间?
11. 1053. Previous Permutation With One Swap.java Level: Medium Tags: [Array, Greedy, Permutation]
12. 1007. Minimum Domino Rotations For Equal Row.java Level: Medium Tags: [Array, Greedy]
13. 253. Meeting Rooms II.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort, Sweep Line]
给一串数字pair, 代表会议的开始/结束时间. 找同时又多少个会议发生(需要多少件房间)
也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。
14. 55. Jump Game.java Level: Medium Tags: [Array, DP, Greedy]
给出步数,看能不能jump to end.
15. 134. Gas Station.java Level: Medium Tags: [Greedy]
给一串gas station array, 每个index里面有一定数量gas.
给一串cost array, 每个index有一个值, 是reach下一个gas station的油耗.
array的结尾地方, 再下一个点是开头, 形成一个circle route.
找一个index, 作为starting point: 让车子从这个点, 拿上油, 开出去, 还能开回到这个starting point
16. 277. Find the Celebrity.java Level: Medium Tags: [Adjacency Matrix, Array, Graph, Greedy, Pruning]
有n个人, 其中有个人是celebrity, 注意必要条件 Celeb knows nobody; Everyone else knows the celeb
. 找到celeb
Note: the relationship graph can be presented as an adjacency matrix, but graph is not directly used in this problem.
17. 1094. Car Pooling.java Level: Medium Tags: [Greedy, Heap, PriorityQueue, Sort]
18. 621. Task Scheduler.java Level: Medium Tags: [Array, Enumeration, Greedy, PriorityQueue, Queue]
19. 122. Best Time to Buy and Sell Stock II.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Status DP]
和Stock I 的区别:可以买卖多次,求总和的最大盈利.
20. 1057. Campus Bikes.java Level: Medium Tags: [Bucket Sort, Greedy, PriorityQueue, Sort]
21. 605. Can Place Flowers.java Level: Easy Tags: [Array, Greedy]
22. 402. Remove K Digits.java Level: Medium Tags: [Greedy, Monotonous Stack, Stack]
23. 767. Reorganize String.java Level: Medium Tags: [Greedy, Hash Table, Heap, Sort, String]
We want to exhaust largest population and merge like merging k list. Problem: largest population may result in them being adjacent. How to resolve?
0. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
1. 621. Task Scheduler.java Level: Medium Tags: [Array, Enumeration, Greedy, PriorityQueue, Queue]
2. 346. Moving Average from Data Stream.java Level: Easy Tags: [Design, Queue, Sliding Window]
给一个interface, design一个structure, 能够计算moving window average.
0. Jump Game II.java Level: Hard Tags: [Array, Coordinate DP, DP, Greedy]
给一串数字 是可以跳的距离. goal: 跳到最后的index 所可能用的最少次数.
1. Triangles.java Level: Medium Tags: [Array, Coordinate DP, DFS, DP, Memoization]
给一个list<list> triangle, 细节原题. 找 min path sum from root.
2. Longest Increasing Continuous subsequence II.java Level: Medium Tags: [Array, Coordinate DP, DP, Memoization]
3. Longest Increasing Continuous subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP]
https://leetcode.com/problems/longest-continuous-increasing-subsequence/description/
O(n)跑2遍for. O(1)是用了两个int来存:每次到i点时,i点满足条件或不满足条件所有的longestIncreasingContinuousSubsequence. 特点:返跑一回,ans还是继续和left轮的ans作比较;求的所有情况的最大值嘛。
4. Russian Doll Envelopes.java Level: Hard Tags: [Binary Search, Coordinate DP, DP]
俄罗斯套娃, 这里用envelope来表现. 给一串array, 每一个[x, y] 是envelope 长宽. [[5,4],[6,4],[6,7],[2,3]].
看用这些套娃, 可以最多套几个.
5. Number of Longest Increasing Subsequence.java Level: Medium Tags: [Coordinate DP, DP]
给一串 unsorted sequence, 找到长 increasing subsequence 的个数!
6. Longest Increasing Subsequence.java Level: Medium Tags: [Binary Search, Coordinate DP, DP, Memoization]
无序数组, 找最长的上升(不需要连续)数组 的长度. 先做O(n^2), 然后可否O(nLogN)?
7. Longest Valid Parentheses.java Level: Hard Tags: [Coordinate DP, Stack, String]
给一串string, 里面只有(
, )
. 找最长valid parentheses 的长度.
8. Longest Increasing Path in a Matrix.java Level: Hard Tags: [Coordinate DP, DFS, DP, Memoization, Topological Sort]
m x n 的matrix, 找最长增序的序列长度. 这里默认连续的序列.
还没有做
9. Minimum Swaps To Make Sequences Increasing.java Level: Medium Tags: [Coordinate DP, DP, Status DP]
10. 674. Longest Continuous Increasing Subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP, Sliding Window]
找连续的持续上升子序列的长度.
11. 221. Maximal Square.java Level: Medium Tags: [Coordinate DP, DP]
只能往右边,下面走, 找面积最大的 square. 也就是找到变最长的 square.
12. 62. Unique Path.java Level: Medium Tags: [Array, Coordinate DP, DP]
2D array, 算走到最右下角,有多少种方式.
13. 64. Minimum Path Sum.java Level: Medium Tags: [Array, Coordinate DP, DP]
14. 63. Unique Paths II.java Level: Medium Tags: [Array, Coordinate DP, DP]
跟unique path的grid一样, 目标走到右下角, 但是grid里面可能有obstacle, 不能跨越. 求unique path 的count.
15. 523. Continuous Subarray Sum.java Level: Medium Tags: [Coordinate DP, DP, Math, PreSum, Subarray]
给一个非负数的数列和数字k(可正负, 可为0). 找到连续子序列(长度超过2), 使得这个subarray的sum 是 k的倍数. 问: 是否可能?
16. 361. Bomb Enemy.java Level: Medium Tags: [Coordinate DP, DP]
2D grid, 每个格子里面可能是 'W' wall, 'E' enemy, 或者是 '0' empty.
一个bomb可以往4个方向炸. 求在grid上面, 最大能炸掉多少个敌人.
0. Largest Rectangle in Histogram.java Level: Hard Tags: [Array, Monotonous Stack, Stack]
给n个bar,组成柱状图histogram. 求在这一排柱状图里面可以找到的面积最大的长方形.
思考: 找长方形面积, 无非是找两个index, 然后底边长度 * height.
1. 402. Remove K Digits.java Level: Medium Tags: [Greedy, Monotonous Stack, Stack]
2. 739. Daily Temperatures.java Level: Medium Tags: [Hash Table, Monotonous Stack, Stack]
0. Sort Colors II.java Level: Medium Tags: [Partition, Quick Sort, Sort, Two Pointers]
Sort Color的普通版, sort all k colors in colors array.
Details 参见: https://github.com/awangdev/LintCode/blob/master/Java/Sort%20Color.java
1. Sort Letters by Case.java Level: Medium Tags: [Partition, Sort, String, Two Pointers]
给一串字符(ASCII 大写, 小写字母), 要求sort 小写字母, 在大写字母前面.
字母间的前后顺序无所谓, 也不需要preserve original order .
跟sort color分成相似.
2. Sort Colors.java Level: Medium Tags: [Array, Partition, Quick Sort, Sort, Two Pointers]
给一串数字 nums, 数字代表颜色[0,1,2]; 要求 sort nums, 数字最终按照大小排列.
虽然叫sort color, 其实就是sort 这些 numbers, 只不过抽象了一下.
0. Majority Number II.java Level: Medium Tags: [Enumeration, Greedy]
1. Rotate Image.java Level: Medium Tags: [Array, Enumeration]
2. Ugly Number II.java Level: Medium Tags: [DP, Enumeration, Heap, Math, PriorityQueue]
3. Strobogrammatic Number II.java Level: Medium Tags: [DFS, Enumeration, Math, Sequence DFS]
TODO:
4. Spiral Matrix.java Level: Medium Tags: [Array, Enumeration]
从(0,0)坐标, 走完spiral matrix, 把结果存在list里.
5. Next Closest Time.java Level: Medium Tags: [Basic Implementation, Enumeration, String]
给一个时间string"12:09", 用里面的4个integer组合成其他时间string, 目标找最小的next time.
如果组合出的time string 在input time之前, 默认 + 24 hours.
6. 36. Valid Sudoku.java Level: Easy Tags: [Enumeration, Hash Table]
7. 158. Read N Characters Given Read4 II - Call multiple times.java Level: Hard Tags: [Enumeration, String]
Read N Character using Read4(char[] buf)
的加强版: 可以不断读 read(buf, n)
8. 621. Task Scheduler.java Level: Medium Tags: [Array, Enumeration, Greedy, PriorityQueue, Queue]
9. 246. Strobogrammatic Number.java Level: Easy Tags: [Enumeration, Hash Table, Math, Two Pointers]
根据题意枚举, 再根据规则basic implementation
10. 639. Decode Ways II.java Level: Hard Tags: [DP, Enumeration, Partition DP]
给出一串数字, 要翻译(decode)成英文字母. [1 ~ 26] 对应相对的英文字母. 求有多少种方法可以decode.
其中字符可能是 "*", 可以代表 [1 - 9]
11. 68. Text Justification.java Level: Hard Tags: [Enumeration, String]
按照规则 adjust text. 就是Word里面: 有一行太长, adjust word 中间的space, 然后保证每一行的total width 顶格.
还有一些细节规则, 看原题
12. 157. Read N Characters Given Read4.java Level: Easy Tags: [Enumeration, String]
Read4 题目. 理解题目: 是有个input object buff, 会被populated with data.
13. 273. Integer to English Words.java Level: Hard Tags: [Enumeration, Math, String]
给一个小于 Integer.MAX_VALUE (2^31 - 1) 的数字, 转换成英语. (不需要加 'and')
14. 65. Valid Number.java Level: Hard Tags: [Enumeration, Math, String]
分析edge case, 和各种情况, 然后判别是否是valid number
0. 152. Maximum Product Subarray.java Level: Medium Tags: [Array, DP, PreProduct, Subarray]
从一组数列(正负都有)里面找一串连续的子序列, 而达到乘积product最大值.
1. 238. Product of Array Except Self.java Level: Medium Tags: [Array, PreProduct]
给一串数字, output rst[n], 每个index是 除了nums[i]以外 所有itemd的乘积.
0. Convert Expression to Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Polish Notation (PN).
1. Expression Evaluation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Minimum Binary Tree, Stack]
给一个公式 expression, array of strings, 然后evaluate expression 结果.
2. Search Range in Binary Search Tree .java Level: Medium Tags: [BST, Binary Tree]
给一个BST, integer range (k1, k2), 找range 里面所有的integer.
3. Segment Tree Modify.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给一个segmentTree, node里面存max. 写一个modify function: modify(node, index, value).
4. Expression Tree Build.java Level: Hard Tags: [Binary Tree, Expression Tree, Minimum Binary Tree, Stack]
给一串字符, 表示的是 公式 expression. 把公式变成expression tree
5. Segment Tree Query II.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
6. Basic Calculator.java Level: Hard Tags: [Binary Tree, Expression Tree, Math, Minimum Binary Tree, Stack]
给一个expression String, 要evaluate这个expression的值.
Expression string 里面包括 +, -, 整数, 开合括号, 还有space.
7. Convert Expression to Reverse Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Reverse Polish Notation (RPN).
8. [lint]. Segment Tree Build II.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个array, 建造segment tree structure,
每个treeNode 里面存这个range里的 max value, return root node.
9. [lint]. Segment Tree Query.java Level: Medium Tags: [Binary Tree, DFS, Divide and Conquer, Lint, Segment Tree]
给了segment Tree, node里面有Max value, 找[start,end]里面的max
10. [lint]. Segment Tree Build.java Level: Medium Tags: [Binary Tree, Divide and Conquer, Lint, Segment Tree]
给一个区间[startIndex, endIndex], 建造segment tree structure, return root node.
11. 987. Vertical Order Traversal of a Binary Tree.java Level: Medium Tags: [BFS, Binary Tree, DFS, Hash Table, Tree]
space: O(n)
Very similar to 314. Binary Tree Vertical Order Traversal
with 1 special condition: if 2 nodes at same (offset, level):
sort it by its value
12. 257. Binary Tree Paths.java Level: Easy Tags: [Backtracking, Binary Tree, DFS]
给一个binary tree, 返回所有root-to-leaf path
13. 114. Flatten Binary Tree to Linked List.java Level: Medium Tags: [Binary Tree, DFS]
给一个binary tree, 把tree做成 linked list的形式, in-place.
0. 398. Random Pick Index.java Level: Medium Tags: [Reservior Sampling]
0. 142. Linked List Cycle II.java Level: Medium Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
1. 141. Linked List Cycle.java Level: Easy Tags: [Cycle Detection, Linked List, Slow Fast Pointer, Two Pointers]
2. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
0. Convert Expression to Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Polish Notation (PN).
1. Expression Evaluation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Minimum Binary Tree, Stack]
给一个公式 expression, array of strings, 然后evaluate expression 结果.
2. Expression Tree Build.java Level: Hard Tags: [Binary Tree, Expression Tree, Minimum Binary Tree, Stack]
给一串字符, 表示的是 公式 expression. 把公式变成expression tree
3. Basic Calculator.java Level: Hard Tags: [Binary Tree, Expression Tree, Math, Minimum Binary Tree, Stack]
给一个expression String, 要evaluate这个expression的值.
Expression string 里面包括 +, -, 整数, 开合括号, 还有space.
4. Convert Expression to Reverse Polish Notation.java Level: Hard Tags: [Binary Tree, DFS, Expression Tree, Stack]
给一串字符, 用来表示公式expression. 把这个expression转换成 Reverse Polish Notation (RPN).
0. Search a 2D Matrix II.java Level: Medium Tags: [Binary Search, Divide and Conquer]
给matrix, 每一行sorted, 每一列从上往下sorted, 找target是否存在
1. Closest Number in Sorted Array.java Level: Easy Tags: [Binary Search]
2. Interval Minimum Number.java Level: Medium Tags: [Binary Search, Divide and Conquer, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的最小值.
3. Find Minimum in Rotated Sorted Array.java Level: Medium Tags: [Array, Binary Search]
画图, 最小值被rotate之后, 变成array中间的最低谷. 并且, 左边山坡的最小值, 大于右边山坡的最大值. 以此来给binary search做判断.
O(nlogn)
4. Count of Smaller Number.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字, array size = n. 给一串query: 每个query是一个数, 目的找 count# items smaller than query element.
5. Median of Two Sorted Arrays.java Level: Hard Tags: [Array, Binary Search, DFS, Divide and Conquer]
著名的找两个sorted array的中位数. 中位数定义: 如果两个array总长为偶数, 取平均值. 题目要求在 log(m + n) 时间内解决
6. Minimum Size Subarray Sum.java Level: Medium Tags: [Array, Binary Search, Subarray, Two Pointers]
给一串positive integer, 找最短的subarray sum, where the sum >= s
7. Find Peak Element.java Level: Medium Tags: [Array, Binary Search]
binary search. Goal: find peak, where both sides are descending 最左边, 最右边是Integer.MIN_VALUE时候, 也能构成中间数mid是peak的条件.
Note: 没有必要特别check (mid-1)<0或者(mid+1)>=n. 证明:
8. Russian Doll Envelopes.java Level: Hard Tags: [Binary Search, Coordinate DP, DP]
俄罗斯套娃, 这里用envelope来表现. 给一串array, 每一个[x, y] 是envelope 长宽. [[5,4],[6,4],[6,7],[2,3]].
看用这些套娃, 可以最多套几个.
9. Find Peak Element II.java Level: Hard Tags: [Binary Search, DFS, Divide and Conquer]
2Dmatrix, 里面的value有一些递增, 递减的特点(细节比较长, 看原题). 目标是找到peak element
peak: 比周围4个方向的点value大
10. Longest Increasing Subsequence.java Level: Medium Tags: [Binary Search, Coordinate DP, DP, Memoization]
无序数组, 找最长的上升(不需要连续)数组 的长度. 先做O(n^2), 然后可否O(nLogN)?
11. Classical Binary Search.java Level: Easy Tags: [Binary Search]
12. Wood Cut.java Level: Medium Tags: [Binary Search]
二分的思想: 判断的是一个 validate() function, 而不是简单的'=='
不需要sort! 为什么呢? 因为我们不是在given array上面二分, 我们是根据最大值在[0, max]上二分.
Overall time: O(nLogM), where M = largest wood length
13. Subarray Sum II.java Level: Hard Tags: [Array, Binary Search, Two Pointers]
14. Find Minimum in Rotated Sorted Array II.java Level: Hard Tags: [Array, Binary Search]
一个需要严谨思考的题目. 因为有duplicate, 会导致不断平移, 所以最终time complexity是O(n) 所以不如直接扫一遍, 给出答案.
但是还是写一个Binary Search的样子, 只不过worst结果是O(n)
15. Maximum Average Subarray II.java Level: Review Tags: [Array, Binary Search, PreSum]
给int[] nums 和 window min size k. window size可以大于K. 找最大的连续数列average value.
需要仔细去读下面的notes.
16. Copy Books.java Level: Hard Tags: [Binary Search, DP, Partition DP]
给一串书pages[i], k个人, pages[i] 代表每本书的页数. k个人从不同的点同时开始抄书.
问, 最快什么时候可以抄完?
17. Two Sum II - Input array is sorted.java Level: Medium Tags: [Array, Binary Search, Two Pointers]
升序array, 找2SUM.
18. Interval Sum.java Level: Medium Tags: [Binary Search, Lint, Segment Tree]
给一串数字 int[], 然后一个query Interval[], 每个interval是 [start, end], 找query 区间里的sum.
19. Guess Number Higher or Lower.java Level: Easy Tags: [Binary Search]
binary search 公式
20. Interval Sum II.java Level: Hard Tags: [Binary Search, Lint, Segment Tree]
SegmentTree大集合. Methods: build, query, modify
. 不难。只是要都记得不犯错.
21. Kth Smallest Element in a Sorted Matrix.java Level: Medium Tags: [Binary Search, Heap]
给一个sorted matrix, 找kth smallest number (not distinct)
Related: Kth Largest Element in an Array
22. Last Position of Target.java Level: Easy Tags: [Binary Search]
给一个sorted integer array, 找target出现的最后的index. array 里有重复数字
有重复,不是末尾点,继续binary search
23. Pow(x, n).java Level: Medium Tags: [Binary Search, Math]
傻做就O(n), 要更好就考虑O(logN). 减少重复计算, 一切两半.
注意:
24. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
25. Search for a Range.java Level: Medium Tags: [Array, Binary Search]
给sorted array, 有重复数字, 找跟target重合所在的range.
26. Search a 2D Matrix.java Level: Medium Tags: [Array, Binary Search]
给2D matrix, 每行sorted, 每行的首位都大于上一行的末尾. goal: find target from matrix
27. [lint]. 2 Sum II.java Level: Medium Tags: [Array, Binary Search, Lint, Two Pointers]
与 2sum II - input array is sorted类似. 都是sort array, 然后two pointer.
LintCode的题. 注意找的是greater/bigger than target。
由于给定条件允许O(nLogn):
sort
two pointer
while里面two pointer移动。每次如果num[left]+num[right] > target,那么其中所有num[left++]的加上num[right]都>target.
也就是,num[right]不动,计算加入挪动left能有多少组,那就是: right-left这么多。 全部加到count上去。
然后right--.换个right去和前面的left部分作比较。
28. 275. H-Index II.java Level: Medium Tags: [Binary Search]
找到h-index, 给的citation int[] 已经sorted. h-index 的definition 具体看题目.
Aim to find the lowest index mid, which maximize h = n - mid
29. 222. Count Complete Tree Nodes.java Level: Medium Tags: [Binary Search, DFS, Tree]
Complete Tree就是说, 最后一个level可能是缺node的(不是说最右下角缺node, 别忘了!)
30. 1060. Missing Element in Sorted Array.java Level: Medium Tags: [Binary Search]
31. 875. Koko Eating Bananas.java Level: Medium Tags: [Binary Search]
32. 349. Intersection of Two Arrays.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
33. 315. Count of Smaller Numbers After Self.java Level: Hard Tags: [BST, Binary Indexed Tree, Binary Search, Divide and Conquer, Segment Tree]
给一串数字nums[], 求一个新数组result, where result[i] = # of smaller items on right of nums[i]
34. 88. Search in Rotated Sorted Array II.java Level: Medium Tags: [Array, Binary Search]
35. 367. Valid Perfect Square.java Level: Easy Tags: [Binary Search, Math]
36. 270. Closest Binary Search Tree Value.java Level: Easy Tags: [BST, Binary Search, Tree]
给一个BST, 和一个double target, 走位找到最接近的number.
Concept: Iterate over all logN nodes in the BST and record the closest. Rather than finding the value at +/- 0.5 precision
37. 852. Peak Index in a Mountain Array.java Level: Easy Tags: [Binary Search]
38. 981. Time Based Key-Value Store.java Level: Medium Tags: [Binary Search, Hash Table, TreeMap]
39. 69. Sqrt(x).java Level: Easy Tags: [Binary Search, Math]
40. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
41. 350. Intersection of Two Arrays II.java Level: Easy Tags: [Binary Search, Hash Table, Sort, Two Pointers]
42. 33. Search in Rotated Sorted Array.java Level: Medium Tags: [Array, Binary Search]
43. 34. Find First and Last Position of Element in Sorted Array.java Level: Medium Tags: [Array, Binary Search]
44. 278. First Bad Version.java Level: Easy Tags: [Binary Search]
0. My Calendar I.java Level: Medium Tags: [Array, TreeMap]
Given a list of interval as calendar items. Check if newly added calendar item is overlapping.
Understand it is only checking time, but not requiring to insert into right spot. No need to overthink.
1. 855. Exam Room.java Level: Medium Tags: [PriorityQueue, Sort, TreeMap, TreeSet]
2. 981. Time Based Key-Value Store.java Level: Medium Tags: [Binary Search, Hash Table, TreeMap]
3. 1146. Snapshot Array.java Level: Medium Tags: [Array, Hash Table, TreeMap]
4. 716. Max Stack.java Level: Medium Tags: [Design, Doubly Linked List, Stack, TreeMap]
0. 327. Count of Range Sum.java Level: Hard Tags: [BIT, Divide and Conquer, Merge Sort, PreSum, Segment Tree]
TODO: Write the code + merge function
1. 218. The Skyline Problem.java Level: Hard Tags: [BIT, Divide and Conquer, HashHeap, Heap, PriorityQueue, Segment Tree, Sweep Line]
Binary Indexed Tree?
0. 189. Rotate Array.java Level: Easy Tags: [Array, Rotation]
0. 277. Find the Celebrity.java Level: Medium Tags: [Adjacency Matrix, Array, Graph, Greedy, Pruning]
有n个人, 其中有个人是celebrity, 注意必要条件 Celeb knows nobody; Everyone else knows the celeb
. 找到celeb
Note: the relationship graph can be presented as an adjacency matrix, but graph is not directly used in this problem.
0. 297. Serialize and Deserialize Binary Tree.java Level: Hard Tags: [BFS, DFS, Deque, Design, Divide and Conquer, Tree]
Serialize and Deserialize Binary Tree
1. 239. Sliding Window Maximum.java Level: Hard Tags: [Deque, Heap, Sliding Window]
0. 1117. Building H2O.java Level: Medium Tags: [Lock, Semaphore, Thread]
0. 1117. Building H2O.java Level: Medium Tags: [Lock, Semaphore, Thread]
0. Jump Game II.java Level: Hard Tags: [Array, Coordinate DP, DP, Greedy]
给一串数字 是可以跳的距离. goal: 跳到最后的index 所可能用的最少次数.
1. Missing Ranges.java Level: Medium Tags: [Array]
2. Missing Number.java Level: Easy Tags: [Array, Bit Manipulation, Math]
给一串unique数字, 数字取自 [0 ~ n], 无序, 找第一个skipped的数字.
3. Triangles.java Level: Medium Tags: [Array, Coordinate DP, DFS, DP, Memoization]
给一个list<list> triangle, 细节原题. 找 min path sum from root.
4. Summary Ranges.java Level: Medium Tags: [Array]
给一串sorted list, 中间有缺数字, return 所有数字的range string (example 看题目)
5. Longest Increasing Continuous subsequence II.java Level: Medium Tags: [Array, Coordinate DP, DP, Memoization]
6. Plus One.java Level: Easy Tags: [Array, Math]
简单的实现, 加1, 进位. 唯一取巧的地方, 最后如果要多一位, 一定是10000...这个模式, 可以走捷径, 直接来个+1size的array, 然后第一位=1. 注意,转换成long也不合理,用太多memory.
7. Longest Consecutive Sequence.java Level: Hard Tags: [Array, Hash Table, Union Find]
给一串数字, unsorted, 找这串数字里面的连续元素序列长度 (consecutive序列, 是数字连续, 并不是说要按照原order)
8. Find Minimum in Rotated Sorted Array.java Level: Medium Tags: [Array, Binary Search]
画图, 最小值被rotate之后, 变成array中间的最低谷. 并且, 左边山坡的最小值, 大于右边山坡的最大值. 以此来给binary search做判断.
O(nlogn)
9. Minimum Subarray.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最小值
10. Maximum Average Subarray I.java Level: Easy Tags: [Array, Subarray]
简单的求sum of fixed window k, 同时求max avg, 结尾求余数就好.
11. Median of Two Sorted Arrays.java Level: Hard Tags: [Array, Binary Search, DFS, Divide and Conquer]
著名的找两个sorted array的中位数. 中位数定义: 如果两个array总长为偶数, 取平均值. 题目要求在 log(m + n) 时间内解决
12. Word Search.java Level: Medium Tags: [Array, Backtracking, DFS]
13. Triangle Count.java Level: Medium Tags: [Array]
其实也就是3sum的变形, 或者而说2sum的变形. 主要用2 pointers来做. 注意, 在选index时候每次定好一个 [0 ~ i], 在这里面找点start, end, 然后i 来组成triangle. Note巧妙点: 在此之中, 如果一种start/end/i 符合, 那么从这个[start~end]中, 大于start的都可以, 所以我们count+= end-start. 反而言之, <end的其他index, 就不一定能符合nums[start] + nums[end] > nums[i]
14. Minimum Size Subarray Sum.java Level: Medium Tags: [Array, Binary Search, Subarray, Two Pointers]
给一串positive integer, 找最短的subarray sum, where the sum >= s
15. Construct Binary Tree from Inorder and Postorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Tree]
16. Maximal Rectangle.java Level: Hard Tags: [Array, DP, Hash Table, Stack]
分解开来, 其实是'Largest Rectangle in Histogram', 只不过这里要自己model heights. 一个2D array里面的rectangle, 最终也是用height * width做出来的. 巧妙在于, 把每一行当做底边, 算出这个底边, 到顶部的height:
如果看具体实例, 有些row似乎是白算的, 但是没有办法, 这是一个搜索的过程, 最终会比较出最优解.
Coordinate DP?
17. Longest Increasing Continuous subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP]
https://leetcode.com/problems/longest-continuous-increasing-subsequence/description/
O(n)跑2遍for. O(1)是用了两个int来存:每次到i点时,i点满足条件或不满足条件所有的longestIncreasingContinuousSubsequence. 特点:返跑一回,ans还是继续和left轮的ans作比较;求的所有情况的最大值嘛。
18. Max Area of Island.java Level: Easy Tags: [Array, DFS]
19. Find Peak Element.java Level: Medium Tags: [Array, Binary Search]
binary search. Goal: find peak, where both sides are descending 最左边, 最右边是Integer.MIN_VALUE时候, 也能构成中间数mid是peak的条件.
Note: 没有必要特别check (mid-1)<0或者(mid+1)>=n. 证明:
20. K Empty Slots.java Level: Hard Tags: [Array, BST, TreeSet]
题目解析后: find 2 number, that: 1. k slots between the 2 number, 2. no slots taken between the two number.
21. Game of Life.java Level: Medium Tags: [Array]
unlimited border? 可能需要分割board. 用大框分割, 每次换行的时候, 重复计算2行就好了. see details below.
22. Rotate Image.java Level: Medium Tags: [Array, Enumeration]
23. Combination Sum IV.java Level: Medium Tags: [Array, Backpack DP, DP]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
24. Wiggle Sort.java Level: Medium Tags: [Array, Sort]
方法1: 排序, nLog(n). 然后把直线上坡变成层叠山峰, 需要每隔几个(题目中是每隔2位)就做个swap 造成高低不平. Note: 每隔山峰之间是相互没有关系的, 所以每次只要操心 [i], [i-1]两个位置就好了.
方法2: O(n) 看好奇数偶数位的规律, 然后根据题目给出的规律, 跑一遍, 每次只关注两个位置: 把不合适的[i], [i-1]调换位置就好了.
方法3: 跟法2一样, 只是更巧妙一点罢了: 第一遍想太多. 其实做一个fall-through就能把问题解决,原因是因为: 这样的fall-through每次在乎两个element,可以一口气搞定,无关乎再之前的elements。 特别的一点:flag来巧妙的掌控山峰和低谷的变化。又是神奇的一幕啊! 这样子的奇观,见过就要知道了,没见过的时候有点摸不着头脑。
25. [tool] Quick Select - Median.java Level: Easy Tags: [Array, Lint, Quick Select, Quick Sort, Two Pointers]
给一串无序数组, 找到median(sort之后 位置在中间的数字).
26. Friends Of Appropriate Ages.java Level: Medium Tags: [Array, Math]
27. Best Time to Buy and Sell Stock III.java Level: Hard Tags: [Array, DP, Sequence DP]
比stock II 多了一个限制:只有2次卖出机会.
28. Subsets II.java Level: Medium Tags: [Array, BFS, Backtracking, DFS]
给一串integers(may have duplicates), 找到所有可能的subset. result里面不能有重复.
29. Container With Most Water.java Level: Medium Tags: [Array, Two Pointers]
30. Coins in a Line II.java Level: Medium Tags: [Array, DP, Game Theory, Memoization, MiniMax]
给一串coins, 用values[]表示; 每个coin有自己的value. 先手/后手博弈, 每次只能 按照从左到右的顺序, 拿1个或者2个棋子, 最后看谁拿的总值最大.
MiniMax的思考方法很神奇, 最后写出来的表达式很简单
Time O(n) Space O(n): dp[], sum[]
31. The Smallest Difference.java Level: Medium Tags: [Array, Sort, Two Pointers]
32. Subarray Sum II.java Level: Hard Tags: [Array, Binary Search, Two Pointers]
33. Submatrix Sum.java Level: Medium Tags: [Array, Hash Table, PreSum]
给一个int[][] matrix, 找一个sub matrix, where the sum == 0.
34. Number of Airplane in the sky.java Level: Medium Tags: [Array, Interval, PriorityQueue, Sort, Sweep Line]
35. Find Minimum in Rotated Sorted Array II.java Level: Hard Tags: [Array, Binary Search]
一个需要严谨思考的题目. 因为有duplicate, 会导致不断平移, 所以最终time complexity是O(n) 所以不如直接扫一遍, 给出答案.
但是还是写一个Binary Search的样子, 只不过worst结果是O(n)
36. Maximum Average Subarray II.java Level: Review Tags: [Array, Binary Search, PreSum]
给int[] nums 和 window min size k. window size可以大于K. 找最大的连续数列average value.
需要仔细去读下面的notes.
37. My Calendar I.java Level: Medium Tags: [Array, TreeMap]
Given a list of interval as calendar items. Check if newly added calendar item is overlapping.
Understand it is only checking time, but not requiring to insert into right spot. No need to overthink.
38. Two Sum II - Input array is sorted.java Level: Medium Tags: [Array, Binary Search, Two Pointers]
升序array, 找2SUM.
39. Coins in a Line III.java Level: Hard Tags: [Array, DP, Game Theory, Interval DP, Memoization]
LeetCode: Predict the Winner
还是2个人拿n个coin, coin可以有不同的value.
只不过这次选手可以从任意的一头拿, 而不限制从一头拿. 算先手会不会赢?
40. Partition Array by Odd and Even.java Level: Easy Tags: [Array, Two Pointers]
41. Combination Sum III.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一个integer k, 和一个target n.
从positive数字[1 ~ 9], 找到所有unique的 组合(combination) int[], size = k, 要求每个combination的和 = n.
(隐藏条件, 需要clarify): 同一个candidate integer [1 ~ 9], 只可以用一次.
42. Best Time to Buy and Sell Stock with Transaction Fee.java Level: Medium Tags: [Array, DP, Greedy, Sequence DP, Status DP]
跟Stock II 一样, 买卖无限, 需先买在卖. 附加条件: 每个sell transaction要加一笔fee.
43. Maximum Subarray II.java Level: Medium Tags: [Array, DP, Greedy, PreSum, Sequence DP, Subarray]
给一串数组, 找数组中间 两个不交互的 subarray 数字之和的最大值
44. Sort Colors.java Level: Medium Tags: [Array, Partition, Quick Sort, Sort, Two Pointers]
给一串数字 nums, 数字代表颜色[0,1,2]; 要求 sort nums, 数字最终按照大小排列.
虽然叫sort color, 其实就是sort 这些 numbers, 只不过抽象了一下.
45. Word Ladder II.java Level: Hard Tags: [Array, BFS, Backtracking, DFS, Hash Table, String]
给一串string, start word, end word. 找到所有从 startWord -> endWord的最短路径list.
变化方式: mutate 1 letter at a time.
46. Spiral Matrix.java Level: Medium Tags: [Array, Enumeration]
从(0,0)坐标, 走完spiral matrix, 把结果存在list里.
47. The Spiral Matrix II.java Level: Medium Tags: [Array]
48. Partition Array.java Level: Medium Tags: [Array, Quick Sort, Sort, Two Pointers]
给一串数字, 和 int k. 根据k的值partition array, 找到第一个i, nums[i] >= k.
49. Max Sum of Rectangle No Larger Than K.java Level: Hard Tags: [Array, BST, Binary Search, DP, Queue, TreeSet]
给定一个非空的二维矩阵matrix与一个整数k,在矩阵内部寻找和不大于k的最大矩形和。
50. Search for a Range.java Level: Medium Tags: [Array, Binary Search]
给sorted array, 有重复数字, 找跟target重合所在的range.
51. Search a 2D Matrix.java Level: Medium Tags: [Array, Binary Search]
给2D matrix, 每行sorted, 每行的首位都大于上一行的末尾. goal: find target from matrix
52. Largest Rectangle in Histogram.java Level: Hard Tags: [Array, Monotonous Stack, Stack]
给n个bar,组成柱状图histogram. 求在这一排柱状图里面可以找到的面积最大的长方形.
思考: 找长方形面积, 无非是找两个index, 然后底边长度 * height.
53. [lint]. Product of Array Exclude Itself.java Level: Medium Tags: [Array, Lint]
54. [lint]. Anagrams.java Level: Medium Tags: [Array, Hash Table, Lint]
把anagram找到并output
55. [lint]. 3 Sum Closest.java Level: Medium Tags: [Array, Lint, Two Pointers]
3Sum 的一种简单形式, 并且都没有找index, value, 而只是找个sum罢了.
double for loop。 2Sum只能用土办法 left/right 2 pointers。 O(n^2)
注意:check closest时候用long, 以免int不够用
56. [lint]. Unique Characters.java Level: Easy Tags: [Array, Lint, String]
determine if characters are unique in string
57. [lint]. Subarray Sum.java Level: Easy Tags: [Array, Hash Table, Lint, PreSum, Subarray]
给一串数字, 找其中的一个subarray的 [start, end] index, 条件: subarary sum == 0.
58. [lint]. Recover Rotated Sorted Array.java Level: Easy Tags: [Array, Lint]
rotate的意思,是有个点断开,把一边的array节选出来放在另外一边。 Rotate三步: rotate前半 rotate后半 rotate全部
注意先找到断点。
59. [lint]. 2 Sum II.java Level: Medium Tags: [Array, Binary Search, Lint, Two Pointers]
与 2sum II - input array is sorted类似. 都是sort array, 然后two pointer.
LintCode的题. 注意找的是greater/bigger than target。
由于给定条件允许O(nLogn):
sort
two pointer
while里面two pointer移动。每次如果num[left]+num[right] > target,那么其中所有num[left++]的加上num[right]都>target.
也就是,num[right]不动,计算加入挪动left能有多少组,那就是: right-left这么多。 全部加到count上去。
然后right--.换个right去和前面的left部分作比较。
60. 42. Trapping Rain Water.java Level: Hard Tags: [Array, Stack, Two Pointers]
这道题目的方法比较多.
61. 448. Find All Numbers Disappeared in an Array.java Level: Easy Tags: [Array, Bucket Sort]
62. 849. Maximize Distance to Closest Person.java Level: Easy Tags: [Array, Basic Implementation, Two Pointers]
给一排座位, 一个人去坐: 找离两边�的人都最远的地方(中间点), return 跟旁边人的最大distance
是Exam Room 的同种概念, 简单化题目: 这里只考虑一个人就好了
63. 766. Toeplitz Matrix.java Level: Easy Tags: [Array]
64. 1053. Previous Permutation With One Swap.java Level: Medium Tags: [Array, Greedy, Permutation]
65. 56. Merge Intervals.java Level: Medium Tags: [Array, PriorityQueue, Sort, Sweep Line]
给一串int[Interval] (unsorted), 把所以Interval merge起来.
66. 665. Non-decreasing Array.java Level: Easy Tags: [Array]
67. 244. Shortest Word Distance II.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
68. 80.Remove Duplicates from Sorted Array II.java Level: Medium Tags: [Array, Two Pointers]
给一个sorted array, 把重复的去掉: 也就是把不重复的按照顺序贴上来, array末尾多余的位置无所谓.
最多可重复出元素的数量不超过2个. return unique item 的长度.
69. 674. Longest Continuous Increasing Subsequence.java Level: Easy Tags: [Array, Coordinate DP, DP, Sliding Window]
找连续的持续上升子序列的长度.
70. 1007. Minimum Domino Rotations For Equal Row.java Level: Medium Tags: [Array, Greedy]
71. 485. Max Consecutive Ones.java Level: Easy Tags: [Array, Basic Implementation]
72. 896. Monotonic Array.java Level: Easy Tags: [Array]
basic implementation
73. 26.Remove Duplicates from Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
给一个sorted array, 把重复的去掉: 也就是把不重复的按照顺序贴上来, array末尾多余的位置无所谓.
return unique item 的长度.
74. 41. First Missing Positive.java Level: Hard Tags: [Analysis, Array, Edge Case]
给一串无序数字, 有负数: 找这个array里面第一个 missing的 positive integer
missing positive integer 其实是以 [1, n] 来做比较的.
75. 717. 1-bit and 2-bit Characters.java Level: Easy Tags: [Array]
理解题目:
Greedy. 从第一个bit开始: 如果 % 2 == 1, 一定是跳两位; 如果0, 一定是跳一位. loop to end, and see if index reaches the end.
用DP硬做了一下:
76. 53. Maximum Subarray.java Level: Easy Tags: [Array, DFS, DP, Divide and Conquer, PreSum, Sequence DP, Subarray]
给一串数组, unsorted, can have negative/positive num. 找数组中间 subarray 数字之和的最大值
77. 152. Maximum Product Subarray.java Level: Medium Tags: [Array, DP, PreProduct, Subarray]
从一组数列(正负都有)里面找一串连续的子序列, 而达到乘积product最大值.
78. 259. 3Sum Smaller.java Level: Medium Tags: [Array, Sort, Two Pointers]
79. 977. Squares of a Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
80. 31. Next Permutation.java Level: Medium Tags: [Array, Permutation]
81. 238. Product of Array Except Self.java Level: Medium Tags: [Array, PreProduct]
给一串数字, output rst[n], 每个index是 除了nums[i]以外 所有itemd的乘积.
82. 62. Unique Path.java Level: Medium Tags: [Array, Coordinate DP, DP]
2D array, 算走到最右下角,有多少种方式.
83. 15. 3Sum.java Level: Medium Tags: [Array, Sort, Two Pointers]
84. 55. Jump Game.java Level: Medium Tags: [Array, DP, Greedy]
给出步数,看能不能jump to end.
85. 189. Rotate Array.java Level: Easy Tags: [Array, Rotation]
86. 119. Pascal's Triangle II.java Level: Easy Tags: [Array, Basic Implementation]
简单处理 list. code is very similar to Pascal triangle I.
87. 277. Find the Celebrity.java Level: Medium Tags: [Adjacency Matrix, Array, Graph, Greedy, Pruning]
有n个人, 其中有个人是celebrity, 注意必要条件 Celeb knows nobody; Everyone else knows the celeb
. 找到celeb
Note: the relationship graph can be presented as an adjacency matrix, but graph is not directly used in this problem.
88. 245. Shortest Word Distance III.java Level: Medium Tags: [Array, Design, Hash Table, Two Pointers]
跟243/244不同: 这里允许list里面有重复的word.
89. 621. Task Scheduler.java Level: Medium Tags: [Array, Enumeration, Greedy, PriorityQueue, Queue]
90. 747. Largest Number At Least Twice of Others.java Level: Easy Tags: [Array]
多种简单操作:
91. 88. Search in Rotated Sorted Array II.java Level: Medium Tags: [Array, Binary Search]
92. 561. Array Partition I.java Level: Easy Tags: [Array]
给串数字, size=2n, 找pairs, 然后需要sum of min(pair) 最大.
(a1, b1), (a2, b2), ..., (an, bn) which makes sum of min(ai, bi) for all i from 1 to n as large as possible.
93. 39. Combination Sum.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (no duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 可以用任意多次.
94. 1040. Moving Stones Until Consecutive II.java Level: Medium Tags: [Array, Sliding Window]
95. 88. Merge Sorted Array.java Level: Easy Tags: [Array, Two Pointers]
给两个排好序的数组, merge. 其中一个数组nums1有多余的位置
96. 122. Best Time to Buy and Sell Stock II.java Level: Easy Tags: [Array, DP, Greedy, Sequence DP, Status DP]
和Stock I 的区别:可以买卖多次,求总和的最大盈利.
97. 243. Shortest Word Distance.java Level: Easy Tags: [Array, Two Pointers]
98. 414. Third Maximum Number.java Level: Easy Tags: [Array, PriorityQueue]
99. 1267. Count Servers that Communicate.java Level: Medium Tags: [Array, Graph]
100. 169. Majority Element.java Level: Easy Tags: [Array, Bit Manipulation, Divide and Conquer, Moore Voting, Sort]
101. 78. Subsets.java Level: Medium Tags: [Array, BFS, Backtracking, Bit Manipulation, DFS]
给一串unique integers, 找到所有可能的subset. result里面不能有重复.
102. 380. Insert Delete GetRandom O(1).java Level: Medium Tags: [Array, Design, Hash Table]
103. 560. Subarray Sum Equals K.java Level: Medium Tags: [Array, Hash Table, PreSum, Subarray]
给一串数字, 找其中的 # of subarray的 where subararySum == k.
104. 219. Contains Duplicate II.java Level: Easy Tags: [Array, Hash Table]
Unsorted array, 找出是否有duplicate elemenets: 必要条件是, 这两个element的index i,j 的大小最多相差k.
105. 287. Find the Duplicate Number.java Level: Medium Tags: [Array, Binary Search, Binary Search on Value, Cycle Detection, Slow Fast Pointer, Two Pointers]
106. 217. Contains Duplicate.java Level: Easy Tags: [Array, Hash Table]
无序数组, 找是否有重复element, return true/false.
107. 64. Minimum Path Sum.java Level: Medium Tags: [Array, Coordinate DP, DP]
108. 229. Majority Element II.java Level: Medium Tags: [Array, Moore Voting]
109. 121. Best Time to Buy and Sell Stock.java Level: Easy Tags: [Array, DP, Sequence DP]
给个array of stock prices, 限制能交易(买/买)一轮, 问如何找到最大profit.
110. 1146. Snapshot Array.java Level: Medium Tags: [Array, Hash Table, TreeMap]
111. 605. Can Place Flowers.java Level: Easy Tags: [Array, Greedy]
112. 1. Two Sum.java Level: Easy Tags: [Array, Hash Table]
113. 118. Pascal's Triangle.java Level: Easy Tags: [Array, Basic Implementation, List]
114. 283. Move Zeroes.java Level: Easy Tags: [Array, Two Pointers]
Move non-zero elements to front of array; preseve order.
115. 63. Unique Paths II.java Level: Medium Tags: [Array, Coordinate DP, DP]
跟unique path的grid一样, 目标走到右下角, 但是grid里面可能有obstacle, 不能跨越. 求unique path 的count.
116. 105. Construct Binary Tree from Preorder and Inorder Traversal.java Level: Medium Tags: [Array, DFS, Divide and Conquer, Hash Table, Tree]
如题
117. 40. Combination Sum II.java Level: Medium Tags: [Array, Backtracking, Combination, DFS]
给一串数字candidates (can have duplicates), 和一个target.
找到所有unique的 组合(combination) int[], 要求每个combination的和 = target.
注意: 同一个candidate integer, 只可以用一次.
118. 724. Find Pivot Index.java Level: Easy Tags: [Array, PreSum]
119. 33. Search in Rotated Sorted Array.java Level: Medium Tags: [Array, Binary Search]
120. 1170. Compare Strings by Frequency of the Smallest Character.java Level: Easy Tags: [Array, String]
121. 34. Find First and Last Position of Element in Sorted Array.java Level: Medium Tags: [Array, Binary Search]
122. 689. Maximum Sum of 3 Non-Overlapping Subarrays.java Level: Hard Tags: [Array, DP]
123. 149. Max Points on a Line.java Level: Hard Tags: [Array, Geometry, Hash Table, Math]
给list of (x,y) coordinates. Determine # of points on the same line
124. 57. Insert Interval.java Level: Hard Tags: [Array, PriorityQueue, Sort, Sweep Line]
125. 611. Valid Triangle Number.java Level: Medium Tags: [Array, Two Pointers]
0. Perfect Rectangle.java Level: Hard Tags: [Design, Geometry, Hash Table]
看的list of coordinates 是否能组成perfect rectangle, 并且不允许overlap area.
1. 149. Max Points on a Line.java Level: Hard Tags: [Array, Geometry, Hash Table, Math]
给list of (x,y) coordinates. Determine # of points on the same line
0. 416. Partition Equal Subset Sum.java Level: Medium Tags: [Backpack, DP]
0. 686. Repeated String Match.java Level: Easy Tags: [Basic Implementation, Edge Case, String]
Track: 纸上分析edge case. Validation helps speed it up.
1. 41. First Missing Positive.java Level: Hard Tags: [Analysis, Array, Edge Case]
给一串无序数字, 有负数: 找这个array里面第一个 missing的 positive integer
missing positive integer 其实是以 [1, n] 来做比较的.
0. Triangles.java Level: Medium Tags: [Array, Coordinate DP, DFS, DP, Memoization]
给一个list<list> triangle, 细节原题. 找 min path sum from root.
1. Longest Increasing Continuous subsequence II.java Level: Medium Tags: [Array, Coordinate DP, DP, Memoization]
2. Longest Increasing Subsequence.java Level: Medium Tags: [Binary Search, Coordinate DP, DP, Memoization]
无序数组, 找最长的上升(不需要连续)数组 的长度. 先做O(n^2), 然后可否O(nLogN)?
3. Coins in a Line II.java Level: Medium Tags: [Array, DP, Game Theory, Memoization, MiniMax]
给一串coins, 用values[]表示; 每个coin有自己的value. 先手/后手博弈, 每次只能 按照从左到右的顺序, 拿1个或者2个棋子, 最后看谁拿的总值最大.
MiniMax的思考方法很神奇, 最后写出来的表达式很简单
Time O(n) Space O(n): dp[], sum[]
4. Longest Increasing Path in a Matrix.java Level: Hard Tags: [Coordinate DP, DFS, DP, Memoization, Topological Sort]
m x n 的matrix, 找最长增序的序列长度. 这里默认连续的序列.
还没有做
5. Coins in a Line III.java Level: Hard Tags: [Array, DP, Game Theory, Interval DP, Memoization]
LeetCode: Predict the Winner
还是2个人拿n个coin, coin可以有不同的value.
只不过这次选手可以从任意的一头拿, 而不限制从一头拿. 算先手会不会赢?
6. Burst Balloons.java Level: Hard Tags: [DP, Divide and Conquer, Interval DP, Memoization]
一排球, 每个球有value, 每次扎破一个, 就会积分: 左中间右 的值. 求, 怎么扎, 最大值?
TODO: Need more thoughts on why using dp[n + 2][n + 2] for memoization, but dp[n][n] for interval DP.
7. 1216. Valid Palindrome III.java Level: Hard Tags: [DFS, DP, Memoization, String]
8. 509. Fibonacci Number.java Level: Easy Tags: [DP, Math, Memoization]
9. 322. Coin Change.java Level: Medium Tags: [Backpack DP, DFS, DP, Memoization]
给一串不同数额的coins, 和total amount to spent. 求 最少 用多少个coin可以组合到这个amount. 每种coins个数不限量.
10. 140. Word Break II.java Level: Hard Tags: [Backtracking, DFS, DP, Hash Table, Memoization]
找出所有 word break variations, given dictionary. (Word Break I
only checks possibility)
利用 memoization: Map<prefix, List<suffix variations>>
11. 70. Climbing Stairs.java Level: Easy Tags: [DP, Memoization, Sequence DP]
每一步可以走1步或者2步, 求总共多少种方法爬完梯子.
12. 170. Two Sum III - Data structure design.java Level: Easy Tags: [Design, Hash Table, Memoization]
13. 516. Longest Palindromic Subsequence.java Level: Medium Tags: [DFS, DP, Interval DP, Memoization]
给一个string s, 找最长的sub-sequence which is also palindrome.
注意!subsequence并不是substring, 是可以skip letter / non-continuous character sequence
14. 1043. Partition Array for Maximum Sum.java Level: Medium Tags: [DFS, DP, Graph, Memoization]
0. 145. Binary Tree Postorder Traversal.java Level: Medium Tags: [Stack, Tree, Two Stacks]
如题, POST-ORDER traversal.
LeetCode给了hard, 应该是觉得stack的做法比较难想到.
0. 124. Binary Tree Maximum Path Sum.java Level: Hard Tags: [DFS, DP, Tree, Tree DP]
找max path sum, 可以从任意treeNode 到任意 treeNode.