Rank
Name
Score
Finish Time
Q1 (3)
Q2 (5)
Q3 (5)
Q4 (6)
165 / 12400
YoungForest
18
1:07:14
0:03:22 🐞1
0:16:01 🐞1
0:27:41 🐞1
0:52:14
提前40min AK。虽然因为粗心大意,前三题每题WA一次,导致15min罚时,但好的一点是这周应该不用每天残酷打卡了。正好全力以赴,准备周三的硕士论文答辩。
三年的硕士生涯全靠周三一天了,毕其功于一役,加油,Forest!
1869. Longer Contiguous Segments of Ones than Zeros
签到题。统计连续字串的长度,更新最长长度即可。
因为更新最长长度的代码写错位置,写到了if的分支里,WA了一次。应该无论如何都要更新,所以要写在外面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : bool checkZeroOnes (string s) { vector<int > longest (2 , 0 ) ; int length = 0 ; char last = '2' ; for (char c : s) { if (c != last) { last = c; length = 1 ; } else { ++length; } longest[c - '0' ] = max (longest[c - '0' ], length); } return longest[1 ] > longest[0 ]; } };
时间复杂度: O(N),
空间复杂度: O(1).
1870. Minimum Speed to Arrive on Time
直接暴力二分怼。之前总结的binary search模版很好用。基本只需要改几行代码就行了。
因为浮点数精度WA了一次。10^-5
不够小,建议以后都用10^-9
。
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 class Solution { using ll = int ; public : int minSpeedOnTime (vector<int >& dist, double hour) { int lo = 1 ; int hi = 1e7 + 1 ; auto binary = [&](ll lo, ll hi, function<bool (const ll)> predicate) -> int { while (lo < hi) { ll mid = lo + (hi - lo) / 2 ; if (predicate (mid)) { hi = mid; } else { lo = mid + 1 ; } } return lo; }; auto pred = [&](const ll x) -> bool { double ans = 0 ; int i; for (i = 0 ; i + 1 < dist.size (); ++i) { ans += (dist[i] + x - 1 ) / x; } ans += dist[i] / static_cast <double >(x); return ans <= hour + 1e-9 ; }; ll ans = binary (lo, hi, pred); if (ans >= hi) return -1 ; else return ans; } };
时间复杂度: O(log dist[i] * dist.length) = log 10^5 * 10^5,
空间复杂度: O(1).
1871. Jump Game VII
很容易想到暴力的N^2解法。
从头开始遍历,如果是0的话,更新之后所有可以到达的位置。
时间复杂度为: s.length * (maxJump - minJump) = 10% * 10^5.
优化的方向是“更新之后所有可以到达的位置”。显然,这步更新因为有很多重复的更新,因此花费很多。但是,我们考虑每次其实是更新一个区间(range),而且区间的大小相等,而且区间总是向右移动的。因此,我们发现,更新区间并不需要遍历minJump…maxJump,只需要从maxJump向前开始遍历,如果和之前已经更新过的区间重合了,就直接break结束更新就好了。因为每个位置最多被更新一次,因此时间复杂度是 s.length.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : bool canReach (string s, int minJump, int maxJump) { const int n = s.size (); vector<bool > reachable (n, false ) ; reachable[0 ] = true ; for (int i = 0 ; i < n; ++i) { if (s[i] == '0' ) { if (reachable[i]) { for (int j = min (maxJump, n - 1 - i); j >= minJump; --j) { if (reachable[i + j]) break ; reachable[i + j] = true ; } } } } return s[n-1 ] == '0' && reachable[n - 1 ]; } };
时间复杂度: O(s.length),
空间复杂度: O(s.length).
1872. Stone Game VIII
类似之前的Stone Game,这种最优玩法的题目大多数属于 动态规划问题。
即通过搜索所有可能的选择,找到最优结果。过程中有很多重复子问题,因此需要使用动态规划。
最优化目标是(Alice’s score - Bob’s score),Alice想要最大化,Bob想要最小化。其实都是最大化(我的分数 - 对方的分数)。
另外,观察到操作的结果其实是石头的和,因此需要提前计算前缀和数组。
问题转化成,从前缀和[i:]中挑一个数,使得的分数差最大;之后的后手只能从被挑的位置之后挑。
定义
dp(i) 为从前缀和[i:]中挑一个数的最大分数差,
则状态转移方程为:
dp(i) = max(presum[j] + dp(j + 1) for j in range(i,)).
时间复杂度为 O(N^2), 显然超时。
然而,在状态转移方程中其实还有重叠子问题, for loop max可以只计算一次,因此可以进一步优化到 O(N).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int stoneGameVIII (vector<int >& stones) { const int n = stones.size (); vector<int > presum (n + 1 ) ; presum[0 ] = 0 ; for (int i = 0 ; i < n; ++i) { presum[i+1 ] = presum[i] + stones[i]; } int maxDp = numeric_limits<int >::min (); vector<int > dp (n + 2 , 0 ) ; dp[n+1 ] = 0 ; for (int i = n; i >= 2 ; --i) { maxDp = max (maxDp, presum[i] - dp[i + 1 ]); dp[i] = maxDp; } return maxDp; } };
时间复杂度: O(N),
空间复杂度: O(N).