力扣——连续的子数组和(官方解法三详解)

题目描述

给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

示例 1:

输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:

输入:[23,2,6,4,7], k = 6
输出:True
解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。

官方解法:(解法链接)

方法 3: 使用 HashMap [Accepted]
算法

在这种方法中,我们使用 HashMap 来保存到第 ii 个元素为止的累积和,但我们对这个前缀和除以 kk 取余数。原因如下:

我们遍历一遍给定的数组,记录到当前位置为止的 sum%ksum 。一旦我们找到新的 sum%ksum 的值(即在 HashMap 中没有这个值),我们就往 HashMap 中插入一条记录 (sum%k, i)(sum 。

现在,假设第 ii 个位置的 sum%ksum 的值为 remrem 。如果以 ii 为左端点的任何子数组的和是 kk 的倍数,比方说这个位置为 jj ,那么 HashMap 中第 jj 个元素保存的值为 (rem + nk)%k(rem+n∗k)%k ,其中 nn 是某个大于 0 的整数。我们会发现 (rem + nk)%k = rem(rem+n∗k)%k=rem ,也就是跟第 ii 个元素保存到 HashMap 中的值相同。

基于这一观察,我们得出结论:无论何时,只要 sum%ksum 的值已经被放入 HashMap 中了,代表着有两个索引 ii 和 jj ,它们之间元素的和是 kk 的整数倍。因此,只要 HashMap 中有相同的 sum%ksum%k ,我们就可以直接返回 \teat{True} 。

public class Solution {
    
    
    public boolean checkSubarraySum(int[] nums, int k) {
    
    
        int sum = 0;
        HashMap < Integer, Integer > map = new HashMap < > ();
        map.put(0, -1);
        for (int i = 0; i < nums.length; i++) {
    
    
            sum += nums[i];
            if (k != 0)
                sum = sum % k;
            if (map.containsKey(sum)) {
    
    
                if (i - map.get(sum) > 1)
                    return true;
            } else
                map.put(sum, i);
        }
        return false;
    }
}
解读:

在官方的三种解法中,这个解法是时间复杂度最低的算法,当然遇到此类题型,一定要考虑使用前缀和以及map来思考。
作者的思路也很简单,在前缀和的基础上做出了一点改进,我们遍历数组,并每次将前缀和求余之后再存入map(如果题目要求得出子数组的起始位置和结束位置的话,我们map存入的键后面跟上遍历坐标i就行)。为什么要这么做呢,我们首先来看一个例子。

  • arr = {3,4,3,4,5} k = 11;
  • 定义p[i]表示arr前i项和并求余
  • p[1] = 3; /*3
  • p[2] = 7; /*3,4
  • p[3] = 10; /*3,4,3
  • p[4] = 3; /*3,4,3,4

此时我们发现p中已经包含来余数3,这样就可以知道数组中有子数组的和为k的n倍,因为当a数对于b数求余等于c,d数再对b数求余仍然得到了c,这就说明a数一定是加上了一个能被b整除的数,即这个数就是n倍的k,同理也就说明存在子数组的和为n倍的k,这样就得到了题目的求解。

猜你喜欢

转载自blog.csdn.net/baldicoot_/article/details/109827421