Leetcode 每日一题和每日一题的下一题刷题笔记 2/30
写在前面
这是我参与更文挑战的第2天,活动详情查看:更文挑战
快要毕业了,才发现自己被面试里的算法题吊起来锤。没办法只能以零基础的身份和同窗们共同加入了力扣刷题大军。我的同学们都非常厉害,他们平时只是谦虚,口头上说着自己不会,而我是真的不会。。。乘掘金鼓励新人每天写博客,我也凑个热闹,记录一下每天刷的前两道题,这两道题我精做。我打算每天刷五道题,其他的题目嘛,也只能强行背套路了,就不发在博客里了。
本人真的只是一个菜鸡,解题思路什么的就不要从我这里参考了,编码习惯也需要改进,各位如果想找刷题高手请教问题我觉得去找 宫水三叶的刷题日记 这位大佬比较好。我在把题目做出来之前尽量不去看题解,以免和大佬的内容撞车。
另外我也希望有得闲的大佬提供一些更高明的解题思路给我,欢迎讨论哈!
好了废话不多说开始第一天的前两道题吧!
2021.6.2 每日一题
这个月估计每日一题全都是前缀和了,上个月每日一题全是异或,那之后做题的思路就直接往这上面靠了。
前 j 个数求和,减去前 i 个数求和,之间的差就是一段连续子数组的和,只要这个值是 k 的倍数就可以了。然后来写个代码
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int nums_len = nums.size();
// std::cout << nums_len << std::endl;
if (nums_len < 2) {
return false;
}
// std::cout << "asda";
std::vector<long long> sum(nums_len, 0);
for (int i = 1; i < nums_len; i++) {
sum[i] = sum[i - 1] + nums[i - 1];
}
if (nums_len == 2) {
return ((nums[0] + nums[1]) % k == 0);
}
if (nums_len == 3) {
bool result = ((nums[0] + nums[1]) % k == 0)
|| ((nums[1] + nums[2]) % k == 0)
|| ((nums[0] + nums[1] + nums[2]) % k == 0);
return result;
}
bool has_subarray_meet_condition = false;
for (int i = 0; i < nums_len; i++) {
for (int j = i + 2; j < nums_len; j++) {
auto subarray_sum = nums[j] + sum[j] - sum[i];
has_subarray_meet_condition = has_subarray_meet_condition || (subarray_sum % k == 0);
// std::cout << subarray_sum << " ";
if (has_subarray_meet_condition == true) {
return true;
}
}
}
if (sum[nums_len - 1] % k == 0) {
// std::cout << sum[nums_len - 1] << std::endl;
return true;
}
return false;
}
};
我一个滑铲,好家伙遇到特例了,排除掉特例。。。又遇到特例了。。。再排除,然后给我一个阴间测试样例,类似这样的:
好家伙,看来思维上偷懒是不行了,直接算前缀和然后相减这样是两层循环,大概是这样的复杂度会超时。看来应该有更简单的方法。我当时脑子懵了是没想出来,后来点到题解上打眼一看满篇的同余,好家伙,有思路了。
同余的思路很简单,被减数 sum[j] 和减数 sum[i] 的差是 k 的倍数,那么被减数和减数对 k 求余数的结果应该是一样的。
所以多维护一个数组,存这种余数,余数相同证明找到 k 的倍数了,然后看一下两个位置距离是否大于 2 即可。
前面的代码要大改,舍弃很多累赘的部分。现在代码写成了这样:
...
你们简单想象一下,这里我又写了一个双层循环的垃圾代码,肯定还是超时的,这个时候应该反思一下,不超时的关键是减少一层循环,而不是什么同余。同余只是一种手段,把时间复杂度的一层循环换到了空间上。我需要的是把前面有相同余数的位置取出来,很自然的想到哈希表(我已经还给我的老师了,和我一样忘了哈希表和散列函数的可以去面壁思过了哈哈哈)。键值对键值对天天用,可能是熟视无睹了吧,嗯,是这样。
这里要想清楚一件事,谁当键谁当值。我要快速找到有同样余数的那个位置离我现在的距离,求距离需要用 i 和 j,这个是值。用键来快速查,键就是余数。
现在的代码就只剩一层循环了,这样应该不超时了吧(别翻车啊)
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int nums_len = nums.size();
// std::cout << nums_len << std::endl;
if (nums_len < 2) {
return false;
}
unordered_map<int, int> former_position;
// position before the very beginning of `nums`
former_position[0] = -1;
// std::cout << former_position[0];
int remainder = 0;
for (int i = 0; i < nums_len; i++) {
remainder = (remainder + nums[i]) % k;
if (former_position.count(remainder)) {
int previous_index = former_position[remainder];
if (i - previous_index >= 2) {
return true;
}
} else {
former_position[remainder] = i;
}
} // end for
return false;
}
};
写的过程中会发现前两个元素直接满足情况的时候距离算的有问题,然后把全部元素加起来满足情况的时候距离算的也有问题,这个时候都是余数等于0,往前找没找到,然后我在最开始的时候往 former_position 里面塞了一个 (0, 0) 结果也不行,距离还是有问题,说明这一开始塞的键值对应该是 (0, -1),减去负一相当于加一,这样距离就对了,余数是 0 保证能找到这个位置上来。改完这些,我的代码其实和官方给的解法已经一样了。
我做算法题的有时会把从零开始的下标改成从一开始的下标,但是后面改着改着改乱了,还不如从零开始。这道题还好不是数组,实际上这么写是从负一开始的。
2021.6.2 每日一题下面的题
随机抽一道题,来康康今天有什么
抽到了一个算两个年月日时间之间相差的天数???这种题用时间戳秒杀不就行了???好吧似乎大佬们都是直接上公式的,尔等菜鸡还在调库。这个题先放一放,公式有点复杂没看懂。
换一道能看得懂还能学到一点好懂的知识的题吧。
这题打眼一看肯定和背包问题有点关系。背包问题,动态规划这些以前有一篇被转载过很多次的文章叫背包九讲,大家可以自己去搜,原作者我不记得了,所以链接就不放了。很遗憾这些知识点我也还给背包九讲的作者了。。。
动态规划最重要的就是把状态转移的表达式写出来,我是一下子写不出来的,我得在纸上推半天才能写出来一个很接近正确答案的错误答案。(“2和4之间的数字是几?”表情包.gif)
写代码
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n = arr.size();
unordered_map<int, int> hash;
vector<int> f(n * n);
int ans = 0;
for (int i = 0; i < n; ++i) hash[arr[i]] = i;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (hash.count(arr[i] - arr[j]) && (arr[i] - arr[j] < arr[j])) {
auto idx = hash[arr[i] - arr[j]];
f[j * n + i] = f[idx * n + j] + 1;
ans = max(ans, f[j * n + i] + 2);
}
}
}
if (ans < 3) return 0;
return ans;
}
};
正经人不要用二维数组,直接一维数组就够了,反正两层循环兜底,根本不用怕。
小结
前缀和,同余,哈希表,LIS模型,不要用二维数组