Medium题目总结

来源:互联网 发布:左耳最后说了什么 知乎 编辑:程序博客网 时间:2024/06/01 21:33

方法技巧题:

search a 2D matrix II: https://leetcode.com/problems/search-a-2d-matrix-ii/

从左下角开始,如果元素大于target则行减1,如果小于target则列加1


product of array except self: https://leetcode.com/problems/product-of-array-except-self/

设定两个数head和tail分别记录前/后n个元素的乘积,让head从前往后遍历,tail从后往前遍历,每次先更新结果数组的元素再更新head/tail


Repeated DNA Sequence: https://leetcode.com/problems/repeated-dna-sequences/

因为只有ATCG四种可能,所以可以用2'b00, 2'b01, 2'b10和2'b11分别代表。使用一个int变量作为hash table的index,

index = index << 2 | getIndex(s[i]) & 0xfffff,当在hash table中对应的值是1时将序列放入结果(0xffff是20个1'b1,20=10*2)


Minimum Size Subarray Sum: https://leetcode.com/problems/minimum-size-subarray-sum/

想象一个window从array开头开始增长,对window内的元素求和,先右端右移增加到sum >= s,然后再将左端右移并从sum中减去被移出window的元素直到sum < s,计算当前window长度,若小于min_len则将min_len更新为当前window长度+1。注意要将min_len更新为array的size+1(O(n))

另一种相对复杂的O(n log n)算法是先将array从前向后求和存储在新array叫sums中,对sums中每一个>=s的元素,寻找从sums左侧开始第一个大于sums[i] - s的元素(叫first),则从first开始到 i 就是一个可能的min_len。寻找first时用binary search效率较高


3Sum: https://leetcode.com/problems/3sum/

先sort数组,然后遍历数组。对于某个元素 i,先求反,令head = i + 1,tail = nums.size() - 1,检查head + tail是否等于-nums[i],若小于则head++,大于则tail--,等于就push_back到结果里。push_back之后将 i 移动到下一个与当前nums[i]不相等的地方


Set Matrix Zeros: https://leetcode.com/problems/set-matrix-zeroes/

对于每一行,如果要置零,则将该行第一个元素置零;对于每一列,如果该列要置零,则把该列第一个元素置零。特别要注意的是,[0][0]表示的是第一行,第一列是否置零单独用一个变量表示,并且当列(j)为0时,更新这个变量而非该列第一个元素(因为它是[0][0])


Remove Duplicates from Sorted Array II: https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/

将count初始化为0,检查[i - 2 - count]与[i]是否相等,如果相等count++,否则替换[i - count]为[i]


Convert Sorted List to Binary Search Tree: https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/

感觉自己的方法对边界的处理比较巧妙。。helper函数的第二个参数是list的tail的下一个节点,因为通过slow/fast指针找到list的中点之后,并不能知道slow前面的节点是什么,所以递归调用helper(head, slow)时,是对head到slow但不包括slow的所有节点进行处理


Binary Tree Postorder Traversal: https://leetcode.com/problems/binary-tree-postorder-traversal/

按照root,right,left的顺序traversal树,然后对result进行reverse即可


Binary Tree Inorder Traversal: https://leetcode.com/problems/binary-tree-inorder-traversal/

设一个指针t,初始化为root,当t不为NULL时将其加入栈且让t = t->left;若t == NULL则从栈中pop出一个节点赋给t,将t->val加入result,然后令t = t->right。只要t != NULL或stack不为空就重复这个过程


Lowest Common Ancestor of a Binary Tree: https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/

使用递归。如果当前节点为NULL/p/q则返回,由于返回顺序是从底层到顶层,所以如果其中一个节点是另一个节点的祖先则祖先节点将被返回。对左右儿子分别进行递归,如果左右儿子的返回值都不为NULL,说明p、q在当前节点的两侧,则当前节点就是LCA,否则LCA就是不为NULL的那个返回值


Linked List Cycle II: https://leetcode.com/problems/linked-list-cycle-ii/

slow == fast后,令slow = head,然后slow和fast都移动一步,二者相等时即为cycle的开始。注意两点:1)slow和fast都要从head开始;2)slow和fast相等后,要用fast == NULL || fast->next == NULL来判断有没有cycle,而不能用slow == fast,因为slow和fast都初始化为head


Longest Substring Without Repeating Characters: https://leetcode.com/problems/longest-substring-without-repeating-characters/

最简单的方法是检查每一个字符开始的最长序列,这样的时间复杂度为O(n ^ 2)。更好的方法是设置一个名为index大小为256的vector记录每个字符上次出现的位置(初始化为-1),用一个变量start表示没有重复字符的substring的开始。遍历string,如果index[s[i]] >= start,则说明当前字符在之前出现过,且上次出现的位置在start或之后(也就是说当前substring已经包含了s[i]),这时,将start更新为index[s[i]] + 1,这样就保证从start到当前位置永远是一个没有重复字符的subtring


Longest Palindromic Substring: https://leetcode.com/problems/longest-palindromic-substring/

寻找回文串时,设一个left一个right,初始化为相同的值mid,如果s[right] == s[right + 1]则增加right直到二者不相等,然后令mid = right + 1;然后如果s[left - 1] == s[right + 1],那么left--,right++,直到left/right到达边界或二者不再相等停止,记录当前回文串的长度,如果是当前的最大值则记录当前字符串的长度和起始位置。当size - mid > max_len / 2时一直重复这个过程


Binary Search Tree Iterator: https://leetcode.com/problems/binary-search-tree-iterator/

设置一个cur指针指,设置一个stack。next()函数将cur所有的左儿子存储在stack中,直到cur成为NULL,然后将cur设为stack的top的右儿子,并将stack的top返回。当cur != NULL || !s.empty()时则还有节点未被输出


Jump Game: https://leetcode.com/problems/jump-game/

用变量last表示能当前能达到的最远位置,令 i = 0开始,在它小于等于last时(last之前的位置都能到达),last = max(last, i + nums[i]),如果last >= nums.size() - 1则返回true;如果for循环退出则返回false


Container With Most Water: https://leetcode.com/problems/container-with-most-water/

先计算最宽的面积和高度h,如果有更大的面积,则它的高度应该大于当前高度。当height[left] <= h时left++并且当height[right] <= h时right--


3Sum Smaller: https://leetcode.com/problems/3sum-smaller/

设置一个 i 遍历0 ~ size - 2,对每次遍历,设置start = i + 1,end = size - 1,在start < end时,减小end直到第一个end满足[i] + [start] + [end] < target,将 end - start添加到结果。为了使时间复杂度为O(n ^ 2)需要注意两点:1)对每个 i 内的循环,start一直增加,结束的条件是start < end,这样才能保证 i 内一次循环的时间复杂度为O(n);2)对于start内的循环,end初始化为size - 1,以后就以上一次的end作为新的end,而不再重新初始化


Permutation Sequence: https://leetcode.com/problems/permutation-sequence/

初始化一个名为num的char型vector存放[1, n],结果左边的第 i 位(i = 0 ~ n - 1)都是用num的第tmp = k / (n - i - 1)! 个,使用完后将用掉的数字erase掉,然后令k -= tmp * fact(n - i - 1)。注意以下几点:1)当k == 1时,其实是用的所有可选的第一个,下标是0,所以初始化时k--;2)结果一共有n个数字,所以循环应该重复n次,然而在计算倒数第二位时已经是fact(1),所以在设置fact的vector时应该多加一个fact[0] = 1,保证在处理最后一位时不用进行特殊处理

这道题也可以借用next permutation的方法去做


Gray Code: https://leetcode.com/problems/gray-code/

如果已经获得了(n - 1)的Gray Code,只要将结果最左边添加一个'1'再逆序加入结果即可获得(n)


Reverse Linked List II: https://leetcode.com/problems/reverse-linked-list-ii/

为了不出错,找到before_m,node_m,node_n,after_n,然后reverse node_m到node_n,最后再将链表连起来。注意要使用dummy


Sort Colors: https://leetcode.com/problems/sort-colors/

利用类似partition array的思想(quick sort的一步),将zero初始化为0,two初始化为size - 1,用 i 遍历整个数组,如果nums[i] == 0则swap(nums[zero++], nums[i++]),如果nums[i] == 1则i++,如果nums[i] == 2则swap(nums[i], nums[two--])

另一种方法是用red,white,blue分别表示已遍历序列中0、1、2最后一个位置的下一个位置,对新访问到的元素,将其插入到对应的位置,并将它后面的颜色向右移动一个,具体实现方法为:如果nums[i] == 2,则三者所指向的位置都被设为对应的值并且都右移一个位置;如果nums[i] == 1,则只有white和blue设成对应的值并右移一个位置;如果nums[i] == 2则只要将blue右移一个位置即可。方法的思维难度较大,记住就行了。。见详细代码


Gas Station: https://leetcode.com/problems/gas-station/

计算sum += gas[i] - cost[i],total += gas[i] - cost[i]。当sum < 0时,说明从index无法到达当前的 i,则下一个可能成立的index = i + 1。最后如果total < 0则返回 -1,否则返回index。如果从index无法到达 i ,则在index和 i 之间的所有点都无法到达 i,而index之前的所有节点都不可能到达index,所以可能的正确答案只在 i 的后面。当 i = size - 1时,如果此时sum < 0,则说明size - 1之前的所有节点都无法到达size - 1,而size - 1之后没有节点了,则此时肯定是没有满足条件的节点的,所以 index = i + 1也不会出错


Fraction to Recurring Decimal: https://leetcode.com/problems/fraction-to-recurring-decimal/

使用一个unordered_map记录remainder和它出现时result的长度;检查remainder是否已经出现过,如果已经出现则在它上次出现的位置插入"(",然后result = result + ')',退出;否则将其加入hash table,remainder *= 10,将remainder / denominator加入result,并令remainder %= denominator


Contains Duplicate III: https://leetcode.com/problems/contains-duplicate-iii/

创建一个二叉树,每个节点存储nums[i]和 i,以nums[i]为序。遍历vector中的元素,如果树中不存在满足条件的元素则进行插入(注意如果nums[i]的值在树中已经存在,需要将这个节点的index更新为当前的 i)。查找时,首先令nums[i]在正确范围内,然后再判断 i 是否满足条件。

这道题简单的做法是用一个set,每次插入前先删除nums[i - k - 1],然后用lower_bound判断大于等于nums[i] - t的元素是否存在并且它是否小于等于t,如果存在就返回true,否则将新元素插入(其实set也是用BST实现的)


H-Index: https://leetcode.com/problems/h-index/

O(nlogn): 先排序,h初始化为N,若citations[i] >= h则返回h,否则h--

O(n): 用一个vector记录对应引用次数出现的次数,记录完后从右边开始累加,直到sum >= hash[i]返回 i


Count Complete Tree Nodes: https://leetcode.com/problems/count-complete-tree-nodes/

分别计算左儿子和右儿子的最左儿子的深度,如果二者相等,则左子树是满的,加上左子树的节点个数,从右子树开始下一次循环;否则右子树是满的,加上右子树节点的个数,从左子树开始下一次循环。注意新循环开始后不需要重新计算left_level,将其 -1 即可,但right_level要重新计算


Ugly Number II: https://leetcode.com/problems/ugly-number-ii/

设一个vector名为result,表示第[i + 1]个ugly number,则result[i] = min(result[two] * 2, min(result[three] * 3, result[five] * 5)),其中two/three/five分别表示当前result中,第一个满足result[two] * 2 / result[three] * 3 / result[five] * 5 大于目前所有ugly number的下标。注意每次计算完result[i]后,都要通过result[i] == result[two/three/five] * 2/3/5判断是否要对two/three/five加1,因为比如6 == 2 * 3 == 3 * 2会出现两次


Permutations II: https://leetcode.com/problems/permutations-ii/

这道题有三种方法:

1)每次erase掉一个节点,然后再insert回来(backtracking)

2)用一个unordered_map记录每个值出现的次数,每次遍历所有值,当出现次数>0时加入tmp并令次数-1,然后递归调用,完成后将这次加入tmp的值pop_back,并令次数+1,直到tmp.size() == nums.size()(backtracking, DFS)

3)借用nextPermutation,先排序,再依次计算下一个permutation并加入结果


Basic Calculator II: https://leetcode.com/problems/basic-calculator-ii/

逻辑上简单的方法是用一个stack,当操作符为'+'时 tmp 入栈,当操作符为'-'时 -tmp 入栈,当操作符为 '*'或'/' 时进行计算,结果入栈,最后将所有的栈中元素加起来

空间为O(1)的方法是,设置一个op记录之前的操作符,再用result和cur记录最终结果和临时结果。当输入为数时cur每次根据操作符进行计算;当输入为操作符时,更新op,如果是'+'或'-',则之前计算的所有结果都是正确的,result += cur,cur = 0;否则是'*'或'/',则不更新result。最后返回result + cur


Majority Element II: https://leetcode.com/problems/majority-element-ii/

使用Boyer-Moore Majority Vote算法,即先选一个数作为candidate,如果遇到的数等于candidate,count++,否则count--,如果count == 0则选择当前的数作为新的candidate并令count = 1,最后检查candidate出现的次数是否满足要求


Different Ways to Add Parentheses: https://leetcode.com/problems/different-ways-to-add-parentheses/

遍历input,如果当前字符是操作符,则递归计算input.substr(0, i)和input.substr(i + 1),然后将二者的结果组合加入result;如果result为空,则说明没有操作符,全是数字,则直接将stoi(input)加入结果即可


Meeting Rooms II: https://leetcode.com/problems/meeting-rooms-ii/

两种方法:1)使用map<int,int>,[start]++表示需要一个新的房间,[end]--表示释放一个房间,由于map是排序的,遍历map用sum计算累加和,则result = max(result, sum)

2)使用最小堆记录当前需要的房间数。首先将输入按start排序,然后遍历输入,当当前的end >= heap.top()时,说明heap.top()已经结束,将其pop出,然后再将当前的end插入heap,则result = max(result, static_cast<int>(heap.size()))


Flatten 2D Vector: https://leetcode.com/problems/flatten-2d-vector/

当有空vector时比较容易出错。对hasNext()它的功能是寻找是否存在下一个元素,如果存在则令i,j指向该元素,如果当前vector为空,则i++直到找到新的非空vector或越界为止


Verify Preorder Sequence in Binary Search Tree: https://leetcode.com/problems/verify-preorder-sequence-in-binary-search-tree/

用一个stack记录所有的节点。模拟preorder遍历的过程,从根节点开始,如果当前节点大于栈顶元素,说明当前节点是栈顶节点的右儿子/栈顶节点的祖先节点的右儿子,也就是说栈顶元素所有的左儿子已经遍历完了,那么后面再遇到的所有节点都应该大于栈顶元素,令root = top()并pop,直至栈为空或当前节点不再大于top(),然后将当前节点入栈。然后,后面遇到的所有节点要么是root的右儿子,要么是root的祖先的右儿子,也就是说后面所有的节点都应该大于root,如果出现小于root的,则返回false

如果想要空间为O(1),则需要将输入数组作为栈。可以设置一个变量名为stack_top,表示栈顶在输入中的index,有元素出栈则stack_top--,入栈则stack_top++


Range Sum Query 2D: https://leetcode.com/problems/range-sum-query-2d-immutable/

设一个2D vector,令sum[i][j]表示matrix[0][0] + ... + matrix[i][j]。在计算sum时可以使用一个row_sum存储当前行从开始到现在的sum,则sum[i][j] = (i > 0 ? sum[i - 1][j] : 0) + row_sum


Range Sum Query - Mutable: https://leetcode.com/problems/range-sum-query-mutable/

要使用binary indexed tree。详细参考:

http://www.geeksforgeeks.org/binary-indexed-tree-or-fenwick-tree-2/

https://zh.wikipedia.org/wiki/%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84


Minimum Height Trees: https://leetcode.com/problems/minimum-height-trees/

建立每个节点的neighbor并求出度。每次将所有的叶子节点(也就是度为1的点)向上移动一个位置,然后更新所有节点的度,将度为1的点加入queue,计算未被访问的节点的个数count,直到count<=2。注意每一轮要让所有的叶子节点都向上移动一次,一整轮结束后才判断count。思想参考:http://algobox.org/minimum-height-trees/


Sparse Matrix Multiplication: https://leetcode.com/problems/sparse-matrix-multiplication/

A[i][j]只能和B[j][*]相乘。遍历A,当A[i][j] != 0 时,遍历B[j][k = 0...],当B[j][k] != 0时,result[i][k] += A[i][j] * B[j][k]


Binary Tree Vertical Order Traversal: https://leetcode.com/problems/binary-tree-vertical-order-traversal/

给每个节点一个权值叫position,root的是0,左儿子-1,右儿子+1,然后用一个queue层序遍历树并记录在map<int, vector<int> >中,最后按权值由小到大输出即可。注意不要用递归,因为要保证上层的节点出现在权值相同的vector的前面


Wiggle Sort II: https://leetcode.com/problems/wiggle-sort-ii/

O(nlogn)时间,O(n)空间:先将nums存在sorted中,然后对sorted排序,将前一半元素逆序放在nums的偶数位置,将后一半逆序放在nums的奇数位置

O(n)时间,O(n)空间:先用std::nth_element找出中位数,然后利用类似color sort的方法进行partition(小于mid的放左边,等于mid的放一起,大于mid的放右边)。然后创建一个新的vector,逆序将前一半放在偶数位置,逆序将后一半放在奇数位置,最后将这个新的vector赋给nums

O(n)时间,O(1)空间:类似上一种方法,不过避免partition而直接用交换将元素放到正确的位置,需要建立原位置与新位置的对应关系,参考:

https://leetcode.com/discuss/77133/o-n-o-1-after-median-virtual-indexing


Patching Array: https://leetcode.com/problems/patching-array/
用变量miss表示当前可能缺失的最小的数,初始化为1,则前面遍历过的所有数可以表示[1, miss)。如果(i < nums.size() && nums[i] <= miss),则可以将nums[i]加到[1, miss)中,从而将可能缺失的最小的数变为miss + nums[i];否则,miss是无法表示的,则添加miss,此时可能缺失的最小的数就变成了miss + miss。要注意一下miss在做加法的时候可能会发生溢出,要进行下判断(或直接将miss声明为long)


Verify Preorder Serialization of a Binary Tree: https://leetcode.com/problems/verify-preorder-serialization-of-a-binary-tree/

形式为"X,#,#"的节点可以转化为一个"#"节点,其中"X"表示任意一个整数。当无法再找到",#,#"时,检查结果是否为"#",如果是则返回true,否则返回false。注意"X"可能有多个数字,所以在找到",#,#"的起始位置后应向前搜索到","


Largest BST Subtree: https://leetcode.com/problems/largest-bst-subtree/

O(n),recursive:top-down,用一个helper函数记录每个节点的size,min_value和max_value,当一个节点的左右儿子都是valid并且它大于左儿子的max_value,小于右儿子的min_value时更新result

O(nlogn),non-recursive:bottom-up,对树进行post-order traverse,用unordered_map记录每个顶点的size和是否valid,对一个节点,也是只有它的左右儿子都valid并且大于左儿子的最大值,小于右儿子的最小值时更新result(每个节点也只访问一次,logn的来源是寻找左儿子的最大值和右儿子的最小值。其实也可以用O(n)实现,不过要再多用两个unordered_map记录以某个节点为根的树的最大值和最小值)


Increasing Triplet Subsequence: https://leetcode.com/problems/increasing-triplet-subsequence/

可以用DP求出以每个元素为结尾的最长增长序列,大于等于3时返回true。但有更简单的方法就是用num1和num2,保证num1 < num2,然后遍历所有元素,如果nums[i] <= num1则更新num1,否则如果nums[i] <= num2则更新num2,否则num[i] > num2 > num1,返回true


Top K Frequent Elements: https://leetcode.com/problems/top-k-frequent-elements/

除了使用priority_queue之外(O(nlogk)),这道题还可以用bucket sort做:先统计每个数出现的次数,然后声明一个vector<vector<int> > bucket,大小是nums.size(),bucket[i]存放的是出现次数为i + 1的数,然后将出现次数由多到少的数加入结果,直至得到k个数为止。时间复杂度为O(n)


Flatten Nested List Iterator: https://leetcode.com/problems/flatten-nested-list-iterator/

1)通过递归将所有的元素存在一个queue里,再一个一个返回

2)用一个stack存储某个NestedInteger,hashNext()函数负责保证栈顶是一个int:如果栈顶是int则返回true,否则持续pop并将其vector中的元素逆序放入stack直至栈为空或栈顶为int;next()负责返回栈顶的整数并将其pop


Design Snake Game: https://leetcode.com/problems/design-snake-game/

用deque存储body,queue存储food,需要注意的一点是在检查是否会碰到自己时应该先把尾巴pop_back掉。可以额外用一个unordered_set<int>来储存body以加快对body的搜索


Reverse Words in a String: https://leetcode.com/problems/reverse-words-in-a-string/

用count记录要消除的空格的数量,然后通过s[i - count] = s[i]将要保留的字符移动到正确的位置,再数出最后的空格以得到结果的size,然后将结果reverse,再对每个单词内部进行reverse即可


Line Reflection: https://leetcode.com/problems/line-reflection/

找出最大和最小的x坐标,则如果有对称轴一定是line = x_min + x_max。用unordered_map<int, unordered<set> >记录所有的坐标(y - x),对每一个点检查(line - x, y)是否存在即可


Sort Transformed Array: https://leetcode.com/problems/sort-transformed-array/

不要从对称轴开始找,从数组的两端开始找。当 a <= 0时,按从小到大的顺序加入result;在a > 0时,先将大的加入结果,最后再reverse即可


Bomb Enemy: https://leetcode.com/problems/bomb-enemy/

用count[i][j]表示当前位置所能bomb的敌人总数量,head/tail分别记录从开头/结尾到当前位置所能bomb的敌人数量,用head和tail更新count[i][j],再分别更新head/tail即可,思想参考product of array except self


Nested List Weight Sum II: https://leetcode.com/problems/nested-list-weight-sum-ii/

仍然用递归。假设最外层的权值是x,则每深入一层权值就变为x - 1, x - 2, ...,所以用depth记录最大层数,cur记录当前层数,用count记录对应每一层所加的x的个数,则每层sum -= getInteger() * cur; count += getInteger()。

这道题也可以转化成iterative的,可以用一个stack<pair<iterator, iterator> >,first是当前的iterator,second是.end(),这样就可以避免递归






Binary Search:

Find Minimum in Rotated Sorted Array: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

用binary search寻找从左边开始第一个小于最后一个元素


Find Peak Element: https://leetcode.com/problems/find-peak-element/

如果mid是peak,返回;如果mid在上升区间,left = mid;否则,right = mid


Divide Two Integers: https://leetcode.com/problems/divide-two-integers/

设置一个tmp初始化为divisor,count初始化为1,如果tmp左移一位小于等于dividend,则将其左移一位,并将count左移一位,直到tmp左移一位后不再小于dividend,将count加到result里,令dividend -= tmp,然后再将tmp初始化为divisor,重复这个过程直到dividend < divisor。注意两点:1)必须声明dd和ds为long long,因为取绝对值时INT_MIN会overflow;2)对于long long取绝对值要用labs函数


Pow(x, n): https://leetcode.com/problems/powx-n/

不用管n的正负,只要n != 0就 x *= x,n /= 2,如果n是奇数则result *= x


Search in Rotated Sorted Array II: https://leetcode.com/problems/search-in-rotated-sorted-array-ii/

不要找起点,用分类讨论直接找。需要注意的是,除了nums[mid] > nums[right]和nums[mid] < nums[right]的情况外,如果nums[mid] == nums[right],我们无法知道结果到底在left ~ mid还是mid ~ right,因为nums[mid] == nums[right],所以我们可以令right--而不会丢失唯一的元素





Dynamic Programming:

Coin Change: https://leetcode.com/problems/coin-change/

声明数组num[amount + 1],全部初始化为0。i开始遍历num[0, amount],依次加硬币的所有面值,检查以得到数值大小为下标的num是否为0,如果是0,则将它更新为num[i] + 1,否则将其置为min(原值,num[i] + 1)


Unique Binary Search Trees II: https://leetcode.com/problems/unique-binary-search-trees-ii/

问题可分解为子问题:从[1, n]中选一个作为root,求以root为根,[1, root - 1]为左儿子,[root + 1, n]为右儿子。通过递归求出左右儿子所有的可能性(此时得到的左/右儿子的vector已经是完整的左/右子树了),再将所有左右儿子的组合push进result即可。需要注意的是,为了在某个儿子为NULL时方便地组合左右儿子,当helper函数的左边界大于右边界时返回的是vector<TreeNode*> (1, NULL)。然而当输入的n == 0时,最顶层函数结果应该是一个空的vector而不是含有一个NULL元素的vector,所以要对n == 0进行特殊处理


Decode Ways: https://leetcode.com/problems/decode-ways/

不要初始化dp数组的前两个元素,将dp声明为size + 1,添加一个dp[0] = 1。每次只判断当前字符和它前面的一个字符


House Robber II: https://leetcode.com/problems/house-robber-ii/

可以看成是求[0, n - 2]和[1, n - 1]的最大值。空间复杂度可以优化为O(1)


Best Time to Buy and Sell Stock: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

设置一个变量min_price记录访问过的最小价格min_price = min(min_price, price[i]),设定max_profit = max(max_profit, price[i] - min_price)


Best Time to Buy and Sell Stock with Cooldown: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

设置两个大小为size + 1的数组buy和sell,分别表示在第 i 天,当操作序列的最后一次操作为buy/sell时的最大利润。对于buy,可能今天买,由于i - 1天是cooldown,则此时的值buy[i] = sell[i - 2] - price[i];也可能最后一次买发生在昨天或更早,今天什么也不做,则此时buy[i] = buy[i - 1],所以,buy[i] = max(buy[i - 1], sell[i - 2] - price[i])。对于sell类似,sell[i] = max(sell[i - 1], buy[i - 1] + price[i])。最后return sell[i]即可。需要注意的是,声明的数组大小为size + 1,在最前面添加了一个[0],对于buy,buy[0]要置为INT_MIN,因为buy[1]肯定是负的;对于sell[0]置零即可

这种算法的空间复杂度可以优化为O(1),通过不声明数组而使用4个变量pre_buy,pre_sell,buy和sell即可。要注意初始化时令pre_sell = sell = 0,pre_buy = INT_MIN;在循环中,在更新了buy之后再更新pre_sell,因为buy[i]用到的是sell[i - 2]


Maximum Product Subarray: https://leetcode.com/problems/maximum-product-subarray/

设定max_value和min_value分别记录以[i -1]为结尾的连乘subarray的最大和最小值。在更新max_value时,选择:连乘到当前位置/以当前位置作为新的subarray起始的值,二者中较大的那个:max_value = max(max_value * nums[i], nums[i])。min_value同理。每次loop更新result = max(result, max_value)


Maximum Subarray: https://leetcode.com/problems/maximum-subarray/

dp的方法很简单,就是判断是使用到[i - 1]的序列还是从当前位置重新开始。使用divide and conquer方法的思路是,将数组从中间分成两个子数列,分别求每个子数列的:从最左侧开始的最大和数列lmax,从最右侧向左开始的最大和数列rmax,数列所有元素的和sum和数列的最大子数列mx。则当前数列的最大子数列mx可能是mx1,mx2或lmax1 + rmax2;lmax是max(lmax1, sum1 + lmax2),rmax是max(rmax2, rmax1 + sum2)


Longest Increasing Sequence: https://leetcode.com/problems/longest-increasing-subsequence/

1)O(n^2):建立一个大小为size,元素均初始化为1的vector,名为max_len,存储以第 [i] 个元素为结尾的最长递增序列的长度,从i = 1开始,遍历它前面的每个max_len的值,将max_len[i]更新为max(max_len[i], max_len[j] + 1),则max_len中的最大值即为结果

2)O(n log n):创建一个vector名为tail,将其初始化为nums[0],从i = 1开始遍历nums:如果nums[i]小于tail[0],则tail[0] = nums[i];如果nums[i]大于tail.back()(tail中的元素是以升序排列的),则将其push_back到tail中;如果nums[i] > tail[0] && nums[i] < tail.back(),则将tail中第一个大于nums[i]的元素替换成nums[i](用binary search)。这样做的具体原理参考文章:

http://www.geeksforgeeks.org/longest-monotonically-increasing-subsequence-size-n-log-n/


Maximal Square: https://leetcode.com/problems/maximal-square/

可以创建一个新的同样大小的二维数组dp,其中的每个元素表示以当前点为右下角时所能表示的最大正方形的边长。即如果matrix[i][j] == '0',则dp[i][j] = 0;若matrix[i][j] == '1',则dp[i][j] = min(dp[i][j - 1], dp[i - 1][j - 1], dp[i - 1][j]) + 1。但注意到其实对于每个dp,只需要检查它左侧,上侧和左上侧的三个元素,所以其实不需要二维数组dp,只要用一个vector记录前一行的结果即可


Paint House: https://leetcode.com/problems/paint-house/

第 i 个位置是red/blue/green的最小值


House Robber III: https://leetcode.com/problems/house-robber-iii/

对树进行post-order traverse,将以每个节点为根的树所能获得的最大值存在名为value的unordered_map<TreeNode*, int>中。每个节点的value要么是抢root,加左儿子的两个儿子的value和右儿子的两个儿子的value;要么是不抢root,左右儿子的value和。最后返回value[root]即可


Counting Bits: https://leetcode.com/problems/counting-bits/

result[i] = result[i & (i - 1)] + 1;





Depth-first Search & Breadth-first Search:

Course Schedule: https://leetcode.com/problems/course-schedule/

建立一个名为next_nodes大小为numCourses的vector记录每个课程所能到达的下一个课程,vector中的每个元素是一个unordered_set,设定一个vector<bool>记录一个课程是否被访问过,再设定一个vector<int>记录当前的节点路径。在dfs时,将一个节点插入path,在对它的dfs结束后,再将这个节点从path中删除。需要注意的是,为了避免MLE,对next_nodes的调用要call by reference,避免每个递归函数声明自己的next_nodes而产生MLE(其实这道题可以用topological sort来做)


Walls and Gates: https://leetcode.com/problems/walls-and-gates/

注意这里不要用visited来判断一个点是否被访问过,否则会产生TLE。当临近节点的值小于等于当前节点时,就不必这个临近节点放入队列了


Word Ladder: https://leetcode.com/problems/word-ladder/

注意这道题要用bfs。像这种找最小的题最好用bfs,因为找到了立刻就可以返回;如果用dfs的话要找出所有的结果,而很多的结果是不需要的


Course Schedule II: https://leetcode.com/problems/course-schedule-ii/

其实就是topological sorting。由于prerequisites中可能有重复,所以要先创建一个大小为numCourses的vector<unordered_set<int>>记录每个节点能达到的下一个节点,再计算每个节点的入度,然后将所有入度为0的节点加入queue,当queue不为空时每次pop出front加入结果,并将front所有下一节点的入度-1,如果下一个节点的入度变为0则加入queue


Clone Graph: https://leetcode.com/problems/clone-graph/

创建一个unordered_map<int,*node>名为hash存放新节点的label和位置的映射关系,创建一个unordered_set<int>名为done判断原图中的某个节点是否已经彻底完成,再创建一个queue<*node>存放bfs所需要遍历的节点。每次先检查queue的front节点在hash中是否存在,如果不存在则创建这个节点,然后遍历它所有的neighbor,如果neighbor节点不存在就创建它,然后将新的neighbor加入这个queue的front节点的neighbor(注意这里只更新当前节点的neighbor),并检查这个neighbor是否在done中,如果不在则将其加入queue


Graph Valid Tree: https://leetcode.com/problems/graph-valid-tree/

判断两点:图无圈,从任意一个点出发能访问到所有的点。使用bfs,首先建立每个点的邻接点(如边[0,1]在[0]的邻居中加入1,在[1]的邻居中加入0,注意这里如果first == second直接返回false),然后将0加入queue,当queue不为空时,检查front是否已访问过,如果访问过表示有圈,返回false,否则visited[front] = true,然后将它所有的邻居放入queue,并将front从它所有邻居节点的邻居中erase,最后再判断是不是所有节点都被访问了

这道题还有一个更简单的方法(union find),就是建立一个名为parent的vector,记录每个节点最深的根节点。初始化时parent[i] = i,然后对edges中的每一个first和second,寻找它们最深的根节点(以first为例,while(first !=parent[first]) first = parent[first]),如果first和second有相同的根节点,由于first和second又相连,则说明存在圈,返回false,否则令second的最深根节点为first,最后再返回edges.size() == n - 1即可(因为要想是一个valid tree边的个数必然等于n - 1,这样就避免了检查是否每个元素都被访问过)。详细参考:https://leetcode.com/discuss/66984/simple-and-clean-c-solution-with-detailed-explanation


Number of Connected Components in an Undirected Graph: https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/

思维上简单的方法是用bfs,建立neighbor通过queue数出连通域

更简单的方法是使用union find。count初始化为n,设定一个大小为n的vector名为parent,记录每个节点的父亲节点(初始化为它本身)。对于一个edge的两个节点,找出他们最深的父亲节点,如果不相同,说明这两个点之前未被连接,则count--,否则什么也不做。每次都令第二个节点最深的父亲节点为第一个节点


Reconstruct Itinerary: https://leetcode.com/problems/reconstruct-itinerary/

首先建立节点所能到达的邻居,记录在名为neighbor的unordered_map<string, multiset<string> >中。首先按照最优路径进行搜索直到无法继续进行,则这个路径就是一个正确的主路径,如果有未访问的节点,则这些节点一定形成了一个圈,只要将这个圈插入到结果中即可。如果当前节点有未使用的邻居关系,则将这个邻居加入stack并将邻居关系从neighbor中删除;如果当前节点没有未使用的邻居关系了,则将其加入结果,并从stack中pop出。详细解释参考这里

这里之所以不能简单地用unordered_map<string, multiset<string> >进行dfs和erase是因为可能出现排名在后面的neighbor能形成圈的情况,这样一来会导致结果遗漏了圈 






Backtracking:

Subset: https://leetcode.com/problems/subsets/

模板。

这道题可以用iterative的方法去做,即将1左移size位得到end,令 i = [0, end),每一位的0/1表示使用/不使用nums中的某个元素,对应每一个 i 设一个 j 遍历nums,检查(i & (1 << j)),如果不为0则将nums[j]加入到tmp_result


Palindrome Permutation II: https://leetcode.com/problems/palindrome-permutation-ii/

首先检查是否存在palindrome,然后将s排序,遍历所有元素,对可以作为头尾的递归求解s.substr(0, i) + s.substr(i + 2),将结果存在tmp_result中,再将所有tmp_result中的元素加上头尾push_back到result中(其实可以不用先判断是否有palindrome)


Generalized Abbreviation: https://leetcode.com/problems/generalized-abbreviation/

在helper的参数里设一个bool的can,表示当前结果是否可以用数字进行缩写,只有当前一次没有使用数字时can = true。每次tmp_result += s[0],然后调用s.substr(1)。如果can == true,则可行的数字为1~size,分别再对s.substr(1)调用helper,但这次can = false


Android Unlock Patterns: https://leetcode.com/problems/android-unlock-patterns/

记录使用过的点,已经用过的点的数目cur,对有 k 个点的图案,在cur <= k时检查所有的点[i][j],如果[i][j]不是当前的点且没被用过且是合法的下一个点时,继续搜索。注意在计算 k 时其实 [1, k]的图案数都已经计算过了,所以将其存在数组中即可;另外点有对称关系可以分为三组。优化和详细解释参考:

https://leetcode.com/discuss/104552/share-thinking-process-backtracking-solution-optimization




奇技淫巧题

Rotate Image: https://leetcode.com/problems/rotate-image/
先将行之间reverse,然后以对角线为轴swap


Bulb Switcher: https://leetcode.com/problems/bulb-switcher/

return sqrt(n)


Maximum Product of Word Lengths: https://leetcode.com/problems/maximum-product-of-word-lengths/

设置一个vector名为key,对每一个string,key[i] |= 1 << (word[i][j] - 'a'),j是一个string中所有的字符,这样通过key[i]就能知道word[i]中有哪些字符。在计算最大长度时,如果key[i] & key[j] == 0则说明二者没有共同的字符


Integer Break: https://leetcode.com/problems/integer-break/

假设最大的乘积为p,如果将p分解为2 * (p - 2)且分解后的结果不小于原p的话,则2 * (p - 2) > =p,得出p >= 4,也就是说如果p中有大于等于4的因子,则应该将其分解。一种方法是分解成2 * (p - 2),另一种是分解成3 * (p - 3),令3 * (p - 3) >= p得出p >= 4.5,在p >= 4时也成立,所以p中不应该有>=4的因子,也就是说p应该只有因子2和3。又因为6 = 2 + 2 + 2 = 3 + 3,而2 * 2 * 2 < 3 * 3,所以如果有多余两个的2,则应该用两个3代替,也就是说p中2的个数应该<=2个。然后分情况讨论:1) n % 3 == 0,则返回pow(3, n / 3);2)如果n % 3 == 2,则返回2 * pow(3, n / 3);3)如果n % 3 == 1,则返回4 * pow(3, n / 3 - 1)。注意要特别讨论一下n == 3和n == 2的情况

0 0