Hard题目总结

Posted xero10

tags:

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

 

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,左子树包含左儿子的可加的sum) + max(0,右子树包含右儿子的可加的sum)

所以设定一个helper(TreeNode* root, int &max, int &max_addable)。注意这里“可加”要求一个节点只能将自己与左儿子/右儿子相加,否则因为加了两个儿子,已经出现了一个end to end的path,就不再可加了

 

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 个元素的大小,将小的那个数列的前k / 2 个元素丢掉,然后再寻找两个数列的第 k - k / 2个元素。注意两点:1)第 k / 2 个元素的下标其实是 [k / 2 - 1];2) 在比较两个数列的[start + k / 2 - 1]时要注意判断是否越界,如果越界则将进行比较的值设为INT_MAX以拿掉另外一个数列的 k / 2个元素。之所以可以这样是因为越界数列元素的个数肯定小于 k / 2,所以k肯定不会出现在被拿掉的数列的前 k / 2个元素中;3)注意下一次寻找的是第 k - k / 2个元素,而不是 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[i]里,然后再从后向前找有第二个transaction时的最大利润。

第一步:dp,某天的最大利润或是前一天的最大利润(今天什么都不做),或者是今天卖出的最大利润:profit[i] = max(profit[i -1], prices[i] - min_price)

第二部:从后向前,以 i 表示第二次买入在第 i 天时的总最大利润,则 result = max(result, max_price - prices[i] + profit[i -1])。这里的max_price是从右向左更新的,所以能正确表示在 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为空的情况:由于stack是递增的,所以index为空证明在[0, i - 1]没有比height[i]更小的值了,所以可以把 i 作为矩形的宽。这里的思想是由于stack是递增的,所以以stack中某个值为高的最大矩形的左边是到index.back() + 1,右边是到 i - 1 (因为height[i]是第一个 > height[index.back()]的,否则index.back()在之前就已经被pop出来了)

另外这里有一个技巧是可以在input数组最后加一个-1,这样就不用单独处理数组iterate完了然而stack非空的情况

 

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/

1)思想是只有括号要特殊处理,所以当遇到新的左括号时将当前的结果result和操作符add分别加入两个stack,然后将result和add分别重置为0和-1;当遇到右括号时如果stack非空则将栈顶pop出,然后与当前的结果进行计算。注意每次计算结束后add要重置为-1

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

3)另一种更高效的方法是用一个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/

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

2) 用一个dqueue存高度递减的pair<height[i], i>, 遍历height,如果height[i]比deque里的最后一个值高,则持续pop_back,每pop出来一个值后将当前back到 i 的水量加入result。注意计算水量时宽度是back => i,高度是min(back, height[i]) - 上一次pop出来的值

 

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/

这道题可以分为3步:

1) 先检查s是不是palindrome,如果是的话返回

2) 将result初始化为直接将s变成palindrome的结果,即将s直接reverse然后加起来。这里注意s的第一个元素可以不用插入对称的,并且如果s的开头有重复的字符,这些重复的字符也不需要考虑对称,无论他们是奇数个还是偶数个

3) 令 i = [1, s.size() - 1),检查以它为中心的palindrome的最大长度,如果左侧能到达边界(left < 0),则说明已经找到以 i 为中心的palindrome,则只要在s前面插入右侧剩下未匹配的字符即可,此时也可以更新result因为一定找到了一个更小的palindrome。这里需要注意处理一下 i 是重复的情况

 

或者用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]的在同一条线上的最多的点,则只要它检查与 i + 1 ~ n - 1所组成的直线即可,因为经过小于[i]的点的直线在之前肯定已经被找到过了。

对于每一个点[i],计算它与之后每个点的斜率,只要斜率相同则几个点一定在通一条直线上(因为直线已经确定经过点[i]),然后用一个hash table,以斜率作为key,拥有相同斜率的点作为value,value最大的即使经过点[i]最多的直线(注意这里hash table里没有计入[i])。但是斜率可能是一个无限小数,所以不能用它作为key,我们可以用化简过的分子(xi - xj)分母(yi - yj)所组成的string作为key(要用一个特殊字符将分子分母分隔开)。要化简分子分母需要找到二者的gcd(最大公约数),然后将二者除以gcd即可。这里要注意处理一下斜率无限大和为0的特殊件情况,即直线是垂直或者水平的,分别用一个变量来track即可。还注意要处理一下[i]的值重复出现的情况,用一个变量same来track即可(初始化为1)。不需要track [i + 1, n - 1]的重复因为他们已经被track在了hashtable里

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

 

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

O(nlogn)时间,O(n)空间:思想是按照rating从小到大来确定每个人的candy数。建立一个map<int,vector<int>> rating_to_index,key为rating的值,value为对应的index;再建立一个vector<int> candies表示每个人应得的candy数,初始化为0。然后按顺序遍历rating_to_idx,则当处理一个idx时,只需要判断它和candies[idx - 1]和candies[idx+ 1]的关系,以左边为例,如果candies[idx - 1]为0,则说明对应的rating[idx - 1] > rating[idx],则应该令candies[idx]取可能的最小值,即1;如果ratings[idx - 1] == ratings[idx],则同样可以取最小值;否则candies[idx] = candies[idx-1] + 1。右边同理。

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)。具体参考这里

 

String Transforms Into Another String: https://leetcode.com/problems/string-transforms-into-another-string/

用一个unordered_map<char,char> conversion表示str1中每一个char所应该match到的str2对应的char(即conversion[str1[i]] = str2[i])。因为str1中的每个char只能变为同样的新的char,所以如果str1中有某个char要变成两个不同的新的char,则肯定无法成功。遍历完两个str之后,如果conversion的size < 26,则说明有一个未被str1使用的char,则这个char可以作为过渡来保证str1中所有的变换不会影响后面等待需要变换的char(想象ab=>ba,如果先把a=>b,则str1就变成了bb,后面的变换会受到影响。这里不代表后面的变换就不能进行,只是如果不会影响后面的变换的话则肯定可以完成所有的变换)。如果str1使用了所有的char,则此时依然存在成功变换的可能(考虑例子"abcdefghijklmnopqrstuvwxyz"=>"bcdefghijklmnopqrstuvwxyzq",这里虽然str1用了所有的char,但是str2中“q”出现了两次,所以可以将str1中的“z”先变成“p”,然后将两个“p”都变成“q”),成功的条件是str1中有两个不同的char都变换到了str2中的同一个char,这种情况下可以先把这两个str1中的char变换了,然后就可以省出一个str1中不再存在的char作为过渡,进而让str1的变换成功。注意这里要提前处理一下str1 == str2的情况

 

Minimum Window Subsequence: https://leetcode.com/problems/minimum-window-subsequence/

尝试对S[s_it]和T[t_it]进行匹配,如果二者不相等则s_it++,否则令t_it++并判断是否匹配完了所有的T(如果未匹配完则s_it++继续),在匹配完的情况下,说明s_it是最后一个匹配的字符,则从它向前逆向寻找最短的能完成匹配的S的subsequence(此时一定能找到)。此时我们就获得了一个可能结果。下次进行循环时让s_it移动到逆向找到的以当前为结尾的最短匹配的开头的下一个位置(已经找到最短匹配了,可以从这个最短匹配开始而不是从正向match到T[0]的s_it),然后t_it = 0继续新的寻找即可。这个方法的时间是O(mn)的,考虑输入"aaaaaaa"和“aa”的情况

这个题还有一个DP的方法,但是并没有很intuitive并且时间复杂度是一样的还需要额外的空间

 

Range Module: https://leetcode.com/problems/range-module/

这个题有那么几个(specifically,one in add,one in remove)很重要但又没那么intuitive的地方,必须要记住才可以。整体的思想就是用一个map<int,int>来存interval,在add的时候,找到会被当前add影响的所有interval并将他们erase掉,然后将新的interval加入即可。这里可以借助upper_bound(left)和upper_bound(right)找到对应的左右边界,这里的重点是判断左右边界head和tail的关系(tail指向要被erase的最后一个元素的右边一个元素),如果他们相等,则说明已有的interval和要添加的没有重叠,则直接添加[left] = right;只有在head != tail时才需要erase,也就是在这个时候才需要考虑已有interval对要添加的interval的影响。在remove的时候同理也要判断head == tail,如果相等说明没有重叠,也就是没有interval需要被remove,直接返回;如果head != tail,则说明需要remove,此时注意第一个和最后一个被remove的interval可能出现partial removal,也就是说remove后要生成两个比原来更小的interval,这时候需要定义min_left = min(left, head->left), max_right = max(right, (--tail)->second),则min_left < left时需要在左边insert一个[min_left, left];max_right > right时在右边insert一个[right, max_right]

 

 

 

 

 

BSF & DSF:

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

简单的方法:

先用bfs,一边进行搜索一边构建图(即建立一个名为neighbor的unordered_map<string, vector<string>>记录每一个节点所能到达的下一个节点),在得到endWord后,利用neighbor通过dfs产生答案。在bfs的过程中,可以用一个queue<string>来存储每一个iteration,可以用一个空string来分离不同的iteration。注意要用一个unordered_set<string> used来记录当前层用过的word,然后再当前层iteration结束的时候从dict里erase所有used的word以保证最短(因为一个word被使用一次后不应该再被使用第二次)。另外erase必须要在bfs一个iteration之后进行,因为当前iteration重复出现某个word是正确的。

高效的方法:

从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],因为判断can[i + 1][j + 1]时是判断用了s1的前i + 1和s2的前j + 1,所以总的长度是 i + 1 + j + 1,则s3的最后一个下标应该是i + 1 + j + 1 - 1,也就是i + j + 1;2)初始化时,先将can[0][0]初始化为true,然后对i/j == 0的情况进行特殊处理(即处理用0个s1/s2的字符):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/

solution 1: time O(n), space O(n)

用一个名为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)

solution 2: time O(n^2), space O(1)

思想类似判断valid parentheses。用left和right记录左右括号的数量:

if (s[i] == '(') left++;

else

    if (left > right) right++; 可能产生了一个以当前位置为结尾的valid string,则记录以当前位置为结尾向前的最长valid string并与结果比较

    else left = right = 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),其中第一项表示今天不进行transaction;第二项表示今天进行transaction,而要实现最大利润则应该卖出,所以prices[j] + tmpMax表示在第 j 天卖出的最大可能结果。tmpMax表示在当前时间之前有至多 i - 1次transaction且最后进行buy时的最大利润(这个buy可以发生在任何时间,只要end with buy即可),初始化为-prices[0],每次更新为tmpMax = max(tmpMax, dp[i - 1][j - 1] - prices[j]),以供下次循环使用。这里需要注意的是外循环必须用k,因为内循环需要从第1天开始(不需要从第0天开始,因为第0天最多只能进行1个transaction,最大利润永远是0),而tmpMax对每一个内循环的开始都必须初始化为-prices[0]来确保end with buy

当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表示当前高度。对每行,先从左向右计算left和height,再从右向左计算right。

对每一行,用一个cur_left表示当前行的左边界。height[i][j]非0时,左边界可能由当前行左边界决定,也有可能由前一行的左边界决定(当height > 1时需要考虑之前行的左边界,考虑 i - 1行即可),所以left[i][j] = max(cur_left, left[i - 1][j])。注意如果height[i][j]我们需要update cur_left,并且set left[i][j] = 0以保证万一 i+1 行用到了left[i][j],max的结果依然正确。

同理从右到左处理right[i][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。min_hp[i][j]表示能survive当前点以及右/下节点所需的最小生命,则min_hp[i][j] = max(current, min(right,down))。要注意在计算right/down的时候要计入dungeon[i][j],因为如果dungeon[i][j] > 0,则当前节点增加了生命,所以要从右/下所需的就变小了;如果当前节点扣除了生命,则要满足右/下节点需要更多的生命

 

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或这里这里的一个回答

 

The Most Similar Path in a Graph: https://leetcode.com/problems/the-most-similar-path-in-a-graph/

定义一个2D vector min_edit_distance,min_edit_distance[i][j]表示当用names[j]匹配targetPath[i]时的总最短edit distance(此时target[0:i-1]已经被匹配过了),则min_edit_distance[i][j] = min_distance[i - 1][k] + edit_current,这里 k 是 j 所有的neighbor,edit_current表示是否要修改names[j],也就是说min_edit_distance[i][j]等于 j 所有的neighbor里match到targetPath[i - 1]的距离中最小的加上当前names[j]的edit。则最后遍历min_edit_distance[targetPath.size() - 1][i]即可找到最小的总edit distance。但是因为这里要返回具体的每个city而不是最短距离,所以我们要能知道每一个点的最短距离是从哪一个neighbor过来

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

CodeForces 1118F2. Tree Cutting (Hard Version)

[CTSC2016]时空旅行

noip部分题目总结

记录一下有用的博客

DP 斜率优化题目/决策单调性题目

一些专题上的题目总结