从两个区间到多笔交易:一道算法题的进化之旅
本文从一个简单的区间选择问题出发,逐步扩展其难度与适用范围,整体思路由浅入深,层层递进,尝试揭示背后的通用算法设计方法。
问题一:选择至多两个不重叠区间以最大化长度和
问题描述
给定 \(n\) 个左右端点 \([l_i, r_i],l_i\le r_i\),每个最终选择的区间需满足由某个左端点 \(l_x\) 与某个右端点 \(r_y\) 组成(\(y \ge x\)),要求从中选出至多两个两两不重叠的区间 \([start_i, end_i]\),使得总长度 \(\sum (end_i - start_i)\) 最大。
分析
\(2n\) 个端点,每个端点记录其类型(左端点或右端点)与对应的数值。为便于处理,我们按这些端点在数轴上的位置排序。由于只选择至多两个区间,我们可以枚举一个分割点,将区间划分为左右两部分,分别求得最大可选区间长度之和。为实现该策略,我们可以预处理两个数组:
- MaxLeft[i]:表示从左到第 \(i\) 个点(含)中可以选出的最大单一区间长度;
- MaxRight[i]:表示从第 \(i\) 个点(含)到右侧可以选出的最大单一区间长度。
遍历所有可能的分割点,取最大值:
维护 MaxLeft 的过程:若当前为左端点:$ \text{MaxLeft}[i] = \text{MaxLeft}[i - 1] $,同时更新最小左端点 \(MinL\);若当前为右端点:$ \text{MaxLeft}[i] = \max(\text{MaxLeft}[i - 1], \text{value}_i - MinL) $,即:
MaxRight 可类比从右向左更新,最终时间复杂度为 \(O(n)\)。
问题二:选择至多 \(k\) 个不重叠区间以最大化长度和
问题描述
在问题一的基础上,将最多选取的区间个数拓展为 \(k\)。求最多选取 \(k\) 个两两不重叠的区间使得 \(\sum (end_i - start_i)\) 最大。
分析
此问题无法直接枚举分割点,需使用动态规划,定义状态:
- \(dp[i][j]\) 表示前 \(i\) 个点中选出 \(j\) 个不重叠区间所能得到的最大值;
- 边界条件为 \(dp[i][0] = 0, dp[0][j] = 0\);
- 最终答案为 \(dp[2n][k]\)。
状态转移
-
若当前为左端点:不可能构成新区间,直接继承前一状态: \(dp[i][j] = dp[i - 1][j]\)
-
若当前为右端点,有两种选择:
- 不选当前节点:\(dp[i][j] = dp[i - 1][j]\);
- 选当前节点,枚举所有之前的左端点 \(s\),更新为:\(dp[i][j] = \max(dp[i][j], dp[s - 1][j - 1] + \text{value}_i - \text{value}_s) \quad \text{if } \text{type}_s = l\)
转移方程汇总:
初始实现时间复杂度为 \(O(n^2k)\),可优化查询,降至 \(O(nk \log n)\)。
问题三:股票买卖问题与区间问题的关联
问题描述
- 给定数组 \(prices\),表示某支股票每天的价格。设计一个算法,最多完成 两笔 不重叠的买卖交易,求最大收益。
- 同样输入 \(prices\),现在最多完成 \(k\) 笔交易,求最大收益。
分析
这是 LeetCode 中经典问题:
本质是对价格序列寻找若干个不重叠的“低买高卖”区间。观察可知:
- 任意一笔交易的有效区间应从局部最小值买入至局部最大值卖出;
- 若 \(prices[i+1] \le prices[i]\),则应跳过,因为买入不具优势。
据此可从原始 \(prices\) 序列提取出 \(m\) 个有效的上升区间对 \((l_i, r_i)\),满足 \(l_i < r_i\) 且 \(r_i > l_{i+1}\),由此,原问题等价于从 \(m\) 个区间中选择最多 \(k\) 个两两不重叠区间使得 \(\sum (r_i - l_i)\) 最大,转化为问题一与问题二的形式,即可复用上述算法模型求解。

浙公网安备 33010602011771号