Skip to content
dsa450for five year olds

Dynamic Programming · 26 of 60

Maximum sum of pairs with specific difference

Make partner pairs of close numbers so the paired numbers add up to the biggest total, with nobody in two pairs.

Simple way
time O(2^n)
room O(n)
Clever way
time O(n log n)
room O(n)

The problem, simply

You get a list of numbers and a number K. Two numbers may form a pair if their difference is strictly less than K. Each number can be in at most one pair, and some numbers may stay alone.

Give back the biggest possible sum of all numbers that end up in pairs.

A story to remember it

Picture it

arraygiven
012345635101517129
arraysorted
012345635910121517

After sorting, close numbers sit side by side. Pairs must differ by less than 4. Pairing 3-5, 10-12 and 15-17 gives 62, and 9 stays alone.

  • answer
  • ignored

Examples

A normal case

In
arr = [3, 5, 10, 15, 17, 12, 9], K = 4
Out
62

Pairs 3 and 5, 10 and 12, 15 and 17. Their sum is 8 + 22 + 32 = 62. 9 could pair with 10, but then 12 is left alone and the total is lower.

Only one pair works

In
arr = [5, 15, 10, 300], K = 12
Out
25

10 and 15 differ by 5. Every other pair is 12 or more apart.

Nobody can pair

In
arr = [1, 10, 20], K = 5
Out
0

All gaps are 5 or more, so no pairs and a sum of 0.

Equal numbers

In
arr = [4, 4, 4], K = 1
Out
8

Two 4s differ by 0, which is less than 1. The third 4 has no partner left.

Way 1: the simple way

Sort the list. Then from the biggest number down, each number either stays alone or pairs with its left neighbor.

  1. f(i) is the best sum using sorted numbers 0 to i.
  2. Skip number i: f(i - 1).
  3. If a[i] - a[i - 1] < K, pair them: a[i] + a[i - 1] + f(i - 2).
  4. f(0) and below are 0.

Why only the left neighbor? After sorting, the left neighbor is the closest smaller number. Any pair that reaches further back can swap partners with the neighbor and stay valid with the same or a bigger sum.

tree
skipskipskipskippairpairskippairpairskipskippairf(4)f(3)f(2)f(1)f(0)f(-1)f(1)f(0)f(-1)f(2)f(1)f(0)f(-1)

Sorted first five: [3, 5, 9, 10, 12]. f(i) is the best using numbers 0 to i. Each call either skips number i or pairs it with number i - 1. f(2) is solved twice and f(1) three times. 9 and 5 differ by 4, so f(2) has no pair branch.

  • seen
java
import java.util.Arrays;

class Solution {
    public int maxSumPairs(int[] arr, int k) {
        int[] a = arr.clone();
        Arrays.sort(a);                                     // partners stand side by side
        return best(a, a.length - 1, k);
    }

    private int best(int[] a, int i, int k) {
        if (i <= 0) return 0;                               // zero or one number left
        int skip = best(a, i - 1, k);
        int pair = 0;
        if (a[i] - a[i - 1] < k) {
            pair = a[i] + a[i - 1] + best(a, i - 2, k);    // pair with the left neighbor
        }
        return Math.max(skip, pair);
    }
}

How long it takes: up to two branches per call, O(2^n) (it grows like the Fibonacci numbers). Extra room: the waiting calls, O(n).

Way 2: the clever way

Remember each f(i) in an array dp and fill it from left to right.

  1. Sort. Set dp[0] = 0.
  2. For i from 1: dp[i] = dp[i - 1] (skip).
  3. If a[i] - a[i - 1] < K, also try a[i] + a[i - 1] + dp[i - 2] (with dp[-1] = 0).
  4. The answer is dp[n - 1].

Pair with the left neighbor or skip

01 / 07

gridbest sum using numbers 0 to i
359101215170
arraysorted
012345635910121517i

Sort first: 3, 5, 9, 10, 12, 15, 17. With only the number 3 there is nobody to pair with, so the best is 0.

  • looking now
  • checking
  • answer
  • rejected
  • just added
java
import java.util.Arrays;

class Solution {
    public int maxSumPairs(int[] arr, int k) {
        int n = arr.length;
        if (n < 2) return 0;
        int[] a = arr.clone();
        Arrays.sort(a);
        int[] dp = new int[n];                              // dp[0] = 0: one number cannot pair
        for (int i = 1; i < n; i++) {
            dp[i] = dp[i - 1];                              // leave a[i] alone
            if (a[i] - a[i - 1] < k) {
                int before = i >= 2 ? dp[i - 2] : 0;
                dp[i] = Math.max(dp[i], before + a[i] + a[i - 1]);   // pair the neighbors
            }
        }
        return dp[n - 1];
    }
}

How long it takes: sorting is O(n log n), the fill is O(n). Extra room: the dp array and the sorted copy, O(n).

Way 3: greedy from the top

Since we want a big sum, the biggest numbers matter most. Walk the sorted list from the right. If the biggest remaining number can pair with its left neighbor, pair them and jump two steps. Otherwise drop it and step once.

This is safe because the top number, if it can pair at all, gains the most from pairing now, and its neighbor is its best partner.

java
import java.util.Arrays;

class Solution {
    public int maxSumPairs(int[] arr, int k) {
        int[] a = arr.clone();
        Arrays.sort(a);
        int sum = 0;
        int i = a.length - 1;
        while (i > 0) {
            if (a[i] - a[i - 1] < k) {      // the two biggest left can pair
                sum += a[i] + a[i - 1];
                i -= 2;
            } else {
                i -= 1;                     // a[i] has no partner
            }
        }
        return sum;
    }
}

Same O(n log n) time, but only O(1) extra room besides the sort. It works because all numbers are added, never subtracted. With negative numbers you would want the DP, because pairing two negatives lowers the sum.

Watch out for

  • Forgetting to sort. Without sorting, good partners are not neighbors and the DP misses them.
  • Sorting the caller's array in place. Clone first if the input must not change.
  • dp[i - 2] when i = 1 goes out of bounds. Treat it as 0.

What interviewers ask next

  • Count the most pairs instead of the biggest sum? Same sort, then greedy from the left: pair neighbors whenever they fit.
  • What if numbers can be negative? Use the DP and allow "skip" even when a pair fits. The greedy can fail.
  • Why are we allowed to sort? The answer only depends on which numbers end up together, not on where they stood, so any order is fine.
  • Return the pairs. Walk dp backwards and see whether each dp[i] came from skipping or pairing.

Remember this

Grown-up words: lining up kids is sorting, partners are disjoint pairs, the note at each spot is dp[i], swapping partners is an exchange argument, and Way 3 is a greedy that only works because every value is positive.