ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode 3634:平衡数组的最少删除元素算法解析

LeetCode 3634:平衡数组的最少删除元素算法解析

1. 题目解析与核心思路

这道LeetCode 3634题要求我们找到使数组平衡所需的最少移除元素数目。所谓"平衡数组",根据题目定义,是指数组中所有元素都满足nums[i] <= nums[i+1]或者nums[i] >= nums[i+1](即整体呈非递减或非递增排列)。换句话说,我们需要通过最少的删除操作,让剩余数组保持单调性。

1.1 问题转化

首先我们需要明确几个关键点:

  1. 删除操作可以针对任意位置的元素
  2. 最终数组必须完全单调(非递减或非递增)
  3. 需要找到删除元素最少的方案

这个问题可以转化为:在原始数组中找到最长的单调子序列(非递减或非递增),然后用数组长度减去这个子序列长度就是需要删除的元素数目。

1.2 算法选择

对于这类子序列问题,常见的解法有:

  • 动态规划(时间复杂度O(n²))
  • 排序+滑动窗口(时间复杂度O(nlogn))
  • 贪心算法(可能无法得到最优解)

考虑到题目要求最优解且n可能较大(根据LeetCode常见数据范围),我们选择排序+滑动窗口的方案,它能在合理时间复杂度内解决问题。

2. 排序+滑动窗口解法详解

2.1 算法步骤

  1. 排序原始数组:首先对数组进行升序和降序两种排序
  2. 滑动窗口匹配:在排序后的数组中,寻找与原数组匹配的最长子序列
  3. 计算最小删除:用数组长度减去找到的最长子序列长度

2.2 具体实现

def minRemovalsToBalance(nums): n = len(nums) if n <= 1: return 0 # 生成升序和降序排列的数组 sorted_asc = sorted(nums) sorted_desc = sorted(nums, reverse=True) max_len = 0 # 检查非递减情况 i = j = 0 while i < n and j < n: if nums[i] >= sorted_asc[j]: j += 1 i += 1 max_len = max(max_len, j) # 检查非递增情况 i = j = 0 while i < n and j < n: if nums[i] <= sorted_desc[j]: j += 1 i += 1 max_len = max(max_len, j) return n - max_len

2.3 复杂度分析

  • 时间复杂度:O(nlogn)(排序占主导)
  • 空间复杂度:O(n)(需要存储排序后的数组)

3. 算法优化与变种

3.1 空间优化

我们可以优化空间使用,不需要存储整个排序后的数组:

def minRemovalsToBalance(nums): n = len(nums) if n <= 1: return 0 def longestSubseq(sorted_nums): j = 0 for num in nums: if num >= sorted_nums[j]: j += 1 if j == n: break return j sorted_asc = sorted(nums) sorted_desc = sorted(nums, reverse=True) max_len = max(longestSubseq(sorted_asc), longestSubseq(sorted_desc)) return n - max_len

3.2 处理重复元素

当数组中有大量重复元素时,可以进一步优化:

def minRemovalsToBalance(nums): from collections import defaultdict freq = defaultdict(int) for num in nums: freq[num] += 1 if len(freq) == 1: # 所有元素相同 return 0 # 其余情况按原算法处理 # ...

4. 实际应用场景

这种算法在以下场景中有实际应用价值:

  1. 数据清洗:在数据分析前,确保数据列保持单调性
  2. 时间序列处理:确保传感器数据等时间序列的单调性
  3. 数据库优化:为某些查询优化创建单调索引
  4. 金融分析:分析股票价格等需要单调性的金融数据

5. 常见错误与调试技巧

5.1 边界条件处理

常见错误包括:

  • 忽略空数组或单元素数组情况
  • 未考虑所有元素相同的情况
  • 滑动窗口索引越界

调试建议:

  • 先测试空数组和单元素数组
  • 测试全相同元素的数组
  • 测试严格递增/递减的数组

5.2 性能优化

当n很大时(>10^5):

  • 考虑使用更高效的排序算法
  • 可以尝试并行处理升序和降序检查
  • 对于特定分布的数据,可以使用计数排序等线性排序算法

6. 扩展思考

6.1 其他解法对比

动态规划解法虽然直观,但时间复杂度较高:

def minRemovalsDP(nums): n = len(nums) if n <= 1: return 0 # dp[i][0]表示以i结尾的最长非递减子序列长度 # dp[i][1]表示以i结尾的最长非递增子序列长度 dp = [[1]*2 for _ in range(n)] for i in range(1, n): for j in range(i): if nums[j] <= nums[i]: dp[i][0] = max(dp[i][0], dp[j][0]+1) if nums[j] >= nums[i]: dp[i][1] = max(dp[i][1], dp[j][1]+1) max_len = max(max(x[0], x[1]) for x in dp) return n - max_len

6.2 相关问题

这类问题还有一些变种值得探索:

  1. 允许最多k次删除操作,能得到的最长平衡子序列
  2. 考虑子序列的严格单调性(严格递增/递减)
  3. 二维数组的平衡性问题

7. 实际编码建议

在LeetCode等平台提交时:

  1. 先处理边界条件(空数组、单元素数组)
  2. 添加必要注释说明算法思路
  3. 对大规模数据测试用例进行性能测试
  4. 考虑使用生成器表达式减少内存使用

例如优化后的最终实现:

def minRemovalsToBalance(nums): n = len(nums) if n <= 1: return 0 def getMaxLen(sorted_nums): j = 0 for num in nums: if num >= sorted_nums[j]: j += 1 if j == n: break return j sorted_asc = sorted(nums) sorted_desc = sorted(nums, reverse=True) max_len = max(getMaxLen(sorted_asc), getMaxLen(sorted_desc)) return n - max_len

这个实现简洁高效,适合在编程竞赛中使用。在实际工程中,可能需要添加更多错误处理和日志记录。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进