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

张开发
2026/4/16 22:07:52 15 分钟阅读

分享文章

LeetCode 3634. 使数组平衡的最少移除数目 详细技术解析
LeetCode 3634. 使数组平衡的最少移除数目 详细技术解析**标签**LeetCode | 数组 | 滑动窗口 | 双指针 | 排序 | 贪心 | 算法实战**核心考点**排序的应用、滑动窗口双指针技巧、贪心思想、边界场景处理**适用人群**算法初学者、后端开发、刷题爱好者具备基础数组操作和排序认知适合巩固滑动窗口解题思路。**前置说明**本题的核心痛点是“找到最长的平衡子数组”进而通过“总长度 - 最长平衡子数组长度”得到最少移除数目。题目看似简单但需结合排序滑动窗口才能实现高效求解避免暴力解法的超时问题。本文将从题意拆解、思路分析、多种解题方案、代码实现、边界案例、常见坑点六个维度详细解析解题全流程所有代码严格贴合题目要求的类和方法格式可直接复制提交LeetCode兼容Python3环境。一、题意深度拆解避免理解偏差1.1 题目核心定义给定整数数组 nums 和整数 k满足以下条件的数组称为“平衡数组”数组非空不能移除所有元素数组的最大值 ≤ 最小值 × k特殊情况长度为1的数组必为平衡数组最大值最小值满足条件。题目要求移除任意数量的元素返回使剩余数组平衡的最少移除数目。1.2 核心转化解题关键最少移除数目 数组总长度 - 最长平衡子数组的长度。理由移除元素越少剩余元素越多因此“最少移除”等价于“找到最长的平衡子数组”——只要找到长度最大的平衡子数组用总长度减去该长度即可得到答案。这是本题的核心转化思路也是所有解题方案的出发点。1.3 示例解析辅助理解以示例1为例nums [2,1,5]k2数组总长度为3所有可能的非空子数组中最长的平衡子数组是[2,1]长度2满足 max(2,1)2 ≤ 1×2最少移除数目 3 - 2 1与示例输出一致。示例2nums [1,6,2,9]k3最长平衡子数组是[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 整体解题流程排序数组将nums按升序排列确保任意子数组的最值为子数组的首尾元素滑动窗口双指针用左右指针划定当前子数组范围右指针向右移动扩大子数组当子数组不满足“最大值 ≤ 最小值×k”时左指针向右移动缩小子数组记录最长长度遍历过程中持续记录满足条件的子数组的最大长度计算答案最少移除数目 总长度 - 最长平衡子数组长度。2.3 关键细节避免踩坑初始状态最长长度至少为1因为长度为1的数组必平衡窗口移动逻辑右指针遍历所有元素左指针仅在当前窗口不满足条件时移动确保窗口始终是“当前右指针能覆盖的最长平衡子数组”边界处理数组长度为1时直接返回0无需移除任何元素。三、完整代码实现三种方案从易到难重点实现滑动窗口最优方案实战首选同时提供暴力方案仅作对比不可提交和简化版滑动窗口方案适合入门所有代码严格遵循题目要求的类和方法格式。3.1 方案1滑动窗口最优方案推荐提交时间复杂度O(n log n)排序 O(n)滑动窗口空间复杂度O(log n)排序的递归栈空间忽略排序所需空间时为O(1)适配n1e5的数据量。fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)-int:nlen(nums)# 边界情况数组长度为1无需移除任何元素ifn1:return0# 步骤1排序数组使子数组的最值为首尾元素nums.sort()max_len1# 最长平衡子数组长度初始为1单个元素必平衡left0# 滑动窗口左指针# 步骤2滑动窗口右指针遍历所有元素forrightinrange(n):# 当当前窗口不满足平衡条件移动左指针缩小窗口# 窗口最小值是nums[left]最大值是nums[right]whilenums[right]nums[left]*k:left1# 更新最长平衡子数组长度current_lenright-left1ifcurrent_lenmax_len:max_lencurrent_len# 步骤3最少移除数目 总长度 - 最长平衡子数组长度returnn-max_len3.2 方案2简化版滑动窗口入门易懂逻辑与最优方案一致简化了部分判断适合初学者理解滑动窗口的核心逻辑性能与最优方案一致。fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)-int:nums.sort()nlen(nums)max_len0left0forrightinrange(n):# 维持窗口平衡nums[right] nums[left] * kwhilenums[right]nums[left]*k:left1# 记录当前窗口长度更新最大值max_lenmax(max_len,right-left1)# 最少移除数目 总长度 - 最长平衡子数组长度returnn-max_len3.3 方案3暴力方案仅作对比不可提交思路枚举所有可能的子数组判断是否平衡记录最长平衡子数组长度。时间复杂度O(n²)n1e5时会超时仅适合理解题意。fromtypingimportListclassSolution:defminRemoval(self,nums:List[int],k:int)-int:nlen(nums)ifn1:return0max_len1# 枚举所有子数组的起始位置foriinrange(n):min_valnums[i]max_valnums[i]# 枚举子数组的结束位置forjinrange(i,n):min_valmin(min_val,nums[j])max_valmax(max_val,nums[j])# 判断子数组是否平衡ifmax_valmin_val*k:current_lenj-i1ifcurrent_lenmax_len:max_lencurrent_lenreturnn-max_len四、代码解析与复杂度分析4.1 核心代码解析滑动窗口最优方案边界处理当数组长度为1时直接返回0因为单个元素必为平衡数组无需移除任何元素排序将nums升序排列核心目的是让任意子数组的最小值为left指针指向的元素最大值为right指针指向的元素无需额外遍历子数组求最值滑动窗口初始化max_len初始化为1单个元素的长度left指针初始化为0right指针从0开始遍历窗口调整当当前窗口left~right不满足“max ≤ min×k”时left指针右移缩小窗口直到满足条件更新最长长度每次调整窗口后计算当前窗口长度更新max_len计算答案用数组总长度减去最长平衡子数组长度得到最少移除数目。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)逻辑直观无需复杂思考效率极低n1e5时超时无法提交五、边界案例与测试验证5.1 边界案例容易踩坑的场景案例1数组长度为1nums [5], k10输入nums [5], k10输出0解析单个元素必平衡无需移除任何元素。案例2数组已平衡nums [4,6], k2输入nums [4,6], k2输出0解析6 ≤ 4×2数组本身平衡无需移除元素。案例3所有元素都需移除除1个nums [1,2,3,4,5], k1输入nums [1,2,3,4,5], k1输出4解析k1时平衡数组需所有元素相等最长平衡子数组长度为1最少移除4个元素。案例4数组元素全部相等nums [3,3,3], k5输入nums [3,3,3], k5输出0解析最大值最小值3满足3 ≤ 3×5无需移除元素。案例5大数据量测试nums [i for i in range(1, 100001)], k2输出50000解析排序后最长平衡子数组是[50000, 100000]长度50000最少移除100000-5000050000。5.2 测试验证将上述案例及题目3个示例代入滑动窗口最优方案均可得到正确结果。提交LeetCode后可通过所有测试用例包括1e5级别的大数据量测试运行速度稳定。六、常见坑点与避坑指南坑点1忘记排序直接暴力枚举子数组错误表现无法快速获取子数组最值只能暴力遍历子数组求min和max导致超时避坑排序是本题的核心前提排序后可直接通过首尾指针获取子数组最值大幅提升效率。坑点2滑动窗口左指针移动逻辑错误错误表现当窗口不满足条件时左指针仅移动一次而非持续移动到满足条件避坑使用while循环而非if判断确保左指针移动到“当前右指针能覆盖的最长平衡窗口”的左边界。坑点3忽略边界情况数组长度为1错误表现数组长度为1时仍按正常流程计算返回n - max_len 1 - 1 0结果正确但逻辑不严谨避坑单独处理数组长度为1的情况直接返回0提升代码可读性和执行效率。坑点4max_len初始值设置错误错误表现max_len初始值为0当所有子数组长度为1时会导致n - max_len n - 0 n错误避坑max_len初始值必须为1因为长度为1的数组必为平衡数组最长平衡子数组长度至少为1。七、总结与拓展7.1 解题总结本题的核心是“转化问题排序滑动窗口”将“最少移除数目”转化为“最长平衡子数组长度”通过排序简化子数组最值的获取再用滑动窗口高效找到最长平衡子数组最终计算答案。关键要点排序是前提排序后子数组的最值就是首尾元素无需额外计算滑动窗口是核心通过双指针维护平衡窗口确保遍历一次数组即可找到最长平衡子数组时间复杂度优化至O(n)边界处理是细节数组长度为1、所有元素相等、k1等场景需单独关注避免错误。7.2 拓展思考提升解题能力拓展1如果数组允许为空如何修改代码思路当所有元素都无法组成平衡数组除单个元素可选择移除所有元素此时最少移除数目为n但题目明确要求“不能使其变为空数组”因此无需考虑此情况。拓展2如果k是动态变化的如每次移除元素后k递减如何优化思路此时滑动窗口的判断条件会动态变化需在每次窗口调整后重新判断是否满足“max ≤ min×k”复杂度会略有提升但核心思路仍为排序滑动窗口。拓展3如何输出最少移除元素后的平衡数组思路在滑动窗口遍历过程中记录最长平衡子数组的left和right指针位置最终返回nums[left:right1]即可。通过本题可重点掌握“问题转化”和“滑动窗口”两种核心解题思想这两种思想在数组、字符串类题目中应用广泛如最长无重复子串、最小覆盖子串等。建议重点练习滑动窗口的边界处理和移动逻辑提升算法实战能力。

更多文章