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−10, p-10,p−1)。

6. 总结

  • 核心思路是利用前缀和取模 将问题转化为"余数匹配",再通过哈希表快速定位满足条件的子数组;
  • 初始版本的问题在于前缀和未取模、子数组长度计算逻辑混乱、嵌套循环效率低;
  • 优化后的官方题解通过哈希表记录余数下标,将时间复杂度从 O(n2)O(n^2)O(n2) 降至 O(n)O(n)O(n),是该问题的最优解法之一。
相关推荐
AndrewHZ5 小时前
【LLM技术全景】RAG 从原理到实战——检索增强生成完整指南
人工智能·深度学习·算法·llm·检索增强·生成式模型·rag
LuminousCPP5 小时前
单链表专题(三)-刷题复盘篇:从快慢指针到环形链表 II 数学推导
数据结构·经验分享·笔记·学习·算法·链表
-dzk-5 小时前
【贪心算法】LC 121.买卖股票的最佳时机
算法·贪心算法
hanlin035 小时前
动态规划专练:力扣第718、1143题
算法·leetcode·动态规划
ChaoZiLL6 小时前
二叉树经典例题
数据结构·算法
不正经学生6 小时前
C语言字符串函数进阶:安全版函数 + 错误处理 + 子串查找
c语言·开发语言·c++·算法·面试
3A Cloud6 小时前
从「心即理」到提示词工程:深度解析 ClawHub 上的阳明心学技能(yangming-xinxue)
人工智能·笔记
三克的油6 小时前
算法基础-1
数据结构·c++·算法
oort1236 小时前
OortCodex 是奥尔特云 OortCloudSmart 推出的国产化工程化 AI 编码 Agent(奥尔特云编码智能体)
大数据·人工智能·算法
TheBestRucy6 小时前
基于Dify的旅游攻略&王者荣耀攻略智能助手项目
服务器·开发语言·人工智能·python·算法·旅游