LeetCode每日一题——523. 连续的子数组和(同余定理)
前言
同余定理
数论中的重要概念。给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系。
题目描述
给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:
子数组大小 至少为 2 ,且
子数组元素总和为 k 的倍数。
如果存在,返回 true ;否则,返回 false 。
如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 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 * 6 且 7 是一个整数。
示例 3:
输入:nums = [23,2,6,4,7], k = 13
输出:false
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 109
0 <= sum(nums[i]) <= 231 - 1
1 <= k <= 231 - 1
解题思路——前缀和+hash
一开始看到这题就想用暴力枚举所有子区间,然后利用前缀和优化成n^2级别,可是发现数据量给到了10^5,顿时人傻了,不知道这种情况怎么去优化。后面看了官方题解:才理解了一点。这样要转换一种思维,如果想到了【同余定理】的话,这题几乎很容易就做出来啦哈哈,可惜的是我没想到(枯了!!!)所以只能写个博客记录一下自己的菜鸡时光。
有了同余定理这个基础,我们就可以将 prefix[i] - prefix[j - 1] = n * k 这个式子变换成另一种写法,即 prefix[i] / k - prefix[j - 1] / k = n ,这样就转换成了我们同余定理的基本形式。接下来我们就可以来讲解一下具体的解题步骤了。
预处理
我们先准备一个变量res和一个map集合,需要计算每个下标对应的前缀和除以 k 的余数即可,使用哈希表存储每个余数第一次出现的下标。规定空的前缀的结束下标为 −1,由于空的前缀的元素和为 0,因此在哈希表中先一开始存入键值对 (0,−1)
基本步骤
通过一个变量来计算前缀和,然后将当前的前缀和对k求余,然后判断此时map集合中是否包含当前前缀和对k求余之后的结果,若包含,则取出之前的下标计算出长度,若长度>=2,就直接返回true,反之则不做任何处理,继续后续循环,直到循环结束
说明:map集合中我们为啥记录的是每个余数第一次出现的下标,因为这样才能尽量保证子数组的长度更大,换言之:长度大的都不行,小的更不行了。
AC代码
1 class Solution { 2 public boolean checkSubarraySum(int[] nums, int k) { 3 int n = nums.length; 4 Map<Integer, Integer> map = new HashMap<>(); 5 map.put(0, -1); 6 int res = 0; 7 for (int i = 0; i < n; i++) { 8 res += nums[i]; 9 res %= k; 10 // 判断有没有相同的余数 11 if (map.containsKey(res)) { 12 if (i - map.get(res) >= 2) { 13 return true; 14 } 15 } else { 16 map.put(res, i); 17 } 18 } 19 return false; 20 } 21 }