๐ ์ด๋ณด์ ์นํ ๊ฐ์ด๋ 'Divide an Array Into Subarrays With Minimum Cost II' - Problem 3013 (C++, Python, JavaScript)
Source: Dev.to
Source: โฆ
Problem Overview
๋ฐ์ดํฐ๋ฅผ ์ต์ ์ ๊ตฌ๊ฐ์ผ๋ก ๋๋๋ ๊ฒ์ ์ปดํจํฐ ๊ณผํ์์ ๊ณ ์ ์ ์ธ ๊ณผ์ ๋ก, ํน์ ์ ์ฝ ์กฐ๊ฑด๊ณผ ํจ์จ์ฑ ์ฌ์ด์ ๊ท ํ์ ์๊ตฌํฉ๋๋ค.
์ด ๋ฌธ์ ๋ ๋ฐฐ์ด์ ๊ฐ์ฅ ์ ๋ ดํ ๋น์ฉ์ผ๋ก ๋ถํ ํ๋ฉด์, ๋ถํ ์ง์ ๋ค์ด ์๋ก ๊ฑฐ๋ฆฌ ์ ํ์ ๋ง์กฑํ๋๋ก ํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๋ ๊ฒ์
๋๋ค. ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ๊ธฐ๋ฒ๊ณผ ์ ๋ ฌ๋ ์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๊ธฐ ์ํ ๊ณ ๊ธ ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ฒฐํฉํ๋ ํ๋ฅญํ ์ฐ์ต์ด ๋ฉ๋๋ค.
You are given
- ์ ์ ๋ฐฐ์ด
nums. - ์ ์
k, ๋ง๋ค์ด์ผ ํ ๋ถ๋ถ ๋ฐฐ์ด์ ๊ฐ์. - ์ ์
dist, ๋ ๋ฒ์งธ ๋ถ๋ถ ๋ฐฐ์ด๊ณผ ๋ง์ง๋ง ๋ถ๋ถ ๋ฐฐ์ด์ ์์ ์ธ๋ฑ์ค ์ฌ์ด์ ์ต๋ ๊ฑฐ๋ฆฌ ์ ํ.
Goal
์ ์ฒด ๋น์ฉ์ ์ต์ํํฉ๋๋ค. ์ฌ๊ธฐ์ ๋ถ๋ถ ๋ฐฐ์ด์ ๋น์ฉ์ ์ฒซ ๋ฒ์งธ ์์๊ฐ ๋ฉ๋๋ค.
์ฒซ ๋ฒ์งธ ๋ถ๋ถ ๋ฐฐ์ด์ ํญ์ ์ธ๋ฑ์คโฏ0์์ ์์ํ๋ฏ๋ก nums[0]๋ ํญ์ ๋น์ฉ์ ํฌํจ๋ฉ๋๋ค.
์ฐ๋ฆฌ๋ ๋ฐฐ์ด์ ๋๋จธ์ง ๋ถ๋ถ์์ kโ1๊ฐ์ ์์ ์ธ๋ฑ์ค๋ฅผ ์ ํํด ํฉ์ ์ต์ํํ๊ณ , dist ์ ์ฝ์ ๋ง์กฑํด์ผ ํฉ๋๋ค.
Formalisation
k๊ฐ์ ๋ถ๋ถ ๋ฐฐ์ด ์์ ์ธ๋ฑ์ค๋ฅผ
[ p_1, p_2, \dots, p_k ]
๋ผ๊ณ ํ๊ฒ ์ต๋๋ค.
p_1์ ํญ์0์ ๋๋ค.1 โฆ nโ1๋ฒ์์์kโ1๊ฐ์ ์ธ๋ฑ์ค๋ฅผ ์ ํํฉ๋๋ค.- ์ ์ฝ ์กฐ๊ฑด
[ p_k - p_2 \le \text{dist} ]
์ ๋ ๋ฒ์งธ๋ถํฐ k๋ฒ์งธ๊น์ง ์ ํ๋ ๋ชจ๋ ์ธ๋ฑ์ค๊ฐ ํฌ๊ธฐ dist์ธ ์๋์ฐ ์์ ๋ค์ด๊ฐ์ผ ํจ์ ์๋ฏธํฉ๋๋ค.
์ด dist ํฌ๊ธฐ์ ์๋์ฐ๋ฅผ ๋ฐฐ์ด ์ ์ฒด์ ์ฌ๋ผ์ด๋ํ๋ฉด์, ๊ทธ ์๋์ฐ ์์ ์๋ kโ1๊ฐ์ ๊ฐ์ฅ ์์ ์์๋ค์ ํฉ์ ๋น ๋ฅด๊ฒ ์ฐพ์์ผ ํฉ๋๋ค.
์ด๋ฅผ ํจ์จ์ ์ผ๋ก ์ํํ๊ธฐ ์ํด **Binary Indexed Tree (Fenwick Tree)**์ coordinate compression์ ์ฌ์ฉํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ํตํด ์ซ์๋ค์ โ๋ญํฌโํํ๊ณ , ์ด์ง ๋ฆฌํํ
(binary lifting)์ ์ด์ฉํด ๋ก๊ทธ ์๊ฐ ์์ ๊ฐ์ฅ ์์ ๊ฐ๋ค์ ์ฐพ์ ์ ์์ต๋๋ค.
Example
nums = [1,3,2,6,4,2], k = 3, dist = 3
์ฐ๋ฆฌ๋ ์ฐฝ์์ kโ1 = 2๊ฐ์ ์ธ๋ฑ์ค๋ฅผ ์ ํํด์ผ ํฉ๋๋ค.
-
์ฐฝ์ ํฌ๊ธฐ๋
dist + 1 = 4์ ๋๋ค. -
์ฒซ ๋ฒ์งธ ์ ํํ ์ธ๋ฑ์ค๊ฐ
1์ด๋ฉด, ๋ง์ง๋ง ์ธ๋ฑ์ค๋ ์ต๋1 + dist = 4๊น์ง ๊ฐ๋ฅํฉ๋๋ค.์ธ๋ฑ์ค
1 โฆ 4๋ฅผ ๋ณด๋ฉด:[3, 2, 6, 4].
๊ฐ์ฅ ์์ ๋ ์๋2์3์ ๋๋ค.
๋น์ฉ =nums[0] + 2 + 3 = 1 + 5 = 6. -
์ฒซ ๋ฒ์งธ ์ ํํ ์ธ๋ฑ์ค๊ฐ
2์ด๋ฉด, ๋ง์ง๋ง ์ธ๋ฑ์ค๋5๊น์ง ๊ฐ๋ฅํฉ๋๋ค.์ธ๋ฑ์ค
2 โฆ 5๋ฅผ ๋ณด๋ฉด:[2, 6, 4, 2].
๊ฐ์ฅ ์์ ๋ ์๋2์2์ ๋๋ค.
๋น์ฉ =1 + 2 + 2 = 5.
๊ฐ๋ฅํ ์ต์ ๋น์ฉ์ 5์ ๋๋ค.
์ฐธ์กฐ ๊ตฌํ
C++ (GNUโฏC++17)
#include <bits/stdc++.h>
using namespace std;
class Solution {
int max_rank;
vector<int> bit_count;
vector<long long> bit_sum;
vector<int> sorted_values;
// Add / remove a value from the BIT
void update(int idx, int count_delta, long long val_delta) {
for (; idx <= max_rank; idx += idx & -idx) {
bit_count[idx] += count_delta;
bit_sum[idx] += val_delta;
}
}
// Query sum of smallest `target` elements
long long query(int target) {
int idx = 0;
int cur_k = 0;
long long cur_s = 0;
for (int i = 1 << (31 - __builtin_clz(max_rank)); i; i >>= 1) {
int next_idx = idx + i;
if (next_idx <= max_rank && cur_k + bit_count[next_idx] < target) {
idx = next_idx;
cur_k += bit_count[next_idx];
cur_s += bit_sum[next_idx];
}
}
// add the remaining part of the (targetโcur_k)โth element
return cur_s + 1LL * (target - cur_k) * sorted_values[idx];
}
public:
int minCost(vector<int>& nums, int k, int dist) {
int n = nums.size();
// coordinate compression
sorted_values = nums;
sort(sorted_values.begin(), sorted_values.end());
sorted_values.erase(unique(sorted_values.begin(), sorted_values.end()), sorted_values.end());
max_rank = sorted_values.size();
bit_count.assign(max_rank + 1, 0);
bit_sum.assign(max_rank + 1, 0);
auto get_rank = [&](int val) {
return lower_bound(sorted_values.begin(), sorted_values.end(), val) - sorted_values.begin() + 1;
};
int target_k = k - 1; // we need kโ1 additional starts
// initialise the window (first `dist + 1` elements, but not beyond the array)
for (int i = 1; i <= min(dist, n - 1); ++i) {
update(get_rank(nums[i]), 1, nums[i]);
}
long long min_cost = nums[0] + query(target_k);
// slide the window across the array
for (int i = 2; i + dist < n; ++i) {
// remove element leaving the window
update(get_rank(nums[i - 1]), -1, -nums[i - 1]);
// add new element entering the window
update(get_rank(nums[i + dist]), 1, nums[i + dist]);
long long cur = nums[0] + query(target_k);
min_cost = min(min_cost, cur);
}
return (int)min_cost;
}
};
Python
def minCost(nums, k, dist):
n = len(nums)
# ---- coordinate compression ----
sorted_unique = sorted(set(nums))
ranks = {val: i + 1 for i, val in enumerate(sorted_unique)}
max_rank = len(sorted_unique)
bit_count = [0] * (max_rank + 1) # how many numbers of each rank
bit_sum = [0] * (max_rank + 1) # sum of those numbers
def update(rank: int, c_delta: int, v_delta: int) -> None:
while rank <= max_rank:
bit_count[rank] += c_delta
bit_sum[rank] += v_delta
rank += rank & -rank
def query(target: int) -> int:
idx = 0
cur_k = 0
cur_s = 0
i = max_rank.bit_length()
while i >= 0:
next_idx = idx + (1 << i)
if next_idx <= max_rank and cur_k + bit_count[next_idx] < target:
idx = next_idx
cur_k += bit_count[next_idx]
cur_s += bit_sum[next_idx]
i -= 1
return cur_s + (target - cur_k) * sorted_unique[idx]
target_k = k - 1
# initialise the window (first `dist + 1` elements, but not beyond the array)
for i in range(1, min(dist + 1, n)):
update(ranks[nums[i]], 1, nums[i])
min_cost = nums[0] + query(target_k)
# slide the window across the array
for i in range(2, n - target_k + 1):
# remove the element that leaves the window
update(ranks[nums[i - 1]], -1, -nums[i - 1])
# add the new element entering the window
update(ranks[nums[i + dist - 1]], 1, nums[i + dist - 1])
cur = nums[0] + query(target_k)
min_cost = min(min_cost, cur)
return min_cost
Source: โฆ
1])
# add the new element that enters the window (if any)
if i + dist < n:
update(ranks[nums[i + dist]], 1, nums[i + dist])
cur = nums[0] + query(target_k)
min_cost = min(min_cost, cur)
return min_cost
์๋ฐ์คํฌ๋ฆฝํธ (Node.js)
function minCost(nums, k, dist) {
const n = nums.length;
// ---- coordinate compression ----
const sortedUnique = Array.from(new Set(nums)).sort((a, b) => a - b);
const ranks = new Map();
sortedUnique.forEach((val, i) => ranks.set(val, i + 1));
const maxRank = sortedUnique.length;
const bitCount = new Array(maxRank + 1).fill(0);
const bitSum = new Array(maxRank + 1).fill(0n); // use BigInt for safety
const update = (rank, cDelta, vDelta) => {
while (rank <= maxRank) {
bitCount[rank] += cDelta;
bitSum[rank] += BigInt(vDelta);
rank += rank & -rank;
}
};
const query = (target) => {
let idx = 0, curK = 0, curS = 0n;
for (let i = Math.floor(Math.log2(maxRank)); i >= 0; i--) {
const nextIdx = idx + (1 << i);
if (nextIdx <= maxRank && curK + bitCount[nextIdx] < target) {
idx = nextIdx;
curK += bitCount[nextIdx];
curS += bitSum[nextIdx];
}
}
const remaining = target - curK;
return curS + BigInt(remaining) * BigInt(sortedUnique[idx]);
};
const targetK = k - 1;
// initialise the window (first `dist + 1` elements, but not beyond the array)
for (let i = 1; i <= Math.min(dist, n - 1); i++) {
update(ranks.get(nums[i]), 1, nums[i]);
}
let minCost = BigInt(nums[0]) + query(targetK);
// slide the window across the array
for (let i = 2; i + dist < n; i++) {
// remove element leaving the window
update(ranks.get(nums[i - 1]), -1, -nums[i - 1]);
// add new element entering the window
update(ranks.get(nums[i + dist]), 1, nums[i + dist]);
const cur = BigInt(nums[0]) + query(targetK);
if (cur < minCost) minCost = cur;
}
return Number(minCost);
}
Key Techniques
Coordinate Compression
When the value range (up to 10^9 or larger) far exceeds the number of elements n, we map each distinct value to a compact rank. This lets us use the ranks as indices in arrays (e.g., Fenwick trees).
Dual Fenwick Tree
One BIT stores counts of elements, the other stores sums of their values. Together they enable fast โsum of the smallestโฏK elementsโ queries.
Binary Lifting on BIT
Instead of a linear scan or a binary search over the BIT, we use binary lifting to locate the prefix where the cumulative count first reaches (or exceeds) K. This reduces the search to O(logโฏmaxRank).
Why This Matters
The problem is a classic example of handling streaming data with tight time constraints:
-
In realโworld systems (e.g., highโfrequency trading platforms), you often need the โbestโฏK prices in the lastโฏT minutes.โ
-
A sliding window combined with an orderโstatistic data structure (the dual BIT) provides the necessary efficiency.
-
Although the problem appears daunting as a โHardโ challenge, breaking it down into:
- Window management, and
- Efficient rankโbased queries
makes the solution both understandable and implementable.