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),是该问题的最优解法之一。
相关推荐
alphaTao9 小时前
LeetCode 每日一题 2026/3/16-2026/3/22
linux·windows·leetcode
空空潍9 小时前
LeetCode力扣 hot100一刷完结
算法·leetcode
leaves falling9 小时前
搜索插入位置(第一个≥target的位置)
算法
lcreek9 小时前
LeetCode 1162.地图分析
算法·leetcode·bfs
寒月小酒9 小时前
3.20 OJ
算法
AI科技星10 小时前
基于空间光速螺旋归一化的动力学方程推导与数值验证
人工智能·线性代数·算法·机器学习·平面
读忆10 小时前
你是否用过Tailwind CSS?你是在什么情况下使用的呢?
前端·css·经验分享·笔记·taiiwindcss
bbbb36510 小时前
排序算法的演进史:从冒泡到快速再到TimSort的技术7
数据结构·算法·排序算法
不染尘.10 小时前
排序算法详解1
开发语言·数据结构·c++·算法·排序算法