LeetCode 每日一题笔记 日期:2025.11.30 题目:1590.使数组和能被 P 整除

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.11.30
  • 题目:1590.使数组和能被 P 整除
  • 难度:中等
  • 标签:数组 前缀和 哈希表

1. 题目理解

问题描述 :给你一个正整数数组 nums,请你移除最短子数组 (可以为空),使得剩余元素的和能被 p 整除。不允许将整个数组都移除。请返回需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1

示例

示例 1:

输入:nums = [3,1,4,2], p = 6

输出:1

解释:数组和为 10,不能被 6 整除。移除子数组 [4] 后,剩余和为 6,可被 6 整除。
示例 2:

输入:nums = [6,3,5,2], p = 9

输出:2

解释:移除子数组 [5,2] 后,剩余和为 9,可被 9 整除。

2. 解题思路

核心观察

  • 设数组总和对 p 的余数为 x,若 x=0,则无需移除子数组;
  • 需移除的子数组和对 p 的余数必须等于 x(这样剩余和对 p 的余数为 0);
  • 利用前缀和取模 +哈希表快速找到满足余数条件的最短子数组。

算法步骤

  1. 计算数组总和对 p 的余数 x,若 x=0 直接返回 0
  2. 维护一个哈希表,记录前缀和余数 对应的最早出现的下标
  3. 遍历数组,计算当前前缀和余数 y,并计算目标余数 (y - x + p) % p(避免负数);
  4. 若哈希表中存在目标余数,计算当前子数组长度并更新最小值;
  5. 遍历结束后,若最短长度等于数组长度,返回 -1,否则返回最短长度。

3. 代码实现(初始版本)

java 复制代码
class Solution {
    public int minSubarray(int[] nums, int p) {
        // 初始化前缀和数组与memory数组
        int []prefix =new int[nums.length];
        int []memory =new int[p];

        // 计算前缀和数组(注:初始版本未对前缀和取模,存在逻辑错误)
        prefix[0]=nums[0]%p;
        for(int i=1;i<nums.length;i++){
           int a=nums[i]%p;
           prefix[i]=a+prefix[i-1]; // 错误:未对累加结果取模,会导致余数错误
        }

        // 计算数组总和对p的余数
        int end =prefix[nums.length-1]%p;
        if(end == 0){return 0;}
        
        // 逻辑处理(注:初始版本嵌套循环效率低且逻辑混乱)
        for(int i=1;i<nums.length;i++){
            for(int j=0;j<i;j++){
                int b=(prefix[i]-prefix[j])%p;
                if(memory[b]>j||memory[b]==0){memory[b]=j+1;} // 错误:子数组长度计算逻辑错误
            }
        }

        // 结果输出(注:初始版本判断条件错误)
        if(memory[end]==0){return -1;}
        return memory[end];
    }
}

4. 代码优化说明(官方题解版本)

java 复制代码
class Solution {
    public int minSubarray(int[] nums, int p) {
        // 计算数组总和对p的余数x
        int x = 0;
        for (int num : nums) {
            x = (x + num) % p; // 累加过程中取模,避免溢出
        }
        // 总和已能被p整除,直接返回0
        if (x == 0) {
            return 0;
        }

        // 哈希表:记录前缀和余数对应的最早下标
        Map<Integer, Integer> index = new HashMap<Integer, Integer>();
        int y = 0; // 当前前缀和余数
        int res = nums.length; // 初始化最短长度为数组长度

        for (int i = 0; i < nums.length; i++) {
            index.put(y, i); // 记录当前前缀和余数对应的下标
            y = (y + nums[i]) % p; // 更新前缀和余数
            // 计算目标余数(避免负数)
            int target = (y - x + p) % p;
            // 找到目标余数,更新最短长度
            if (index.containsKey(target)) {
                res = Math.min(res, i - index.get(target) + 1);
            }
        }

        // 无法满足条件则返回-1,否则返回最短长度
        return res == nums.length ? -1 : res;
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n)O(n)O(n),其中 nnn 是数组长度。仅需遍历数组一次,哈希表操作时间为 O(1)O(1)O(1);
  • 空间复杂度 :O(p)O(p)O(p),哈希表最多存储 ppp 个不同的余数(余数范围为 [0,p−1][0, p-1][0,p−1])。

6. 总结

  • 核心思路是利用前缀和取模 将问题转化为"余数匹配",再通过哈希表快速定位满足条件的子数组;
  • 初始版本的问题在于前缀和未取模、子数组长度计算逻辑混乱、嵌套循环效率低;
  • 优化后的官方题解通过哈希表记录余数下标,将时间复杂度从 O(n2)O(n^2)O(n2) 降至 O(n)O(n)O(n),是该问题的最优解法之一。
相关推荐
兩尛1 小时前
HJ43 迷宫问题
算法
小龙报1 小时前
【算法通关指南:数据结构与算法篇(五)】树的 “自我介绍”:从递归定义到存储绝技(vector vs 链式前向星)
c语言·数据结构·c++·算法·链表·启发式算法·visual studio
报错小能手1 小时前
数据结构 顺序栈
数据结构·算法
@游子1 小时前
内网渗透笔记-Day9
笔记
点云SLAM1 小时前
C++包装器之类型擦除(Type Erasure)包装器详解(4)
c++·算法·c++17·类型擦除·c++高级应用·c++包装器·函数包装
yugi9878382 小时前
TDOA算法MATLAB实现:到达时间差定位
前端·算法·matlab
弘毅 失败的 mian2 小时前
Git 基本操作
大数据·经验分享·笔记·git·elasticsearch
t198751282 小时前
基于因子图与和积算法的MATLAB实现
开发语言·算法·matlab
le serein —f2 小时前
用go实现-回文链表
算法·leetcode·golang