早起跟着英雄哥刷算法--day2

提示:本文参考英雄哪里出来的文章,原文连接⭐算法入门⭐《前缀和》中等02 —— LeetCode 974. 和可被 K 整除的子数组


一、题目

给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
样例输入: A = [4,5,0,-2,-3,1], K = 5
样例输出: 7

原题链接:

LeetCode 974. 和可被 K 整除的子数组


二、解题思路

1.暴力

代码如下:

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        int n = nums.size();
        if(nums.empty()){
            return 0;
        }
        vector<int> sums = nums;
        int count = 0;
        sums[0] = nums[0];
        for(int i = 1; i < n; i++) {
            sums[i] = sums[i-1] + nums[i];
        }
        for(int i = 0; i < n; ++i) {
            if(sums[i] % k == 0) {
                count++;
            }
        }
        for(int i = 0; i < n-1; i++) {
            for(int j = i+1; j < n; j++) {
                if((sums[j] - sums[i]) % k == 0) {
                    count++;
                }
            }
        }
        return count;
    }
};

结果运行超时

在这里插入图片描述
发现条件限制为:

  • 1 < = n u m s . l e n g t h < = 3 ∗ 1 0 4 1 <= nums.length <= 3 * 10^4 1<=nums.length<=3104
  • − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 104<=nums[i]<=104
  • 2 < = k < = 1 0 4 2 <= k <= 10^4 2<=k<=104

输入的数据量比较大,暴力行不通,更换思路。


2.哈希表 + 逐一统计 (Hush未掌握)

思路:来自 和可被 K 整除的子数组
通常,涉及连续子数组问题的时候,我们使用前缀和来解决。

我们令 P [ i ] = n u m s [ 0 ] + n u m s [ 1 ] + … + n u m s [ i ] P[i]=nums[0]+nums[1]+…+nums[i] P[i]=nums[0]+nums[1]++nums[i]。那么每个连续子数组的和 s u m ( i , j ) sum(i,j) sum(i,j) 就可以写成 P [ j ] − P [ i − 1 ] P[j] - P[i-1] P[j]P[i1](其中 0 < i < j 0 < i < j 0<i<j)的形式。此时,判断子数组的和能否被 k k k 整除就等价于判断 ( P [ j ] − P [ i − 1 ] )   m o d   k = = 0 (P[j] - P[i-1]) \bmod k == 0 (P[j]P[i1])modk==0,根据 同余定理,只要 P [ j ]   m o d   k = = P [ i − 1 ]   m o d   k P[j] \bmod k == P[i-1] \bmod k P[j]modk==P[i1]modk,就可以保证上面的等式成立。

因此我们可以考虑对数组进行遍历,在遍历同时统计答案。当我们遍历到第 ii 个元素时,我们统计以 ii 结尾的符合条件的子数组个数。我们可以维护一个以前缀和模 kk 的值为键,出现次数为值的哈希表 record \textit{record} record,在遍历的同时进行更新。这样在计算以 ii 结尾的符合条件的子数组个数时,根据上面的分析,答案即为 [ 0... i − 1 ] [0...i-1] [0...i1]中前缀和模 k k k 也为 P [ i ]   m o d   k P[i] \bmod k P[i]modk的位置个数,即 record [ P [ i ]   m o d   k ] \textit{record}[P[i] \bmod k] record[P[i]modk]

最后的答案即为以每一个位置为数尾的符合条件的子数组个数之和。需要注意的一个边界条件是,我们需要对哈希表初始化,记录 record [ 0 ] = 1 \textit{record}[0] = 1 record[0]=1,这样就考虑了前缀和本身被 k k k 整除的情况。

代码如下:

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int, int> record = {{0, 1}};
        int sum = 0, ans = 0;
        for (int elem: nums) {
            sum += elem;
            // 注意 C++ 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
            int modulus = (sum % k + k) % k;
            if (record.count(modulus)) {
                ans += record[modulus];
            }
            ++record[modulus];
        }
        return ans;
    }
};

3.复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。我们只需要从前往后遍历一次数组,在遍历数组的过程中,维护哈希表的各个操作均为 O ( 1 ) O(1) O(1),因此总时间复杂度为 O ( n ) O(n) O(n)

空间复杂度: O ( m i n ( n , k ) ) O(min(n,k)) O(min(n,k)),即哈希表需要的空间。当 n ≤ k n \leq k nk 时,最多有 n n n 个前缀和,因此哈希表中最多有 n + 1 n+1 n+1 个键值对;当 n > k n > k n>k 时,最多有 k k k 个不同的余数,因此哈希表中最多有 k k k 个键值对。也就是说,哈希表需要的空间取决于 n n n k k k 中的较小值。


三.总结

对哈希表的不了解给我在理解此题的路上造成了巨大的障碍,此题未解决,先告一段落;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值