Best Sightseeing Pair
Problem
i has an attractiveness values[i]. Visiting
spots i < j scores values[i] + values[j] + i - j: their combined
value minus the distance between them. Return the maximum score over all pairs.values = [8,1,5,2,6]Output11 (i=0, j=2: 8+5+0-2)values = [1,2]Output22 ≤ n ≤ 5·104 · 1 ≤ values[i] ≤ 1000
Try it yourself
class Solution {
public int maxScoreSightseeingPair(int[] values) {
}
}
(values[i] + i) + (values[j] - j). For each j we only need the
best values[i] + i seen so far, which a single running maximum
provides.Instinct Best sum or product of a contiguous subarray in one pass? At each index choose - extend the run ending here or restart - and track the global best.
Memory hack Carry the bag only while it helps - a negative past is dead weight, drop it and restart here.
Open the Kadane's Running Best guide →Approaches
Brute ForceTry Every Pair
O(n2)Space O(1)Evaluate the formula for every pair i < j and keep the
largest score. It follows the definition directly, so it is a reliable reference
for testing.
public int maxScoreSightseeingPair(int[] values) { int best = Integer.MIN_VALUE; for (int i = 0; i < values.length; i++) { for (int j = i + 1; j < values.length; j++) { best = Math.max(best, values[i] + values[j] + i - j); } } return best; }
About 1.25·109 pairs at the limit — too slow.
BetterPrefix-Maximum Array
O(n)Space O(n)Precompute leftBest[j] = max of values[i] + i over
all i < j. Then each j scores
leftBest[j] + values[j] - j, and we take the maximum — the
separation of terms removes the inner loop.
public int maxScoreSightseeingPair(int[] values) { int n = values.length; int[] leftBest = new int[n]; // leftBest[j] = max(values[i] + i) for i < j leftBest[1] = values[0]; for (int j = 2; j < n; j++) { leftBest[j] = Math.max(leftBest[j - 1], values[j - 1] + j - 1); } int best = Integer.MIN_VALUE; for (int j = 1; j < n; j++) { best = Math.max(best, leftBest[j] + values[j] - j); } return best; }
Linear time, but stores an extra array that is only read once per index.
OptimalSingle Pass with Running Max
O(n)Space O(1)Fold the prefix array into one variable bestLeft. At each
j, first score j as the right spot using
bestLeft, then let j become a candidate left spot by
updating bestLeft with values[j] + j.
public int maxScoreSightseeingPair(int[] values) { int bestLeft = values[0]; // max of values[i] + i so far int best = Integer.MIN_VALUE; for (int j = 1; j < values.length; j++) { best = Math.max(best, bestLeft + values[j] - j); // j as the right spot bestLeft = Math.max(bestLeft, values[j] + j); // j as a future left spot } return best; }
Dry runvalues = [8,1,5,2,6]
- start8L1526bestLeft=8
- j=18L1j5268+1-1=8, best=8
- j=28L15j268+5-2=11, best=11
- j=38L152j68+2-3=7
- j=48L1526j8+6-4=10, ans 11 ✔
Same linear time with constant memory — the interview answer.
j before updating bestLeft —
otherwise you may pair a spot with itself.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Try Every Pair | O(n2) | O(1) |
| Better | Prefix-Maximum Array | O(n) | O(n) |
| Optimal | Single Pass with Running Max | O(n) | O(1) |
- Updating
bestLeftbefore scoring — allowsi == j. - Tracking the max of
values[i]alone — ignores the distance penalty. - Initialising the answer to 0 is fine here (scores ≥ 1), but
MIN_VALUEis safer in general.
- Rewrite the formula into an
i-only part and aj-only part first. - Relate it to Best Time to Buy and Sell Stock: track the best “buy” so far.
- Walk through the prefix-array version, then show it collapses to one variable.