You are given a 2D integer array intervals, where intervals[i] = [li, ri, weighti]. Interval i starts at position li and ends at ri, and has a weight of weighti. You can choose up to 4 non-overlapping intervals.
The score of the chosen intervals is defined as the total sum of their weights.
Return the lexicographically smallest array of at most 4 indices from intervals with maximum score, representing your choice of non-overlapping intervals.
Two intervals are said to be non-overlapping if they do not share any points. In particular, intervals sharing a left or right boundary are considered overlapping.
Input: intervals =[[1,3,2],[4,5,2],[1,5,5],[6,9,3],[6,7,1],[8,9,1]]Output: [2,3]Explanation:
You can choose the intervals with indices 2, and 3with respective weights of
5, and 3.
Input: intervals =[[5,8,1],[6,7,7],[4,7,3],[9,10,6],[7,8,2],[11,14,3],[3,5,5]]Output: [1,3,5,6]Explanation:
You can choose the intervals with indices 1,3,5, and 6with respective
weights of 7,6,3, and 5.
This problem can be solved using a 0/1 knapsack approach since we need to choose at most 4 intervals. The key insight is to sort intervals by their start time and use dynamic programming to track the maximum weight achievable when selecting exactly j intervals ending at position i. For each interval, we can either skip it or take it (if it doesn’t overlap with previously selected intervals).
Sort intervals by start time l, and append original indices to track the answer.
Precompute the next non-overlapping interval for each interval using binary search to find the first interval that starts after the current interval ends.
Use DP where dp[i][j] represents the maximum weight when considering intervals from position i onwards and selecting exactly j intervals.
Also track choose[i][j] to store the indices of selected intervals for reconstruction.
For each interval, apply 0/1 knapsack logic: either skip the current interval or take it (and jump to the next non-overlapping interval).
When weights are equal, choose the lexicographically smaller index combination.
Process DP backwards from the last interval to the first.
classSolution {
public: vector<int> maximumWeight(vector<vector<int>>& intervals) {
int n = intervals.size();
// Add original indices to intervals
for (int i =0; i < n; ++i) {
intervals[i].push_back(i);
}
// Sort by start time
sort(intervals.begin(), intervals.end());
// Precompute next non-overlapping interval for each interval
vector<int> nxt(n);
for (int i =0; i < n; ++i) {
int end = intervals[i][1];
int l =0, r = n;
while (l < r) {
int m = (l + r) /2;
if (intervals[m][0] > end) r = m;
else l = m +1;
}
nxt[i] = l;
}
constint INF =1e9;
vector<vector<int64_t>> dp(n +1, vector<int64_t>(5, 0));
vector<vector<array<int, 4>>> choose(n +1, vector<array<int, 4>>(5, {INF, INF, INF, INF}));
// DP backwards
for (int i = n -1; i >=0; --i) {
for (int c =3; c >=0; --c) {
// Option 1: Skip current interval
int64_t skip = dp[i +1][c];
array<int, 4> svec = choose[i +1][c];
// Option 2: Take current interval
int64_t take = dp[nxt[i]][c +1] + intervals[i][2];
array<int, 4> tvec = choose[nxt[i]][c +1];
tvec[3] = intervals[i][3]; // Add current interval's original index
sort(tvec.begin(), tvec.end());
// Choose better option (or lexicographically smaller if equal)
if (skip > take || (skip == take && svec < tvec)) {
dp[i][c] = skip;
choose[i][c] = svec;
} else {
dp[i][c] = take;
choose[i][c] = tvec;
}
}
}
// Extract result
vector<int> ans;
for (int id : choose[0][0]) {
if (id != INF) ans.push_back(id);
}
return ans;
}
};
classSolution {
funmaximumWeight(intervals: Array<IntArray>): IntArray {
val n = intervals.size
// Add original indices to intervals
val newIntervals = Array(n) { i -> intArrayOf(intervals[i][0], intervals[i][1], intervals[i][2], i)
}
// Sort by start time
newIntervals.sortWith { a, b ->when {
a[0] != b[0] -> a[0].compareTo(b[0])
a[1] != b[1] -> a[1].compareTo(b[1])
else-> a[2].compareTo(b[2])
}
}
// Precompute next non-overlapping interval for each interval
val nxt = IntArray(n)
for (i in0 until n) {
val end = newIntervals[i][1]
var l = 0var r = n
while (l < r) {
val m = (l + r) / 2if (newIntervals[m][0] > end) {
r = m
} else {
l = m + 1 }
}
nxt[i] = l
}
val INF = 1_000_000_000
val dp = Array(n + 1) { LongArray(5) }
val choose = Array(n + 1) { Array(5) { IntArray(4) { INF } } }
// DP backwards
for (i in n - 1 downTo 0) {
for (c in3 downTo 0) {
// Option 1: Skip current interval
val skip = dp[i + 1][c]
val svec = choose[i + 1][c].clone()
// Option 2: Take current interval
val take = dp[nxt[i]][c + 1] + newIntervals[i][2]
val tvec = choose[nxt[i]][c + 1].clone()
tvec[3] = newIntervals[i][3] // Add current interval's original index
tvec.sort()
// Choose better option (or lexicographically smaller if equal)
if (skip > take || (skip == take && compare(svec, tvec) < 0)) {
dp[i][c] = skip
choose[i][c] = svec
} else {
dp[i][c] = take
choose[i][c] = tvec
}
}
}
// Extract result
return choose[0][0].filter { it!= INF }.toIntArray()
}
privatefuncompare(a: IntArray, b: IntArray): Int {
for (i in0 until 4) {
if (a[i] != b[i]) {
return a[i].compareTo(b[i])
}
}
return0 }
}
classSolution:
defmaxWeight(self, intervals: list[list[int]]) -> list[int]:
n = len(intervals)
arr = sorted([(r, l, w, i) for i, (l, r, w) in enumerate(intervals)])
ends = [r for r, l, w, i in arr]
dp = [[0]*5for _ in range(n+1)]
idx = [[[] for _ in range(5)] for _ in range(n+1)]
for i in range(1, n+1):
for k in range(1, 5):
l = bisect.bisect_left(ends, arr[i-1][1])
if dp[i-1][k] > dp[l][k-1] + arr[i-1][2]:
dp[i][k] = dp[i-1][k]
idx[i][k] = idx[i-1][k][:]
elif dp[i-1][k] < dp[l][k-1] + arr[i-1][2]:
dp[i][k] = dp[l][k-1] + arr[i-1][2]
idx[i][k] = idx[l][k-1][:] + [arr[i-1][3]]
else:
a = idx[i-1][k][:]
b = idx[l][k-1][:] + [arr[i-1][3]]
if a < b:
dp[i][k] = dp[i-1][k]
idx[i][k] = a
else:
dp[i][k] = dp[l][k-1] + arr[i-1][2]
idx[i][k] = b
best =0 ans = []
for k in range(1, 5):
if dp[n][k] > best:
best = dp[n][k]
ans = idx[n][k][:]
elif dp[n][k] == best and (not ans or idx[n][k] < ans):
ans = idx[n][k][:]
ans.sort()
##### Rust
impl Solution {
pubfnmaximum_weight(mut intervals: Vec<Vec<i32>>) -> Vec<i32> {
let n = intervals.len();
// Add original indices to intervals
for i in0..n {
intervals[i].push(i asi32);
}
// Sort by start time
intervals.sort_by(|a, b| {
a[0].cmp(&b[0])
.then_with(|| a[1].cmp(&b[1]))
.then_with(|| a[2].cmp(&b[2]))
});
// Precompute next non-overlapping interval for each interval
letmut nxt =vec![0; n];
for i in0..n {
let end = intervals[i][1];
letmut l =0;
letmut r = n;
while l < r {
let m = (l + r) /2;
if intervals[m][0] > end {
r = m;
} else {
l = m +1;
}
}
nxt[i] = l;
}
constINF: i32=1_000_000_000;
letmut dp =vec![vec![0i64; 5]; n +1];
letmut choose =vec![vec![[INF; 4]; 5]; n +1];
// DP backwards
for i in (0..n).rev() {
for c in (0..=3).rev() {
// Option 1: Skip current interval
let skip = dp[i +1][c];
let svec = choose[i +1][c];
// Option 2: Take current interval
let take = dp[nxt[i]][c +1] + intervals[i][2] asi64;
letmut tvec = choose[nxt[i]][c +1];
tvec[3] = intervals[i][3]; // Add current interval's original index
tvec.sort();
// Choose better option (or lexicographically smaller if equal)
if skip > take || (skip == take && svec < tvec) {
dp[i][c] = skip;
choose[i][c] = svec;
} else {
dp[i][c] = take;
choose[i][c] = tvec;
}
}
}
// Extract result
choose[0][0]
.iter()
.filter(|&&id| id !=INF)
.copied()
.collect()
}
}
⏰ Time complexity: O(n log n), where n is the number of intervals. The sorting takes O(n log n) and the DP with binary search preprocessing takes O(n log n) total time.
🧺 Space complexity: O(n), for the DP tables and index tracking arrays.ms