You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求解移除最少数据点以获得指定增减性单调函数的算法

嘿,你的问题其实刚好对应一个经典的算法问题——找最长单调子序列,这正是解决「移除最少点」需求的最优方案,我给你理清楚:

核心逻辑:转成最长合法子序列问题

要移除最少的点,本质就是要找到最长的符合目标单调性的子序列(递增要求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。
  • 最终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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:17:40