前缀和:和可被K整除的子数组(normal)

本题经重构思考+深度研究各个方面总结。

目录

思路

结构

实现

前缀和

哈希值纳入

余数

非正式版本

调试验证:

bug

深度分析此bug

最后版本


题目链接在这

元素限制:

  • -104 <= nums[i] <= 104

求数组内连续元素和是K整数倍的子数组数目。

连续+和值

思路

思路一:滑动窗口

结合滑动窗口------同向双指针+单调性+不可回复性(l无法回退到初位置0判断)。

由于存在负数导致单调性丧失,本题每次r移动后,l都要进行从最初位置到r位置元素和的分别判断->O(N^2)。

滑动窗口+哈希表

哈希表存储和值->前缀和思想

思路二:前缀和

向后查找(下标小的方向)特定值的**个数(不是找到而是个数)**来弥补字段和距离整除的差距值。

☆☆☆具体实现规则:

我们发现若某一段连续数组元素和想要被K整除,那么在其前缀和值fdpi 的基础上必须在fdp先前下标1,i-1中找到目标fdpn出现的次数(1 <= n <= i-1),就是寻找首余数fdpi%K和其副余数(12%7的副余数-2,-3%7的副余数4),而因此每一段数组和的哈希表元素的纳入方式是hashdp\[i%k]++。

结构

实现

前缀和

cpp 复制代码
vector<int> fdp(nums.size()+1,0);
for(int i = 1;i <= nums.size();i++)
{
     fdp[i] = fdp[i-1]+nums[i-1];
     //printf("%d ",fdp[i]); 验证日志
}

哈希值纳入

cpp 复制代码
hash[fdp[i]%k]++;

余数

首余数与副余数

cpp 复制代码
//首余数
fdp[i]%k;
//副余数
int t = fdp[i]%k > 0? fdp[i]%k-k : fdp[i]%k+k;

非正式版本

cpp 复制代码
void test1(vector<int>& nums, int k)
{
    vector<int> fdp(nums.size() + 1, 0);
    for (int i = 1; i <= nums.size(); i++)
    {
        fdp[i] = fdp[i - 1] + nums[i - 1];
        //printf("fdp[i]:%d ", fdp[i]);
    }
    int count = 0;

    //哈希映射查找
    unordered_map<int, int> hash;

    for (int i = 1; i <= nums.size(); i++)
    {
        hash[fdp[i] % k]++;
        //首余数处理
        count += hash[fdp[i] % k];
        //副余数处理
        int t = fdp[i] % k > 0 ? fdp[i] % k - k : fdp[i] % k + k;
        count += hash[t];
    }
    cout << count;
}

调试验证:

走读发现:

首余数处理的部分,每次都会将刚刚纳入的值hashdp\[i]加上作为首余数,但我们找的是0,i-1间的目标首余数,因此对hash值"-1"忽略本次进循环时的++造成的误差。

bash 复制代码
count += hash[fdp[i]%k]-1;

问:既然首余数出现这种问题,那么副余数会不会呢?

如果会的话:

①fdpi % k == fdpi % k - k --->k == 0

②fdpi % k == fdpi % k + k --->k==0

以上两者均不会出现因为:

①题目" 2 <= k <= 10^4"

②任何数%0 无意义报错!

bug

当为k的整数倍的numsi-1第一次出现,count主余数应该使得count+=1;但因为"-1"导致加的是"0"。 // 丢失值使得数据比预期小

深度分析此bug

问:若后续继续出现numsi-1的整数倍会怎么样?

可以预见的是由于第一次落下"1"hash0没有加上,以后再次出现就会"永远比预期差一步"。 // 1 2 3 4 ->每次出现分别会差一次的值。

解决办法:

在哈希初始化时就对hash0++;

差值形成具体体现如下:

|---|-----------|-------|---|---|-----------|-------|
| i | hash0 | count | | i | hash0 | count |
| 2 | 1 | 0 | | 2 | 2 | 1 |
| 3 | 2 | 1 | | 3 | 3 | 3 |
| 4 | 3 | 3 | | 4 | 4 | 6 |

最后版本

cpp 复制代码
void test1(vector<int>& nums, int k)
{
    vector<int> fdp(nums.size() + 1, 0);
    for (int i = 1; i <= nums.size(); i++)
    {
        fdp[i] = fdp[i - 1] + nums[i - 1];
        //printf("fdp[i]:%d ", fdp[i]);
    }
    int count = 0;
    //哈希映射查找
    unordered_map<int, int> hash;
    hash[0]++;
    for (int i = 1; i <= nums.size(); i++)
    {
        hash[fdp[i] % k]++;
        //首余数处理
        count += hash[fdp[i] % k] - 1;
        //副余数处理
        int t = fdp[i] % k > 0 ? fdp[i] % k - k : fdp[i] % k + k;
        count += hash[t];
    }
    cout << count;
}

写给我自己的话:算法是当下(Linux IO中途刚到)学习的重点,价值在于严谨思维逻辑的锻炼、也是当下阶段提高思维方法有效武器。该算法题目纯手搓+额外问题的自行研究弱化学习内容面的扩大的作用,这种拓展研究能力调试能力更是真正价值的体现,坚持与调整算法题目的解答不仅作用于解题,更作用于我简介的"打破固有陋习"调整"学习方法"。(2026-0523)

相关推荐
QiLinkOS11 小时前
第三视觉理解徐玉生与他的商业活动(30)
大数据·c++·人工智能·算法·开源协议
mit6.82411 小时前
阅读的核心,是再读
c++
疯狂打码的少年11 小时前
【操作系统】页面置换算法(OPT/FIFO/LRU)
算法
小O的算法实验室11 小时前
2026年CIE,优化客货协同运输:综合地铁系统的列车容量动态分配
算法
Coder_Shenshen12 小时前
西门子S7CommPlus协议鉴权算法原理与流程详解
网络·后端·算法
硕风和炜13 小时前
【LeetCode: 2492. 两个城市间路径的最小分数 + DFS】
java·算法·leetcode·深度优先·dfs·bfs·并查集
upgrador13 小时前
基础知识:C++ STL构造函数的左闭右开惯例及其实现原理
开发语言·c++
我是一颗柠檬14 小时前
【Java项目技术亮点】加权轮询负载均衡算法
java·算法·负载均衡
灯厂码农14 小时前
C语言动态内存分配完全指南(malloc、calloc、realloc、free)
java·c语言·算法
凯瑟琳.奥古斯特15 小时前
K次取反最大化数组和解法(力扣1005)
开发语言·c++·算法·leetcode·职场和发展