最小化线段点间距与阈值平均差值的高效算法问询
问题重述
给定线段上的有序点数组 points(升序排列)和阈值 k,需选取包含首尾端点的点序列,使得连续点间距与 k 的平均绝对偏差最小,即最小化:
$$\frac{1}{m} \sum_{i=0}^{m-1} |d_i - k|$$
其中 m 是选点序列的间隔数(选点数量 = m+1),$d_i = p_{i+1} - p_i$ 是连续选点的间距。
核心最优算法:二分查找 + 分治优化动态规划
由于平均偏差最小化无法直接等价于总偏差最小化(总偏差相同但间隔数不同时,平均偏差会有差异),我们采用二分查找可行的平均偏差值,配合分治优化的动态规划验证可行性,最终找到最优解,时间复杂度可达 $O(n \log n \log C)$(C 为偏差值的精度范围)。
1. 二分查找平均偏差λ
我们的目标是找到最小的λ,使得存在选点序列满足:
$$\sum_{i=0}^{m-1} |d_i - k| \leq \lambda \cdot m$$
变形为等价判断式:
$$\sum_{i=0}^{m-1} (|d_i - k| - \lambda) \leq 0$$
二分查找流程:
- 初始范围:
low=0,high=max(points[-1]-points[0], k)(覆盖最大可能的单间隔偏差) - 每次取中间值
mid,验证是否存在选点序列使得上述不等式成立 - 根据验证结果调整二分范围:若
dp[-1] ≤ 0则λ可行,尝试更小值;否则需增大λ,直到达到所需精度(如1e-6)
2. 分治优化的DP验证可行性
对于给定的λ,定义dp[i]为从points[0]到points[i]的最小总和:
$$dp[i] = \min_{0 \leq j < i} \left( dp[j] + |points[i] - points[j] - k| - \lambda \right)$$
初始化dp[0] = 0,其余dp[i]设为无穷大。若最终dp[-1] ≤ 0,则λ是可行的。
由于转移方程中的|x - y|是凸函数,满足四边形不等式,可采用分治优化将时间复杂度从 $O(n^2)$ 降至 $O(n \log n)$:
- 递归处理区间
[l, r],已知最优前驱j的范围是[opt_l, opt_r] - 计算区间中点
mid的最优前驱opt_mid - 递归处理左半区间
[l, mid-1](最优前驱范围[opt_l, opt_mid])和右半区间[mid+1, r](最优前驱范围[opt_mid, opt_r])
3. 回溯获取最优选点序列
找到最优λ后,再次运行DP(记录每个dp[i]对应的前驱点索引),从points[-1]回溯至points[0],反转路径得到最优选点序列。
小规模数据简化方案(O(n²) DP)
若点的数量n较小(如n≤1000),可直接使用朴素动态规划:
- 定义
dp[i] = (min_avg_deviation, prev_index, interval_count),其中min_avg_deviation是到points[i]的最小平均偏差,prev_index是前驱点索引,interval_count是到i的间隔数 - 对于每个
i,遍历所有j < i,计算从j到i的平均偏差:
$$new_avg = \frac{dp[j].min_avg_deviation \cdot dp[j].interval_count + |points[i]-points[j]-k|}{dp[j].interval_count + 1}$$ - 记录最小的
new_avg对应的j作为prev_index,并更新interval_count - 最终从
points[-1]回溯得到序列
示例验证
以points = [0,2,5,6,8,9],k=3为例:
- 二分找到最优λ≈0.6667
- 运行DP得到
dp[5](对应点9)的最优前驱是3(对应点6),dp[3]的前驱是1(对应点2),dp[1]的前驱是0(对应点0) - 回溯得到序列
[0,2,6,9],与示例一致
内容的提问来源于stack exchange,提问作者ShmoonMaster

