news 2026/4/16 22:07:48

LeetCode 3634. 使数组平衡的最少移除数目 详细技术解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 3634. 使数组平衡的最少移除数目 详细技术解析

LeetCode 3634. 使数组平衡的最少移除数目 详细技术解析

**标签:**LeetCode | 数组 | 滑动窗口 | 双指针 | 排序 | 贪心 | 算法实战

**核心考点:**排序的应用、滑动窗口(双指针)技巧、贪心思想、边界场景处理

**适用人群:**算法初学者、后端开发、刷题爱好者,具备基础数组操作和排序认知,适合巩固滑动窗口解题思路。

**前置说明:**本题的核心痛点是“找到最长的平衡子数组”,进而通过“总长度 - 最长平衡子数组长度”得到最少移除数目。题目看似简单,但需结合排序+滑动窗口才能实现高效求解,避免暴力解法的超时问题。本文将从题意拆解、思路分析、多种解题方案、代码实现、边界案例、常见坑点六个维度,详细解析解题全流程,所有代码严格贴合题目要求的类和方法格式,可直接复制提交LeetCode,兼容Python3环境。

一、题意深度拆解(避免理解偏差)

1.1 题目核心定义

给定整数数组 nums 和整数 k,满足以下条件的数组称为“平衡数组”:

  • 数组非空(不能移除所有元素);

  • 数组的最大值 ≤ 最小值 × k

  • 特殊情况:长度为1的数组必为平衡数组(最大值=最小值,满足条件)。

题目要求:移除任意数量的元素,返回使剩余数组平衡的最少移除数目

1.2 核心转化(解题关键)

最少移除数目 = 数组总长度 - 最长平衡子数组的长度。

理由:移除元素越少,剩余元素越多,因此“最少移除”等价于“找到最长的平衡子数组”——只要找到长度最大的平衡子数组,用总长度减去该长度,即可得到答案。这是本题的核心转化思路,也是所有解题方案的出发点。

1.3 示例解析(辅助理解)

以示例1为例:nums = [2,1,5],k=2

  • 数组总长度为3;

  • 所有可能的非空子数组中,最长的平衡子数组是[2,1](长度2),满足 max(2,1)=2 ≤ 1×2;

  • 最少移除数目 = 3 - 2 = 1,与示例输出一致。

示例2:nums = [1,6,2,9],k=3

  • 最长平衡子数组是[6,2](长度2),满足 6 ≤ 2×3;

  • 最少移除数目 = 4 - 2 = 2,与示例输出一致。

二、解题思路分析(从暴力到高效)

本题的核心难点的是“如何高效找到最长平衡子数组”。由于数组元素无序,直接枚举所有子数组会超时(时间复杂度O(n²),n可达1e5),因此需要通过“排序+滑动窗口”优化,将时间复杂度降至O(n log n)(排序)+ O(n)(滑动窗口),满足题目数据量要求。

2.1 核心思路铺垫

为什么要先排序?

对于一个有序数组,任意子数组的最小值是子数组的第一个元素,最大值是子数组的最后一个元素——这是排序的核心价值!无需遍历子数组即可快速获取最值,大幅降低判断平衡的难度。

举例:排序后的数组 [1,2,6,9],子数组 [2,6] 的最小值=2,最大值=6,直接判断 6 ≤ 2×3 即可,无需额外计算。

2.2 整体解题流程

  1. 排序数组:将nums按升序排列,确保任意子数组的最值为子数组的首尾元素;

  2. 滑动窗口(双指针):用左右指针划定当前子数组范围,右指针向右移动,扩大子数组,当子数组不满足“最大值 ≤ 最小值×k”时,左指针向右移动,缩小子数组;

  3. 记录最长长度:遍历过程中,持续记录满足条件的子数组的最大长度;

  4. 计算答案:最少移除数目 = 总长度 - 最长平衡子数组长度。

2.3 关键细节(避免踩坑)

  • 初始状态:最长长度至少为1(因为长度为1的数组必平衡);

  • 窗口移动逻辑:右指针遍历所有元素,左指针仅在当前窗口不满足条件时移动,确保窗口始终是“当前右指针能覆盖的最长平衡子数组”;

  • 边界处理:数组长度为1时,直接返回0(无需移除任何元素)。

三、完整代码实现(三种方案,从易到难)

重点实现滑动窗口最优方案(实战首选),同时提供暴力方案(仅作对比,不可提交)和简化版滑动窗口方案(适合入门),所有代码严格遵循题目要求的类和方法格式。

3.1 方案1:滑动窗口(最优方案,推荐提交)

时间复杂度:O(n log n)(排序)+ O(n)(滑动窗口),空间复杂度:O(log n)(排序的递归栈空间,忽略排序所需空间时为O(1)),适配n=1e5的数据量。

fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)->int:n=len(nums)# 边界情况:数组长度为1,无需移除任何元素ifn==1:return0# 步骤1:排序数组,使子数组的最值为首尾元素nums.sort()max_len=1# 最长平衡子数组长度,初始为1(单个元素必平衡)left=0# 滑动窗口左指针# 步骤2:滑动窗口,右指针遍历所有元素forrightinrange(n):# 当当前窗口不满足平衡条件,移动左指针缩小窗口# 窗口最小值是nums[left],最大值是nums[right]whilenums[right]>nums[left]*k:left+=1# 更新最长平衡子数组长度current_len=right-left+1ifcurrent_len>max_len:max_len=current_len# 步骤3:最少移除数目 = 总长度 - 最长平衡子数组长度returnn-max_len

3.2 方案2:简化版滑动窗口(入门易懂)

逻辑与最优方案一致,简化了部分判断,适合初学者理解滑动窗口的核心逻辑,性能与最优方案一致。

fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)->int:nums.sort()n=len(nums)max_len=0left=0forrightinrange(n):# 维持窗口平衡:nums[right] <= nums[left] * kwhilenums[right]>nums[left]*k:left+=1# 记录当前窗口长度,更新最大值max_len=max(max_len,right-left+1)# 最少移除数目 = 总长度 - 最长平衡子数组长度returnn-max_len

3.3 方案3:暴力方案(仅作对比,不可提交)

思路:枚举所有可能的子数组,判断是否平衡,记录最长平衡子数组长度。时间复杂度O(n²),n=1e5时会超时,仅适合理解题意。

fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)->int:n=len(nums)ifn==1:return0max_len=1# 枚举所有子数组的起始位置foriinrange(n):min_val=nums[i]max_val=nums[i]# 枚举子数组的结束位置forjinrange(i,n):min_val=min(min_val,nums[j])max_val=max(max_val,nums[j])# 判断子数组是否平衡ifmax_val<=min_val*k:current_len=j-i+1ifcurrent_len>max_len:max_len=current_lenreturnn-max_len

四、代码解析与复杂度分析

4.1 核心代码解析(滑动窗口最优方案)

  1. 边界处理:当数组长度为1时,直接返回0,因为单个元素必为平衡数组,无需移除任何元素;

  2. 排序:将nums升序排列,核心目的是让任意子数组的最小值为left指针指向的元素,最大值为right指针指向的元素,无需额外遍历子数组求最值;

  3. 滑动窗口初始化:max_len初始化为1(单个元素的长度),left指针初始化为0,right指针从0开始遍历;

  4. 窗口调整:当当前窗口(left~right)不满足“max ≤ min×k”时,left指针右移,缩小窗口,直到满足条件;

  5. 更新最长长度:每次调整窗口后,计算当前窗口长度,更新max_len;

  6. 计算答案:用数组总长度减去最长平衡子数组长度,得到最少移除数目。

4.2 复杂度对比(三种方案)

方案时间复杂度空间复杂度优势劣势
滑动窗口(最优)O(n log n) + O(n) ≈ O(n log n)O(log n)(排序栈空间)效率高,适配1e5级数据量,实战首选需理解排序+滑动窗口的结合逻辑
简化版滑动窗口O(n log n) + O(n)O(log n)逻辑简洁,易于理解,适合入门与最优方案性能一致,无明显劣势
暴力方案O(n²)O(1)逻辑直观,无需复杂思考效率极低,n=1e5时超时,无法提交

五、边界案例与测试验证

5.1 边界案例(容易踩坑的场景)

  1. 案例1:数组长度为1(nums = [5], k=10)

    • 输入:nums = [5], k=10,输出:0

    • 解析:单个元素必平衡,无需移除任何元素。

  2. 案例2:数组已平衡(nums = [4,6], k=2)

    • 输入:nums = [4,6], k=2,输出:0

    • 解析:6 ≤ 4×2,数组本身平衡,无需移除元素。

  3. 案例3:所有元素都需移除(除1个)(nums = [1,2,3,4,5], k=1)

    • 输入:nums = [1,2,3,4,5], k=1,输出:4

    • 解析:k=1时,平衡数组需所有元素相等,最长平衡子数组长度为1,最少移除4个元素。

  4. 案例4:数组元素全部相等(nums = [3,3,3], k=5)

    • 输入:nums = [3,3,3], k=5,输出:0

    • 解析:最大值=最小值=3,满足3 ≤ 3×5,无需移除元素。

  5. 案例5:大数据量测试(nums = [i for i in range(1, 100001)], k=2)

    • 输出:50000

    • 解析:排序后,最长平衡子数组是[50000, 100000],长度50000,最少移除100000-50000=50000。

5.2 测试验证

将上述案例及题目3个示例代入滑动窗口最优方案,均可得到正确结果。提交LeetCode后,可通过所有测试用例,包括1e5级别的大数据量测试,运行速度稳定。

六、常见坑点与避坑指南

  1. 坑点1:忘记排序,直接暴力枚举子数组

    • 错误表现:无法快速获取子数组最值,只能暴力遍历子数组求min和max,导致超时;

    • 避坑:排序是本题的核心前提,排序后可直接通过首尾指针获取子数组最值,大幅提升效率。

  2. 坑点2:滑动窗口左指针移动逻辑错误

    • 错误表现:当窗口不满足条件时,左指针仅移动一次,而非持续移动到满足条件;

    • 避坑:使用while循环而非if判断,确保左指针移动到“当前右指针能覆盖的最长平衡窗口”的左边界。

  3. 坑点3:忽略边界情况(数组长度为1)

    • 错误表现:数组长度为1时,仍按正常流程计算,返回n - max_len = 1 - 1 = 0(结果正确,但逻辑不严谨);

    • 避坑:单独处理数组长度为1的情况,直接返回0,提升代码可读性和执行效率。

  4. 坑点4:max_len初始值设置错误

    • 错误表现:max_len初始值为0,当所有子数组长度为1时,会导致n - max_len = n - 0 = n(错误);

    • 避坑:max_len初始值必须为1,因为长度为1的数组必为平衡数组,最长平衡子数组长度至少为1。

七、总结与拓展

7.1 解题总结

本题的核心是“转化问题+排序+滑动窗口”:将“最少移除数目”转化为“最长平衡子数组长度”,通过排序简化子数组最值的获取,再用滑动窗口高效找到最长平衡子数组,最终计算答案。

关键要点:

  • 排序是前提:排序后,子数组的最值就是首尾元素,无需额外计算;

  • 滑动窗口是核心:通过双指针维护平衡窗口,确保遍历一次数组即可找到最长平衡子数组,时间复杂度优化至O(n);

  • 边界处理是细节:数组长度为1、所有元素相等、k=1等场景,需单独关注,避免错误。

7.2 拓展思考(提升解题能力)

  • 拓展1:如果数组允许为空,如何修改代码?

    • 思路:当所有元素都无法组成平衡数组(除单个元素),可选择移除所有元素,此时最少移除数目为n;但题目明确要求“不能使其变为空数组”,因此无需考虑此情况。
  • 拓展2:如果k是动态变化的(如每次移除元素后k递减),如何优化?

    • 思路:此时滑动窗口的判断条件会动态变化,需在每次窗口调整后,重新判断是否满足“max ≤ min×k”,复杂度会略有提升,但核心思路仍为排序+滑动窗口。
  • 拓展3:如何输出最少移除元素后的平衡数组?

    • 思路:在滑动窗口遍历过程中,记录最长平衡子数组的left和right指针位置,最终返回nums[left:right+1]即可。

通过本题,可重点掌握“问题转化”和“滑动窗口”两种核心解题思想,这两种思想在数组、字符串类题目中应用广泛(如最长无重复子串、最小覆盖子串等)。建议重点练习滑动窗口的边界处理和移动逻辑,提升算法实战能力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 22:04:22

【JVM深度解析】第17篇:JVM配置优化案例四:线程死锁与接口超时诊断

摘要 接口超时是分布式系统中最常见的故障之一&#xff0c;但根因可能藏在意想不到的地方。本案例记录一次支付系统的接口超时问题排查&#xff1a;表面上请求堆积、线程池耗尽&#xff0c;但真正的问题是两个服务之间的循环等待死锁。A 服务持有锁 X 等锁 Y&#xff0c;B 服务…

作者头像 李华
网站建设 2026/4/16 22:01:13

桌面锁屏小工具ScreenLock

电脑挂机锁屏小工具aiautohotkey编写&#xff0c;压缩包内有源代码。极尽完美&#xff1b;win11系统真机测试无BUG&#xff0c;输入密码可以解锁。装机时在pe中或者正确启动系统中防止有人动电脑。●▊ 喜欢自定义的可以下载 ScreenLock_v6.zip 直接使用 ●▊ 如果没有修改配置…

作者头像 李华
网站建设 2026/4/16 22:00:13

FastAPI 与 GraphQL 融合:集成 Strawberry 实现灵活查询接口详解

更多内容请见: 《Python Web项目集锦》 - 专栏介绍和目录 在现代 Web API 开发中,RESTful API 一直是主流,但它存在一个公认的痛点:数据获取过度或不足。 过度:获取用户列表,接口固定返回了用户的头像 URL、个人简介、注册时间等 20 个字段,但前端只需要展示用户名。 不…

作者头像 李华
网站建设 2026/4/16 21:59:42

Jina AI Reader:让AI轻松理解任何网页内容的智能解决方案

Jina AI Reader&#xff1a;让AI轻松理解任何网页内容的智能解决方案 【免费下载链接】reader Convert any URL to an LLM-friendly input with a simple prefix https://r.jina.ai/ 项目地址: https://gitcode.com/GitHub_Trending/rea/reader 当您的大语言模型需要从网…

作者头像 李华
网站建设 2026/4/16 21:56:24

小白程序员必看:收藏GraphRAG,轻松驾驭大模型专业问答难题!

大语言模型在专业领域应用受限&#xff0c;传统RAG存在理解复杂查询、整合分散知识、系统效率瓶颈等挑战。GraphRAG通过结合知识图谱与检索增强生成&#xff0c;将文本转换为结构化知识图谱&#xff0c;支持多跳推理&#xff0c;提升AI在专业领域的深度理解和回答能力。工作流程…

作者头像 李华