[前缀和] 523. 连续的子数组和

249 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

每日刷题 2022.06.09

题目

  • 给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:
    • 子数组大小 至少为 2 ,且
    • 子数组元素总和为 k 的倍数。
    • 如果存在,返回 true ;否则,返回 false 。
  • 如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。

示例

  • 示例1
输入: nums = [23,2,4,6,7], k = 6
输出: true
解释: [2,4] 是一个大小为 2 的子数组,并且和为 6 。
  • 示例2
输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42 。 
42 是 6 的倍数,因为 42 = 7 * 67 是一个整数。
  • 示例3
输入: nums = [23,2,6,4,7], k = 13
输出: false

提示

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^9
  • 0 <= sum(nums[i]) <= 2^31 - 1
  • 1 <= k <= 2^31 - 1

解题思路(解题以及优化的思路都是对的,就是细节方面处理的不好,一直wa

  • 根据题意可以获得三个关键信息:
    • 子数组的大小至少为2(需要判断前缀和之间的数值是大于等于2)
    • 子数组元素总和要能够整除k
    • 0可以整除任何k
  • 如果能够找到符合上述条件的子数组,返回true;否则返回false

超时处理,优化

  • 在样例k = 2000000000的时候,超出时间限制。

寻找优化方案:

  • 优化1: 特判一下:整个数组中的连续的0的个数是大于等于2,那么就直接返回true
  • 优化2: 既然计算了前缀和,那么需要找能够整除k的情况,也就是说前缀和要大于等于k才能够,整除k
    • 因此在计算前缀和的时候,就可以找到前i个数值加和大于等于k的位置,记为idx。后续的取前缀和之差的时候,就可以从这里开始。

注意事项

连续0的统计(小学生都会的,我wa了三发,在这个问题上)

  • 首先并不是统计整个数组中所有的0的个数,而是统计连续的0的个数
  • 其次统计连续的0的个数,使用标记变量不好处理
  • 最终的思想:记数组中连续的0的个数为zero
    • 当数组中遇到的数等于0,那么就一直zero++
    • 遇到第一个不等于0的,就将zero置为0,并且取最值保留此次zero的数据。此时就会遇到问题:如果你的数组中的所有数都是0,那么直到找到数组的最后都没有找到不等于0的,那么最终你就没有取到连续的0的个数。
    • 因此,需要在每次数组中的数等于0的时候,就取最值,这样再也不怕找不到不等于0的情况了。
if(nums[i - 1] === 0) {
  // 必须是连续
  zero++;
  max = Math.max(zero, max);
}else {
  // 下一个不同的话,就要将zero置为0
  zero = 0;
}

AC代码

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {boolean}
 */
var checkSubarraySum = function(nums, k) {
  // 0 是 k 的倍数
  // 只要存在一个包含的就可以返回true
  // 连续的
  let n = nums.length, sum = new Array(n + 1).fill(0), idx = Infinity,zero = 0, flag = false,max = 0;
  for(let i = 1; i <= n; i++) {
    if(nums[i - 1] === 0) {
      // 必须是连续
      zero++;
      max = Math.max(zero, max);
    }else {
      // 下一个不同的话,就要将zero置为0
      zero = 0;
    }
    sum[i] = sum[i - 1] + nums[i - 1];
    if(sum[i] >= k && !flag) {
      flag = true;
      idx = i;
    }
  }
  // 如果连续的0大于2
  if(max >= 2) return true;
  // 如果整个数组所有的数加起来的前缀和都没有k大,那么就无法整除,返回false
  if(idx == Infinity) return false;
  for(let i = 1; i <= n; i++) {
    // 注意:连续的数组的长度要大于等于2
    for(let j = idx >= i + 1 ? idx : i + 1; j <= n; j++) {
      // 计算连续子串的前缀和差值
      const s = sum[j] - sum[i - 1];
      // 是否能够整除k
      if(s % k == 0 || s == 0) {
        // 找到一个符合的,就返回true
        return true;
      }
    }
  }
  // 如果一个都不能整除k,就返回false 
  return false;
};