Medium题目总结

Posted xero10

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Medium题目总结相关的知识,希望对你有一定的参考价值。

方法技巧题:

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,初始化为相同的值center,如果s[right] == s[right + 1]则增加right直到二者不相等,然后令center = right + 1;然后如果s[left - 1] == s[right + 1],那么left--,right++,直到left/right到达边界或二者不再相等停止,记录当前回文串的长度,如果是当前的最大值则记录当前字符串的长度和起始位置。当size - center > 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/

用一个vector<bool>记录数字 i + 1 有没有被用过。对于每一位,它右边所有permutation的可能性共有 x! 个(x为右边的位数),则对每一位,它应该用第 remainder / x! 个数。remainder初始化为 k - 1,每次 remainder %= x!,循环直到remainder == 0,然后将没有用过的数字顺序加入result即可。

这道题也可以借用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/

1)想象 * 和 / 被括号括起来,则遇到operator右边的数时检查下一个op是什么,如果当前是 +- 而下一个是*/,则需要把当前结果和op入栈;如果当前是 */ 而下一个是 +-,则应该判断是不是需要将栈中结果pop出进行计算;否则直接进行计算即可

2)用一个stack,当操作符为'+'时 tmp 入栈,当操作符为'-'时 -tmp 入栈,当操作符为 '*'或'/' 时进行计算,结果入栈,最后将所有的栈中元素加起来

3)空间为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)用两个min_heap分别记录开始时间和结束时间。每次比较两个堆顶,如果start.top() < end.top()则说明有个新的会开始了,则count++并start.pop();如果start.top() > end.top()说明有一个会结束了,则count--并end.pop();如果二者相等则说明有一个会结束同时一个会开始,则count不变,将两个堆pop即可(也可以用两个vector做sort)

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

3)使用最小堆记录当前需要的房间数。首先将输入按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/

先将string reverse,再将每个单词reverse,最后将每个所有单词移动到最前面即可

(或,用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(),这样就可以避免递归

 

Flatten Binary Tree to Linked List: https://leetcode.com/problems/flatten-binary-tree-to-linked-list/

1)递归:flatten左子树,flatten右子树,然后将二者连起来变成新的右子树。需要加一个helper来得到每个子树的rightmost leaf

2)将当前root的右子树连到左子树,然后将新的左子树变成当前root的右子树,将左子树置为NULL,然后移动指针到新的右子树,重复直到当前root为NULL

3)方法1)的iterative版本,本质上是postorder traversal,需要注意的是a) 如果当前节点已经是stack.top()的右儿子,则do nothing; b) 当a)为false时,当前cursor变成stack.top()的右子树后要将prev = NULL,否则会导致这个左儿子被误认为stack.top()的右儿子而没有被flatten

 

Populating Next Right Pointers in Each Node II: https://leetcode.com/problems/populating-next-right-pointers-in-each-node-ii/

思想为level order traversal,因为之前的level已经建立了next的连接,所以可以借助之前level来构建当前level。设置一个root_cursor表示已经建立连接的前一层,一个level_cursor表示要建立连接的当前层。用两层的nested loop,外层loop root_cursor,每次将其更新为已完成的当前层的第一个节点来前进到下一次;内层也loop root_cursor,但是是在当前层内loop,每次将其更新为root_cursor->next。这里可以对每一层申明一个dummy header来避免对level_cursor是否为NULL的判断 

 

Copy List with Random Pointer: https://leetcode.com/problems/copy-list-with-random-pointer/

O(n) space: 直观的方法,用一个map<Node*,Node*>存放已有node到新node的对应关系

O(1) space: 思想是修改list在每个节点后面插入copy的新的节点。第一遍遍历原list,在每个节点后面插入一个新的节点,这个新节点的random指向它所复制的节点的random (即当前所遍历的节点的random);然后第二遍遍历被修改过的list,可以将每个新节点的random更新为新的random (即原来random的next);最后一遍遍历将新插入的节点从原list中删除并连接为新的也是要返回的list

 

Sort List: https://leetcode.com/problems/sort-list/

可以用bottom up的merge sort来做,因为linked list可以随意改变元素的顺序,所以不需要extra space。设一个变量sublist_len用来表示每次merge的sublist的size,然后定义一个split()来获得一个长度为sublist的list,调用split()两次即可获得两个可以进行merge的sublist,然后再用一个merge()来merge二者。注意这里需要知道merge后的list的前面一个节点和后面一个节点好能让merge后的list们能被正确的连接起来。为了简单可以将split后的两个sublist的最后一个节点的next指向NULL

 

Binary Tree Upside Down: https://leetcode.com/problems/binary-tree-upside-down/

1)recursive:对每一个点,先对左儿子做upside down,完成之后root->left就成了左子树的最右边的leaf,则此时root->left->left = root->ritght; root->left->right = root;,此时root,左儿子和右儿子都完成了upside down,将root的左右儿子都置为NULL,然后return。注意recursion终止的条件是root == NULL || root->left == NULL (root->left == NULL说明root在upside down之后的结果里没有父亲节点了)

2)iterative:类似对树的中序遍历,一直将节点加入stack并更新为左儿子直到为NULL。此时令cursor = top()并对stack进行pop,则pop后的top就是cursor原来的父节点,此事进行变换cursor->left = root->right; cursor->right = root;,这时cursor,cursor的父节点和cursor的兄弟都处理完了,则应该将cursor父节点(也就是top())的左右儿子置为NULL以避免重复访问,然后令cursor = NULL进行下一次循环

 

Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit: https://leetcode.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/

O(n^2):遍历nums[i],找到每个以 i 为开头的最长的subarray。只要确保对每个以 i 为开头的subarray的max_value - min_value <= limit即可

O(nlogn):想象有一个window,用一个map,key为nums[i],value为对应的值出现的次数,则每次增加window的tail,如果nums[tail]与map中最大或最小值的差>limit,则head++并令map[nums[head]]--,直到map为空或满足limit,这时head到tail就是一个可能的结果

O(n):参考Sliding Window Maximum(https://leetcode.com/problems/sliding-window-maximum/),仍然是一个window,可以用两个deque分别来track最大值和最小值(最大值的deque要降序,最小值的deque要升序。以最大值为例,我们保持降序是因为当最大值被移出window之外时,它后面的值可能成为新的最大值,所以要保留它之后比他小的值。我们不关心它前面比他小的值,因为对window来说从当前位置到以后的位置,只要当前最大值前面的值存在,当前最大值就存在;当前最大值不存在时它前面的任何值都不再存在)。则每次循环在保证两个deque升序/降序之后将nums[tail]加入deque,当nums[max_values.front()] - num[min_values.front()] > limit时,如果head是某个deque的第一个值(即最大值/最小值),将其从对应的deque中pop出,并head++,直到limit被满足,我们就得到了一个可能的结果

 

Maximum Points You Can Obtain from Cards: https://leetcode.com/problems/maximum-points-you-can-obtain-from-cards/

用一个长度为k的window,用sum记录这个window的和,这个window以cardPoints.size() - k作为起点向右移动(移出cardPoints的部分从左边重新开始,想象循环右移),令sum每次加入新的window结尾并去掉左边移出的部分,直到 i == cardPoints.size(),每次更新result

 

Expressive Words: https://leetcode.com/problems/expressive-words/

我们称重复出现的字符(比如“aaa”)为一个group,则对word[i]需要将其的每个group与S的group依次进行比较来决定是不是stretchy。首先要判断是不是同一个char,如果不是则不是strechy;如果是的话,计算word[i]的当前group的长度和S当前group的长度,如果前者大于后者,则word[i]的这个group没有办法extend成S的group,也就是说word[i]不是stretchy的;否则判断S的group长度是不是>= 3,如果< 3,则说明word[i]的group不能extend,此时要保证是stretchy则两个group的长度必须相等;如果>= 3,则word[i]的group总可以extend成S的group。持续判断到word[i]和S都恰好结束,则得到了一个stretchy的结果。

这里为了避免每次重新计算S每个group的程度和位置,可以用一个vector<int> start_index来记录S中每一个新的group开始的index,则每个group对应的char是S[start_index[i]],每个group的长度为start_index[i + 1] - start_index[i]。这里为了避免特殊考虑最后一个group,可以在start_index的结尾加一个S.size()

 

Hand of Straights: https://leetcode.com/problems/hand-of-straights/

O(nlogn):这个题最直观的方法是用一个map<int,int>来计算每一个值出现的次数,然后每次从最小的开始,判断是不是有连续的W个,并且如果有的话将对应的值--或者erase

O(n): 用一个unordered_map<int,int>计算每个值出现的次数,再定义一个名为seed的queue,seed里的值 i 都是没有 i-1存在的,也就是说seed里对应的值是一个可能序列的开始。则每次从里面取一个,判断是不是有连续的W个并且--或erase,这里需要额外判断一下当前的值会不会是新的seed,如果是的话则要加入seed。这里还要注意一下检查完 [seed + 0, seed + W - 1] 后还要检查一下 seed + W 是不是会成为一个新的seed,因为考虑[1,2,3,4], W = 2,则因为在第一个iteration结束后1和2都被erase了,则此时3就成为了新的seed

 

 

 

 

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/

1) 画图然后Binary search,分类讨论即可。注意如果[mid] == [left], 要对mid--来判断mid在哪一段

2) 先binary search找最小值,然后分段再binary search

 

 

 

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]中选一个m作为root,求以m为根,[1, m - 1]为左儿子,[m + 1, n]为右儿子。通过递归求出左右儿子所有的可能性(此时得到的左/右儿子的vector已经是完整的左/右子树了),再将所有左右儿子的组合push进result即可。

也可以用DP的方法做,需要一个3D vector subTrees, subTrees[i][j]表示以 i 为起点,长度为 j 的所有可能的集合。按长度递增递归,则[m, n]的所有组合即是对所有x∈[m, n],以x为root,[m, x - 1]为左子树,[x + 1, n]为右子树的所有可能。(但是你如果这么写code的话傻逼leetcode会产生heap overflow error)

 

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;

 

Find Two Non-overlapping Sub-arrays Each With Target Sum: https://leetcode.com/problems/find-two-non-overlapping-sub-arrays-each-with-target-sum/

定义一个vector<int> min_len将所有元素都初始化为INT_MAX,min_len[i]表示 i 向左(包括i)满足和为target的最短的subsequence的长度,则min_len[i] = min(min_len[i - 1], x),这里 x 表示以arr[i]为结尾和为target的subsequence的长度。然后我们可以用一个window,window的右侧每次+1,如果window_sum小于target则更新min_len[i] = min_len[i - 1](因为此时x不存在)然后continue;如果window_sum > target则window左侧+1直到window_sum <= target。如果这时window_sum < target,同理min_len[i] = min_len[i - 1]然后continue;如果window_sum == target,则min_len [i] = min(min_len[i - 1] , end - start + 1);同时如果min_len[start - 1] != INT_MAX,则说明以start为分界线,它左侧和右侧都有一个和为target的subsequence,可以更新result

 

 

 

 

Depth-first Search & Breadth-first Search:

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

BFS(其实就是topological sort):用一个unordered_map<int,int> prerequisite_count来记录每门课的prerequisite数,再用另一个unordered_map<int,vector<int>> follow_up来记录每门课所指向的所有下一门课,再用一个vector<int> can_take记录现在可以take的课,则can_taken可以被初始化为prerequisite_count为0的课,然后遍历can_take[i],对每个can_take[i]再遍历它所有的follow_up[j],令prerequisite[follow_up[can_take[i]][j]]--,如果== 0则说明这门课没有prerequisite了,可以将其加入新的tmp_can_take并从prerequisite_count中earse。当前can_take遍历结束后将其更新为tmp_can_take,直到can_take为空,这时如果prerequisite_count也为空则所有的课都已经上过了,否则就有没有办法上的课

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

 

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/

1) rotate后[x][y] => [y][n - x - 1],从一个点开始rotate总会回到这个点。一行一行rotate(其实rotate第 i 行时 n - i - 1 行也被rotate了),直到遇到已经rotate过的行。注意column index的起始值是 i ,并且不应该rotate最后一个column

2) 先将行之间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的情况

以上是关于Medium题目总结的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode: 55. Jump Game(Medium)

Medium题目总结

Java 求解验证二叉搜索树

leetcode55 跳跃游戏(Medium)

Codevs 1029(遍历问题 )

Java 求解二叉搜索树中的众数