Leetcode 每日一题和每日一题的下一题刷题笔记 2/30

243 阅读4分钟

Leetcode 每日一题和每日一题的下一题刷题笔记 2/30

写在前面

这是我参与更文挑战的第2天,活动详情查看:更文挑战

快要毕业了,才发现自己被面试里的算法题吊起来锤。没办法只能以零基础的身份和同窗们共同加入了力扣刷题大军。我的同学们都非常厉害,他们平时只是谦虚,口头上说着自己不会,而我是真的不会。。。乘掘金鼓励新人每天写博客,我也凑个热闹,记录一下每天刷的前两道题,这两道题我精做。我打算每天刷五道题,其他的题目嘛,也只能强行背套路了,就不发在博客里了。

本人真的只是一个菜鸡,解题思路什么的就不要从我这里参考了,编码习惯也需要改进,各位如果想找刷题高手请教问题我觉得去找 宫水三叶的刷题日记 这位大佬比较好。我在把题目做出来之前尽量不去看题解,以免和大佬的内容撞车。

另外我也希望有得闲的大佬提供一些更高明的解题思路给我,欢迎讨论哈!

好了废话不多说开始第一天的前两道题吧!

2021.6.2 每日一题

523. 连续的子数组和

这个月估计每日一题全都是前缀和了,上个月每日一题全是异或,那之后做题的思路就直接往这上面靠了。

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;
    }
};


我一个滑铲,好家伙遇到特例了,排除掉特例。。。又遇到特例了。。。再排除,然后给我一个阴间测试样例,类似这样的:

leetcode-cn.com/submissions…

好家伙,看来思维上偷懒是不行了,直接算前缀和然后相减这样是两层循环,大概是O(n2)O(n^2)这样的复杂度会超时。看来应该有更简单的方法。我当时脑子懵了是没想出来,后来点到题解上打眼一看满篇的同余,好家伙,有思路了。

同余的思路很简单,被减数 sum[j] 和减数 sum[i] 的差是 k 的倍数,那么被减数和减数对 k 求余数的结果应该是一样的。

sum[j]sum[i]=kx\texttt{sum}[j] - \texttt{sum}[i] = k * x

sum[i]÷k=a...b\texttt{sum}[i] \div k = a ... b

sum[j]÷k=(sum[i]+kx)÷k=(a+x)...b\texttt{sum}[j] \div k = (\texttt{sum}[i] + k * x) \div k = (a + x) ... b

所以多维护一个数组,存这种余数,余数相同证明找到 k 的倍数了,然后看一下两个位置距离是否大于 2 即可。

前面的代码要大改,舍弃很多累赘的部分。现在代码写成了这样:

...

你们简单想象一下,这里我又写了一个双层循环的垃圾代码,肯定还是超时的,这个时候应该反思一下,不超时的关键是减少一层循环,而不是什么同余。同余只是一种手段,把时间复杂度的一层循环换到了空间上。我需要的是把前面有相同余数的位置取出来,很自然的想到哈希表(我已经还给我的老师了,和我一样忘了哈希表和散列函数的可以去面壁思过了哈哈哈)。键值对键值对天天用,可能是熟视无睹了吧,嗯,是这样。

这里要想清楚一件事,谁当谁当。我要快速找到有同样余数的那个位置离我现在的距离,求距离需要用 ij,这个是。用键来快速查,就是余数。

现在的代码就只剩一层循环了,这样应该不超时了吧(别翻车啊)


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 保证能找到这个位置上来。改完这些,我的代码其实和官方给的解法已经一样了。

image.png

我做算法题的有时会把从零开始的下标改成从一开始的下标,但是后面改着改着改乱了,还不如从零开始。这道题还好不是数组,实际上这么写是从负一开始的。

2021.6.2 每日一题下面的题

随机抽一道题,来康康今天有什么

抽到了一个算两个年月日时间之间相差的天数???这种题用时间戳秒杀不就行了???好吧似乎大佬们都是直接上公式的,尔等菜鸡还在调库。这个题先放一放,公式有点复杂没看懂。

1360. 日期之间隔几天

换一道能看得懂还能学到一点好懂的知识的题吧。

873. 最长的斐波那契子序列的长度

这题打眼一看肯定和背包问题有点关系。背包问题,动态规划这些以前有一篇被转载过很多次的文章叫背包九讲,大家可以自己去搜,原作者我不记得了,所以链接就不放了。很遗憾这些知识点我也还给背包九讲的作者了。。。

动态规划最重要的就是把状态转移的表达式写出来,我是一下子写不出来的,我得在纸上推半天才能写出来一个很接近正确答案的错误答案。(“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模型,不要用二维数组

参考链接

哈希表

LIS模型

【C++】经典DP—LIS模型变种,可优化为一维