Skip to content

Latest commit

 

History

History
4149 lines (2259 loc) · 154 KB

ReviewPage.md

File metadata and controls

4149 lines (2259 loc) · 154 KB

Review Page

This page summarize the solutions of all problems. For thoughts,ideas written in English, refer to deach individual solution. New problems will be automatically updated once added.

0. Alien Dictionary.java Level: Hard

Not Done yet。 Topological sort.


1. Anagrams.java Level: Medium

  1. HashMap 的做法. sort每个string, 存进HashMap, 重复的就是anagrams,最后输出。
    toCharArray Arrays.sort Stirng.valueOf(char[]) 时间nLO(logL),L是最长string的长度。

  2. Arrays.toString(arr)的做法。arr是int[26], assuming only have 26 lowercase letters.
    Count occurrance, 然后convert to String,作为map的key. Time complexity: nO(L)

  3. 另一种做法:http://www.jiuzhang.com/solutions/anagrams/

    1. take each string, count the occurrance of the 26 letters. save in int[]count.
    2. hash the int[] count and output a unique hash value.
      hash = hash * a + num
      a = a * b.
    3. 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.


2. Binary Representation.java Level: Hard

首先要分两半解决,断点是'.': str.split("\.");

Integer那一半好弄,whie loop里: num%2, num/2。

Decimal那边复杂点. 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. Binary Tree Inorder Traversal.java Level: Easy

法一:
Recursive: Divide and Conquer, with helper(dfs) method

法二:
Stack: Add left nodes all the way
Print curr
Move to right, add right if possible.

注意stack.pop()在加完left-most child 的后,一定要curr = curr.right.

若不右移,很可能发生窘境:
curr下一轮还是去找自己的left-most child,不断重复curr and curr.left, 会infinite loop, 永远在左边上下上下。


4. Binary Tree Level Order Traversal II.java Level: Medium

方法1: 跟Binary Tree Level Order Traversal一样,只不过存result一直存在存在0位.

方法2(略复杂, 不需要): 普通BFS,用一个queue,加上一个queue.size()来交替换行. 或者多用一个queue来存下个level的nodes

方法3: DFS, 根据level来append每个list rst里面add(0,...)每次都add在list开头


5. Binary Tree Level Order Traversal.java Level: Medium

方法1. 最普通,Non-recursive: BFS, queue, 用个queue.size()来end for loop:换行。
或者用两个queue. 当常规queue empty,把backup queue贴上去。

方法2. Recursive with dfs:
每个level都应该有个ArrayList. 那么用一个int level来查看:是否每一层都有了相应的ArrayList。
如果没有,就加上一层。
之后每次都通过DFS在相应的level上面加数字。


6. Binary Tree Longest Consecutive Sequence.java Level: Medium

屌炸天的4行代码。Divide and Conquer

主要想法:
Recursive用好。首先在这个level比一比,可否成。
不成的话,另立门户, count = 1。
然后左右开弓。再把结果拿过来比较一下就好了。


7. Binary Tree Maximum Path Sum II.java Level: Medium

比Binary Tree Maximum Path Sum I 简单许多. 因为条件给的更多:at least 1 node + have to start from root => have to have root.

方法1:
维持一个global或者recursive里的sum。traversal entire tree via DFS. 简单明了。

方法2:
Single path: either left or right.
If the path sum < 0, just skip it.


8. Binary Tree Path Sum.java Level: Easy

Binary Tree的一个基本题。
遍历到底,比较sum vs. target。
注意divide的情况。要把遍历的例子写写。

LeetCode: Path Sum II


9. Binary Tree Postorder Traversal.java Level: Easy

最prefer 2 stack的做法:
stack1和stack2合作。倒水。记这个做法。。。挺神奇的。

Divide and Conquer 的recursive方法也非常明了!

注意,这些binary tree traversal的题目,常常有多个做法:recursive or iterative


10. Binary Tree Preorder Traversal.java Level: Easy

Preorder 写写, stack

  1. Divide and conquer
  2. Stack(NON-recursive) push curr, push right, push left.
  3. recursive with helper method

11. Binary Tree Right Side View.java Level: Medium

最右:即level traversal每一行的最末尾.

BFS,用queue.size()来出发saving result.


12. Binary Tree Serialization.java Level: Medium

方法1: BFS. Non-recursive, using queue. 想法直观。level-order traversal. save到一个string里面就好。

方法2: DFS. Recursive. 需要一点思考。basically divide and conquer. 但是代码相对来说短。


13. Binary Tree Zigzag Level Order Traversal.java Level: Medium

简单的level traversal.根据level奇数偶数而add到不同位子.


14. Building Outline.java Level: Hard

又叫做skyline

看网上的解答做, 思路很漂亮。 (http://codechen.blogspot.com/2015/06/leetcode-skyline-problem.html?_sm_au_=isVmHvFmFs40TWRt)

跟scan line的approach类似:

  1. 把所有点分出来, 每个点有index x, 再加上一个height.
  2. 在这个list上排序,根据index和height(注意用负数标记building start point,这样保证start在end 之前。). 叫做 heightPoints
  3. 在processs时候用max-heap (reversed priorityqueue),在ieteraete heightPoints 来存最大的height . 遇到peek,就是一个合理的解
    处理1:因为start,end的height都存在了heightPoints里面,这里就是用来check end of bulding的,然后把height 从queue里面remove. 处理2:重复x 上面的许多height? priorityqueue给了我们最高,这okay了;那么其他的重复点,用一个int prev来mark之前做过的,一旦重复,跳过。

想法非常natural。 大题目,脑子乱。
看了解答再去想,挺naturally doable的。


15. Change to Anagram.java Level: Easy

简单的check int[26] 26个小写字母是否需要改变。若需要count+1.

主要HackerRank里要注意自己写: Scanner, import java.util, non-static method ...etc.

注意: 最后count出来要除以2:字母不同,会在不同的字母位上加减count,那么就是刚好重复计算了一遍。所以除以二。


16. Classical Binary Search.java Level: Easy

while: start + 1 < end mid = start + (end - start) / 2; 末尾double check start, end.


17. Climbing Stairs.java Level: Easy

方法1: DP。爬坡到i点总共有的方法,取决于i-1点和i-2的情况。也就是DP(i-1) + DP(i-2).

还可以用滚动数组优化一点:因为用到的变量就只有i,i-1,i-2,可以被替代。 注意要写好‘滚动’的代码。

方法2: DFS但是timeout


18. Closest Binary Search Tree Value.java Level: Easy

Binary Search. 记录找到过的closest. 直到tree leaf, 找完return


19. Closest Number in Sorted Array.java Level: Easy

跟Closest Binary Search Tree Vlaue类似:

Binary search. 考虑mid-1, mid+1.
一旦没有mid = target.index。 那么target最终就narrow down在(mid-1,mid) 或者(mid,mid+1)


20. ColorGrid.java Level: Medium

用HashMap, 理解题目规律,因为重复的计算可以被覆盖,所以是个优化题。

消灭重合点:
如果process当下col, 其实要减去过去所有加过的row的交接点。。。
再分析,就是每次碰到row 取一个单点, sumRow += xxx。
然后process当下col时候, sum += colValue * N - sumRow. 就等于把交叉所有row(曾经Process过的row)的点减去了。很方便。

最后read in 是O(P), process也是O(P).


21. Combination Sum II.java Level: Medium

还是DFS. 和Combination Sum I 类似.
确保Helper是用i+1,下一层的数字, 不允许重复。


22. Combination Sum.java Level: Medium

递归,backtracking. 非常normal。需要先sort.
记得求sum时候也pass 一个sum进去,backtracking一下sum也,这样就不必每次都sum the list了。

题目里面所同一个元素可以用n次,但是,同一种solution不能重复出现。如何handle?

  1. 用一个index (我们这里用了start)来mark每次recursive的起始点。
  2. 每个recursive都从for loop里面的i开始,而i = start。 也就是,下一个iteration,这个数字会有机会被重复使用。
  3. 同时,确定在同一个for loop里面,不同的Index上面相同的数字,不Process两遍。用一个prev 作为checker.

假如[x1, x2, y, z], where x1 == x2, 上面做法的效果: 我们可能有这样的结果: x1,x1,x1,y,z
但是不会有:x1,x2,x2,y,z
两个solution从数字上是一样的,也就是duplicated solution, 要杜绝第二种。


23. Combinations.java Level: Medium

Combination DFS。 画个图想想. 每次从1~n里面pick一个数字i

因为下一层不能重新回去 [0~i]选,所以下一层recursive要从i+1开始选。


24. Compare Strings.java Level: Easy

比较一下大小, null.

然后用char[]来count chars from A. 再对照chars in B.


25. Complete Binary Tree.java Level: Easy

BFS

Use a flag . 当出现了第一次有 null children的node的时候,
说明complete tree的最低level出现了。
自此以后,queue再不该有node再有child, queue后面出现的node的左右孩子应该都是null.


26. Construct Binary Tree from Inorder and Postorder Traversal.java Level: Medium

写个Inorder和Postorder的例子。利用他们分left/right subtree的规律解题。

Postorder array 的末尾, 就是当下层的root.
在Inorder array 里面找到这个root,就刚好把左右两边分割成left/right tree。

这题比较tricky地用了一个helper做recursive。 特别要注意处理index的变化, precisely考虑开头结尾

可惜有个不可避免的O(n) find element in array.


27. Container With Most Water.java Level: Medium

类似木桶理论。盛水的最高取决于最低的那面墙。 左右两墙,往中间跑动。 另,若一面墙已经小于另外一面,就要移动,换掉矮墙(可能下一面更高,或更低);但决不能换掉当下的高墙,因为低墙已经limit的盛水的上限,若高墙移动,导致两墙之间距离减少,就注定水量更少了。(弄啥来,不能缺心眼啊)


28. Contains Duplicate II.java Level: Easy

方法1: HashTable<value, list of duplicates>, brutly check agains the list 方法2: 很巧妙地根据k range地条件, 把HashSet里面的值控制在[i - k, i]. 那么一旦match, 就符合条件.

这两种做法很艺术: 一般的想法是把符合条件的index找出来, 集中处理 第二种做法是限定选拔的candidate, 不合格就去掉, 那么一旦有符合条件的(duplicates), 那么一定中.


29. Contains Duplicate III.java Level: Medium

与Contains Duplicate II 类似概念. TreeSet有BST 因此可以直接用, 而不用自己构建BST 简化题目里面的重要条件 Math.abs(A-B) <= t 而推断出需要用 TreeSet.ceiling(x): return number greater or equal to x. 这个用法要记住吧, 没别的捷径.


30. Contains Duplicate.java Level: Easy

方法1: No brain: HashSet. O(n), 但是实际上比方法2 要慢. 方法2: 排序, 重复数会排在一起. Arrays.sort() time complexity nLog(n)


31. Convert Binary Search Tree to Doubly Linked List.java Level: Medium

会iterative traverse Binary Search Tree就好(Stack && handle left-dig-down), 然后create Doubly-ListNode 时候注意就好.

注意inorder traversal在check right node的事后,
不论right == null or != null, 每次都要强行move to right.

如果不node = node.right,
很可能发生窘境:
node alays = stack.top(), 然后stack.top()一直是一开始把left 全部遍历的内容。所以就会infinite loop, 永远在左边上下上下。


32. Convert Expression to Polish Notation.java Level: Hard

还是Expression Tree (Min-Tree).

根据题意,Tree出来以后,来个Pre-order-traversal.

Note: label需要是String.虽然 Operator是长度为1的char, 但是数字可为多位。


33. Convert Expression to Reverse Polish Notation.java Level: Hard

build expression tree。

这个里面把TreeNode就当做成我们需要的node,里面扩展成有left/right child的node.

建造Expression Tree,然后根据 Reverse Polish Notation 的定义,来个post-traversal就行了。


34. Convert Integer A to Integer B.java Level: Easy

Bit Manipulation

a^b 显示出bit format里面有不同binary code的数位.

每次 (a^b)>>i 移动i位之后, 再 & 1时其实是指留下这一位的数字.

count it up


35. Copy List with Random Pointer.java Level: Medium

Basic Implementation, 其中用了一下HashMap:

遍历head.next .... null.
每一步都check map里面有没有head。没有?加上。
每一步都check map里面有没有head.random。没有?加上。


36. Cosine Similarity.java Level: Easy

basic implementation


37. Count 1 in Binary.java Level: Easy

  1. 可以把integer -> string -> char array.

  2. 或者就 count += num << i & 1


38. Count and Say.java Level: Easy

Basic implementation. Count duplicates and print


39. Count of Smaller Number before itself.java Level: Hard

与Count of Smaller Number非常类似。以实际的value来构成segment tree,leaf上存(count of smaller number)。

Trick: 先Query,再modify.
每次Query时候,A[i]都还没有加入到Segment Tree 里面,而A[i+1,...etc]自然也还没有加进去。
那么就自然是coutning smaller number before itself.
刁钻啊!

另外注意:
在modify里面:多Check了root.start <= index 和 index <= root.end。 过去都忽略了。以后可以把这个也写上。
(其实是Make sense的,就是更加严格地check了index再 root.left 或者 root.right里面的站位)


40. Count of Smaller Number.java Level: Medium

和平时的segment tree问题不同。 0 ~ n-1代表实际数字。是造一个based on real value的segment tree. Modify时,把array里面的value带进去,找到特定的位子(leaf),然后count+1.

最终在SegmentTree leaf上面全是array里面实际的数字。

trick:
在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 进去。
这样八成是另外一种咯。


41. Count Primes.java Level: Easy

什么是prime number: >=2的没有除自己和1以外公约数的数。

还有另外一个定义方法!!
这个n,有没有小于n的一个i,而达到: i*i + # of i = n. 如果有,那就不是 prime。

方法很牛逼也很数学。没做的时候可能想不到。做了之后就觉得,哎,我去,有道理啊。
简而言之:简历一个boolean长条,存isPrime[]。 然后从i=2, 全部变true.
然后利用这个因子的性质,非prime满足条件: self*self, self * self + self ... etc.
所以就check每一个j, j+i, j+i+i, 然后把所有non-prime全部mark成false.
最后,数一遍还剩下的true个数就好了


42. Course Schedule II.java Level: Medium

详细的中文分析,看Course Schedule I


43. Course Schedule.java Level: Medium

有点绕,但是做过一次就明白一点。
是topological sort的题目。一般都是给有dependency的东西排序。

最终都会到一个sink node, 再不会有向后的dependency, 在那个点截止。
我就已这样子的点为map的key, 然后value是以这个node为prerequisite的 list of courses.

画个图的话,prerequisite都是指向那个sink node, 然后我们在组成map的时候,都是从sink node 发散回来到dependent nodes.

在DFS里面,我们是反向的, 然后,最先完全visited的那个node, 肯定是最左边的node了,它被mark的seq也是最高的。

而我们的sink node,当它所有的支线都visit完了,seq肯定都已经减到最小了,也就是0,它就是第一个被visit的。

最终结果: 每个有pre-requisit的node都trace上去(自底向上),并且都没有发现cycle.也就说明schedule可以用了。


44. Data Stream Median.java Level: Hard

把Input stream想成向上的山坡。山坡中间那点,自然就是median.

前半段,作为maxHeap,关注点是PriorityQueue的峰点,也就是实际上的median.

后半段,作为minHeap,正常的PriorityQueue。 开头是最小的。

Note:题目定义meadian = A[(n-1)/2],也就是说maxHeap需要和minHeap长度相等,或者多一个element,最后可以直接poll() and return.


45. Delete Digits.java Level: Medium

数位靠前的,权值更大. 所以硬来把靠前的相对更大的(跟following digit相比)去掉。


46. Delete Node in the Middle of Singly Linked List.java Level: Easy

Just do it. Link curr.next to curr.next.next


47. Encode and Decode Strings.java Level: Medium

方法1:
用数字+"#"+string来encode.
基于我们自己定的规律, 在decode的里面不需要过多地去check error input, assume所有input都是规范的.
decode就是找"#",然后用"#"前的数字截取后面的string.

Old Solution:
Cast character into int. 串联起来, seperate by "LINE".
handle empty list [], or just null: 要把Null特别mark一下为‘NULL’, 这样decode时才能check到。 adminadmin


48. ExcelSheetColumnNumber .java Level: Easy

'A' - 'A' = 0. 所以 char - 'A' + 1 = 题目里的对应数位。
26位运算和10位一样嘛,num += 每位的digit * Math.pow(26, 数位号)。


49. Expression Evaluation.java Level: Hard

Build Expression Tree的另外一个变形,依然Min Tree.

build好Min Tree以后,做PostTraversal. Divde and Conquer:
先recursively找到 left和right的大小, 然后evaluate中间的符号。

Note:

  1. Handle数字时,若left&&right Child全Null,那必定是我们weight最大的数字node了。
  2. 若有个child是null,那就return另外一个node。
  3. prevent Integer overflow during operation:过程中用个Long,最后结局在cast back to int.

50. Expression Tree Build.java Level: Hard

和Max-tree一样,感谢http://blog.welkinlan.com/2015/06/29/max-tree-lintcode-java/

这个题目是Min-tree, 头上最小,Logic 和max-tree如出一辙

注意treeNode,为了帮助ExpressionTreeNode 排序。它加了一个weight based on expression,协助build Min-Tree 排序。

Space: O(n) Time on average: O(n).


51. Fast Power.java Level: Medium

a^n可以被拆解成(aaa*a....*a), 是乘机形式,而%是可以把每一项都mod一下的。所以就拆开来take mod.

这里用个二分的方法,recursively二分下去,直到n/2为0或者1,然后分别对待.

注意1: 二分后要conquer,乘积可能大于Integer.MAX_VALUE, 所以用个long.

注意2: 要处理n%2==1的情况,二分时候自动省掉了一份,要乘一下。


52. Fibonacci.java Level: Easy

方法1: DP array.

方法1.1: 滚动数组, 简化DP。

方法2: recursively calculate fib(n - 1) + fib(n - 2). 公式没问题, 但是时间太长, timeout.


53. Find Peak Element II.java Level: Review

Operated with 2D array


54. Find the Connected Component in the Undirected Graph.java Level: Medium

BFS遍历,把每个node的neighbor都加进来。

一定注意要把visit过的node Mark一下。因为curr node也会是别人的neighbor,会无限循环。

Component的定义:所有Component内的node必须被串联起来via path (反正这里是undirected, 只要链接上就好)

这道题:其实component在input里面都已经给好了,所有能一口气visit到的,全部加进queue里面,他们就是一个component里面的了。

而我们这里不需要判断他们是不是Component。


55. Find the Weak Connected Component in the Directed Graph.java Level: Medium

Identify这是个union-find问题还挺巧妙。
看到了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拿出来就好了。


56. Flatten 2D Vector.java Level: Medium

大概意思就是把2D list里面的element全部遍历一遍。 注意啊,一开始理解题意搞错:我以为是必须要排序正确,所以上来就PriorityQueue+HashMap搞得无比复杂。其实,这个跟一个nxn的matrix遍历,是没区别的拉。 所有来个x,y,把2d list跑一变。


57. Flip Game II.java Level: Medium

12.06.2015 recap: 注意:不要乱改input s. recursive call 需要用原始的input s.

这个题目李特是屌炸天的。 我飞了九牛二虎之力(路子对),但是代码写的七荤八素,好长好长好长好长的。 结果正解,三四行就搞定了。真是心有不甘啊。 想法如下: 保证p1能胜利,就必须保持所有p2的move都不能赢。 同时,p1只要在可走的Move里面,有一个move可以赢就足够了。 (题目里面用一个for loop + 只要 满足条件就return true来表达 OR的意思:p1不同的路子,�赢一�种就行了) p1: player1 p2: player2


58. Flip Game.java Level: Easy

这个题目是很寂寞的. 2 pointer可以做, 在网上又搜了一下,貌似可以有很多牛逼的优化,我暂时还没去看。 很郁闷的就是条件不明,原来只需要从'++'转到'--'的情况,反过来没必要关注...搞了我半天啊


59. Fraction to Recurring Decimal.java Level: Medium

不难想到处理除法:考虑正负,考虑小数点前后。主要是小数点以后的要着重考虑。 很容易忽略的是integer的益处。


60. Generate Parentheses.java Level: Medium

递归。 看thought.取或者不取(, )

Note: 在DFS时, 可以pass object (String) and re-create every time; or pass a reference (StringBuffer) and maintain it


61. Gray Code.java Level: Medium

题目蛋疼,目前只接受一种结果。

BackTracking + DFS:
Recursive helper里每次flip一个 自己/左边/右边. Flip过后还要恢复原样.遍历所有.

曾用法(未仔细验证):
基本想法就是从一个点开始往一个方向走,每次flip一个bit, 碰壁的时候就回头走。


62. Group Anagrams.java Level: Medium

方法一: 60%

和check anagram 想法一样:转化并sort char array,用来作为key。

把所有anagram 存在一起。注意结尾Collections.sort().

O(NKlog(K)), N = string[] length, k = longest word length

优化:80% ~ 97%

用固定长度的char[26] arr 存每个字母的frequency; 然后再 new string(arr).
因为每个位子上的frequency的变化,就能构建一个unique的string

错误的示范: 尝试先sort input strs[],但是NlogN 其实效率更低. 13%


63. Group Shifted Strings.java Level: Easy

相同shift规则的string, 能被推算到同一个零起始点,就是共同减去一个char,最后就相等。以此作为key,用HashMap。一目了然。

记得根据题目意思,一开始要String[] sort一下。


64. H-Index II.java Level: Medium

H-index的一个优化。 binary search


65. H-Index.java Level: Medium

例子写出来,发现可以sort以后按照定义搜索一遍。 nlogn. 当然,搜索一遍时候可以优化,用binary search. 但是没意义,因为array.sort已经用了nlogn

o(n)也可以,用bucket. 比较巧妙。


66. Hamming Distance.java Level: Easy

bit: XOR, &, shift>>


67. Happy Number.java Level: Easy

Basic Implementation of the requirements.

用HashSet存查看过的数值。若重复,return false.


68. Hash Function.java Level: Easy

解释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会变得太大,所以不能算完再%...


69. HashHeap.java Level: Hard

非题.是从九章找来的HashHeap implementation.


70. HashWithArray.java Level: Easy


71. HashWithCustomizedClass(LinkedList).java Level: Medium

练习HashMap with customized class.


72. Heapify.java Level: Medium

Heap用的不多. 得用一下, 才好理解。
通常default 的PriorityQueue就是给了一个现成的min-heap:所有后面的对应element都比curr element 小。

Heapify里面的siftdown的部分: 只能从for(i = n/2-1 ~ 0), 而不能从for(i = 0 ~ n/2 -1): 必须中间开花,向上跑的时候才能确保脚下是符合heap规则的

Heapify/SiftDown做了什么?
确保在heap datastructure里面curr node下面的两个孩子,以及下面所有的node都遵循一个规律。
比如在这里,若是min-heap,就是后面的两孩子都要比自己大。若不是,就要swap。

还是要记一下min-heap的判断规律:for each element A[i], we will get A[i * 2 + 1] >= A[i] and A[i * 2 + 2] >= A[i].

siftdown时:在curr node和两个son里面小的比较。如果的确curr < son, 搞定,break while.
但若curr 并不比son小,那么就要换位子,而且继续从son的位子往下面盘查。


73. Heaters.java Level: Easy

第一步: 生题型, 理解题意需要时间: 从字面和画图而言, 就是定住房子一个个过,房子左右的distance需要足够达到heater. 目标是招尽可能小的radius, 所以house和heater紧贴着是必要的. 在for loop里面定下house,把heater当作一个区间移动, 达到的第一个合适区间,这就是当下最小的理想radius,取这个值跟既定radius作比较。 比较之后,继续移动house,再试着移动heater区间去match。

第二步: Binary Search

注意! 题目没有说given array是否sort, 我们必须sort才能够区间移动或者binary search. TODO: http://www.cnblogs.com/grandyang/p/6181626.html


74. House Robber III.java Level: Hard

由于无法用简单的方法构造DP array, 所以采取了普通的DFS。

The catch:
判断当下的node是否被采用,用一个boolean来表示.

  1. 如果curr node被采用,那么下面的child一定不能被采用。
  2. 如果curr node不被采用,那么下面的children有可能被采用,但也可能略过,所以这里用Math.max() 比较一下两种可能有的dfs结果。

75. Identical Binary Tree.java Level: Easy

Divide, && 每种情况(左右一一对应)
注意 null states


76. Implement Queue using Stacks.java Level: Easy

双Stack. 一个是等于是queue,一个是backfillStack. Tricky: 是在pop()和peek()的时候backfill, 并且要等到stack用完再backfill. 写一下例子就知道,如果提早backfill,stack.peek()就不是queue的head了.


77. Implement Stack by Two Queues.java Level: Easy

两个Queue,交互倒水 用一个Temp做swap

做法1: 逻辑在top()/pop()里, 每次换水,查看末尾项.

做法2: 逻辑在push里面:

  1. x 放q2。
  2. q1全部offer/append到q2.
  3. 用一个Temp做swap q1, q2. q1的头,就一直是最后加进去的值.

78. Implement Stack using Queues.java Level: Easy

两个Queue,交互倒水 用一个Temp做swap

做法1: 逻辑在top()/pop()里, 每次换水,查看末尾项.

做法2: 逻辑在push里面:

  1. x 放q2。
  2. q1全部offer/append到q2.
  3. 用一个Temp做swap q1, q2. q1的头,就一直是最后加进去的值.

79. Implement Stack.java Level: Easy

stack 后入先出. Data Structure: ArrayList return/remove ArrayList的末尾项。


80. Implement Trie (Prefix Tree).java Level: Medium

Trie自己不多用到。 如果是遇到一个一个字查询的题,可以考虑一下。 构建TrieNode的时候要注意:如何找孩子?如果是个map的话,其实就挺好走位的。 而且,每个node里面的 char 或者string有时候用处不大, 可以为空。但是有些题目,比如在结尾要return一些什么String,就可以在end string那边存一个真的String。


81. IndexMatch.java Level: Easy

有序, 假设有这样的数字:target.
target 左边的数字,一定不比index大,target右边的数字,一定比index大。
这样可以binary search.O(logn)


82. Inorder Successor in Binary Search Tree.java Level: Medium

画inorder图,发现规律.每个node的后继node(successor)有几种情况:

  1. node.right 是个leaf到底了。那么就return.
  2. set rightNode = node.right, 但发现rightNode has a lot left children to leaf.
  3. 比如, node.right == null, 也就是node自己是leaf,要回头看山顶找Inorder traversal规则里的下一个。
    发现:其实就是每层都把路过的curr node放在stack里,最上面的,就是当下改return的那个successor:) Done.

83. Insert Interval.java Level: Easy

方法1:Scan Line
Interval 拆点,PriorityQueue排点。
Merge时用count==0作判断点。

PriorityQueue: O(logN). 扫n点,总共:O(nLogn)

方法2:
O(n) 直接找到可以insert newInterval的位子. Insert。 这里已经给了sorted intervals by start point. 所以O(n)

然后loop to merge entire interval array

另外: 因为interval已经sort, 本想用Binary Search O(logn). 但是找到interval insert position, merge还是要用 O(n)。
比如刚好newInterval cover entire list....


84. Insert Node in a Binary Search Tree .java Level: Easy

往Binary Search Tree里面加东西,一定会找到一个合适的leaf加上去。

那么:就是说someNode.left or someNode.right是null时,就是insert node的地方。

找到那个someNode就按照正常的Binary Search Tree规律。


85. Intersection of Two Arrays.java Level: Easy

方法1: 用到hashset找unique && duplicate: O(m+n) 方法2: 可以用binary search 找数字. Note:binary search一定需要array sorted: nLog(m)


86. Intersection of Two Linked Lists.java Level: Easy

长短list,找重合点。 长度不同的话,切掉长的list那个的extra length。 那么起点一样后,重合点就会同时到达。


87. Interval Minimum Number.java Level: Medium

SegtmentTree, methods: Build, Query. 这题是在SegmentTreeNode里面存min.

类似的有存:max, sum, min


88. Interval Sum II.java Level: Hard

SegmentTree大集合。记得几个Methods: Build, Query, Modify. 不难。只是要都记得不犯错:)


89. Interval Sum.java Level: Medium

其实是segment tree 每个node上面加个sum。

记得Segment Tree methods: Build, Query

Note: 存在SegmentTreeNode里面的是sum. 其他题目可能是min,max ... or something else.


90. Invert Binary Tree.java Level: Easy

non-recursive: BFS with queue。 或者regular recurisve - divide and conquer.


91. Isomorphic Strings.java Level: Easy

HashMap 来确认match。有几种情况考虑:

  1. Match. 就是map.containsKey, map.containsValue, and char1 == char2. Perfect.

  2. Either Key not exist, or Value not exit. False;

  3. Both key and Value exist, but map.get(char1) != char2. Miss-match. False.

  4. None of Key or Value exist in HashMap. Then add the match.


92. Jump Game II.java Level: Hard

Greedy, 图解 http://www.cnblogs.com/lichen782/p/leetcode_Jump_Game_II.html

维护一个range, 是最远我们能走的.

index/i 是一步一步往前, 每次当 i <= range, 做一个while loop, 在其中找最远能到的地方 maxRange

然后更新 range = maxRange

其中step也是跟index是一样, 一步一步走.

最后check的condition是,我们最远你能走的range >= nums.length - 1, 说明以最少的Step就到达了重点。Good.


93. Kth Largest Element.java Level: Medium

用Quick Sort 里面partion的一部分。
partion的结果是那个low, 去找 low==nums.size() - k, 也就是倒数第K个。
没找到继续partion recursively.

sort的过程是排一个从小到大的list. (同样的代码还可以好xth smallest,mid变成x就好)

Quick Sort:
每个iteration, 找一个pivot,然后从low,和high都和pivot作比较。
找到一个low>pivot, high<pivot, 也就可以swap了。
得到的low就是当下的partion point了


94. Kth Smallest Sum In Two Sorted Arrays.java Level: Hard

用priority queue. 每次把最小的展开,移位。分别x+1,或者y+1:
因为当下的Min里面x,y都是最小的。所以下一个最小的不是(x+1,y),就是(x,y+1)。

每次就poll()一个,放2个新candidate进去就好了。 注意,这样的做法会用重复,比如例子(7,4)会出现两次。用一个HashSet挡一下。

注意,HashSet的唯一性,用一个"x,y"的string就可以代为解决。


95. Longest Common Prefix.java Level: Medium

Nested loop, 每一次比较所有string 同位是否相等。

相等,append string. 不等,return.


96. Longest Increasing Continuous subsequence.java Level: Easy

O(n)跑2遍for. O(1)是用了两个int来存:每次到i点时,i点满足条件或不满足条件所有的longestIncreasingContinuousSubsequence. 特点:返跑一回,ans还是继续和left轮的ans作比较;求的所有情况的最大值嘛。


97. Longest Palindromic Substring.java Level: Medium

方法1: 从中间劈开. 遍历i,从n个不同的点劈开:每次劈开都看是否可以从劈开出作为palindromic的中点延伸。
Worst case: 整个string都是相同字符,time complexity变成: 1 + 2 +3 + ... +n = O(n^2)

方法2: 穷举double for loop. O(n^2)


98. Longest Univalue Path.java Level: Easy

弄明白path的意义: 连接node的edge. 要找MAX, 可以在class scope里面定义一个max variable.

用minimum amount of code来概括几种不同的情况: left == root, right == root, or left == root == right.


99. Longest Word in Dictionary.java Level: Easy

方法1: 按大小排序 -> 从最大的开始做contains()的比较 -> 结果再按照字母表顺序(lexicographically) sort一下. 但是Collections.sort()了两次, 而且再list.contains(), 比较慢

方法2: 先排序, 以最简单的size==1以及set.contains()找match. 如果找到, 因为已经按照字母表排序, 找到的这个肯定是这个长度里面最符合的解答. 然后brutally找下一个更大的. 法2比法1好, 因为只用了一次sort, nLog(n). 然后其余都是O(1)的contains. 法1有两个sort(), 然后在list上面contains(), 所以比较耗时.

方法3: 应该可以有一个用Trie的方式做的, 还没有考虑.


100. Lowest Common Ancestor II.java Level: Easy

这个题有个奇葩的地方,每个node还有一个parent。 所以可以自底向上.

  1. 曾经做的hashset的优化,找到的都存hashset. exist就return那个duplicate.

  2. 普通做法:2 lists。 自底向上。利用parent往root方向返回。

注意:无法从root去直接搜target node 而做成两个list. 因为根本不是Binary Search Tree!


101. Lowest Common Ancestor of a Binary Search Tree.java Level: Medium

方法1: 利用 BST的性质,可以直接搜到target node,而做成两个长度不一定相等的list。然后很简单找到LCA 方法2: Brutly寻找p和q的common ancestor, 然后recursively drive left/right. 非常巧妙, 但是也比较局限; 稍微变条件, 就很难recursive.


102. Lowest Common Ancestor.java Level: Easy

普通的Binary Tree,node child 自顶向下蔓延。

方法1:O(1) sapce O(h). Recursive. 循环的截点是:
当root == null或者 A B 任何一个在findLCA底部被找到了(root== A || root == B),那么就return 这个root.

三种情况:

  1. A,B都找到,那么这个level的node就是其中一层的parent。其实,最先recursively return到的那个,就是最底的LCA parent.
  2. A 或者 B 找到,那就还没有公共parent,return 非null得那个。
  3. A B 都null, 那就找错了没有呗, return null

//无法找到target element, 因为不是Binary Search Tree
//[Not Working]:O(n) space O(h) time。把两条线binary search出来。找第一个不同的parent. 代码长。 Iterative


103. LRU Cache.java Level: Hard

timeout method, 天真的来了一个O(n) 的解法,结果果然timeout.
一个map<key,value>存数值。一个queue来存排位。
每次有更新,就把最新的放在末尾;每次超过capaticity,就把大头干掉。很简单嘛,但是跑起来太久,失败了。

于是就来了第二个做法。其实还是跟方法一是类似的。
用了一个特别的双向的LinkNode,有了head和tail,这样就大大加快了速度。
主要加快的就是那个‘更新排位’的过程,过去我是O(n),现在O(1)就好了。

巧妙点:

  1. head和tail特别巧妙:除掉头和尾,和加上头和尾,就都特别快。
  2. 用双向的pointer: pre和next, 当需要除掉任何一个node的时候,只要知道要除掉哪一个,
    直接把node.pre和node.next耐心连起来就好了,node就自然而然的断开不要了。

一旦知道怎么解决了,就不是很特别,并不是难写的算法:
moveToHead()
insertHead()
remove()


104. Majority Number II.java Level: Medium

分三份:a b c考虑。若a, countA++, 或b, countB++,或c,countA--,countB--.

最后出现的两个count>0的a和b,自然是potentially大于1/3的。其中有一个大于1/3.

比较a和b哪个大,就return哪一个。


105. Majority Number III.java Level: Medium

与其他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最大的那个数。


106. Matrix Zigzag Traversal.java Level: Easy

分析4个step:right, left-bottom,down,right-up
implement时注意index.有点耐心


107. Max Area of Island.java Level: Easy

虽然Easy, 但用到DFS最基本的想法.

  1. dive deep
  2. mark VISITED
  3. sum it up

更要注意, 要从符合条件value==1的地方开始dfs. 对于什么island都没有的情况,area应该位0, 而不是Integer.MIN_VALUE, 问清楚考官那小伙, 别写顺手。


108. Max Tree.java Level: Hard

Should memorize MaxTree. 依次类推,会做Min-Tree, Expression Tree

Stack里,最大的值在下面。利用此性质,有这样几个step:

1
把所有小于curr node的,全Pop出来, while loop, keep it going.
最后pop出的这个小于Curr的node:它同时也是stack里面pop出来小于curr的最大的一个,最接近curr大小。(因为这个stack最大值靠下面)
把这个最大的小于curr的node放在curr.left.

2
那么,接下去stack里面的一定是大于curr:
那就变成curr的left parent. set stack.peek().right = curr.

3
结尾:stack底部一定是最大的那个,也就是max tree的头。


109. Maximal Square.java Level: Medium

DP问题

从边长为2的正方形看起,看左上角的那个点。
如何确定是个正方形?首先看左上点是不是1,然后看右边,右下,下面的点是不是1.

DP就是根据这个特征想出来。dp[i,j]: 从右下往左上推算,包括当前点在内的所能找到的最大边长。
注意dp[i,j]被右边,右下,下面三点的最短点所限制。这就是fn.

Init:
把右边,下边两个边缘init一遍,存matrix在这两条边上的值,代表的意思也就是dp[i][j]在这些点上的初始值:变成为1 or 0.


110. Maximum Average Subarray II.java Level: Review


111. Maximum Depth of Binary Tree.java Level: Easy

DFS: Divide and conquer. 维持一个最大值。


112. Maximum Subarray.java Level: Easy

方法1 比较像DP, 维持一个sums[i]: 从i向前位数, 所有正数的和. 一旦sums[i - 1]<0, 意味着sums[i-1]对maxSum没有好处, 那么就assign: sums[i]=nums[i] 这个做法比较中规中矩, makes sense

方法2(better) 想着用一用prefix sum. 把值一个个叠加。 然后presum[j] - presum[i- 1] 就是 (i,j)之间的和。


113. Median of two Sorted Arrays.java Level: Hard

Not done


114. Meeting Rooms II.java Level: Medium

方法1:PriorityQueue + 一个Class来解决。O(nlogn)

方法2:这里有尝试了一下用一个sorted Array + HashMap: 也还行,但是handle edge的时候,HashMap 要小心,因为相同时间start和end的map key 就会重复了。


115. Meeting Rooms.java Level: Easy

方法1: 找是否有overlap. priorityQueue 按照start time排序好以后, 比较current和peek: current.end > peek.start? 方法2: Scan line, class Point{pos, flag}, PriorityQueue排序。计算count

注意接头点要考虑所有开会结会的情况,不要恰巧漏掉相接的点。
开会的是超人。瞬间移动接上下一个会议。


116. Merge Intervals.java Level: Easy

方法1:O(nlogn)
扫描线+Count无敌手。注意start end把interval给合起来。
count==0的时候,就是每次start end双数抵消的时候,就应该是一个interval的开头/结尾。写个例子就知道了。

空间:O(2n) -> O(n)
时间,priorityqueue: O(nlogn)

记得怎么写comparator

在 LeetCode里面,Scan line比方法2要快很多.

方法2:
Collections.sort() on interval.start之后,试着跑一遍,按照merge的需求,把需要merge的地方续好,然后减掉多余的interval就好。

(不知为何LeetCode把Merge Interval, Insert Interval 标为Hard)

Collections.sort(..., new comparator): sort by Interval.start.

画两个相连的Interval, prev, curr: prev只有 prev.end覆盖了 curr.start, 才需要merge. 那么比较一下, marege.
记得如果merge, 一定要list.remove(i), 并且i--, 因为改变了List的大小。

若没有重合,就继续iteration: prev = curr. move on.


117. Merge k Sorted Arrays.java Level: Medium

由Merge k sorted list启发。用PriorityQueue,存那k个首发element。

PriorityQueue需要存储单位。自己建一个Class Node 存val, x,y index.
因为array里没有 'next' pointer,只能存x,y来推next element


118. Merge k Sorted Lists.java Level: Medium

用Priorityqueue来排列所有list的leading node.

记得k lists 需要是已经sort好的。

时间:n*O(logk)
PriorityQueue: logk

这个题目可以有好几个衍生:
比如,如果k很大,一个机器上放不下所有的k list怎么办? 比如,如果Merge起来的很长,一个机器上放不下怎么办?

Note:

  1. 不要忘记customized priority需要一个customized new Comparator()
  2. Given list 里面也可能有null node, 不要忘记查.

119. Merge Sorted Array.java Level: Easy

A够长,那么可以从A的尾部开始加新元素。
注意,从尾部,是大数字优先排末尾的.


120. Merge Two Binary Trees.java Level: Easy

基础binary tree traversal. 注意对null child的判断


121. Merge Two Sorted Lists.java Level: Easy

小的放前。每次比head大小。
while过后,把没完的list一口气接上。

一开始建一个node用来跑路, 每次都存node.next = xxx。存一个dummy。用来return dummy.next.


122. Min Stack.java Level: Easy

双Stack:一个正常stack,另一个minStack存当下level最小值. 注意维护minStack的变化

另外. 如果要maxStack,也是类似做法


123. Minimum Absolute Difference in BST.java Level: Easy

BST: inorder-traversal: 先left node(adding to stack till left leav), 再process stack.peek (mid node), 再 add rightNode && dive to rightNode.left leaf


124. MinimumDepthOfBinaryTree.java Level: Easy

Divide and Conquery一个最小值. 注意处理Leaf的null, 用Integer.MAX_VALUE代替,这样可以避免错误counting.


125. Multiply Strings.java Level: Medium

想法不难。turn into int[], 然后每个位子乘积,然后余数carrier移位。

但是做起来有很多坑。适合面试黑。

  1. 数字‘123’, 在数组里面, index == 0 是 ‘1’。 但是我们平时习惯从最小位数开始乘积,就是末尾的'3'开始。 所以!翻转两个数字先!我去。这个是个大坑。

  2. 乘积product,和移动Carrier都很普通。

  3. !!最后不能忘了再翻转。

  4. 最后一个看坑。要是乘积是0,就返回‘0’。 但是这个其实可以在开头catch到没必要做到结尾catch。

用到几个StringBuffer的好东西:
reverse();
sb.deleteCharAt(i)

找数字,或者26个字母,都可以:
s.charAt(i) - '0'; //数字
s.charAt(i) - 'a'; //字母


126. Next Permutation.java Level: Medium

需斟酌。

Permutation的规律:

  1. 从小的数字开始变化因为都是从小的数字开始recursive遍历。
  2. 正因为1的规律,所以找大的断点数字要从末尾开始: 确保swap过后的permutation依然是 前缀固定时 当下最小的。

steps:

  1. 找到最后一个上升点,k
  2. 从后往前,找到第一个比k大的点, bigIndex
  3. swap k && bigIndex
  4. 最后反转 (k+1,end)

127. Nim Game.java Level: Easy

著名Nim游戏。 写一些,发现n=4,5,6,7,8...etc之后的情况有规律性: 谁先手拿到4就输了. 最终很简单n%4!=0就可以了


128. Number of Airplane in the sky.java Level: Medium

把Interval拆分成数轴上的Point:
起飞mark 1
降落mark -1
用PriorityQueue排序, loop through queue, 计算(起飞+降落)值可能有的max。

注意: 同时起飞和降落,就是 1 - 1 = 0. 所以在while loop里面有第二个while loop,
当坐标x重合时,在这里做完所有x点的加减,然后再比较 max。
这避免了错误多count,或者少count


129. One Edit Distance.java Level: Medium

理解Edit: 就是删除,增加,和替换。
换完之后,理论上换成的String 就应该全等
一旦找到不一样的char, 就判断那三种可能性


130. Palindrome Permutation II.java Level: Medium

permutation的综合题:

  1. validate Input 是不是可以做palindromic permutation. 这个就是(Palindrome Permutation I)
  2. 顺便存一下permutation string的前半部分和中间的single character(if any)
  3. DFS 做unique permutation: given input有duplicate characters。

131. Palindrome Permutation.java Level: Easy

注意,条件里面没说是否全是lower case letter


132. Pascal's Triangle II.java Level: Easy

简单处理array list.


133. Permutation Index.java Level: Easy

和Permutation Sequence相反的题目。思想类似。

题目为Easy,琢磨很久,分析:
每个数位的数字,都是跳过了小于这数字开头的多种可能。

举例【6,5,2】吧。我们找6,5,2是permudation里面的第几个。
正常排序,也就是permutation的第一个,应该是【2,5,6】
如果要从首位,2,变成6,要跨过多少可能性呢?
很简单,就是问:小于6的数字有多少个呢?(2,5).每个数字变成head,都有各自的一套变化,都有(n-1)!种可能。

本题做法:每个(n-1)!加起来。 Note:(n-1) means, 开头的数字(2,5)各带出多少种排列,也就是不就是(n-1)!嘛。 这一步,计算数量很简单: (有几个小于6的数字) ×(除去head剩下有多少个数字)!

以上 ,都是为了把6推上皇位,而牺牲的条数。

那么把6推上去以后,还有接下去的呢。

接下去要看5,2.
6确定,后面permudation可变的情况有可能是【6,5,2】,那还可能是【6,2,5】呢。

Same process, 看given 数组的第二位5,算它接下去:
1. 有几个数字小于5呢?
2. 除去5,还有几个数字可以 factorial呢?
3. 一样的。第一步就结果乘以第二步。

最后接下去要看最后一个元素2了。

6,5,2全看过了以后,加起来。
就是【6,5,2】上位,所踏过的所有小命啊!

我这解释太生动了。因为耗费了好长时间思考...


134. Permutation Sequence.java Level: Medium

k是permutation的一个数位。而permutation是有规律的。

也就是说,可以根据k的大小来判断每一个数位的字符(从最大数位开始,因为默认factorio从最大数位开始变化)。

于是先求出n!, 然后 k/n!就可以推算出当下这一个数位的字符。然后分别把factorio 和 k减小。

另外, 用一个boolean[] visited来确保每个数字只出现一次。

这个方法比计算出每个permutation要efficient许多。


135. Permutations II.java Level: Medium

方法1: Mark visited. 并且要检查上一层recursive时有没有略过重复element. 并且要排序,通过permutation规律查看是否排出了重复结果。

背景1:在recursive call里面有for loop, 每次从i=0开始, 试着在当下list上加上nums里面的每一个。
从i=0开始,所以会依次recursive每一个nums:因此,例如i=2,肯定比i=3先被访问。也就是:取i=2的那个list permutation肯定先排出来。

背景2:重复的例子:给出Input[x, y1, y2], 假设y的值是一样的。那么,{x,y1,y2}和{x,y2,y1}是相同结果。

综上,y1肯定比y2先被访问,{x,y1,y2}先出。 紧随其后,在另一个recursive循环里,{x,y2...}y2被先访问,跳过了y1。
重点:规律在此,如果跳过y1,也就是visited[y1] == false, 而num[y2] == num[y1],那么这就是一个重复的结果,没必要做,越过。

结果:那么,我们需要input像{x,y1,y2}这样数值放一起,那么必须排序。

方法2: 一个办法就是给一个visited queue。 和queue在所有的地方一同populate. 然后visited里面存得时visited indexes。 (Not efficient code. check again)


136. Permutations.java Level: Medium

Recursive Backtracking: 取,或者不取. Improvement: maintain list (add/remove elements) instead of 'list.contains'

Iterative: 用个queue,每次poll()出来的list, 把在nums里面能加的挨个加一遍。 However, code is a bit massive.


137. Populating Next Right Pointers in Each Node II.java Level: Hard

非perfect tree, 也就是有random的null children. DFS+BFS

Populating Next Right Pointers in Each Node I 里面依赖parent.next.left来作链接,但现在这个parent.next.left很可能也是Null.

  1. 于是需要移动parent去找children level的next node。
  2. 并且每次在一个level, 要用BFS的思想把所有parent 过一遍,也就是把parent 正下方的children全部用.next链接起来
    原因: 到下一层children变成parent, 他们需要彼此之间的connection, grand children才可以相互连接。

Note: runtime O(n * 2^log(n) ) = O(n^2), not good.


138. QuickSort.java Level: Easy

代码是不难的.

首先partition. 返还一个partition的那个中间点的位置。 然后劈开两半。 前后各自 quick sort, recursively

注意:在partition里面, 比较的时候nums[start] < pivot, nums[end]>pivot, 如果写成了 <= 会 stack overflow.

但是: 在partition array那个题目里面,第二个 nums[end] >= pivot, 是要去加上这个 ‘=’的


139. Rehashing.java Level: Medium


140. Remove Duplicates from Sorted Array.java Level: Easy

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

141. Remove Duplicates from Sorted List.java Level: Easy

一旦node.next 和node是重复,跳


142. Remove Invalid Parentheses.java Level: Hard

NOT DONE


143. Remove Node in Binary Search Tree.java Level: Hard

方法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。


144. Reshape the Matrix.java Level: Easy

读例子理解题意. 理清counter case. Basic implementation


145. Reverse Integer.java Level: Easy

方法1: 转换成String 然后 reverse 方法2: 每次加上x%10,然后x不断减小~0


146. Reverse Linked List.java Level: Easy

建立新list。每次把newList append 在current node的后面。
用head来循环所有node。


147. Reverse String.java Level: Easy

Similar to Reverse Integer. 可以用StringBuffer, 也可以two pointer reverse head/tail


148. Reverse Words in a String II.java Level: Medium

In-place reverse.

reverse用两回. 全局reverse。局部:遇到空格reverse。

注意:结尾点即使没有' '也要给reverse一下最后一个词。


149. Reverse Words in a String.java Level: Medium

几种不同的方法flip:
坑: 1. 结尾不能有空格。 2. 注意,如果Input是 ‘ ’的话,split以后就啥也没有了。check split以后 length == 0

另个题目Reverse Words in String (char[]) 可以in-place,因为条件说char[]里面是没有首尾空格,好做许多哟.


150. reverseInteger.java Level: Easy


151. Roman to Integer.java Level: Easy

熟悉罗马字母规则

  1. 'I V X L C D M' 分别代表的数字
  2. 列举combo的情况,需要从原sum里面减掉多加的部分: 'IV, IX'减2, 'XL, XC'减20, 'CD, CM'减200.

https://en.wikipedia.org/wiki/Roman_numerals


152. Rotate Image.java Level: Medium

找到个转角度的规律公式。用一个temp。in place.


153. Search Range in Binary Search Tree .java Level: Medium

等于遍历了所有k1<= x <= k2的x node。

如果是用Binary Search Tree搜索,那么一般是if (...) else {...},也就是一条路走到底,直到找到target.

这里, 把 left/right/match的情况全部cover了,然后把k1,k2的边框限制好,中间就全部遍历了。


154. Search Rotated in Sorted Array.java Level: Hard

方法1:O(logn) 还是把它先当做正常的sorted list开始搜。
但是在比较的时候,多比较一个A[start] < A[mid]?
在1 和 2 里面分别讨论 target 的位置
1. A[start] < A[mid] ?
说明在前半段
- start<target<mid
- target > mid
2. A[start] > A[mid]
说明 start 还在前半段,而mid在后半段
- mid < target < end
- target < mid

方法2:O(logn)
1. binay search break point
2. binary search target
注意等号,在判断target在前半段还是后半段:if (A[p1] <= target && target <= A[breakPoint])


155. Segment Tree Build II.java Level: Medium

给的是Array。注意找区间内的max, assign给区间。 其余和普通的segment tree build一样

给了array,但是并不根据array里的内容排位,而是依然根据index in [0, array.length - 1]割开区间,break到底,
最终start==end。同时assign max=A[start] or A[end]

往上,parent一层的max:就是比较左右孩子,其实都是在两个sub-tree里面比较sub-tree的max。

这就好做了:
先分,找到left/right,比较max,在create current node,再append到当前node上面。

实际上是depth-first, 自底向上建立起的。


156. Segment Tree Build.java Level: Medium

按定义:
左孩子:(A.left, (A.left+A.rigth)/2)
右孩子:((A.left+A.rigth)/2+1, A.right)


157. Segment Tree Modify.java Level: Medium

Recursively 在segment tree里面找index, update it with value.

每个iteration,很可能(要么左手,要么右手)max就变了。所以每次都left.max and right.max compare一下。
最后轮回到头顶,头顶一下包括头顶,就全部都是max了。

Divde and Conquer


158. Segment Tree Query II.java Level: Medium

和 Segment Tree Query I 以及其他Segment Tree问题没啥区别。这个就是return个count。

这个题目考了validate input source:input 的start,end可能超出root[start,end]。
那么第一步就要先clear一下。完全不在range就return 0. 有range重合就规整到root的range.


159. Segment Tree Query.java Level: Medium

给了segment Tree, node里面有Max value, 找[start,end]里面的max

[start,end]跟mid相比,可能:
全在mid左
全在mid右
包含了mid: 这里要特别break into 2 query method

按定义:
mid = (root.start + root.end)/2


160. Shortest Word Distance.java Level: Easy

找short distance, wordB可以在wordA的前后;而同一时间,只需要计算一个最近的up to date的distance。 greedy不断变更A/B index再做比较即可。


161. Single Number.java Level: Easy

Bit XOR: 当两个bit不同时,return 1. 题目正要消光所有重复出现的数儿留下出现一次的那个.


162. Sqrt(x).java Level: Easy

理解题意, 从[0, x]找一个可以m*m=x的值. 注意, 如果找不到, 最后问考官该return一个什么值:按道理,因为return int, 会取整,那么return一个平方最close to x就可以. 注意 mid 用 long, 因为很可能超过最大int.


163. String Permutation.java Level: Easy

把#of occurrences 存进HashMap, 第一个string 做加法,第二个string做减法。最后看是否有不等于0的作判断。


164. String to Integer(atoi).java Level: Easy

方法1: 问清情况,一点一点把case都涉及到。

方法2: 用regular expression。if (!str.matches("[+-]?(?:\d+(?:\.\d*)?|\.\d+)")). 猛了一点


165. Strobogrammatic Number II.java Level: Medium

耗了一点时间。本以为需要DP一下,把做过的n存一下。后来发现,其实就是剥皮,一层一层,是一个central-depth-first的,钻到底时候,return n=1,或者n=2的case,然后开始backtracking。 难的case先不handle.到底之后来一次O(n) scan. 总共的时间起码是O(n/2) + O(n), 所以还是O(n)


166. Strobogrammatic Number.java Level: Easy

根据题意枚举, 再根据规则basic implementation


167. Subarray Sum Closest.java Level: Medium

?


168. Subarray Sum.java Level: Easy

分析出,如果sum[0a]=x, 然后sum[0b]=x, 说明sum(a ~ b] = 0.

这样理解后,用hashMap存每个sum[0~i]的值和index i. 如果有重复,就找到了一组sum为0的数组。

169. Subset.java Level: Medium

最基本的递归题目。
坑:记得一开头sort一下 nums。 因为要升序。那么整体就是O(nlogn)

注意:用level/index来track到哪一步。最后一level就add into rst

方法1: subset的概念,取或者不取,backtracking. 当level/index到底,return 一个list.

方法2: 用for loop backtracking. 记得:每个dfs recursive call是一种独特可能,先加进rst。

recap:时间久了忘记dfs的两种路子. for loop dfs/backtracking vs. regular dfs


170. Subsets II.java Level: Medium

递归:找准需要pass along的几个数据结构。

和SubsetI类似,先sort input, 然后递归。但是input可能有duplicates.

Using for loop approach: 每个dfs call是一种可能性,直接add into result.
为了除去duplicated result, 如果在递归里面用rst.contains(),就是O(n), which makes overall O(n^2).

这里有个基于sorted array的技巧:
因为我们有mark index。 一旦for loop里面的i!=index,并且nums[i] == nums[i-1],说明x=nums[i-1]已经用过,不需要再用一次:
[a,x1,x2],x1==x2
i == index -> [a,x1]
i == index + 1 -> [a,x2]. 我们要skip这一种。

如果需要[a,x1,x2]怎么办? 其实这一种在index变化时,会在不同的两个dfs call 里面涉及到。

Iterative: 写一写,用个Queue. Not recommended, Again, rst.contains() cost too much.


171. Subtree.java Level: Easy

找到potential subtree, 比较Children.

一点注意:即使找到T1 == T2, 但很可能只是数字相同(这里不是binary search tree!!),而children不同。所以同时要继续recursively isSubtree(T1.left, T2) ...etc.


172. Symmetric Binary Tree.java Level: Easy

Symmetric Tree

注意Symmetric Binary Tree的例子和定义: 是镜面一样的对称. 并不是说左右两个sub-tree相等。

方法1: Recursively check symmetrically相对应的Node. 每个node的children都和镜面另外一边相对的node的children刚好成镜面反射位置。

方法2: 用stack. 左手边sub-tree先加left,再加right child; 右手边sub-tree先加right child, 再加left child。
process时,若symmetric,所有stack里面出来的node会一一对应。


173. Top K Frequent Elements.java Level: Medium

题目有提醒: 不能用O(nLog(n)) 也就是说明要Log(n): 首先想到就是PriorityQueue, 并且不能queue.offer on the fly. 那么就先count, O(n); 再priorityQueue, Log(k), k是unique 数字的总量.


174. Top K Frequent Words.java Level: Medium

方法1:Brutle force用HashMap存frequency, 用ArrayList存lists of words。最后返回从尾部向前数的k个。
注意排序时Collection.sort()的cost是O(nLogk)

方法1-1: 还是用HashMap,但create一个Node class, 然后用PriorityQueue.
PriorityQueue里面用到了 String.compareTo(another String).巧妙。

方法2: Trie && MinHeap屌炸天
http://www.geeksforgeeks.org/find-the-k-most-frequent-words-from-a-file/


175. Topological Sorting.java Level: Medium

比较特别的BFS.

几个graph的condition:

  1. 可能有多个root
  2. directed node, 可以direct backwards.

Steps:
Track all neighbors/childrens. 把所有的children都存在map<label, count>里面 先把所有的root加一遍,可能多个root。并且全部加到queue里面。

然后以process queue, do BFS:
Only when map.get(label) == 0, add into queue && rst.
这用map track apperance, 确保在后面出现的node, 一定最后process.


176. Trapping Rain Water II.java Level: Hard

用PriorityQueue把选中的height排序。为走位,create class Cell {x,y, height}.

注意几个理论:

  1. 从matrix四周开始考虑,发现matrix能Hold住的水,取决于height低的block。
  2. 必须从外围开始考虑,因为水是被包裹在里面,外面至少需要现有一层。

以上两点就促使我们用min-heap: 也就是natural order的PriorityQueue.

process的时候,画个图也可以搞清楚,就是四个方向都走走,用curr cell的高度减去周围cell的高度。 若大于零,那么就有积水。

每个visited的cell都要mark. 去到4个方向的cell,加进queue里面继续process.

这里,有一点,和trapping water I 想法一样。刚刚从外围,只是能加到跟外围cell高度一致的水平面。往里面,很可能cell高度变化。
这里要附上curr cell 和 move-to cell的最大高度。


177. Tweaked Identical Binary Tree.java Level: Easy

Recursive 比对左左,左右,右左,右右


178. Two Strings Are Anagrams.java Level: Easy

方法1:char ascii 用count[256]
坑:不要想象这个是个26letter lowercase. may not be true.

方法2: 若是其他字符encoding, 而不只是utf16-encoding (java char)?
那么就继续用string去做


179. Ugly Number.java Level: Medium

方法1: PriorityQueue排序。用ArrayList check 新的ugly Number是否出现过。

方法1-1:(解释不通,不可取)用PriorityQueue排序。神奇的3,5,7走位:按照题目答案的出发,定了3,5,7以什么规律出现。但是题目并没有特殊表明。

方法2: DP . Not Done yet.


180. Valid Palindrome.java Level: Easy

tutorial:https://www.youtube.com/watch?v=2hNK0Yz53LQ&list=PLZn-UvluQZuNedn1hDzTmNLE8MQWXjKVb

过滤alphanumeric,其他字母掠过


181. Valid Parentheses.java Level: Easy

剥皮过程。解铃还须系铃人
左边的外皮'{['在stack底部
右边的外皮应该和stack顶上的左外皮一一对应


182. Valid Sudoku.java Level: Easy

用HashSet存visited value.

方法1: 在nest for loop里面validate row,col,and block.
validate block要利用i 和 j 增长的规律。
说白了,i && j是按照0~n增长的index, 具体怎么用是可以flexible的。这个方法在同一个nest for loop解决所有运算。

方法2: 单独做block validation: validate block的时候虽然看到了4层for.其实也就是n^2.


183. Word Break.java Level: Medium

DP

方法1:(attempt3 code) state,rst[i]: 从[0~i] inclusive的string是否可以在dict中break开来找到?
function: rst[i] = true if (rst[i - j] && set.contains(s.substring(i - j, i))); j in[0~i]

  1. rst[i - j] 记录的是[0, i-j]这一段是否可以break后在dict找到。
  2. 若true,再加上剩下所有[i-j, i]都能在dict找到,那么rst[i] = rst[0, i - j] && rst[i-j, i] == true

优化:找dict里面最长string, 限制j的增大。

(attempt4 code)
与Word BreakII用同样的DP。 valid[i]: 记录从i到valid array末尾是否valid.


184. Word Ladder II.java Level: Hard


185. Word Ladder.java Level: Medium

BFS Brutle: 在start string基础上,string的每个字母都遍历所有26个字母,换换。

方法2:
用Trie。 理应更快. However implementation可能有点重复计算的地方,LeetCode timeout. 需要再做做。


186. Word Pattern.java Level: Easy

每个char代表一个pattern。用HashMap<char, str>. 但不够,如果a也match dog, b也match dog, 纠错了。比如pattern = "abba", str = "dog dog dog dog"。 因此第二个HashMap<str, char> 反过来。 确保pattern和str一一对应。


187. Word Squares.java Level: Hard

in progress


188. Find Anagram Mappings.java Level: Easy

比较简单, 用HashMap 存index list. 最后再遍历一遍数组A, 列举出所有元素. O(n)


189. Judge Route Circle.java Level: Easy

简单的character checking. 各个方向, 加加减减.


190. Island Perimeter.java Level: Easy

最简单的方法: 每个格子4个墙;每个shared的墙要-2 (墙是两面, -1 * 2) 最后合计结果就好.

不必想太多用HashMap做.但是也可以思考一下:

  • 把每个block相连的block全部存在以当下block为key的list里面. 那么这里需要把2D坐标, 转化成一个index.
  • 最后得到的map, 所有的key-value应该都有value-key的反向mapping, 那么久可以消除干净, 每一次消除就是一个shared wall.
  • 一点点optimization: DFS去找所有的island, 如果island的图非常大, 而island本身不打,那么适合optimize. 但是整体代码过于复杂. 不建议写.

191. First Unique Character in a String.java Level: Easy

方法1: 按照题意, 找到第一个 first index == last index的字母.

方法2: 用hashmap存字母的index, 有些重复字母的index就会是个list. 找到单一index, 结合成list, sort, return list.get(0)


192. Power of Three.java Level: Easy

方法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整除. 一步到位.


193. Plus One.java Level: Easy

简单的实现, 加1, 进位. 唯一取巧的地方, 最后如果要多一位, 一定是10000...这个模式, 可以走捷径, 直接来个+1size的array, 然后第一位=1. 注意,转换成long也不合理,用太多memory.


194. Power of Two.java Level: Easy

跟powerOfThree一样: 可以loop, check mod; 也可以用binary search找合适的数字.


195. Reverse Vowels of a String.java Level: Easy

方法1: two pointer. 前后两个指针, 在while loop里面跑. 注意 i<j, 一旦相遇, 就break. 找到合适的, 就做swap. StringBuffer可以 sb.setCharAt()记得用.

方法2: 拿出所有vowels, 反过来放进去. O(n)


196. Guess Number Higher or Lower.java Level: Easy

binary search 公式


197. Encode and Decode TinyURL.java Level: Medium

其实想到了切入点, 是个可难可简单的题目. 这里的encode就是想办法把url存起来, 然后给个 key. 那么具体怎么做这个key, 简单就可以用一个map, 然后counting. 复杂一点就可以做random letter/number组成key.


198. Wiggle Sort.java Level: Medium

方法1: 排序, nLog(n). 然后把直线上坡变成层叠山峰, 需要每隔几个(题目中是每隔2位)就做个swap 造成高低不平. Note: 每隔山峰之间是相互没有关系的, 所以每次只要操心 [i], [i-1]两个位置就好了.

方法2: O(n) 看好奇数偶数位的规律, 然后根据题目给出的规律, 跑一遍, 每次只关注两个位置: 把不合适的[i], [i-1]调换位置就好了.

方法3: 跟法2一样, 只是更巧妙一点罢了: 第一遍想太多. 其实做一个fall-through就能把问题解决,原因是因为: 这样的fall-through每次在乎两个element,可以一口气搞定,无关乎再之前的elements。 特别的一点:flag来巧妙的掌控山峰和低谷的变化。又是神奇的一幕啊! 这样子的奇观,见过就要知道了,没见过的时候有点摸不着头脑。


199. Queue Reconstruction by Height.java Level: Medium

别无他法, 只能写一遍例子, 找规律,然后greedy.  需要写一遍发现的规律比如: 从h大的开始排列, 先放入k小的. 写comparator的时候要注意正确性. 如果要sort, 并且灵活insert:用arrayList. 自己做一个object. 最后做那个'matchCount'的地方要思路清晰, 找到最正确的spot, 然后greedy insert.

O(n) space, O(nLog(n)) time, because of sorting.

可能有简化的余地, 代码有点太长. 比如试一试不用额外空间?


200. 2 Sum.java Level: Easy Link 解法1:相对暴力简洁, HashMap<value, index>,找到一个value, 存一个; 若在HashMap里面 match 到结果, 就return HashMap里存的index. O(n) space && time.

解法2:Sort array, two pointer 前后++,--搜索。Sort 用时O(nlogn).

  1. 第一步 two pointer 找 value.
  2. 注意,要利用额外的空间保留original array, 用来时候找index. (此处不能用HashMap,因为以value 为key,但value可能重复)
    O(n) space, O(nlogn) time.

201. 2 Sum II - Input array is sorted.java Level: Medium

排序好的array. Two pointer移动start和end,核查sum. 注意sum用long.


202. 2 Sum II.java Level: Medium

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部分作比较。


203. Coin Change.java Level: Medium

DP. 找对方程f[x], 积累到amount x最少用多少个coin: #coin是value, index是 [0~x]. 子问题的关系是: 如果用了一个coin, 那么就应该是f[x - coinValue]那个位置的#coins + 1

注意initialization: 处理边界, 一开始0index的时候, 用value0. 中间利用Integer.MAX_VALUE来作比较, initialize dp[x] 注意, 一旦 Integer.MAX_VALUE + 1 就会变成负数. 这种情况会在coin=0的时候发生.

方法1: 用Integer.MAX_VALUE 方法2: 用-1, 稍微简洁一点, 每次比较dp[i]和 dp[i - coin] + 1, 然后save. 不必要做多次min比较.


204. Unique Path.java Level: Medium

可以DP.计数DP. 注意方程式前两位置加在一起: 前两种情况没有overlap, 也不会缺情况. 注意initialization, 归1. 需要initialize的原因是,也是一个reminder: 在方程中会出现-1index


205. Jump Game.java Level: Medium

给出步数,看能不能reach to end.

Status: 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];


206. Maximum Product Subarray.java Level: Medium

求最值, DP. 两个特别处:

  1. 正负数情况, 需要用两个DP array.
  2. continuous prodct 这个条件决定了在Math.min, Math.max的时候, 是跟nums[x]当下值比较的, 如果当下值更适合, 会舍去之前的continous product, 然后重新开始. 这也就注定了需要一个global variable 来hold result.

207. 3 Sum Closest.java Level: Medium

3Sum 的一种简单形式, 并且都没有找index, value, 而只是找个sum罢了.

double for loop。 2Sum只能用土办法 left/right 2 pointers。 O(n^2)

注意:check closest时候用long, 以免int不够用


208. Triangle Count.java Level: Medium

其实也就是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]


209. 3 Sum.java Level: Medium

方法1: sort array, for loop + two pointer. O(n) 处理duplicate wthin triplets: 如果最外圈的移动点i重复, 一直顺到结尾的最后一个再用. 如果是triplet内有重复, 用完start point, 移动到结尾.

Previous notes: 注意:

  1. 找 value triplets, 多个结果。注意,并非找index。
  2. 要升序, 第一层for loop 从最后一个元素挑起, 保证了顺序。
  3. 去掉duplicate: check用过的同样的数字,都跳掉。不需要用同样的数字再计算一边已有结果。

步骤:

  1. For loop 挑个数字A.
  2. 2Sum 出一堆2个数字的结果
  3. Cross match 步骤1里面的A.

时间 O(n^2), 两个nested loop

另外, 还是可以用HashMap来做2Sum。稍微短点。还是要注意handle duplicates.


210. 4 Sum.java Level: Medium

方法1:

  1. 利用2Sum的原理,把4Sum分为连个2Sum。左一个pair,右一个pair,每个pair里面放2个数字。
  2. 以一个点,i,作为分界口,也要列举出所有i之前的pair,作为基础。
  3. 再尝试从所有i+1后面,找合适的2nd pair。

可以用HashSet, 可以直接比较list里面每一个元素, 保证set不重复. Previous Notes: 在造class Pair时候,要做@override的function: hashCode(), equals(Object d). 平时不太想得起来用。 参见 http://lifexplorer.me/leetcode-3sum-4sum-and-k-sum/

方法2: 3Sum外面再加一层. 参考3Sum. 时间O(n^3)。 但此方法在k-sum时候,无疑过于费时间. O(n^k)


211. k Sum.java Level: Hard

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)


212. Trapping Rain Water.java Level: Hard

方法1: 主要想法和方法2一致: 在山坡下坡的基础上, 一直用stack堆积bottom. 最后遇到上升之前, 此时bottom可以用来跟stack之前堆积的所有下坡index做比较, 算跟他们高度相差的积水. 用了stack记录下坡, 然后用个while loop一挖到底的想法非常棒.

方法2: 2 Pointers, 双面夹击:

  1. 找中间最高bar的index
  2. 两面往中心扫:每次加上(topBarIndex - currIndex)* (elevation from previous index).也就是每次加一个横条。
  3. 每次还要减去block自身的height。

213. Longest Continuous Increasing Subsequence.java Level: Easy

简单的DP:dp[i]存在i点时连续上升#. dp[i]时可能为0, 一旦断开.

方法1: dp[i], maintain max 方法2: 用一个数存current count, maintain max. 也是DP, 稍微简化.


214. Longest Increasing Subsequence.java Level: Medium

方法1: [0 ~ i], 0<i<n: 以i为结尾, 每个不同的i会得出的max-length. 所以每个结尾i都要被考虑一遍. dp[i]: represent the max length aggregated up to index i.

Previous Note: i位和之前的0i-1 都远关系。复杂一点。 每次都考虑oi的所有情况。所以double for loop

方法2: O(n log n)? 还没有做. 是否for loop里面用binary search?


215. Unique Binary Search Tree.java Level: Medium

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的方程, 有点玄学的意思啊! 不好想.


216. Trim a Binary Search Tree.java Level: Easy

方法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: 用迭代, 还没有写.


217. Unique Paths II.java Level: Medium

典型的坐标型DP. 考虑最终结尾需要的状态:如何组成,写出公式. 公式中注意处理能跳掉的block, '到不了', 即为 0 path.


218. Counting Bits.java Level: Medium

Bit题目 用num的数值本身表示DP的状态. 这里, dp[i] 并不是和 dp[i-1]有逻辑关系; 而是dp[i] 和dp[i>>1], 从binary representation看出有直接关系.


219. Bomb Enemy.java Level: Medium

分方向考虑的方法很容易想到,但是四个方向移动的代码比较繁琐. 往上炸: 要从顶向下考虑 往下炸: 要从下向上考虑

熟练写2D array index 的变换.


220. Paint House.java Level: Easy

考虑DP最后一个位置的情况. 发现给出了一些特殊条件, 需要附带在DP[i-1]上, 那么就定义二维数组


221. Decode Ways.java Level: Review

确定末尾的2种状态: single letter or combos. 然后计算出单个letter的情况, 和双数的情况 note: calculate number from characters, need to - '0' to get the correct integer mapping. 注意: check value != '0', 因为'0' 不在条件之中(A-Z)


222. House Robber.java Level: Easy

最基本的dp。
看最后结尾状态的前一个或前两个的情况,再综合考虑当下的。
思考的适合搞清楚当下的和之前的情况的关系。
滚动数组的优化,就是确定了是这类“只和前一两个位子“相关的Fn而推出的。


223. House Robber II.java Level: Medium

和House Robber I 类似, DP. 根据dp[i-1]是否被rob来讨论dp[i]: dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i - 1]);

特别的是,末尾的last house 和 first house相连。这里就需要分别讨论两种情况:

  1. 第一个房子被rob
  2. 第一个房子没被rob

分出两个branch, 可以看做两种状态. 可以考虑用两个DP array, 也可以加一维度, 补充这个状态.


224. Best Time to Buy and Sell Stock I.java Level: Easy

理解意思是关键: 每天都就交易价格,n天只让买卖一次,那就找个最低价买进,找个最高价卖出。 记录每天最小值Min是多少。O(n) 每天都算和当下的Min买卖,profit最大多少.

这里就可以DP, memorize the min[i]: the minimum among [0 ~ i]; 然后用当天的price做减法算max. 更进一步, 用一个min来表示min[i], 因为计算中只需要当下的min.

Brutle: 每天都试着买进,然后之后的每一天尝试卖出. double for loop, O(n^2). timeout. 其中很多都是没必要的计算:[7, 1, 5, 3, 6, 4]。 if we know buyin with 1 is cheapest, we don't need to buyin at 5, 3, 6, 4 later on; we'll only sell on higher prices.


225. Best Time to Buy and Sell Stock II.java Level: Easy

这道题有几种其他不同的思路:

  1. Greedy, 每次有相邻的diff符合profit条件, 就卖了, 最后把所有的diff加在一起. 计算delta, 其实简单粗暴, 也还不错.
  2. 如下, 从低谷找peek, sell.
  3. 繁琐一点的DP. BuyOn[], SellOn[] 从末尾看起
  4. DFS计算所有(timeout).Improvement on DFS -> DP -> calculate sellOn[i] and buyOn[i], and then return buyOn[i]. 有点难想, 但是代码简单, 也是O(n)

找涨幅最大的区间,买卖: 找到低谷,买进:peek = start + 1 时候,就是每次往前走一步;若没有上涨趋势,继续往低谷前进。 涨到峰顶,卖出:一旦有上涨趋势,进一个while loop,涨到底, 再加个profit.

profit += prices[peek - 1] - prices[start]; 挺特别的。 当没有上涨趋势时候,peek-1也就是start, 所以这里刚好profit += 0.

O(n)


226. Best Time to Buy and Sell Stock III .java Level: Hard

比stock II 多了一个限制:只有2次卖出机会。

方法1: DP加状态: 只卖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是一样的. 唯一更好的是, 不需要记录中间买入的时间点.

方法2: 也就是:找峰头;然后往下再找一个峰头。

怎么样在才能Optimize两次巅峰呢?

从两边同时开始找Max!(棒棒的想法)

leftProfit是从左往右,每个i点上的最大Profit。 rightProfit是从i点开始到结尾,每个点上的最大profit. 那么在i点上,就是leftProfit,和右边rightProfit的分割点。在i点,leftProfit+rightProfit相加,找最大值。

三个O(n),还是O(n)


227. Best Time to Buy and Sell Stock IV.java Level: Hard

方法1: DP. 根据StockIII, 不难发现StockIV就是把状态划分为2k+1份. 那么同样的代码, 移植. 注意1: 如果k很大, k>n/2, 那么长度为n的数组里面, 最多也只能n/2个transaction, 那么题目简化为stockII, 给n数组, 无限次transaction. 注意2: n可以用滚动数组(prev/now)代替. 注意3: 最后状态是'没有stock'的都该考虑, 做一个 for 循环比较max. 当然, 来一个profit variable, 不断比较, 也是可以的.

方法2: 记得要理解: 为什么 i-1天的卖了又买,可以和第 i 天的卖合成一次交易?
因为每天交易的price是定的。所以卖了又买,等于没卖!这就是可以合并的原因。要对价格敏感啊少年。

Inspired from here:
http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html

局部最优解 vs. 全局最优解:
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]。


228. Paint House II.java Level: Review

一般的DP被加了状态变成2D. 考虑最后位, 而前一位i-1又被i位的颜色限制, 于是在考虑 min dp[i] 时候, 又多了一层iteration. 所以变成了O(NK^2)

注意:

  1. 序列型dp[i]表示'前i-1个'的结果. 所以dp最好设定为 int[n + 1] size. 然而, 颜色在这里是状态, 所以保留在 j: [ 0~k)
  2. [[8]] 这样的edge case. 跑不进for loop, 所以特殊handle.

Optimization: 如果已知每次都要从cost里面选两个不同的最小cost,那么先把最小挑出来, 就不必有第三个for loop O(NK)


229. 3 Sum Smaller.java Level: Medium

一般的O(n3)肯定不行。在此基础上优化。 发现j,k满足条件时候,(k - j)就是所有 sum <target的情况了。 而一旦>target, 又因为j不能后退,只能k--,那么问题就被锁定了. 这样可以做到O(n2)


230. Array Partition I.java Level: Easy

从结果出发, 只需要找到加法的结果,而不强调具体配对。 找到排列取单数位的规律,再考虑负数和正数的相同规律,即可找到排列求解的方法。


231. 1-bit and 2-bit Characters.java Level: Easy

方法1: Greedy. 从第一个bit开始数: 如果遇到1, 一定是跳两位;如果遇到0, 一定是跳一位. loop to end, and see if index reaches the end.

方法2: 用DP硬做了一下:

  1. 如果i位是0, 那么前面dp[i-1]或者dp[i-2] true就够了.
  2. 如果i位是1, 那么i-1位必须是1才满足规则, 并且 dp[i-2]需要true.

232. Non-decreasing Array.java Level: Easy

比较升序的时候, 必须要估计到 i-1, i, i+1三个数位. 写出来i-1, i+1之间的关系, 然后做合理的fix.

需要真的fix数组, 因为loop through做比较时会用到fix后的数字.


233. Max Consecutive Ones.java Level: Easy

Basic. Math.max track结果。 记得在有对外操作的loop后,一定要把result object清理干净。


234. Find All Numbers Disappeared in an Array.java Level: Easy

方法1: 换到正确的位置。 需要小心handle i, 因为不知道换到nums[i]上的是什么,所以必须原地清理干净 方能move on.

方法2: 做标记! 很巧妙地运用了标记的方法, 标记成负数,证明visit过。 Preserve原数的负数,这样可以继续用此负数的绝对值来寻找原数所该被定的位置。非常巧妙!

方法3: 做标记! 跟方法2类似,也是做标记,这一次是加上一个大于n的数(因为题目给了n的border),最后check一下就好。为不超Integer.MAX_VALUE, 每次加n前,取余数。

做标记的方法固然快,但是相对来说比较hacky,在常规的代码中,估计不会用到.


235. Maximum Average Subarray I.java Level: Easy

简单的求sum, 同时求max, 结尾求余数就好.


236. Largest Number At Least Twice of Others.java Level: Easy

找最大值, 和第二大的值, 看是否符合题意, 就行了. 分析题意, 最简单方法, 可以loop 两遍: 找最值; 作比较. 但其实, 举个反例: 有一个不满足, 就够反对这个'at least twice of alllll others'.


237. Toeplitz Matrix.java Level: Easy

似乎没什么算法特点, 就是array基本运算, 然后分割成一个helper function去做重复计算, 剪短代码. 注意check MxN 的分界线.


238. Backpack.java Level: Medium

考虑: 用i个item (可跳过地取), 是否能装到weight w? 需要从'可能性'的角度考虑, 不要搞成单一的最大值问题.

  1. 背包可装的物品大小和总承重有关.
  2. 不要去找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.

多项式规律:

  1. picked A[i-1]: 就是A[i-1]被用过, weight j 应该减去A[i-1]. 那么dp[i][j]就取决于dp[i-1][j-A[i-1]]的结果.
  2. 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)


239. Sum of Two Integers.java Level: Easy

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)


240. Swap Bits.java Level: Easy

简单, 但是很多知识点:

  1. Hex 0xaaaaaaaa 是1010101....1010; 0x55555555 是01010101....0101
  2. 可以用这两个hex取单数和负数. 如果需要取其他的pattern, 也可以做.
  3. x很可能是negative number, 所以right-shift 要用logic shift, >>> 避免leading负数补位.

241. Update Bits.java Level: Medium

熟悉bits的一些trick:

  • ~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

242. Maximum XOR of Two Numbers in an Array.java Level: Medium

比较难想到. 利用到XOR性质A^B=C, then A=B^C.

  1. 枚举可能的A, 2. 然后一个个猜.

  2. 枚举A: 因为求MAX肯定是找leading-1最多的数字, 那么枚举A从(1000000...000)开始, 每次多一位取1或者0

  3. 因为枚举A的时候是按照每个bit来, 那么B和C也要以同样数位出现. 这里吧B和C变成了prefix的形式, 放在了set里面. 跟2sum用hashmap的思想类似, 每次用枚举的 A^B=C, 看看结果C是否已经在set里面. 如果在, 证明枚举的A可能被B^C得出, 那么就找到了一种情况.

还用到一些技巧: mask = (1 << i); // i位mask mask = mask | (1 << i); // prefix mask


243. Coins in a Line.java Level: Medium

如果我是先手, 每次只能拿1个,或者2个coins, 我如何赢? 只要保证对手在剩下的棋子中挑的时候'有可能败', 那就足够. 设计dp[i]:表示我面对i个coins的局面时是否能赢, 取决于我拿掉1个,或者2个时, 对手是不是会可能输? 所以: dp[i] = !dp[i - 1] || !dp[i-2] 时间: O(n), 空间O(n) 博弈问题, 常从'我的第一步'角度分析, 因为此时局面最简单.

优化: 空间优化O(1)


244. Perfect Squares.java Level: Medium

分割型. 考虑最后的数字: 要是12割个1出来, 剩下11怎么考虑? 割个4出来,剩下8怎么考虑? 就变成了dp = Min{dp[i - j^2] + 1}

时间复杂度: 乍一看是O(nsqrt(n)). 实际也是. 但如何推导? 考虑sqrt(1) + sqrt(2) + ....sqrt(n):找这个的upper bound and lower bound. 最后发现它的两边是 Ansqrt(n) <= actual time complexity <= Bnsqrt(n) 那么就是O(nsqrt(n))啦

Previous Notes: 一开始没clue.看了一下提示。

1. 第一步想到了,从数学角度,可能是从最大的perfect square number开始算起。 2. 然后想法到了dp, 假设最后一步用了最大的maxSqrNum, 那么就在剩下的 dp[i - maxSqrNum^2] +1 不就好了? 3. 做了,发现有个问题...最后一步选不选maxSqrNum? 比如12就是个例子。 然后就根据提示,想到BFS。顺的。 把1~maxSqrNum 都试一试。找个最小的。 看我把12拆分的那个example. 那很形象的就是BFS了。 面试时候,如果拆分到这个阶段不确定,那跟面试官陶瓷一下,说不定也就提示BFS了。


245. Palindrome Partitioning II.java Level: Hard

Find minimum cut: 分割型DP 考虑[j, i - 1] 是否是回文串, 如果是, 那么: dp[i]= min{d[j] + 1}.

利用palindrome的性质, 可以算出 boolean palindrome[i, j]的情况. 这样就给我们的问题合理降维, 目前是time: O(n^2). 不然求一次palindrome, 就是n, 会变成O(n^3)

Previous Notes: Double for loop 检查每种substring string (ij). 若i,j相邻或者同点,那么肯定isPal;否则,i,j之间的(i+1, j-1)一定得isPal。 看上去,在检查i,j的时候,中间按的(i+1, j-1)怎么可能先知道? 其实不然..在j慢慢长大的时候,所有的0j的substring都检查过。所以isPal[i+1][j-1]一定是已经知道结果的。

okay.那么假如以上任意一种情况成立,也就是说isPal[i][j] == true。那就要判断,切到第一层循环参数j的末尾点时,有多少种切法? 想法很顺:我们naturally会想到,把i之前的cut加上ij之间发生的不就好了。 反正现在j不变,现在就看吧i定在哪里,cut[i - 1]是否更小/最小; 再在cut[i-1]基础上+1就完了。 当然,如果i==0, 而 ij又是isPal,那没啥好谈的,不必切,0刀。

最终,刷到cut[s.length() - 1] 也就是最后一点。 return的理所应当。


246. Backpack V.java Level: Medium

与背包1不同: 这里不是check可能性(OR)或者最多能装的size是多少; 而是计算有多少种正好fill的可能性.

对于末尾, 还是两种情况:

  1. i-1位置没有加bag
  2. i-1位置加了bag

两种情况可以fill满w的情况加起来, 就是我们要的结果.

如常: dp[n + 1][w + 1]

方法1: Space: O(MN) Time: O(MN)

方法2: 空间优化, 滚动数组 Space: O(M) * 2 = O(M) Time: O(MN)

方法3: 降维打击, 终极优化: 分析row(i-1)的规律, 发现所有row(i)的值, 都跟row(i-1)的左边element相关, 而右边element是没用的. 所以可以被override. Space: O(M), 真*一维啊! Time: O(MN)


247. Backpack VI.java Level: Medium

拼背包时, 可以有重复item, 所以考虑'最后被放入的哪个unique item' 就没有意义了. 背包问题, 永远和weight分不开关系. 这里很像coin chagne: 考虑最后被放入的东西的value/weigth, 而不考虑是哪个.

1维: dp[w]: fill了weigth w 有多少种方法. 前面有多少种可能性, 就sum多少个: dp[w] = sum{dp[w - nums[i]]}, i = 0~n


248. Copy Books.java Level: Review

k个人copy完i本书. 定义Integer.MAX_VALUE的地方需要注意. Review: 为什么有i level的iteration? Chapter4.1


249. Valid Perfect Square.java Level: Review

Binary找sqrt. 基本 mid+1, mid-1 template. 注意: define index as long.


250. Intersection of Two Arrays II.java Level: Easy

方法1: 用HashMap: 存一个nums1, 再拿nums2 check against map. 时间/空间:O(n)

方法2: Binary search? 需要array sorted. 否则时间O(nlogn)不值得. [没做完, 有错]


251. First Bad Version.java Level: Easy

Binary Search

根据isBadVersion的性质,判断还如何end=mid or start=mid.
isBadVersion 是有方向的嘛,一个点错了,后面全错。


252. Backpack II.java Level: Medium

做了Backpack I, 这个就如出一辙, 只不过: dp存的不是w可否存成功true/false. dp存的是加上sum value的最大值. 想法还是,选了A[i - 1] 或者没选A[i - 1]时候不同的value值.

O(m)的做法:
想想,的确我们只care 最后一行,所以一个存value的就够了. 注意:和bakcpackI的 O(m)一样的,j是倒序的。如果没有更好的j,就不要更新。就是这个道理.

注意: 如果无法达到的w, 应该mark as impossible. 一种简单做法是mark as -1 in dp. 如果有负数value, 就不能这样, 而是要开一个can[i][w]数组, 也就是backpack I 的原型.


253. Backpack III.java Level: Review

可以无限使用物品, 就失去了last i, last unique item的意义: 因为可以重复使用.

  1. 所以可以转换一个角度: 用i种物品, 拼出w, 并且满足题目条件(max value). 这里因为item i可以无限次使用, 所以要遍历使用了多少次K.

  2. K虽然可以无限, 但是也被 k*A[i]所限制: 最大不能超过背包大小.

这样一来, 就close loop, 可以做了.

优化: Review 降维: 需要画图review 变成1个一位数组: 看双行的左右计算方向


254. Longest Palindromic Subsequence.java Level: Medium

区间型动态规划.

  1. 用[i][j]表示区间的首尾
  2. �考虑3个情况: 砍头, 砍尾, 砍头并砍尾 (考虑首尾关系)
  3. Iteration一定是以i ~ j 之间的len来看的. len = j - i + 1; 那么反推, 如果len已知, j = len + i -1;

注意考虑len == 1, len == 2是的特殊情况.


255. Burst Balloons.java Level: Hard

Range DP. 因为数组规律会变, 所以很难找'第一个burst的球'. 反之, 想哪一个是最后burst? 最后burst的那个变成一堵墙: 分开两边, 分开考虑, 加法原理; 最后再把中间的加上.

Range DP 三把斧:

  1. 中间劈开
  2. 砍断首或尾
  3. Range区间作为iteration的根本

Note: print the process. 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 之间的sum. 然后找哪个点开始burst? 设为x。 For loop 所有的点作为x, 去burst。 每次burst都切成了三份:左边可以recusive 求左边剩下的部分的最大值 + 中间3项相乘 + 右边递归下去求最大值。

这个是momorization, 而不纯是DP 因为recursive了,其实还是搜索,但是memorize了求过的值,节省了Processing


256. Scramble String.java Level: Hard

区间型 降维打击 dp[i][j][w]: 从i点和j点开始, 各自走w距离, 得到的S和T是否是scramble string.

具体思考过程看Thoughts.

注意: input s1, s2 在整个题目的主要内容里面, 几乎没有用到, 只是用在initialization时候. 很神奇, 这个initailization 打好了DP的基础, 后面一蹴而就, 用数学表达式就算出了结果.


257. Binary Search Tree Iterator.java Level: Medium

画一下, BST in order traversal. 用stack记录最小值, 放在top. O(h) space. 每次消耗TreeNode, 都看看rightNode(其实就是下一个最小的candidate), 并且一条龙stack叠上rightNode所有的left子孙.

Previous Notes: 用O(h)空间的做法:

理解binary search tree inorder traversal的规律: 先找left.left.left ....left 到底,这里是加进stack. 然后考虑parent,然后再right.

例如这题: stack里面top,也就是tree最左下角的node先考虑,取名rst. 其实这个rst拿出来以后, 它也同时是最底层left null的parent,算考虑过了最底层的parent。 最后就考虑最底层的parent.right, 也就是rst.right.

注意: next()其实有个while loop, 很可能是O(h).题目要求average O(1),所以也是okay的.

用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过,如此便会死循环。


258. Flatten Nested List Iterator.java Level: Medium

方法1: 用queue, 把需要的item全部打出来 方法2: 用stack, 把需要的item先存一行, 每次打开子序列时候, 全部加回stack.


259. Coins in a Line III.java Level: Hard

博弈 + 区间. S(x) = X - Y, 找最大数字差. 如果最大值都大于0, 就是赢了; 如果小于0, 就输了. dp[i][j]表示 从index(i) 到 index(j), 先手可以拿到的最大值与对手的数字差. 也就是S(x) = X - Y. dp[i][j] = max{a[i] - dp[i + 1][j], a[j] - dp[i][j - 1]}

最后判断 dp[0][n] >= 0

区间型动态规划: 找出[i, j]区间内的性质. 子问题: 砍头, 砍尾, 砍头砍尾 loop应该基于区间的length template: 考虑len = 1, len = 2; 设定i的时候一定是 i <= n - len; 设定j的时候, j = len + i - 1;

注意: 如果考虑计算先手[i, j]之间的最大值, 然后可能还需要两个数组, 最后用于比较先手和opponent的得分大小 => 那么就要多开维. 我们这里考虑的数字差, 刚好让人不需要计算先手的得分总值, 非常巧妙.


260. Best Time to Buy and Sell Stock with Cooldown.java Level: Medium

Sequence DP 跟StockIII很像. 分析好HaveStock && NoStock的状态, 然后看最后一步.


261. Find Peak Element.java Level: Medium

binary search. Goal: find peak, where both sides are descending 最左边, 最右边是Integer.MIN_VALUE时候, 也能构成中间数mid是peak的条件.

Note: 没有必要特别check (mid-1)<0或者(mid+1)>=n. 证明:

  1. 最左端: 当start=0, end = 2 => mid = 1, mid-1 = 0;
  2. 最有段: 当end = n - 1, start = n - 3; mid = (start+end)/2 = n - 2; 那么mid + 1 = n - 2 + 1 = n - 1 < n 是理所当然的

262. Longest Common Subsequence.java Level: Medium

经典序列型. 设定dp长度为(n+1), 因为dp[i]要用来表示前i个(ith)时候的状态, 所以长度需要时i+1才可以在i位置, hold住i.

双序列: 两个sequence之间的关系, 都是从末尾字符看起, 分析3种情况:

  1. A最后字符不在common sequence.
  2. B最后字符不在common sequence.
  3. A/B最后字符都在common sequence. 总体+1.

263. Interleaving String.java Level: Hard

双序列DP, 从最后点考虑. 拆分问题的末尾, 考虑和s1, s2 subsequence之间的关联.

求存在性, boolean


264. Letter Combinations of a Phone Number.java Level: Medium

方法1: Iterative with BFS using queue.

方法2: Recursively adding chars per digit


265. Edit Distance.java Level: Hard

两个字符串变话, 找最小值, two sequence DP. 考虑两个字符串变换的最后点: 相等, 互换, 还是缺少? 分析每种情况, 然后列出表达式.

注意, 在i或者j为0的时候, 变成另外一个数字的steps只能是全变.

第一步, 空间时间都是O(MN) 滚动数组优化, 空间O(N)


266. Distinct Subsequences.java Level: Hard

Double Sequence DP: 0. DP size (n+1): 找前nth的结果, 那么dp array就需要开n+1, 因为结尾要return dp[n][m]

  1. 在for loop 里面initialize dp[0][j] dp[i][0]
  2. Rolling array 优化成O(N): 如果dp[i][j]在for loop里面, 就很好替换 curr/prev

267. Regular Expression Matching.java Level: Review


268. Majority Element.java Level: Easy

方法1: Vote 计数, vote++, vote--到最后剩下的就是winner. Time O(n), Space O(1) Majority Number是指超半数. 超半数的数字, 最后都会至少有vote>=1: match current majority number,vote++;if not, vote--. 注意:assume valid input, 是一定有一个majority number的。否则此法不成。[1,1,1,2,2,2,3]是个invalid input,结果是3,当然也错了。

方法2: HashMap count occurance. Time, Space: O(n)

方法3: Bit manipulation. 还没有做.

Related Problems: Majority Number II,超1/3, 那么就分三份处理,countA, countB来计算最多出现的两个。

Majority Number III, 超1/k, 那么自然分k份。这里用到 HashMap。


269. Wildcard Matching.java Level: Hard

Double sequence DP. 与regular expression 很像.

注意1: 分析字符 ?, * 所代表的真正意义, 然后写出表达式. 注意2: 搞清楚initialization 的时候 dp[i][0] 应该always false.当p为empty string, 无论如何都match不了 (除非s="" as well) 同时 dp[0][j]不一定是false. 比如s="",p="*" 就是一个matching.


270. Ones and Zeroes.java Level: Hard

还是Double Sequence, 但是考虑第三种状态: 给的string array的用量. 所以开了3维数组.

如果用滚动数组优化空间, 需要把要滚动的那个for loop放在最外面, 而不是最里面. 当然, 这个第三位define在 dp[][][]的哪个位置, 问题都不大.

另外, 注意在外面calcualte zeros and ones, 节约时间复杂度.


271. Pow(x,n).java Level: Medium

傻做就O(n), 要更好就考虑O(logN). 减少重复计算, 一切两半.

注意:

  • n/2的奇数偶数
  • n的正负
  • n == 0的情况, x == 0, x == 1 的情况.

272. Word Break II.java Level: Review

两个DP一起用.解决了timeout的问题

  1. isWord[i][j], subString(i,j)是否存在dict中?

  2. 用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, 也有坐标反转的做法)

  3. dfs 利用 isValid 和isWord做普通的DFS。

Note: 在Word Break里面用了set.contains(...), 在isValid里面,i 从0开始。 但是,contains()本身是O(n).
在这道题里面应该是因为word dictionary太大,加上nest for, 变成O(n^3)所以timeout.

istead,用一个isWord[i][j],就O(1)判断了i~j是不是存在dictionary里面。


273. Nested List Weight Sum.java Level: Easy

方法1: 简单的处理nested structure, dfs增加depth. 方法2: bfs, queue, 处理queue.size().


274. Same Tree.java Level: Easy

DFS. 确定leaf条件, && with all sub problems.

如果用BFS: 两个queue存每个tree的所有current level node. Check equality, check queue size. Populate next level by nodes at current level.


275. Convert Sorted Array to Binary Search Tree.java Level: Easy

Binary Search Tree特点: 左边的node都比右边的node小. 如果要height相差<1, 必须左右sub tree均分. 做DFS.


276. Binary Tree Maximum Path Sum.java Level: Review

LeetCode: H 用 PathSumType 比较特别. 没有 data structure的时候, 写起来比较繁琐.

第一次做有点难理解,复杂原因是:因为可能有负值啊。不能乱assume正数。
single path max 的计算是为了给后面的comboMax用的。 如果single path max小于0,那没有什么加到parent上面的意义,所以就被再次刷为0.

combo的三种情况:(root可能小于0)

  1. 只有left
    2。 只有右边
  2. root大于0,那么就left,right,curr全部加起来。

情况1和情况2取一个最大值,然后和情况三比较。做了两个Math.max(). 然后就有了这一层的comboMax

12.11.2015 recap:
So totally, 5 conditions:
(save in single)
left + curr.val OR right + curr.val
(save in combo:)
left, right, OR left + curr.val + right


277. Construct Binary Tree from Inorder and Preorder Traversal.java Level: Medium

和Construct from Inorder && Postorder 想法一样。

写出Preorder和Inorder的字母例子,发现Preorder的开头总是这Level的root。依此写helper,注意处理index。


278. Path Sum.java Level: Easy

确定好结尾的条件, DFS

写一写: root == null => false 对parent nodes的影响. 这里发现没影响, 所以可以简化成用1个functionDFS.


279. Add Binary.java Level: Easy

方法一:土办法没技术,把binary换成数字,加起来,再换成binary。如果input很大,那么很可能int,long都hold不住。不保险。

方法二:一般方法,string化为charArray,然后逐位加起,最后记得处理多余的一个carry on 注意: 需要从末尾加起来, 所以要用一个diff来帮助 shortArray[i-diff] 指向 shortArray的相对应index.


280. Add Digits.java Level: Easy

方法1: 普通做法就是按照题意, double-while loop把数字加起来. 第一层循环是O(n), 然后第二层循环就少很多数位, overall O(n)

方法2: 找数学规律, 每过9个数字, 取mod就会开始重复, 所以给所有数字取mod 就可以间接找到答案. O(1)


281. Add Two Numbers.java Level: Medium

LinkedList都已经反转好了,直接做. 遍历两个l1,l2把carry-on处理好,每次生成一个新node,最后检查carry-on.

跟Add Binary的理解方式一模一样.

注意: Linked List 没有天然size. 用DummyNode(-1).next来hold住结果.


282. Add Two Numbers II.java Level: Medium

Singly-linked list需要reverse, 用stack. 最终结果要恢复成input list 那样的sequence方向, 用stack一个个pop()刚好就可以做到.

加法都一样:

  1. sum = carry
  2. carry = sum / 10
  3. sum = sum % 10;

283. Balanced Binary Tree.java Level: Medium

  1. DFS using depth marker: 每个depth都存一下。然后如果有不符合条件的,存为-1. 一旦有 <0 或者差值大于1, 就全部返回Integer.MIN_VALUE. Integer.MIN_VALUE比较极端, 确保结果的正确性。 最后比较返回结果是不是<0. 是<0,那就false. Traverse 整个tree, O(n)

  2. Only calculate depth using maxDepth function. Same concept as in 1, but cost more traversal efforts.


284. Valid Anagram.java Level: Easy

HashMap


285. Populating Next Right Pointers in Each Node.java Level: Medium

方法1:
题目要求DFS. 想清楚了如何在DFS level把几种情况都考虑了, 写起来很简单. 其实basic implementation, 每次处理next链接:

  1. node.left.next = node.right
  2. If node.next != null, link node.right.next = node.next.left;

方法2:
不和题意,用了queue space,与Input成正比。太大。

BFS over Tree。 用Queue 和 queue.size(),老规矩。
process每层queue时, 注意把next pointer加上去就好.


286. Validate Binary Search Tree.java Level: Medium

查看每个parent-child关系。同时把root level上面传下来max,min界限定住。

Note: min/max需要时long type. 如果题目真的给node.val = Integer.MAX_VALUE, 我们需要能够与之比较, long就可以.


287. Convert Sorted List to Binary Search Tree.java Level: Medium

Divide and Conquer
找到mid node

方法1: 用长度来定位mid, 每次找中间点做root, 然后前半段, 后半段分别dfs with length.

方法2: 用快慢pointer 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一下。


288. Flatten Binary Tree to Linked List.java Level: Medium

分析题意后, 按照题意:

  1. reserve right child
  2. DFS flatten部分
  3. 移花接木
  4. flatten 剩下的.

289. Binary Tree Paths.java Level: Easy

方法1:
Recursive:分叉. dfs.

方法2: Iterative:
非递归练习了一下
因为要每次切短list, 所以再加了一个Stack 来存level


290. Minimum Size Subarray Sum.java Level: Medium

方法1: 2 pointer, O(n). 找subarray, start 或 end pointer,每次一格这样移动.

好的策略:

  1. 先找一个solution, 定住end.
  2. 然后移动start; 记录每个solution if occurs
  3. 然后再移动end,往下找。

Note: 虽然一眼看上去是nested loop.但是分析后,发现其实就是按照end pointer移动的Loop。start每次移动一格。总体上,还是O(n)

方法2: Double for loop, base i 每次只+1, 所以最后O(n^2)

方法3: Binary Search, O(nLogN) Not done yet


291. Clone Graph.java Level: Medium

思想: Use HashMap to mark cloned nodes.
先能复制多少Node复制多少. 然后把neighbor 加上

方法一: DFS

  1. copy the node
  2. Mark 'added' using map(old, new)
  3. for loop on the each one of the neighbors: map copy, record in map, and further dfs
  4. once dfs completes, add newNeighbor as neighbor of the new node (get to it via map) 主要思想是: 一旦复制过了, 不必要重新复制

方法二: BFS

  1. Copy the root node, then copy all the neighbors.
  2. Mark copied node in map.
  3. Use queue to contain the newly added neighbors. Need to work on them in the future.

注意: initialize map with (node, newNode)


292. Longest Substring Without Repeating Characters.java Level: Medium

方法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'


293. Minimum Window Substring.java Level: Hard

基本思想: 用个char[]存string的frequency. 然后2pointer, end走到底, 不断validate. 符合的就process as result candidate.

HashMap的做法比char[]写起来要复杂一点, 但是更generic


294. Linked List Cycle.java Level: Easy

O(1) sapce: 用快慢指针。一个跑.next, 一个跑.next.next。 总有一次,fast会因为cycle而追上slow。 那个时候其实slow.val = fast.val.

O(n) space: 用HashMap,一直add elements. 如果有重复,那么很显然是有Cycle


295. Remove Nth Node From End of List.java Level: Medium

O(n), one pace, no extra space 找到窗口, 然后平移, 最后pre 和 head之间 skip一个node就好.


296. Longest Substring with At Most K Distinct Characters.java Level: Medium

大清洗 O(nk)
map.size一旦>k,要把longest string最开头(marked by pointer:start)的那个char抹掉
一旦某一个char要被清除,所以在这个char 的1st and last appearance之间的char都要被清洗from map


297. Linked List Cycle II.java Level: Medium

方法1: 快慢指针, O(1)space.

确认有cycle后, 其实是数学问题: 当head == slow.next时候, head就是cycle starting point. 也就是说,当slow 移动到了那个回溯点,slow.next那个点就刚好是head的那个点...

证明:

  1. 假设慢指针走t步, 快指针走快一倍, 也就是2t.
  2. 我们假设cycle的长度是Y, 而进入cycle之前的长度为X.
  3. 假设慢指针走了m圈cycle, 而快指针走了n圈cycle之后, 两个pointer相遇.
  4. 最终在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: HashMap, O(n) space


298. Kth Smallest Number in Sorted Matrix.java Level: Medium

方法1: 和Merge K sorted Array/ List 类似:使用PriorityQueue。

因为Array的element无法直接找到next,所以用一个class Node 存value, x,y positions.

方法2: Binary Search https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85182/my-solution-using-binary-search-in-c

变型: Kth Largest in N Arrays


299. Find Minimum in Rotated Sorted Array.java Level: Medium

画图, 最小值被rotate之后, 变成array中间的最低谷. 并且, 左边山坡的最小值, 大于右边山坡的最大值. 以此来给binary search做判断.

O(nlogn)


300. Find Minimum in Rotated Sorted Array II.java Level: Hard

一个需要严谨思考的题目. 因为有duplicate, 会导致不断平移, 所以最终time complexity是O(n) 所以不如直接扫一遍, 给出答案.

但是还是写一个Binary Search的样子, 只不过worst结果是O(n)


301. Connecting Graph.java Level: Medium

没有跑过这个程序, 是一个UnionFind的简单实现. Document了每个环节的计算原理/思想.


302. Connecting Graph II.java Level: Medium

Lint还不能跑, 全部按照题意和答案document的.


303. Connecting Graph III.java Level: Medium

还是UnionFind的变形, 这次是算有剩下多少个union. 其实非常简单, 维持一个全局变量count: 一开始count=n, 因为全是散装elements; 每次union, count--.


304. Number of Islands.java Level: Medium

方法1: 两个for loop brutle force。 DFS把每个跟1相关的都Mark一遍.生成一个island.

方法2: 可以用union-find, 就像Number of island II 一样。 只不过这个不Return list, 而只是# of islands 记住UnionFind的模板和几个变化(Connecting Graph I, II, III), 最后归总的代码写起来就比较简单.


305. Number of Islands II.java Level: Hard

方法1: 用int[] father 的unionFind, 需要转换2D position into 1D index. count的加减, 都放在了UnionFind自己的function里面, 方便tracking, 给几个helper function就对了. 这样比较clean Time: O(k * log(mn))

方法2: 用HashMap的Union-find.

把board转换成1D array, 就可以用union-find来判断了。 判断时,是在四个方向各走一步,判断是否是同一个Land. 每走一次operator,都会count++. 若发现是同一个island, count--

Side Note: Proof of UnionFind log(n) time: https://en.wikipedia.org/wiki/Proof_of_O(log*n)_time_complexity_of_union%E2%80%93find


306. Graph Valid Tree.java Level: Medium

复习Union-Find的另外一个种形式。
题目类型:查找2个元素是不是在一个set里面。如果不在,false. 如果在,那就合并成一个set,共享parent.
存储的关键都是:元素相对的index上存着他的root parent.

注意: 结尾要检查, 是否只剩下1个union. Tree必须连接到所有给出的node.

另一个union-find, 用hashmap的:http://www.lintcode.com/en/problem/find-the-weak-connected-component-in-the-directed-graph/


307. Surrounded Regions.java Level: Review

从四个边的edge出发, 像感染僵尸病毒一样扩散, 把靠边的node全部mark, 然后将还是'O'的改成X, 最后回复marks -> 'O'

方法1: UnionFind里面这次用到了一个rank的概念, 需要review

方法2,3: DFS, BFS都好理解,


308. Implement Trie.java Level: Medium

Tire, 也即是 Prefix Tree.

HashMap构建Trie。 Trie三个Method: 

  1. Inset: 加 word
  2. Search: 找word
  3. StartWith: 找prefix

只有两条children的是binary tree. 那么多个children就是Trie。 那么没有left/right pointer怎么找孩子?
用HashMap,以child的label为Key,value就是child node。 HashMap走位

Note:
node里的char在这是optional. 只要在每个TrieNode里面用map存储向下分布的children就好了.
另外有种题目,比如是跟其他种类的search相关,在结尾要return whole string,就可以在node里存一个up-to-this-point的String。


309. Add and Search Word.java Level: Medium

Trie结构, prefix tree的变形: '.'可以代替任何字符,那么就要iterate这个node所有的children.

节点里面有char, isEnd, HashMap<Character, TrieNode>
Build trie = Insert word:没node就加,有node就移动。
Search word:没有node就报错. 到结尾return true

这题因为'.'可以代替任何possible的字符,没一种都是一个新的path,所以recursive做比较好些。
(iterative就要queue了,麻烦点)


310. Word Search II.java Level: Hard

相比之前的implementation, 有一些地方可以优化:

  1. Backtracking时候, 在board[][] 上面mark就可以, 不需要开一个visited[][]
  2. 不需要implement trie的所有方程, 用不到: 这里只需要insert. 普通的trie题目会让你search a word, 但是这里是用一个board, 看board的每一个字母能不能走出个Word. 也就是: 这里的search是自己手动写, 不是传统的trie search() funcombination
  3. TrieNode里面存在 end的时候存string word, 表示到底. 用完了 word = null, 刚好截断重复查找的问题.

Previous Notes: Big improvement: use boolean visited on TrieNode!
不要用rst.contains(...), 因为这个是O(n) 在leetcode还是会timeout(lintcode竟然可以pass)!
在Trie search() method 里面,凡是visit过的,mark一下。

Regular:
for loop on words: inside, do board DFS based on each word.
Time cpmplexity: word[].length * boardWidth * boardHeight * (4^wordMaxLength)

Build Trie with target words: insert, search, startWith.
依然要对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])


311. Word Search.java Level: Medium

Backtracking: 找到开头的字母, 然后recursively DFS 去把word串到底: 每到一个字母, 朝四个方向走, 之中一个true就可以.

Note:每次到一个字母,mark一下'#'. 4个path recurse回来后,mark it back.

Backtracking方法2:
用一个boolean visited[][]