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

数组中恰好K个非连续元素最小和算法实现疑问

数组中选取恰好K个非连续元素的最小和问题解析

问题描述

给定长度为N的整数数组costs和整数K,需返回数组中恰好K个非相邻元素的最小和。约束条件:

  • 1 <= N <= 1000000
  • 1 <= K <= int(N/2)
  • 1 <= costs[i] <= 1e11(1<=i<=N)

直观的二维DP解法(dp[i][j]表示前i个元素选j个非相邻元素的最小和)时间复杂度为O(nk),对于n=1e6的规模完全无法满足性能要求,因此需要O(k log n)的高效算法。

问题分析:算法失效原因

你参考的CodeforcesO(k log n)算法核心是反悔贪心+优先队列,但失效案例(N=5,K=3,cost=[2,1,5,4,7])得到错误结果,本质是对算法的核心逻辑理解偏差——尤其是选中元素后对相邻元素的处理、双向链表维护、虚拟元素的意义这三点没有正确实现。

该案例中唯一合法的选法是选取索引0、2、4的元素(和为14),但错误的算法实现会错误地选中索引1、3的元素后,试图再选一个已被禁用的元素,导致结果错误。

正确的O(k log n)算法实现思路

算法核心是利用最小堆(优先队列)维护当前可选的最小代价元素,结合双向链表跟踪元素的真实邻居,通过虚拟元素实现"反悔"操作,具体步骤如下:

初始化

  1. 维护三个数组:
    • prev[]:记录每个元素当前的左邻居索引,初始prev[i] = i-1,prev[0] = -1
    • next[]:记录每个元素当前的右邻居索引,初始next[i] = i+1,next[N-1] = -1
    • deleted[]:标记元素是否已被删除(无法选中或作为邻居),初始全为false
  2. 将所有元素的(costs[i], i)存入最小堆(按元素值升序排序)
  3. 初始化总和total = 0,选取次数count = 0

核心循环

当count < K时:

  1. 弹出堆顶元素,直到找到未被删除的元素(val, curr)
  2. 将val加入total,count +=1
  3. 找到curr的真实邻居:left = prev[curr],right = next[curr]
  4. 创建虚拟元素:计算虚拟值virtual_val = 0
    • 若left != -1,virtual_val += costs[left],标记deleted[left] = true
    • 若right != -1,virtual_val += costs[right],标记deleted[right] = true
    • virtual_val -= costs[curr]
  5. 更新costs[curr] = virtual_val(此时curr代表"反悔选当前元素,转而选左右邻居"的代价)
  6. 维护双向链表:
    • 若left != -1,prev[curr] = prev[left];否则prev[curr] = -1
    • 若right != -1,next[curr] = next[right];否则next[curr] = -1
    • 若prev[curr] != -1,next[prev[curr]] = curr
    • 若next[curr] != -1,prev[next[curr]] = curr
  7. 将(virtual_val, curr)重新存入堆中

算法逻辑说明

选中元素curr后,其左右邻居无法再被选中。虚拟元素的意义是记录"反悔操作"的代价:如果后续发现选左右邻居比选curr更优,可以通过选中这个虚拟元素,将总和调整为total - costs[curr] + costs[left] + costs[right],等价于放弃curr、转而选left和right,同时保证非相邻的约束。

失效案例验证

以N=5,K=3,cost=[2,1,5,4,7]为例:

  1. 第一次选堆顶元素(1,1),total=1。处理邻居0和2,虚拟值为2+5-1=6,标记0、2为删除,更新链表后将(6,1)放回堆。
  2. 第二次选堆顶元素(4,3),total=5。处理邻居1和4,虚拟值为6+7-4=9,标记1、4为删除,更新链表后将(9,3)放回堆。
  3. 第三次选堆顶元素(9,3),total=5+9=14,达到K=3,返回结果14,与预期一致。

实现注意事项

  • 堆中会存在已被标记为删除的元素,每次取堆顶时必须检查deleted状态,弹出无效元素直到找到有效元素。
  • 双向链表的维护必须准确,否则会导致邻居查找错误,破坏算法逻辑。
  • 由于N可达1e6,需使用高效的优先队列实现(如C++的priority_queue,或Python的heapq,但Python需注意堆的大小控制)。

内容的提问来源于stack exchange,提问作者user27916452

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 00:30:54