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即可

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值