本周的题目要比以往的难,也可以说恰好考到我的知识盲区,DP问题。老实的说,我对DP问题没有过深入的研究。这次DP题目尤其多,尤其是第4题,更是可以可以用经典的背包问题 求解。
Rank
Name
Score
Finish Time
Q1 (4)
Q2 (5)
Q3 (6)
Q4 (8)
576 / 4091
YoungForest
13
0:45:56
0:09:24
0:14:20
0:35:56 2
null
1046. Last Stone Weight
Intuition:
本题解法不难。我首先想到了最暴力的模拟整个smash的过程的解法。因为是签到题,暴力解也够了。
时间复杂度: O(n^2 log n)
空间复杂度: O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int lastStoneWeight (vector<int >& stones) { while (stones.size () > 1 ) { sort (stones.begin (), stones.end ()); int n = stones.size (); stones[n - 2 ] = stones[n - 1 ] - stones[n - 2 ]; stones.pop_back (); } return stones[0 ]; } };
但事实上,同样是模拟smash的过程,找到最大的2个石头。我用的办法是sort,该操作的时间复杂度为O(n log n)
. 大佬们就能想到优先队列(Priority Queue), 找石头的时间复杂度为O(log n)
. 而且我的解法并没有专门处理x == y
的情况,虽然不会出什么问题,但确实不符合模拟过程的初衷。
Priority queue 版本
时间复杂度:O(N log N)
空间复杂度: O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int lastStoneWeight (vector<int >& stones) { priority_queue<int > pq (stones.begin(), stones.end()) ; while (pq.size () > 1 ) { int y = pq.top (); pq.pop (); int x = pq.top (); pq.pop (); if (y > x) pq.push (y - x); } return pq.empty () ? 0 : pq.top (); } };
1047. Remove All Adjacent Duplicates In String
Intuition:
用栈来保存之前的数,依次读取下一个数,视条件进行remove操作。
时间复杂度: O(N)
,
空间复杂度: O(N)
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : string removeDuplicates (string S) { stack<char > s; for (char c : S) { if (s.empty ()) { s.push (c); } else if (s.top () == c) { s.pop (); } else { s.push (c); } } string ret; while (!s.empty ()) { ret.push_back (s.top ()); s.pop (); } reverse (ret.begin (), ret.end ()); return ret; } };
使用 stack 应该是该题的标准解法,大佬们也基本上都用的同样的方法。
1048. Longest String Chain
Intuition:
DP. 对于每个单词,遍历一遍比它短1的所有单词,更新成最大的dp+1。
时间复杂度: O(N^2 * word.length)
空间复杂度: O(N)
.
其中N为words.length
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { bool isPredecessor (const string& last, const string& current) { if (last.size () + 1 != current.size ()) return false ; int flag = 0 ; for (int i = 0 ; i < last.size ();) { if (last[i] == current[i + flag]) { ++i; continue ; } if (flag == 0 ) ++flag; else return false ; } return true ; } public : int longestStrChain (vector<string>& words) { vector<vector<pair<string, int >>> dp (16 ); for (const auto & s : words) { dp[s.size () - 1 ].push_back ({s, 1 }); } int ret = 1 ; for (int i = 1 ; i < 16 ; ++i) { int last_length = i - 1 ; const auto & last_dp = dp[last_length]; auto & current_dp = dp[i]; for (auto & current : current_dp) { for (const auto & last : last_dp) { if (isPredecessor (last.first, current.first)) { current.second = max (current.second, last.second + 1 ); ret = max (ret, current.second); } } } } return ret; } };
Discuss总结:大概只有2种方法,DP(bottom-top)和DFS。DFS的复杂度会更高些,需要用memoization,其实也就是top-bottom的DP。
1049. Last Stone Weight II
Intuition:
backtracking.
时间复杂度:O(N!), N = 30. 所以肯定会超时。
另外,我还尝试了Greedy的方法。
但并没有找到正确的解并证明它,试了2次,都Wrong Answer了。
正确的思路 :
考虑一个smash剩下的石头y - x
,再次被smash,则有剩下的石头z - (y - x) = z - y + x
。
任何一个初始的石头,如果被smash的次数是奇数,则前面是 负号,反之,正号。
所以,原始问题可以转化为 将所有石头分成2份,求2份石头差的绝对值的最小值。
该问题是经典的背包问题之一“minimum knapsack partition”。
Solution: DP for classic knapsack problem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int lastStoneWeightII (vector<int >& stones) { const int sum_bound = 1500 ; bitset<1500 + 1> dp {1 }; int sumOfStones = 0 ; for (auto stone : stones) { sumOfStones += stone; for (int i = sum_bound; i >= stone; --i) { dp[i] = dp[i] + dp[i - stone]; } } int ret = numeric_limits<int >::max (); for (int i = 0 ; i <= sum_bound; ++i) { ret = min (ret, abs (sumOfStones - dp[i]*i*2 )); } return ret; } };
时间复杂度: O(sum_bound * stones.size())
空间复杂度: O(sum_bound)
C++ 中 Bitset 是内含bit 或 Boolean值且大小固定的array。对其不熟悉的同学,可以看看《C++ 标准库 第二版》(C++ Standard library) 这本书。我最近也在看,目的是熟悉一下C++的标准库,学习其中的智慧和用法。
除了使用Bitset外,直接使用set
后记
周五剁手买了一年的LeetCode Premium, 花了159刀,大概1000元RMB。
刷题走起!!!