困难题:连续子数组零尾数问题 | 豆包MarsCode AI刷题

64 阅读4分钟

题目描述

给定一个整数数组 nums 和一个正整数 k,请找出数组中是否存在一个连续的子数组,其元素之和是 k 的倍数(即 sum % k == 0)。要求子数组至少包含两个元素。如果存在,返回 true;否则,返回 false


输入与输出

输入: 一个整数数组 nums,一个正整数 k
输出: 一个布尔值,表示是否存在符合条件的连续子数组。


样例

输入:nums = [23, 2, 4, 6, 7], k = 6  
输出:true  
解释:[2, 4] 的和为 6,是 k 的倍数  
输入:nums = [23, 2, 6, 4, 7], k = 13  
输出:false  
输入:nums = [23, 2, 6, 4, 7], k = 42  
输出:true  
解释:[23, 2, 6, 4, 7] 的和为 42,是 k 的倍数  

关键思路

为解决这个问题,我们需要从数学和算法的角度深入分析问题的核心:

  1. 数学背景:前缀和与模运算

    • 假设一个子数组 nums[i:j] 的和是 sum,且满足 sum % k == 0,那么数组中某两个前缀和的模值必定相等。
    • 假设前缀和在索引 i 处为 prefix_sum[i],在索引 j 处为 prefix_sum[j],如果 prefix_sum[i] % k == prefix_sum[j] % kj - i > 1,那么子数组 nums[i+1:j] 的和是 k 的倍数。
  2. 算法设计:哈希表记录模值出现的位置

    • 用一个哈希表记录每个前缀和对 k 取模的值及其最早出现的索引。
    • 遍历数组时,计算当前前缀和的模值,并检查是否已经出现在哈希表中:
      • 如果存在,且当前索引与之前索引的差值大于等于 2,则返回 true
      • 如果不存在,将当前模值和索引存入哈希表。
  3. 特殊情况处理:k == 0

    • 如果 k == 0,题目要求我们检查是否存在两个连续的 0,因为只有连续的 0 才能满足条件。

代码实现

以下是完整代码实现:

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static boolean checkSubarraySum(int[] nums, int k) {
        // 哈希表存储模值及其索引
        Map<Integer, Integer> prefixSumMod = new HashMap<>();
        prefixSumMod.put(0, -1); // 初始模值为0,对应索引为-1
        
        int totalSum = 0;
        
        for (int i = 0; i < nums.length; i++) {
            totalSum += nums[i]; // 更新前缀和
            
            if (k != 0) {
                totalSum %= k; // 取模值
            }
            
            // 检查当前模值是否已出现
            if (prefixSumMod.containsKey(totalSum)) {
                // 如果索引差大于等于2,返回true
                if (i - prefixSumMod.get(totalSum) > 1) {
                    return true;
                }
            } else {
                // 否则记录当前模值及其索引
                prefixSumMod.put(totalSum, i);
            }
        }
        
        return false; // 未找到符合条件的子数组
    }
    
    public static void main(String[] args) {
        System.out.println(checkSubarraySum(new int[]{23, 2, 4, 6, 7}, 6)); // true
        System.out.println(checkSubarraySum(new int[]{23, 2, 6, 4, 7}, 13)); // false
        System.out.println(checkSubarraySum(new int[]{23, 2, 6, 4, 7}, 42)); // true
    }
}

代码解释

  1. 初始化哈希表

    • 使用 prefixSumMod.put(0, -1) 初始化模值 0 的索引为 -1,方便处理从头开始的子数组。
  2. 累积前缀和并取模

    • totalSum += nums[i] 计算前缀和。
    • 如果 k != 0,则取模,计算当前前缀和的模值。
  3. 模值重复判断

    • 如果当前模值已出现在哈希表中,并且索引差大于等于 2,则返回 true
  4. 记录模值和索引

    • 如果当前模值尚未出现在哈希表中,则记录该模值和索引。

知识总结

在这道题中,我学习到了以下关键知识点:

  1. 前缀和的应用

    • 前缀和是一种非常有用的技巧,可以快速计算数组的区间和。在这道题中,结合模运算,进一步提升了它的应用范围。
  2. 哈希表的优化作用

    • 通过哈希表存储模值及其最早出现的索引,能够在常数时间内判断是否存在符合条件的子数组,从而将时间复杂度从暴力解法的 (O(n^2)) 优化到 (O(n))。
  3. 特殊情况处理

    • k == 0 时,需要特别考虑连续的 0

小结

通过这道题,我深刻体会到数学与编程结合的魅力。利用前缀和与模运算的性质,再通过哈希表优化算法,可以用极低的时间复杂度解决看似复杂的问题。希望这篇文章对大家有所帮助!一起加油!