本题经重构思考+深度研究各个方面总结。
目录
元素限制:
-104 <= nums[i] <= 104
求数组内连续元素和是K整数倍的子数组数目。
连续+和值
思路
思路一:滑动窗口
结合滑动窗口------同向双指针+单调性+不可回复性(l无法回退到初位置[0]判断)。
由于存在负数导致单调性丧失,本题每次r移动后,l都要进行从最初位置到r位置元素和的分别判断->O(N^2)。
滑动窗口+哈希表
哈希表存储和值->前缀和思想
思路二:前缀和
向后查找(下标小的方向)特定值的**个数(不是找到而是个数)**来弥补字段和距离整除的差距值。
☆☆☆具体实现规则:
我们发现若某一段连续数组元素和想要被K整除,那么在其前缀和值fdp[i] 的基础上必须在fdp先前下标[1,i-1]中找到目标fdp[n]出现的次数(1 <= n <= i-1),就是寻找首余数fdp[i]%K和其副余数(12%7的副余数-2,-3%7的副余数4),而因此每一段数组和的哈希表元素的纳入方式是hash[dp[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;
}
调试验证:
走读发现:
首余数处理的部分,每次都会将刚刚纳入的值hash[dp[i]]加上作为首余数,但我们找的是[0,i-1]间的目标首余数,因此对hash值"-1"忽略本次进循环时的++造成的误差。
bash
count += hash[fdp[i]%k]-1;
问:既然首余数出现这种问题,那么副余数会不会呢?
如果会的话:
①fdp[i] % k == fdp[i] % k - k --->k == 0
②fdp[i] % k == fdp[i] % k + k --->k==0
以上两者均不会出现因为:
①题目" 2 <= k <= 10^4"
②任何数%0 无意义报错!
bug
当为k的整数倍的nums[i-1]第一次出现,count主余数应该使得count+=1;但因为"-1"导致加的是"0"。 // 丢失值使得数据比预期小
深度分析此bug
问:若后续继续出现nums[i-1]的整数倍会怎么样?
可以预见的是由于第一次落下"1"hash[0]没有加上,以后再次出现就会"永远比预期差一步"。 // 1 2 3 4 ->每次出现分别会差一次的值。
解决办法:
在哈希初始化时就对hash[0]++;
差值形成具体体现如下:
|---|-----------|-------|---|---|-----------|-------|
| i | hash[0] | count | | i | hash[0] | 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)