Hard题目总结

来源:互联网 发布:数据分析案例 编辑:程序博客网 时间:2024/05/06 10:42

Binary Search

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

两种方法:

1)直接binary search,先判断mid在哪一段,然后判断mid和target的关系,如果结果可能在两段,再判断这段边界(left或right)和target的关系

2)先用binary search找出最小的元素(即从左边开始第一个小于最后一个元素的元素),然后判断target在哪一段,对那一段用普通的binary search即可


Smallest Rectangle Enclosing Black Pixels: https://leetcode.com/problems/smallest-rectangle-enclosing-black-pixels/

直观的方法是用bfs/dfs,但是这道题用binary search更快,即从[0, x]中找出包含'1'的最小行,从[x, row - 1]中找出包含'1'的最大行,从[0, y]中找出包含'1'的最小列,从[y, col - 1]中找出包含'1'的最大列




方法技巧题:

Binary Tree Maximum Path Sum: https://leetcode.com/problems/binary-tree-maximum-path-sum/

对一个节点来说,以该节点为根的树的最大path sum可能是:

a) 左子树最大的path sum

b) 右子树最大的path sum

c) 根节点 + max(0,左子树包含左儿子的最大path sum) + max(0,右子树包含右儿子的最大path sum)

所以设定一个helper(TreeNode* root, int &max_sum, int &max_root)。注意这里的max_root不能跨过根节点,只能是(根节点+左子树) / (根节点+右子树)


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

1)按照root,right,left的顺序traverse,然后将结果reverse

2)设置一个cur指向当前访问的节点,pre指针,指向上一次访问的节点。将cur初始为root,将从cur开始的左儿子依次push入stack,直到NULL;令cur = s.top(),如果cur->right为NULL或pre,表示它没有右儿子或右子树已经遍历结束,则将cur加入result并将其从stack中pop出,令pre = cur并令cur = NULL;否则令cur = cur->right。只要cur != NULL或stack不为空则重复这个过程


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

思想是BFS。计算走1步最远到哪,走2步最远到哪…… 直到第一次>= nums.size() - 1,返回步数。计算时,设一个start表示当前步数的起点,end表示当前步数能到达的最远距离,遍历从start到end的所有元素,如果他们能到达最后一个元素,则返回步数;否则令下一轮的最远距离max_end = max(max_end, i + nums[i]);每次循环结束后将start更新为end + 1,将end更新为max_end。注意要特殊处理一下只有一个元素的情况


Merge k Sorted List: https://leetcode.com/problems/merge-k-sorted-lists/

三种方法:

1)divide & conquer,只要前一半和后一半已经merge到一起了,则只要将这两半merge到一起即可,递归

2)先两两merge,直到最后只剩一个链表。这种方法可以使用iterative而避免递归,即 i 每次循环到 (size + 1) / 2,将lists[2*i]和lists[2 * i + 1]merge到一起放在lists[i],然后size = (size + 1) / 2直到size == 1,返回lists[0]即可(可以先令size = (size + 1) / 2,然后将list[i]和list[i + size]merge到一起放到list[i])

3)设一个priority queue存放所有list的第一个节点,然后每次拿出最小的,如果该节点的下一个节点不为空则将这下一个节点也放入priority queue,直到priority queue为空


Median of Two Sorted Array: https://leetcode.com/problems/median-of-two-sorted-arrays/

可以转换成寻找两个数列从小到大第 k 个数的问题。比较两个数列第 k / 2 - 1个元素的大小,将小的那个数列的前k / 2 - 1个元素丢掉,然后再寻找两个数列的第 k - k / 2个元素。注意两点:1)在比较两个数列的[start + k / 2 - 1]时要注意判断是否越界,如果越界则将进行比较的值设为INT_MAX;2)注意下一次寻找的是第 k - k / 2个元素,而不是 k / 2个元素


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

首先用一个名为profit的vector记录每一天的最大profit,然后令 i = size - 2 ~ 0,表示第二次购买发生在第 i 天,则result = max(result, max_price - prices[i] + (i != 0 ? profit[i - 1] : 0),其中max_price初始化为prices[size - 1],表示从 i 开始到最后的最大价格。有一个空间为O(1)的算法,但是不太好理解,看这里:https://leetcode.com/discuss/18330/is-it-best-solution-with-o-n-o-1 


Largest Rectangle in Histogram: https://leetcode.com/problems/largest-rectangle-in-histogram/

设置一个单调递增的栈index存储下标,当(!index.empty() && height[i] <= height[index.top()])时,将栈顶pop出存放在tmp_index中,则以height[tmp_index]为高的最大面积为 heights[tmp_index] * (i - index.top() - 1),(左边到index.top(),右边到 i - 1),注意这里要特别处理一下index为空的情况


LRU Cache: https://leetcode.com/problems/lru-cache/

使用双向链表和hash table。双向链表的节点是一个类Node,存储key和value;用hash table存储key和Node*。在LRU类的private部分定义Node *newest指向最新的节点。调用get()时,如果it->second == newest则不用进行处理,否则将it->second移动到链表尾部;调用set()时,注意在删除最旧节点时要判断一下它的next是否为NULL,同时要将被删除节点的key从hash table中erase掉


Find Median from Data Stream: https://leetcode.com/problems/find-median-from-data-stream/

用一个最大堆存储小于前一半元素,用一个最小堆存储后一半元素,add()时通过pivot判断应该加入哪个堆,然后对两个堆进行调整,保证small == big或small == big + 1,并更新pivot


Longest Consecutive Sequence: https://leetcode.com/problems/longest-consecutive-sequence/

将所有元素放入一个unordered_set,对nums[i],设置一个upper = nums[i] + 1,在hash table中寻找upper,如果找到则erase,直到upper不存在,于是得到一个上界;同理可以得到下界


Substring with Concatenation of Words: https://leetcode.com/problems/substring-with-concatenation-of-all-words/

用一个hash table记录words中每个word出现的次数。将s分成word_size组,每一组表示可能是正确结果的起始位置,第i组是i, i + 1* word_size, i + 2 * word_size...,按照组对s进行遍历,这样在每一组内,下一个位置可以使用之前位置得到的结果,而避免了重复的工作。对每一组内,令j = i,想象有一个window,window内是一些字符串,用变量start表示window的起始位置,用另一个hash table记录window从start到现在已访问的word及出现次数,用count记录已访问的有效的word数。每次检查一个新的word名为tmp时,如果它不在hash中,则是无效word,此时清空tmp_hash,count = 0并令start += word_size;如果它在hash中则tmp_hash[tmp]++, count++,如果tmp_hash[tmp] > hash[tmp],则将start右移并将移出window的word在tmp_hash中--,直到tmp_hash <= hash[tmp]。然后检查count,如果等于总的word数,则加入result。对于j,每次增加word_size,并且注意循环的终止条件应该是j + word_size <= s.size(),有等号


First Missing Positive: https://leetcode.com/problems/first-missing-positive/

将nums进行partition,>0的放到前面,<=0的放到后面,获得>0的元素个数n。因为缺失的数肯定在范围[1, n + 1]内,所以只考虑abs(nums[i]) <= n的情况即可。然后遍历所有的正数(下标范围[0, n - 1]),如果一个数存在且abs(nums[i]) <= n,则将nums[abs(nums[i]) - 1]取反;最后再从头检查nums,第一个大于0的index对应的数是不存在的,返回这个index + 1即可(这里的partition是2-ways partition,注意2-way partition和3-way partition的区别)

另外一种方法是,类似的,也是保证将出现的nums[i]放到nums[nums[i] - 1]的位置。遍历所有的元素,在nums[i] > 0 && nums[i] <= size && nums[nums[i] - 1] != nums[i]时,swap(nums[i], nums[nums[i] - 1])。因为每次swap都能保证至少一个数被放在正确的位置,而一个数一旦被放在了正确位置就不会重复放置,所以虽然有嵌套循环,每个[1, n]的数只会被访问一次,所以时间复杂度是O(n)


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

逻辑上简单的方法是用两个stack,一个名为op保存操作符,一个名为num保存数。当前输入为数的时候,如果op栈顶是+/-则进行计算,否则入num栈;如果当前输入为 '+/-/(' 时,入op栈;如果输入为 ')',如果op栈顶为 '(',则pop,否则用栈顶符号进行计算,然后栈顶成为 '(',进行pop。要注意处理完 ')' 之后如果op栈不为空且栈顶为+/-,则要再进行一次计算,保证同一层的括号之间按照从前往后的顺序计算

另一种更高效的方法是用一个int变量sign表示加减,每次有新的数来就令result += sign * tmp,当遇到 '(' 时将result和sign入栈,遇到 ')' 时再将二者出栈与当前result相加


Longest Substring with At Most Two Distinct Characters: https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/

扩展到k distinct characters的情况:用一个window记录满足条件的substring的起止位置,再用一个大小为256的vector num记录每个字符出现的的次数,每遇到s[i]就num[s[i]]++,如果num[s[i]] == 1则说明出现了新的字符,检查并保证当前window中只有k个字符


Minimum Window Substring: https://leetcode.com/problems/minimum-window-substring/

记录t中每个字符出现的次数,然后用window对s进行遍历,用count记录遇到的t中元素的个数。如果num[s[end]] > 0,说明s[end]是t中的元素,则count++,然后num[s[end]]--;在count == t.size()时首先判断当前的end - start + 1 < window_size是否成立,如果成立则更新result,然后增加start以缩小window的大小,如果num[s[start]] == 0,说明s[start]是t中的一个元素(因为s[start]在之前肯定被访问过,如果它不是t中的元素,则对它--时它会小于0,只有是t中的元素时自减完的结果才是0),则count--,然后start++。最后返回result即可

注意比较一下这道题和上面那道题,一个是求最长的substring,一个是求最短的substring。对于求最长,应该在移动完start的循环之后判断是否更新结果,因为循环中的是invalid的,移动完变成valid;而对于求最短,应该在移动start的循环中判断是否更新结果,因为循环中是valid的,移动完后变成invalid


Wildcard Matching: https://leetcode.com/problems/wildcard-matching/

1)可以用dp做,dp[i + 1][j + 1]表示s[i]和p[j]匹配,在p[j] == '*'的时候,可以不用'*'([i] 到 [j - 1]),也可以至少用一次'*'([i - 1] 到 [j])。注意初始化dp[0][j]

2)用两个变量match和asterick,asterick表示p中上一个星号出现的位置,match表示s中这个星号可能要匹配的下一个位置。在i < s.size()时,如果单字符能匹配,则i++,j++;如果p[j] == '*',则令match = i,asterick = j++;如果之前出现了星号,则i = ++match,j = asterick + 1;否则返回false(以上判断注意有的要保证j < p.size())。s检查完后,剩下的p必须全部是星号则返回true,否则返回false


Trapping Rain Water: https://leetcode.com/problems/trapping-rain-water/

设置left_max和right_max分别表示当前左边和右边的最大高度,每次循环开始先更新二者,如果left_max < right_max,则说明左边的高度成为了限制,计算当前left能容纳的水量并令left++;否则,计算当前right能容纳的水量并令right--


Text Justification: https://leetcode.com/problems/text-justification/

用一个变量line_count记录当前行字符串的长度(不含空格),用另一个变量 j 记录当前行应该用几个单词,则i + j < size && line_count + words[i + j].size() + j <= maxWidth时 j++。在创建每一行时,先将tmp初始化为第一个单词,然后后面每个单词负责加自己前面的空格(注意要特别处理一下一行只有一个单词的情况)。当遇到最后一行时跳出循环进行一下特殊处理


Recover Binary Search Tree: https://leetcode.com/problems/recover-binary-search-tree/

1)递归:设两个first和second指针表示要交换的两个节点(初始化为NULL),pre表示上一次访问的节点。对树进行in-order traverse,如果pre->val > trav->val,如果first == NULL,则first = pre;然后second = trav。遍历结束后交换first和second。如果考虑栈空间,则空间复杂度是O(n)

2)Morris Traversal:利用Morris In-order Traversal对树进行遍历,同样使用first、second和pre。注意一下更新first、second和pre的时候(也就是说in-order traverse输出数值的时候)是trav->left == NULL和tmp->right == trav

In-order Morris Traversal:

1)如果当前节点的左儿子为空,则输出当前节点,并移动到它的左儿子继续

2)如果当前节点的左儿子不为空,则寻找左子树中的最大值

     a)如果左子树最大值的右儿子为空,则令当前节点成为最大值节点的右儿子,移动到当前节点的左儿子继续

     b)如果左子树最大值的右儿子为当前节点,则将其右儿子置空以恢复树的形状,输出当前节点,移动到当前节点的右儿子继续

3)重复1) 和 2),直到当前节点为空

具体的Morris Traverse参考:http://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html


Count of Smaller Number After Self: https://leetcode.com/problems/count-of-smaller-numbers-after-self/

使用divide & conquer。想象数组从中间分成两部分,左右分别已经排序,且每个部分内每个元素右边小于它的元素个数已经得到(存储在result中)。则可以将左右两部分进行merge得到一个完全排序的数组。在merge的过程中,对于右半部分的每个元素,在它后面且小于它的元素个数不变;对于左半部分的数组,在它右边小于它的元素个数等于原来的结果加上当前已经merge过的后半部分的元素的个数。用一个名为sorted_index的vector存储排序的结果(初始化为0, 1, ... size - 1),注意在这里面要存放输入nums对应的index


Count of Range Sum: https://leetcode.com/problems/count-of-range-sum/

使用divide & conquer进行merge,思想类似count of smaller number after self(上面)。首先计算输入的累加和存储在sum中,仍然想象左右两部分已经排序且对应结果已经计算,则在merge的时候,对右半部分没有影响;对于左半部分,只要在右半部分中找到满足sum[j] - sum[i] >= lower && sum[j] - sum[i] <= upper的sum[j]的个数,加到result里即可。由于左右部分各自的内部都是排序的,所以在右半部分寻找满足条件的sum[j]时,右半部分的元素至多被遍历两次(判断lower一次,判断upper一次),所以查找sum[j]的时间是线性的。merge结果存在一个临时vector中,结束后再覆盖sum即可。注意这里只要返回满足条件的range sum总数即可(不像count of smaller number after self要计算每一个元素的对应值),所以不需要额外设置vector存储对应index的结果,而且可以将merge的结果直接覆盖原来的sum(因为对任意部分来说,内部对应的结果计算完成后就不再需要内部顺序)。另外,因为sum[0] = 0,所以在退出recursion的情况left == right时不需要对sum[left]进行判断是否在[lower, upper]内


The Skyline Problem: https://leetcode.com/problems/the-skyline-problem/

因为结果中的起点只可能是某个矩形的左端点或右端点,所以只要考虑每个矩形的左右端点即可。用一个max_heap存储当前有效的矩形(其实可能会有无效的存在,但只要堆顶是有效的即可),每个元素是pair<int, int>,first是矩形的高,second是矩形的右端点,按first排序,first相等的令second大的在堆顶。如果到了输入的末尾,或下一个矩形的左端在堆顶右端的右侧,即(i >= buildings.size() || (!max_heap.empty() && max_heap.top().second < buildings[i][0]))时,应该令start为堆顶右端,并且将失效(右端小于等于start)的矩形从堆中pop出;否则,即新矩形左端在与堆顶有重合时,应该令start为新矩形的左端,并且此时应该将所有左端点等于start的矩形入堆。然后令高度为堆顶的高度,在结果为空或当前高度不等于结果中最后一个元素的高度时加入结果


Number of Islands II: https://leetcode.com/problems/number-of-islands-ii/

令所有联通的节点有共同的根,用一个名为root的vector记录每个节点的根(将二维坐标转化为一维index),初始化为-1。用count记录当前的island数,读取一个新的输入后count++,计算当前输入的index,并令root[index] = index,然后检查这个新的输入的所有邻居,如果有邻居的根不为-1,则检查邻居最深的根和root[index]是否相等,如果不相等,说明新输入应该和这个邻居结合到一起,所以count--,并令root[index] = index = root_neighbor,这样做是为了下一次检查的时候检查下一个邻居和这次的邻居是否联通


Closest Binary Search Tree Value II: https://leetcode.com/problems/closest-binary-search-tree-value-ii/

这道题有两种大方法:

1) O(n):对树进行in-order遍历,用一个名为nums的deque存储遍历过的值,当nums.size() < k时向其中加入元素;当nums.size() >= k时,判断front和新元素哪个更接近target,如果是front更接近,则跳出循环,返回结果;否则对front进行pop,并将新元素加入nums队尾(这样做的道理分三种情况讨论一下就能知道,即\ V /)

2) O(k log n):这个大方法又可以分为两种小的不同实现:

a):用两个队列predecessor和successor存储小于target的最大值/大于target的最小值(注意此时不是遍历所有小于或大于close的元素,而是用类似在bst中查找某个值的方法进行查找),然后每次找predecessor中最大的和successor中最小的,加入结果并更新stack即可,参考这里

b):仍然用两个stack:predecessor和successor。在开始的时候,从root开始,按照寻找最接近target值的方法走到NULL,将小于/大于target的节点加入predecessor/successor。然后每次比较两个stack的顶部元素,取最接近target的加入result,然后对被pop的stack进行更新

上面两种方法相比a)和b)的逻辑是相同的,只不过b)同时建立了两个stack,代码更简洁


Remove Duplicate Letters: https://leetcode.com/problems/remove-duplicate-letters/

用times记录每个字符出现的次数,用used记录一个字符是否被使用。先初始化times,然后遍历s中的所有字符,如果该字符已经被使用则continue,否则在(s[i] < result.back() && times[result.back() - 'a'] > 0)时(即当前字符小于result的最后一个字符且后面还有result的最后一个字符),令used[result.back() - 'a'] = false并且result.pop_back()。然后result += s[i]并令used[s[i] - 'a'] = true(Greedy)


Palindrome Pairs: https://leetcode.com/problems/palindrome-pairs/

将所有的words进行reverse之后存储在一个名为dict的hash table中,然后遍历原words,将words[i]分成left和right两个substring,如果在dict中能找到left并且right是palindrome,则left + right + left是合法的;同理检查right + left + right是不是合法。在分割words[i]时,令left的长度为[0, words[i].size() - 1],以避免""和words[i]的组合出现两次;另外如果left = ""并且能找到合法的left + right + left,则除了{i, dict[i]}之外,还需要将{dict[i], i}也加入结果


Expression Add Operators: https://leetcode.com/problems/expression-add-operators/

用backtracking暴力破解即可。用value记录当前的结果,用pre_value记录上一个数,假设当前数字是tmp,每次对tmp前的符号尝试 +/-/*。对每个起点,令tmp对应的string长度从1到最大。如果是乘法,则调用helper时新的value是value - pre_value + pre_value * tmp,新的pre_value是pre_value * tmp。注意对'0'开头的数字,i 只能取1;另外对start == 0的情况也要特殊处理一下,因为这时不应该在当前数字前添加操作符


Shortest Palindrome: https://leetcode.com/problems/shortest-palindrome/

用KMP。先令tmp = s,然后对tmp进行reverse,再令tmp = tmp + "#" + s,声明next为大小为tmp.size() + 1的vector<int>,则next[tmp.size()]就是tmp最大的公共前后缀,也就是原s中从开头开始的最长palindrome,然后返回tmp.substr(size + 1, size - next[tmp.size()]) + s即可。注意这里添加"#"是为了避免当s = "aa"时得到最大公共前后缀为4


Read N Characters Given Read4 II: https://leetcode.com/problems/read-n-characters-given-read4-ii-call-multiple-times/

因为一次读入了4个,而需要的可能小于4个,所以要将剩下的不需要的存起来,以保证下一次读取时上次读到的没用到的不会丢失。对于剩下的,可以存在数组里,每次用完再向前移动,也可以存在一个queue里,这样就避免了数组内部的移动。注意buf每次是新的,所以不需要统计之前一共读了多少


Find the Duplicate Number: https://leetcode.com/problems/find-the-duplicate-number/

类似Linked List Cycle II,设一个slow一个fast,两者相遇后令一个再从头开始,再相遇时就是circle的entry,也就是duplicate的数。另一种方法是用binary search,先选取中值mid,统计<=mid的元素数count,如果count > mid,说明重复的数在范围[1, mid]之间,否则在[mid + 1, n]之间,再继续进行binary search直到得到结果


Max Points on a Line: https://leetcode.com/problems/max-points-on-a-line/

对每一个点[i],检查它后面所有的点[j],因为此时所有的点都经过[i],则所有斜率相同的点在同一条直线上。令a = xi - xj,b = yi - yj,如果a == 0 && b == 0,说明是同一个点,same++(same初始化为1,表示与[i]相同的点的数);否则如果a == 0,令vertical++;否则如果b == 0,令horizontal++;否则,即a、b都不为0,则计算他们的最大公约数gcd,令a、b分别除以gcd(保证所有斜率相同的点都被记录在一起),然后将to_string(a) + "," + to_string(b)作为key,hash[key]++。在 j 的循环中,每次令tmp_max = max(tmp_max, ++hash[key]),然后 j 的循环退出后,令tmp_max = same + max(tmp_max, max(vertical, horizontal)),再令result = max(result, tmp_max),最后返回result即可

注意两点:1)对每个 i,hash要清空 2)gcd的求法是if(b == 0) return a; else return getGCD(b, a % b),这样对于一条线上符号不同的点(如(1, -2)和(-1, 2))在除以gcd后能得到相同的结果


Candy: https://leetcode.com/problems/candy/

O(n)时间,O(n)空间:创建一个同样大小的vector名为count,先从前往后遍历输入,如果ratings[i] > ratings[i - 1],则count[i] = count[i - 1] + 1,然后再从后往前遍历输入,如果ratings[i] > ratings[i + 1]则count[i] = max(count[i], count[i + 1] + 1),最后对count求和即可

O(n)时间,O(1)空间:思想是从前往后遍历输入,用down表示降序序列的长度(不包括极大值点),用peak表示前一个极大值,然后从前往后遍历输入,如果ratings[i] > ratings[i - 1]则result += peak;如果ratings[i] == ratings[i - 1]则peak = 1, result += 1;否则down++。具体为:在ratings[i] >= ratings[i - 1]时,如果down > 0,那么当前的点是谷底往后第一个增长的点,则对[1, down]求和并加到result上,如果此时down >= peak,则说明一个"^"形状的序列降序序列的长度大于增序序列,则此时应该令peak增加到down + 1,也就是result += down + 1 - peak;处理完down > 0后,如果ratings[i] == ratings[i - 1],则应该令peak = 1,否则说明当前是正在增长中且不是第一个增长的点,则peak = peak + 1,然后将peak加入result。如果ratings[i] < ratings[i - 1],则down++。最后再处理一下down,返回result即可


Maximum Gap: https://leetcode.com/problems/maximum-gap/

使用bucket sort。输入最大值为upper,最小值为lower,则两个数之间的差 >= (upper - lower) / (size - 1)。令gap = (upper  - lower) / (size - 1),bucket的大小即为gap,则bucket_num为(upper - lower) / gap + 1。对于每个bucket,只要记录它的最大值和最小值即可,令result = gap,然后遍历所有的bucket,计算相邻两个非空bucket最大值和最小值的差。在计算gap时,注意它可能为0,如果为0则令gap = 1。在获得upper和lower时可以使用STL函数max_element和min_element,如果得到的upper == lower则直接返回0即可


Sliding Window Maximum: https://leetcode.com/problems/sliding-window-maximum/
用一个名为candidate的deque存储在每个位置可能最大值的index。遍历nums,当candidate.front() < i - k + 1也就是说front在以 i 为结尾的window之外,则pop_front,则所有deque中的元素都是在以 i 为结尾的window内的;并且当nums[candidate.back()] < nums[i]时,pop_back(),因为在window内在 i 之前并且小于nums[i]的元素是不可能成为当前和以后window的max的。然后将nums[i]加入deque尾部,nums[candidate.front()]就是当前window的最大值


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

结合2D - Immutable使用binary indexed tree,两个坐标各一个for循环,嵌套起来即可


Best Meeting Point: https://leetcode.com/problems/best-meeting-point/

首先想象一维的情况,显然结果应该在所有点的内部。如果结果左右两边点的个数不一样的,那么向点多的那一边移动显然会减小结果,所以令距离和最小的点应该是所有点的中值。可以先找到水平方向距离和最小点的坐标,再找到竖直方向距离和最小点的坐标,则二者结合起来就是结果。在寻找中值的时候,可以使用STL的nth_element,它是线性时间的;也可以用sort,不过是非线性时间的


Word Pattern II: https://leetcode.com/problems/word-pattern-ii/

用backtracking进行暴力破解(尝试所有的可能)。注意:1)只要用一个unordered_map<char, string>即可,将用过的string存到unordered_set中即可;2)可以用一个变量min_len表示word的最小长度,则在str中剩下的字符数乘以min_len < pattern中剩下的char数时可以直接返回false。min_len初始化为-1,在递归调用helper的时候进行一下特殊判断即可;3)因为必须严格一对一匹配,所以对于递归函数的出口,当而两个字符串中只有一个越界的时候应该返回false(参考Word Pattern);4)在向hash中添加元素并进行递归调用后不要忘了剪枝


Strobogrammatic Number III: https://leetcode.com/problems/strobogrammatic-number-iii/

用递归生成长度在low和high之间的strobogrammatic的string,然后判断是不是大小也在low和high之间即可。注意可以在获得一个string时就可以判断是否满足条件,而不必将所有结果存储起来再一起判断,只不过要加两个flag:check_low和check_high


Data Stream as Disjoint Intervals: https://leetcode.com/problems/data-stream-as-disjoint-intervals/

1)add: O(1),get: O(nlogn):用一个vector存储所有的元素,用sorted表示vector是否排序,在插入新元素时将sorted置为false,在调用get时对vector进行排序并将sorted置为true

2)add: O(logn),get: O(n): 

     a)构建bst:在get时,对树进行in-order traverse并同时构建结果,注意需要一个pre来记录上一次访问的值

     b)用set:注意set的iterator不是random access iterator,it + 1是非法的,只能it++,所以同理也只能用一个pre来记录上一次访问的值

3)add: O(n),get: O(1):用vector<Interval>记录所有的元素,在加入新元素的同时对已有的Interval进行合并,参考这里


Design Twitter: https://leetcode.com/problems/design-twitter/

1):post - O(1), get - O(n log n), follow - O(1), unfollow - O(1),其中n是该用户关注的人数

用timestamp记录每个tweet的时间,用following记录user之间的关系,然后用unordered_map<int, vector<tweet> >记录每个user的消息。对于get,用priority_queue<pair<rbegin(),  rend()> >记录每个用户所发tweet的iterator。要注意需要添加每个用户follow自己。参考这里

2):post - O(n), get - O(size1), follow - O(max(size1, size2)), unfollow - O(1),其中n是关注该用户的人数,size1是该用户总的newsfeed数,size2是新被followed的人的总post数

依然用timestamp记录每个tweet的时间,用followed_by记录user之间的关系,用unordered_map<int, vector<tweet> >记录每个用户的消息,用unordered_map<int, list<tweet> >记录每个用户由新到旧的总newsfeed。get时,记录结果的同时删除不符合条件的post。在follow时,将被followed的人的所有post加入follower的newsfeed。可以进一步优化:可以只将被followed的人的10个post加入follower的newsfeed


Rearrange String k Distance Apart: https://leetcode.com/problems/rearrange-string-k-distance-apart/

记录每个字符出现的次数,用一个max heap存储vector<pair<char, int> >。在生成结果时,每次都用剩下最多的字符,也就是说max heap的top,同时用一个queue<pair<char, int> >记录前 k 个使用的字符。如果queue的size()等于k,则将其front进行pop,如果front().second不为0则加入max heap。如果max heap为空,说明当前没有可以使用的字符,返回"",否则将其加入result,对应次数-1然后加入queue


Max Sum of Rectangle No Larger Than k: https://leetcode.com/problems/max-sum-of-sub-matrix-no-larger-than-k/

设一个head,一个tail,令head = [0, row - 1],循环嵌套tail = [head, row - 1],对每一个tail用一个名为value大小为row的vector<int>记录从head到当前tail每一行的和,然后在value中寻找和 <= k的subarray。在value中寻找subarray时,一边用cur_sum计算累加和,一边用一个名为sum的set<int>存储之前的累加和,然后在sum中查找it = lower_bound(cur_sum - k),如果存在则result = max(result, cur_sum - *it)。具体参考这里





BSF & DSF:

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

简单的方法:

先用bfs,一边进行搜索一边构建图(即建立一个名为neighbor的unordered_map<string, vector<string>>记录每一个节点所能到达的下一个节点),在得到endWord后,利用neighbor通过dfs产生答案。在bfs的过程中,要使用两个unordered_set<string>记录当前层的节点(cur)和这次新产生的节点(next),然后每次令cur = next,注意每一个节点被访问后需要从wordList中将其删除,避免重复访问

高效的方法:

从beginWord和endWord两端交替进行bfs同时构建图(记录neighbor),将产生的中间结果作为新的beginWord进行下一层寻找,主要要设一个flip来判断此次是从beginWord->endWord还是endWord->beginWord以保证能正确地产生neighbor,当找到一条路径后就退出,利用neighbor通过dfs构造答案。需要注意的一点技巧是,之所以双端bfs比较快是因为路径是从两端向中间增长的,我们要做的就是尽量减少无关结果,所以每次在调用bfs时,如果head.size() > tail.size(),说明用head作为beginWord会产生很多无关结果,所以这时我们不对head进行增长,而直接进行下一次逆向增长,这样可以极大地提高算法的效率


Shortest Distance from All Buildings: https://leetcode.com/problems/shortest-distance-from-all-buildings/

思想是bfs。用一个名为distance的二维vector,每个元素是pair<int,int>,first代表[i][j]能到达的building数,second代表能到达的所有building和的最短距离。遍历输入,对所有的building进行bfs,更新所有它能到达的empty land和对应的距离,并统计building的数目,最后再对能到达所有building的empty land求出最小值即可


Alien Dictionary: https://leetcode.com/problems/alien-dictionary/

用topological sort。注意是单词之间有序,单词内部并未排序


Remove Invalid Parentheses: https://leetcode.com/problems/remove-invalid-parentheses/

用bfs。每次检查当前字符串是否合法,如果合法则加入结果,并令done = true;如果"("多了,则将当前字符串中的每一个"("尝试将其去掉,检查这个结果有没有出现过,如果没有出现过则加入queue;对")"也同理。也可以使用dfs


Word Search II: https://leetcode.com/problems/word-search-ii/

最直观的解法是对每个词进行搜索,但这样会花费很多时间。可以用输入的单词建立一个Trie,然后对board进行搜索,检查当前所形成的string作为前缀是否在在Trie中,如果不存在则移动到下一个board节点;如果存在且当前string就是某个单词,则将其加入result并令对应Trie节点的leaf = false;然后对当前board的邻接点和当前Trie节点对应的儿子继续进行搜索。为了避免在dfs时重复访问某个节点,要将当前访问的board变成 '*' 再dfs,最后再将其复原。注意这里不能使用二维坐标visited来判断一个节点是否访问过,因为以某个board节点开始会形成多条路径(使用visited其实也可以,不过还要进行剪枝,即访问完这个节点后还要visited置为false,不如用 '*' 简单)





Dynamic Programming:

Palindrome Partitioning II: https://leetcode.com/problems/palindrome-partitioning-ii/

对于每一个位置 i,令j = i ~ 0,检查s[j] ~ s[i]是不是palindrome,如果是则cut[i + 1] = min(cut[i + 1], cut[j] + 1)。这里要将cut初始化为size + 1,也就是说位置 i 的cut数是存在cut[i + 1]中的,因为对j == 0,如果0 ~ i 是palindrome,则不需要切,也就是说对应的值应该为0,因此为了这种情况下不进行特别处理,所以在最前面添加一个cut[0] = -1。为了不必重复判断一个substring是不是palindrome,用一个的二维vector isPalindrome[j][i]表示j ~ i 是不是palindrome。这个二维vector初始化为false,当确定某段为palindrome时将其置为true。在判断 j ~ i 是不是palindrome时,只要满足s[i] == s[j] && (i - j < 2 || isPalindrome[j + 1][i - 1])即可


Edit Distance: https://leetcode.com/problems/edit-distance/

设置一个名为count的二维vector,[i][j]表示将word1的前 i 个字符变成word2的前 j 个字符所需要的最小操作数。

若word1[i] == word2[j]:count[i + 1][j + 1] = min(count[i][j], min(count[i][j + 1] + 1, count[i + 1][j] + 1))

若word1[i] != word2[j]:count[i + 1][j + 1] = min(count[i][j] + 1, min(count[i][j + 1] + 1, count[i + 1][j] + 1))

特别需要注意的是,要初始化count[i][0] = i 和count[0][j] = j


Distinct Subsequences: https://leetcode.com/problems/distinct-subsequences/

这道题的意思是,S的subsequence中有几个是和T相同的。设一个名为count的二维vector,count[i + 1][j + 1]表示S的前 i 个字符的subsequence中包含与T的前 j 个字符相同的个数。则对于S[i] != T[j],count[i][j] = count[i - 1][j];对于S[i] == T[j],count[i][j] = count[i - 1][j] + count[i - 1][j - 1]。注意如果T为空,则只有S的空subsequece才等于T,所以初始化时count[i][0] = 1


Interleaving String: https://leetcode.com/problems/interleaving-string/

注意这里要求s1和s2恰好能组成s3,不能有剩余的字符。设一个名为can的二维数组,can[i + 1][j + 1]表示s1的前 i 个字符和s2的前 j 个字符能组成s3的前 i + j 个字符,则can[i + 1][j + 1] = (s1[i] == s3[i + j + 1] && can[i][j + 1]) || (s2[j] == s3[i + j + 1] && can[i + 1][j])。注意两点:1)判断时是s1[i]/s2[j] == s3[i + j + 1];2)初始化时,先将can[0][0]初始化为true,然后对i/j == 0的情况进行特殊处理:can[i + 1][0] = s1[i] == s3[i] && can[i][0];can[0][j + 1] = s2[j] == s3[j] && can[0][j]


Regular Expression Matching: https://leetcode.com/problems/regular-expression-matching/

声明长度+1的二维vector名为match,match[i + 1][j + 1]表示s[i]和p[j]能够match。首先初始化match[0][j],表示p[j]能否match空string,然后进行遍历更新match[i + 1][j + 1],主要有以下几种情况:

1)p[j] != '*':match[i + 1][j + 1] = match[i][j] && (s[i] == p[j] || p[j] == '.')

2)p[j] == '*':match[i + 1][j + 1] = match[i + 1][j - 1] || (match[i][j + 1] && (s[i] == p[j - 1] || p[j - 1] == '.'))

a)但p[j - 1]和p[j]实际为空,则match[i + 1][j + 1] = match[i + 1][j - 1]

b)p[j - 1]出现了至少一次,则match[i + 1][j + 1] = match[i][j + 1] && (s[i] == p[j - 1] || p[j - 1] == '.')

如果仅出现一次,则match[i][j + 1]表示s[i - 1]和p[j]匹配(此时p[j - 1]和p[j]实际为空);如果出现了多次,则match[i][j + 1]自然应该为true


Longest Valid Parentheses: https://leetcode.com/problems/longest-valid-parentheses/
用一个名为dp的vector存储以当前字符为结尾的最长valid输入的长度,如果s[i] == ')'并且s[i - dp[i - 1] - 1] == '(',

则dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0)


Word Break II: https://leetcode.com/problems/word-break-ii/

思想类似word break,设一个名为cut_index的vector<vector<int> >(size + 1),cut_index[i + 1]记录了以 s[i]为结尾,上一个合法cut的位置。例如输入"abcde",["abc","de"],则对'e'来说,cut_index[5] = {2}。用一个max_len记录字典中的最大长度,则对于s[i],只要检查j = i - max_len ~ i - 1即可。只有当cut_index[j + 1]不为空且s.substr(j, i - j)在字典中时,表明以s[i]为结尾有合法的cut,就将 j 加入cut_index[i + 1]。注意在初始化时要在cut_index[0]中加入-1使其不为空(并且-1能保证在生成最后结果时第一个substring能从0开始)。最后检查cut_index[size],如果它不为空,则产生结果。在产生结果时使用backtracking,用一个名为space_index的vector<int>记录一个合法cut的结尾,适当加入空格即可


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

用一个名为dp的二维vector<vector<int> > (k + 1, vector<int>(size, 0)),dp[i][j]表示在第 j 天,最多有 i 次transaction时的最大利润,则dp[i][j] = max(dp[i - 1][j], prices[j] + tmpMax),其中第一项使用至多有[i - 1]次transaction的结果,prices[j] + tmpMax表示在第 j 天卖出的最大可能结果。tmpMax表示在当前时间之前有至多 i - 1次transaction且最后进行buy时的最大利润,初始化为-prices[0],每次更新为tmpMax = max(tmpMax, dp[i - 1][j - 1] - prices[j]),以供下次循环使用

当k >= size / 2时,表示可以进行任意多次transaction,则此时进行一下处理以加速程序。这时就是Best Time to Buy and Sell Stock II,用dp做即可,空间也可优化为O(1)


Maximal Rectangle: https://leetcode.com/problems/maximal-rectangle/

从上往下进行计算,对于每个matrix[i][j],计算以当前元素向上的最大高度为高的矩形的面积。声明三个大小为col的vector:left表示以当前高度所能形成矩形的左边界(初始化为0),right表示以当前高度所能形成矩形的右边界(初始化为col - 1),height表示当前高度。对每行,先从右向左计算right,然后再从左向右计算left、height和面积。例如matrix[i][j] == '1'时,left[j] = max(left[j], cur_left),这里max()中的left[j]是上一行[j]的左边界,cur_left是当前行[j]的左边界


Create Maximum Number: https://leetcode.com/problems/create-maximum-number/

对 i = 0 ~ k,取num1长度为 i 的最大数和num2长度为 k - i 的最大数,如果两个最大数的长度和等于k,则将两者merge起来就可以得到一个可能的结果。用一个名为max_num1,长度为k + 1的二维vector,max_num1[n]表示num1长度为 n 的最大数。在产生最大数时,首先如果num1.size() <= k,则令max_num1[k] = num1,然后在num1中寻找第一个num1[i] < num1[i + 1]的元素,将其erase(如果没有就erase最后一个元素)。下一次loop中寻找erase的元素时,从 i - 1开始向后寻找即可(注意erase了 i,i - 1可能不再合法,如2,1,3)。在merge的时候,如果两个数相等则需要继续向后检查,直到到达边界或找到两个不相等的数为止,然后merge较大的那个(如果有一个数达到边界,则应该merge尚未达到边界的那个)


Burst Balloons: https://leetcode.com/problems/burst-balloons/

令size等于原始数组的长度,在数组的开头和结尾各添加一个1,使用一个(size + 2) * (size + 2)的二维vector名为dp,dp[i][j]表示范围(i, j)内能得到的最大值(不包括边界)。则令len = [1, size],start = [0, size - len],计算对应len长度,区间[start, start + len + 1]的最大值,然后返回dp[0][size + 1]即可。在计算dp[start][end]时,令 i = [start + 1, end - 1],假设 i 是最后一个burst的balloon,则它最后一次burst得到的值是乘以了左右边界的值,

则dp[start][end] = max(dp[start][end], dp[start][i] + dp[i][end] + nums[start] * nums[i] * nums[end])


Scramble String: https://leetcode.com/problems/scramble-string/

如果两个string互为scramble,则它的两个子树一定也是互为scramble的,scramble只能在某个子树内部进行。用一个名为isValid的三维vector,isValid[i][j][len - 1]表示s1以[i]为起点和s2以[j]为起点,长为 len 的substring是不是互为scramble。首先初始化len = 1的情况,然后令len = [1, s1.size()],i = [0, s1.size() - 1],j = [0, s1.size() - 1],更新对应的isValid。在更新isValid[i][j][len - 1]时,令k = [1, len - 1],尝试将s1的substring分割为长度 k 和 len - k两部分,检查对应的s2是不是valid,如果是则更新isValid[i][j][len - 1]为true,最后返回isValid[0][0][s1.size() - 1]即可,时间复杂度是O(n^4)

另外一种方法就是将s1分成两个部分,递归地检查和s2对应的是不是scramble的。在进行递归之前先检查一下s1和s2包含的字符种类和个数是否相等,时间复杂度是指数级的


Dungeon Game: https://leetcode.com/problems/dungeon-game/

从右下向左上进行dp。每个点表示在这点存活需要的最小hp,如果hp <= 0,表示这点加了hp,但到这里时hp应该>0,则此时令cur[j] = 1;而选择从下面还是右面过来应该选二者中存活hp较小的那个,所以cur[j] = max(1, min(cur[j + 1], pre[j]))。注意初始化时pre[col -1]的值


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

用一个名为dp的vector<int>记录当前房子选择某个颜色时的最小总花费,用一个名为min_cost的vector<int>,min_cost[i]表示前一所房子除颜色 i 外选其他某种颜色的最小总花费,则dp[j] = min_cost[j] + costs[i][j]。对于每所房子,用两个平行的for循环分别更新dp[j]和min_cost[j]。在更新min_cost[j]时,需要用一个函数找出min_cost[j]中最小和第二小的值,如果min_cost[j]等于最小值,则说明它就是这个最小值,则此时应该令其等于第二小的值

这道题的空间复杂度可以进一步优化为O(1),可以用四个变量分别记录前一所房子的两个最小值和当前房子的两个最小值


Russian Doll Envelopes: https://leetcode.com/problems/russian-doll-envelopes/

先将输入排序,用名为dp[i]记录以 i 为最外层时所能包括的所有信封数,遍历 j = 0 ~ i - 1即可得到dp[i]

另外这道题的时间可以优化为O(n log n),将信封以width升序,width相同的以height降序进行排列,则对于每个信封 i 只要找到它之前的所有元素中,最大值小于 i 的height的最长增长序列即可。求最长增长序列有一个O(n log n)的奇技淫巧方法,可以参考LIS或这里和这里的一个回答





奇技淫巧题

Number of Digit One: https://leetcode.com/problems/number-of-digit-one/

检查每一位数字可能出现1的个数。令m = 1, 10, 100...表示检查个位、十位、百位...上出现1的个数,pre = n / m,post = n % m,如果pre % 10 >= 2,即当前位的数字>=2,则它出现1的总数为(pre / 10 + 1) * m;如果pre % 10 == 1,即当前位的数字为1,则它出现1的总数为(pre / 10) * m + post + 1;如果pre % 10 == 0,即当前位的数字为0,则它出现1的总数为(pre / 10) * m。要注意一下溢出的问题,只要在m > INT_MAX / 10的时候break就好


Self Crossing: https://leetcode.com/problems/self-crossing/

一共有三种情况:

1 0
原创粉丝点击