Divide an Array Into Subarrays With Minimum Cost II

hard sliding window heap hash table

Problem

Given nums of length n and positive integers k and dist, divide nums into k disjoint contiguous subarrays. The cost of a subarray is its first element. If the subarrays start at indices 0 = i₀ < i₁ < … < iₖ₋₁, the rule is iₖ₋₁ − i₁ ≤ dist. Return the minimum possible total cost nums[0] + nums[i₁] + … + nums[iₖ₋₁].

Since nums[0] is always included, we must pick k−1 more starts from [1, n−1] whose index spread is at most dist, minimizing the sum of nums at those indices.

Inputnums = [1,3,2,6,4,2], k = 3, dist = 3
Output5
Best split is [1,3], [2,6,4], [2]: starts 0, 2, 5 with 5 − 2 = 3 ≤ dist. Cost = nums[0] + nums[2] + nums[5] = 1 + 2 + 2 = 5.

def minimumCost(nums, k, dist):
    n = len(nums)
    m = k - 2                       # how many "extra" starts live in the window
    low, high = [], []              # low: max-heap of m smallest; high: the rest
    where, removed = {}, set()      # location flags + lazy-deletion set
    low_sum = low_size = 0

    def prune(h):
        while h and h[0][1] in removed:
            heapq.heappop(h)

    def add(idx):
        nonlocal low_sum, low_size
        v = nums[idx]
        prune(low)
        if low_size < m or (low and v < -low[0][0]):
            heapq.heappush(low, (-v, idx)); where[idx] = "low"
            low_sum += v; low_size += 1
            if low_size > m:                 # overflow: spill low's max to high
                mv, mi = -low[0][0], low[0][1]
                heapq.heappop(low)
                heapq.heappush(high, (mv, mi)); where[mi] = "high"
                low_sum -= mv; low_size -= 1
        else:
            heapq.heappush(high, (v, idx)); where[idx] = "high"

    def remove(idx):
        nonlocal low_sum, low_size
        removed.add(idx)
        if where.get(idx) == "low":
            low_sum -= nums[idx]; low_size -= 1

    def rebalance():
        nonlocal low_sum, low_size
        prune(low); prune(high)
        while low_size < m and high:          # refill low after deletions
            v, idx = heapq.heappop(high)
            heapq.heappush(low, (-v, idx)); where[idx] = "low"
            low_sum += v; low_size += 1
            prune(high)
        while low and high and -low[0][0] > high[0][0]:
            nv, ni = -low[0][0], low[0][1]    # swap big low ↔ small high
            hv, hi = heapq.heappop(high); heapq.heappop(low)
            heapq.heappush(low, (-hv, hi)); where[hi] = "low"
            heapq.heappush(high, (nv, ni)); where[ni] = "high"
            low_sum += hv - nv; prune(low); prune(high)

    best = float("inf"); hi = 0
    for L in range(1, n):
        Rt = min(L + dist, n - 1)
        while hi < Rt:                        # grow window's right edge
            hi += 1
            if hi > L: add(hi)
        remove(L)                             # L is the anchor; it leaves the pool
        rebalance()
        if low_size >= m:
            best = min(best, nums[L] + low_sum)
    return nums[0] + best
function minimumCost(nums, k, dist) {
  const n = nums.length, m = k - 2;        // m extra starts live in the window
  const low = new MaxHeap(), high = new MinHeap();
  const where = {}, removed = new Set();
  let lowSum = 0, lowSize = 0;

  function add(idx) {
    const v = nums[idx];
    low.prune(removed);
    if (lowSize < m || (low.size() && v < low.top().val)) {
      low.push({ val: v, idx }); where[idx] = "low";
      lowSum += v; lowSize++;
      if (lowSize > m) {                    // overflow: spill low's max to high
        const t = low.pop(); high.push(t); where[t.idx] = "high";
        lowSum -= t.val; lowSize--;
      }
    } else { high.push({ val: v, idx }); where[idx] = "high"; }
  }

  function remove(idx) {
    removed.add(idx);
    if (where[idx] === "low") { lowSum -= nums[idx]; lowSize--; }
  }

  function rebalance() {
    low.prune(removed); high.prune(removed);
    while (lowSize < m && high.size()) {     // refill low after deletions
      const t = high.pop(); low.push(t); where[t.idx] = "low";
      lowSum += t.val; lowSize++; high.prune(removed);
    }
    while (low.size() && high.size() && low.top().val > high.top().val) {
      const a = low.pop(), b = high.pop();  // swap big low with small high
      low.push(b); where[b.idx] = "low";
      high.push(a); where[a.idx] = "high";
      lowSum += b.val - a.val;
      low.prune(removed); high.prune(removed);
    }
  }

  let best = Infinity, hi = 0;
  for (let L = 1; L < n; L++) {
    const Rt = Math.min(L + dist, n - 1);
    while (hi < Rt) { hi++; if (hi > L) add(hi); } // grow right edge
    remove(L);                              // L is the anchor; drop it
    rebalance();
    if (lowSize >= m) best = Math.min(best, nums[L] + lowSum);
  }
  return nums[0] + best;
}
long minimumCost(int[] nums, int k, int dist) {
    int n = nums.length, m = k - 2;          // m extra starts live in the window
    // low: max-heap of the m smallest; high: min-heap of the rest
    PriorityQueue<int[]> low = new PriorityQueue<>((a,b)->b[0]-a[0]);
    PriorityQueue<int[]> high = new PriorityQueue<>((a,b)->a[0]-b[0]);
    Map<Integer,String> where = new HashMap<>();
    Set<Integer> removed = new HashSet<>();
    long lowSum = 0; int lowSize = 0;
    long best = Long.MAX_VALUE; int hi = 0;
    for (int L = 1; L < n; L++) {
        int Rt = Math.min(L + dist, n - 1);
        while (hi < Rt) {                     // grow window's right edge
            hi++;
            if (hi > L) {
                int v = nums[hi];
                while (!low.isEmpty() && removed.contains(low.peek()[1])) low.poll();
                if (lowSize < m || (!low.isEmpty() && v < low.peek()[0])) {
                    low.add(new int[]{v, hi}); where.put(hi, "low");
                    lowSum += v; lowSize++;
                    if (lowSize > m) {          // overflow: spill max to high
                        int[] t = low.poll(); high.add(t); where.put(t[1], "high");
                        lowSum -= t[0]; lowSize--;
                    }
                } else { high.add(new int[]{v, hi}); where.put(hi, "high"); }
            }
        }
        removed.add(L);                       // L is the anchor; drop it
        if ("low".equals(where.get(L))) { lowSum -= nums[L]; lowSize--; }
        while (!low.isEmpty() && removed.contains(low.peek()[1])) low.poll();
        while (!high.isEmpty() && removed.contains(high.peek()[1])) high.poll();
        while (lowSize < m && !high.isEmpty()) {   // refill low after deletions
            int[] t = high.poll();
            if (removed.contains(t[1])) continue;
            low.add(t); where.put(t[1], "low"); lowSum += t[0]; lowSize++;
        }
        while (!low.isEmpty() && !high.isEmpty() && low.peek()[0] > high.peek()[0]) {
            int[] a = low.poll(), b = high.poll();  // swap big low with small high
            low.add(b); where.put(b[1], "low");
            high.add(a); where.put(a[1], "high");
            lowSum += b[0] - a[0];
        }
        if (lowSize >= m) best = Math.min(best, nums[L] + lowSum);
    }
    return nums[0] + best;
}
long long minimumCost(vector<int>& nums, int k, int dist) {
    int n = nums.size(), m = k - 2;          // m extra starts live in the window
    // low: max-heap of m smallest; high: min-heap of the rest (value, index)
    priority_queue<pair<int,int>> low;
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> high;
    unordered_map<int,int> where;            // idx -> 1 (low) or 2 (high)
    unordered_set<int> removed;
    long long lowSum = 0; int lowSize = 0;
    auto pruneLow  = [&]{ while(!low.empty() && removed.count(low.top().second)) low.pop(); };
    auto pruneHigh = [&]{ while(!high.empty() && removed.count(high.top().second)) high.pop(); };
    long long best = LLONG_MAX; int hi = 0;
    for (int L = 1; L < n; L++) {
        int Rt = min(L + dist, n - 1);
        while (hi < Rt) {                     // grow window's right edge
            hi++;
            if (hi > L) {
                int v = nums[hi]; pruneLow();
                if (lowSize < m || (!low.empty() && v < low.top().first)) {
                    low.push({v, hi}); where[hi] = 1; lowSum += v; lowSize++;
                    if (lowSize > m) {          // overflow: spill max to high
                        auto t = low.top(); low.pop(); high.push(t); where[t.second] = 2;
                        lowSum -= t.first; lowSize--;
                    }
                } else { high.push({v, hi}); where[hi] = 2; }
            }
        }
        removed.insert(L);                    // L is the anchor; drop it
        if (where.count(L) && where[L] == 1) { lowSum -= nums[L]; lowSize--; }
        pruneLow(); pruneHigh();
        while (lowSize < m && !high.empty()) {     // refill low after deletions
            auto t = high.top(); high.pop();
            low.push(t); where[t.second] = 1; lowSum += t.first; lowSize++; pruneHigh();
        }
        while (!low.empty() && !high.empty() && low.top().first > high.top().first) {
            auto a = low.top(); low.pop(); auto b = high.top(); high.pop();  // swap
            low.push(b); where[b.second] = 1;
            high.push(a); where[a.second] = 2;
            lowSum += (long long)b.first - a.first; pruneLow(); pruneHigh();
        }
        if (lowSize >= m) best = min(best, (long long)nums[L] + lowSum);
    }
    return (long long)nums[0] + best;
}
Time: O(n log n) Space: O(n)