求解移除最少数据点以获得指定增减性单调函数的算法
嘿,你的问题其实刚好对应一个经典的算法问题——找最长单调子序列,这正是解决「移除最少点」需求的最优方案,我给你理清楚:
核心逻辑:转成最长合法子序列问题
要移除最少的点,本质就是要找到最长的符合目标单调性的子序列(递增要求x[i+1]>=x[i],递减则是x[i+1]<=x[i])。总点数减去这个子序列的长度,就是你需要删掉的最少点数——毕竟保留的点越多,删的自然越少嘛。
基础实现:动态规划(适合小数据量)
假设目标是递增(递减逻辑完全对称,改个比较条件就行):
- 先把所有点按时间戳排序(毕竟时间序列肯定是按时间走的,要是你的数据已经是时间递增的,这步可以跳过)。
- 定义
dp[i]:以第i个点结尾的最长递增子序列的长度,初始每个dp[i] = 1(单个点本身就是长度1的子序列)。 - 遍历每个点i,再回头看所有在它之前的点j:
- 如果
x[i] >= x[j](符合递增规则),那dp[i]就取当前值和dp[j]+1里的较大值。
- 如果
- 最后找
dp数组里的最大值,总点数减去这个值就是答案。
优化版本:贪心+二分查找(大数据量必备)
如果数据点特别多(比如上万条),上面O(n²)的动态规划就太慢了,这时候可以用O(n log n)的优化方法:
- 维护一个
tails数组,tails[k]表示长度为k+1的最长递增子序列的最后一个元素的最小值。 - 遍历每个x值(按时间顺序):
- 如果当前x大于等于
tails的最后一个元素,直接追加到末尾,子序列长度+1。 - 否则,用二分查找找到
tails里第一个大于当前x的位置,把那个位置的值换成当前x。
- 如果当前x大于等于
- 最终
tails的长度就是最长合法子序列的长度,再用总点数减它就行。
对比你之前用的移动中值滤波
移动中值滤波是靠统计规则找异常值,适合处理噪声,但它有个致命问题:没法保证移除的点数最少——它只是平滑数据,可能误删合法点,或者漏删那些刚好在方差范围内但破坏单调性的点。而上面的方法是精准瞄准「移除最少点」这个目标的最优解。
代码示例(Python)
给你写个可直接用的例子,支持递增和递减两种情况:
def min_removals_for_monotonic(points, is_increasing=True): # 先按时间戳排序,确保时间序列递增 points_sorted = sorted(points, key=lambda p: p[0]) x_values = [p[1] for p in points_sorted] total_points = len(x_values) if is_increasing: tails = [] import bisect for num in x_values: # 用bisect_right确保x[i] >= x[j]的规则 idx = bisect.bisect_right(tails, num) if idx == len(tails): tails.append(num) else: tails[idx] = num else: # 递减转成递增处理,取x的负数 neg_x = [-num for num in x_values] tails = [] import bisect for num in neg_x: idx = bisect.bisect_right(tails, num) if idx == len(tails): tails.append(num) else: tails[idx] = num return total_points - len(tails) # 测试一下:移除(2,1)和(4,2),剩下的就是递增序列 test_points = [(1, 2), (2, 1), (3, 3), (4, 2), (5, 4)] print(min_removals_for_monotonic(test_points, is_increasing=True)) # 输出2
额外提醒
- 如果你的原始数据已经是严格按时间递增的,直接跳过排序步骤就行,能省点时间。
- 这个方法完全保留了时间顺序,因为我们是基于时间先后找子序列的,不会打乱原始的时间逻辑。
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

