Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
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.
Example 1:
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 3 with respective weights of 5, and 3.
Example 2:
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 6 with respective weights of 7, 6, 3, and 5.
Constraints:
1 <= intevals.length <= 5 * 104intervals[i].length == 3intervals[i] = [li, ri, weighti]1 <= li <= ri <= 1091 <= weighti <= 109Problem summary: 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.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Binary Search · Dynamic Programming
[[1,3,2],[4,5,2],[1,5,5],[6,9,3],[6,7,1],[8,9,1]]
[[5,8,1],[6,7,7],[4,7,3],[9,10,6],[7,8,2],[11,14,3],[3,5,5]]
two-best-non-overlapping-events)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
class Solution {
public int[] maximumWeight(List<List<Integer>> intervals) {
// Convert input to Interval objects
List<Interval> indexedIntervals = new ArrayList<>();
for (int i = 0; i < intervals.size(); ++i) {
List<Integer> interval = intervals.get(i);
indexedIntervals.add(new Interval(interval.get(0), interval.get(1), interval.get(2), i));
}
indexedIntervals.sort(Comparator.comparingInt(Interval::left));
T[][] memo = new T[indexedIntervals.size()][5];
return dp(indexedIntervals, memo, 0, 4).selected.stream().mapToInt(Integer::intValue).toArray();
}
private record T(long weight, List<Integer> selected) {}
private record Interval(int left, int right, int weight, int originalIndex) {}
private T dp(List<Interval> intervals, T[][] memo, int i, int quota) {
if (i == intervals.size() || quota == 0)
return new T(0, List.of());
if (memo[i][quota] != null)
return memo[i][quota];
T skip = dp(intervals, memo, i + 1, quota);
Interval interval = intervals.get(i);
final int j = findFirstGreater(intervals, i + 1, interval.right);
T nextRes = dp(intervals, memo, j, quota - 1);
List<Integer> newSelected = new ArrayList<>(nextRes.selected);
newSelected.add(interval.originalIndex);
Collections.sort(newSelected);
T pick = new T(interval.weight + nextRes.weight, newSelected);
return memo[i][quota] =
(pick.weight > skip.weight ||
(pick.weight == skip.weight && compareLists(pick.selected, skip.selected) < 0))
? pick
: skip;
}
// Binary searches the first interval that starts after `rightBoundary`.
private int findFirstGreater(List<Interval> intervals, int startFrom, int rightBoundary) {
int l = startFrom;
int r = intervals.size();
while (l < r) {
final int m = (l + r) / 2;
if (intervals.get(m).left > rightBoundary)
r = m;
else
l = m + 1;
}
return l;
}
// Compares two lists of integers lexicographically.
private int compareLists(List<Integer> list1, List<Integer> list2) {
final int minSize = Math.min(list1.size(), list2.size());
for (int i = 0; i < minSize; ++i) {
final int comparison = Integer.compare(list1.get(i), list2.get(i));
if (comparison != 0)
return comparison;
}
return Integer.compare(list1.size(), list2.size());
}
}
// Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
package main
import (
"slices"
"sort"
)
// https://space.bilibili.com/206214
func maximumWeight(intervals [][]int) []int {
type tuple struct{ l, r, weight, i int }
a := make([]tuple, len(intervals))
for i, interval := range intervals {
a[i] = tuple{interval[0], interval[1], interval[2], i}
}
slices.SortFunc(a, func(a, b tuple) int { return a.r - b.r })
n := len(intervals)
type pair struct {
sum int
id []int
}
f := make([][5]pair, n+1)
for i, t := range a {
k := sort.Search(i, func(k int) bool { return a[k].r >= t.l })
for j := 1; j < 5; j++ {
s1 := f[i][j].sum
// 为什么是 f[k] 不是 f[k+1]:上面算的是 >= t.l,-1 后得到 < t.l,但由于还要 +1,抵消了
s2 := f[k][j-1].sum + t.weight
if s1 > s2 {
f[i+1][j] = f[i][j]
continue
}
newId := slices.Clone(f[k][j-1].id)
newId = append(newId, t.i)
slices.Sort(newId)
if s1 == s2 && slices.Compare(f[i][j].id, newId) < 0 {
newId = f[i][j].id
}
f[i+1][j] = pair{s2, newId}
}
}
return f[n][4].id
}
# Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
from dataclasses import dataclass
@dataclass(frozen=True)
class T:
weight: int
selected: tuple[int]
def __iter__(self):
yield self.weight
yield self.selected
class Solution:
def maximumWeight(self, intervals: list[list[int]]) -> list[int]:
intervals = sorted((*interval, i) for i, interval in enumerate(intervals))
@functools.lru_cache(None)
def dp(i: int, quota: int) -> T:
"""
Returns the maximum weight and the selected intervals for intervals[i..n),
where `quota` is the number of intervals that can be selected.
"""
if i == len(intervals) or quota == 0:
return T(0, ())
skip = dp(i + 1, quota)
_, r, weight, originalIndex = intervals[i]
j = bisect.bisect_right(intervals, (r, math.inf))
nextRes = dp(j, quota - 1)
pick = T(weight + nextRes.weight,
sorted((originalIndex, *nextRes.selected)))
return (pick if (pick.weight > skip.weight or
pick.weight == skip.weight and pick.selected < skip.selected)
else skip)
return list(dp(0, 4).selected)
// Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
// class Solution {
// public int[] maximumWeight(List<List<Integer>> intervals) {
// // Convert input to Interval objects
// List<Interval> indexedIntervals = new ArrayList<>();
// for (int i = 0; i < intervals.size(); ++i) {
// List<Integer> interval = intervals.get(i);
// indexedIntervals.add(new Interval(interval.get(0), interval.get(1), interval.get(2), i));
// }
// indexedIntervals.sort(Comparator.comparingInt(Interval::left));
// T[][] memo = new T[indexedIntervals.size()][5];
// return dp(indexedIntervals, memo, 0, 4).selected.stream().mapToInt(Integer::intValue).toArray();
// }
//
// private record T(long weight, List<Integer> selected) {}
// private record Interval(int left, int right, int weight, int originalIndex) {}
//
// private T dp(List<Interval> intervals, T[][] memo, int i, int quota) {
// if (i == intervals.size() || quota == 0)
// return new T(0, List.of());
// if (memo[i][quota] != null)
// return memo[i][quota];
//
// T skip = dp(intervals, memo, i + 1, quota);
//
// Interval interval = intervals.get(i);
// final int j = findFirstGreater(intervals, i + 1, interval.right);
// T nextRes = dp(intervals, memo, j, quota - 1);
//
// List<Integer> newSelected = new ArrayList<>(nextRes.selected);
// newSelected.add(interval.originalIndex);
// Collections.sort(newSelected);
// T pick = new T(interval.weight + nextRes.weight, newSelected);
// return memo[i][quota] =
// (pick.weight > skip.weight ||
// (pick.weight == skip.weight && compareLists(pick.selected, skip.selected) < 0))
// ? pick
// : skip;
// }
//
// // Binary searches the first interval that starts after `rightBoundary`.
// private int findFirstGreater(List<Interval> intervals, int startFrom, int rightBoundary) {
// int l = startFrom;
// int r = intervals.size();
// while (l < r) {
// final int m = (l + r) / 2;
// if (intervals.get(m).left > rightBoundary)
// r = m;
// else
// l = m + 1;
// }
// return l;
// }
//
// // Compares two lists of integers lexicographically.
// private int compareLists(List<Integer> list1, List<Integer> list2) {
// final int minSize = Math.min(list1.size(), list2.size());
// for (int i = 0; i < minSize; ++i) {
// final int comparison = Integer.compare(list1.get(i), list2.get(i));
// if (comparison != 0)
// return comparison;
// }
// return Integer.compare(list1.size(), list2.size());
// }
// }
// Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3414: Maximum Score of Non-overlapping Intervals
// class Solution {
// public int[] maximumWeight(List<List<Integer>> intervals) {
// // Convert input to Interval objects
// List<Interval> indexedIntervals = new ArrayList<>();
// for (int i = 0; i < intervals.size(); ++i) {
// List<Integer> interval = intervals.get(i);
// indexedIntervals.add(new Interval(interval.get(0), interval.get(1), interval.get(2), i));
// }
// indexedIntervals.sort(Comparator.comparingInt(Interval::left));
// T[][] memo = new T[indexedIntervals.size()][5];
// return dp(indexedIntervals, memo, 0, 4).selected.stream().mapToInt(Integer::intValue).toArray();
// }
//
// private record T(long weight, List<Integer> selected) {}
// private record Interval(int left, int right, int weight, int originalIndex) {}
//
// private T dp(List<Interval> intervals, T[][] memo, int i, int quota) {
// if (i == intervals.size() || quota == 0)
// return new T(0, List.of());
// if (memo[i][quota] != null)
// return memo[i][quota];
//
// T skip = dp(intervals, memo, i + 1, quota);
//
// Interval interval = intervals.get(i);
// final int j = findFirstGreater(intervals, i + 1, interval.right);
// T nextRes = dp(intervals, memo, j, quota - 1);
//
// List<Integer> newSelected = new ArrayList<>(nextRes.selected);
// newSelected.add(interval.originalIndex);
// Collections.sort(newSelected);
// T pick = new T(interval.weight + nextRes.weight, newSelected);
// return memo[i][quota] =
// (pick.weight > skip.weight ||
// (pick.weight == skip.weight && compareLists(pick.selected, skip.selected) < 0))
// ? pick
// : skip;
// }
//
// // Binary searches the first interval that starts after `rightBoundary`.
// private int findFirstGreater(List<Interval> intervals, int startFrom, int rightBoundary) {
// int l = startFrom;
// int r = intervals.size();
// while (l < r) {
// final int m = (l + r) / 2;
// if (intervals.get(m).left > rightBoundary)
// r = m;
// else
// l = m + 1;
// }
// return l;
// }
//
// // Compares two lists of integers lexicographically.
// private int compareLists(List<Integer> list1, List<Integer> list2) {
// final int minSize = Math.min(list1.size(), list2.size());
// for (int i = 0; i < minSize; ++i) {
// final int comparison = Integer.compare(list1.get(i), list2.get(i));
// if (comparison != 0)
// return comparison;
// }
// return Integer.compare(list1.size(), list2.size());
// }
// }
Use this to step through a reusable interview workflow for this problem.
Check every element from left to right until we find the target or exhaust the array. Each comparison is O(1), and we may visit all n elements, giving O(n). No extra space needed.
Each comparison eliminates half the remaining search space. After k comparisons, the space is n/2ᵏ. We stop when the space is 1, so k = log₂ n. No extra memory needed — just two pointers (lo, hi).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.
Usually fails on: Two-element ranges never converge.
Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.
Wrong move: An incomplete state merges distinct subproblems and caches incorrect answers.
Usually fails on: Correctness breaks on cases that differ only in hidden state.
Fix: Define state so each unique subproblem maps to one DP cell.