G27
Self-checkedTier 1 12 min readalgo.dp-sequences

DP on sequences: LIS and coin change

Two dynamic programs on a line: the longest increasing subsequence (the quadratic table and the n log n tails array with binary search) and coin change (the fewest coins, the number of ways, and why the order of the loops decides whether orders are counted). The recurrences, tables filled step by step, running times, why greedy fails for coin change, and the standard traps.

Why it exists

Many optimisation problems are about one sequence: pick the longest increasing run from a list, pay an amount with as few coins as possible. They are the simplest dynamic programs, with a one-dimensional table, and each teaches something specific: LIS shows how a DP can be sped up from n2n^2 to nlog⁡nn \log n with a cleverly chosen quantity, and coin change shows how the order of the loops changes what is being counted and why a greedy rule that works for ordinary money fails for other coin systems.

Prerequisites

You need the dynamic programming recipe (dynamic programming) and binary search (binary search).

Precise definitions

Increasing subsequence. Positions i1<i2<⋯<iki_1 < i_2 < \cdots < i_k with ai1<ai2<⋯<aika_{i_1} < a_{i_2} < \cdots < a_{i_k} (strictly; non-decreasing uses ≤\le). The longest increasing subsequence (LIS) has the largest kk. It need not be unique and it need not be contiguous.

Coin change, fewest coins. Coin values c1,…,ckc_1, \ldots, c_k (unlimited supply of each) and an amount TT; find the fewest coins whose values sum to TT (or report that it is impossible).

Coin change, number of ways. The number of different combinations of coins (the order of the coins does not matter: 1+31 + 3 and 3+13 + 1 are one way) with sum TT. If order matters the count is of sequences (compositions).

How it works

LIS: the quadratic dynamic program

Let L[i]L[i] be the length of the longest increasing subsequence that ends at position ii. It is one more than the best L[j]L[j] over the earlier positions j<ij < i whose value is smaller: L[i]=1+max⁡{ L[j]:j<i, aj<ai }(max⁡∅=0),answer=max⁡iL[i].L[i] = 1 + \max\{\, L[j] : j < i,\ a_j < a_i \,\}\quad (\max \emptyset = 0), \qquad \text{answer} = \max_i L[i]. Note the definition: L[i]L[i] is not "the LIS of the first ii elements" (that is the prefix maximum of LL). Time Θ(n2)\Theta(n^2) (each ii looks at all j<ij < i), space Θ(n)\Theta(n). Storing the position jj that gave the maximum allows the subsequence to be printed by following these links back from the best ii.

Longest increasing subsequence: the quadratic dynamic program: start

Computed by a script, not typed
  1. 1L[i] = 1 if no smaller element precedes a[i]
  2. 2L[i] = 1 + max L[j] over j < i with a[j] < a[i]
i = 0i = 1i = 2i = 3i = 4i = 5i = 6i = 7i = 8i = 9
a[i]8374105126119
L[i]
previous position

Step 0

The array a. L[i] is the length of the longest increasing subsequence that ends at position i; it is one more than the best L[j] over earlier positions j with a[j] < a[i].

0 / 10
computed by script: the quantities are measured by running C, or evaluated, one row at a time. python3 scripts/gen-algo-dp-traces.py

The program below computes both the LIS and the coin tables for the array 8,3,7,4,10,5,12,6,11,98, 3, 7, 4, 10, 5, 12, 6, 11, 9.

#include <stdio.h>
 
int main(void) {
    int a[10] = {8, 3, 7, 4, 10, 5, 12, 6, 11, 9}, n = 10;
    int L[10], parent[10];                               /* L[i] = length of the longest increasing subsequence that ENDS at a[i] */
    int best = 0, last = 0;
    for (int i = 0; i < n; i++) {
        L[i] = 1;
        parent[i] = -1;
        for (int j = 0; j < i; j++)
            if (a[j] < a[i] && L[j] + 1 > L[i]) { L[i] = L[j] + 1; parent[i] = j; }
        if (L[i] > best) { best = L[i]; last = i; }
    }
    printf("LIS length %d:", best);
    int seq[10], k = best;
    for (int i = last; i >= 0; i = parent[i]) seq[--k] = a[i];
    for (int i = 0; i < best; i++) printf(" %d", seq[i]);
    printf("\n");
 
    int tails[10], size = 0;                             /* tails[l] = smallest possible last element of an increasing subsequence of length l + 1 */
    for (int i = 0; i < n; i++) {
        int lo = 0, hi = size;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if (tails[mid] < a[i]) lo = mid + 1;
            else hi = mid;
        }
        tails[lo] = a[i];
        if (lo == size) size++;
    }
    printf("tails method: length %d\n", size);
 
    int coins[3] = {1, 3, 4}, T = 12;
    int fewest[13], ways[13];                            /* fewest coins, and the number of different sets of coins, that make each amount */
    fewest[0] = 0;
    ways[0] = 1;
    for (int x = 1; x <= T; x++) { fewest[x] = 1 << 20; ways[x] = 0; }
    for (int x = 1; x <= T; x++)
        for (int c = 0; c < 3; c++)
            if (coins[c] <= x && fewest[x - coins[c]] + 1 < fewest[x]) fewest[x] = fewest[x - coins[c]] + 1;
    for (int c = 0; c < 3; c++)
        for (int x = coins[c]; x <= T; x++) ways[x] += ways[x - coins[c]];
    printf("amount:");
    for (int x = 0; x <= T; x++) printf(" %2d", x);
    printf("\nfewest:");
    for (int x = 0; x <= T; x++) printf(" %2d", fewest[x]);
    printf("\nways:  ");
    for (int x = 0; x <= T; x++) printf(" %2d", ways[x]);
    printf("\n");
    return 0;
}
LIS length 5: 3 4 5 6 11
tails method: length 5
amount:  0  1  2  3  4  5  6  7  8  9 10 11 12
fewest:  0  1  2  1  1  2  2  2  2  3  3  3  3
ways:    1  1  1  2  3  3  4  5  6  7  8  9 11

The LIS has length 55, for example 3,4,5,6,113, 4, 5, 6, 11 (another is 3,4,5,6,93, 4, 5, 6, 9): not unique.

LIS in O(nlog⁡n)O(n \log n): the tails array

Keep an array tails\text{tails} in which tails[l]\text{tails}[l] is the smallest possible last element of an increasing subsequence of length l+1l + 1 among the elements seen so far. This array is always increasing (a smaller tail exists for shorter subsequences). For each new element xx: binary-search the first tail that is not smaller than xx and replace it by xx; if every tail is smaller than xx, append xx (the longest subsequence has grown). The length of the LIS is the final size of the array. Each element costs one binary search: time Θ(nlog⁡n)\Theta(n \log n), space Θ(n)\Theta(n).

Why it is right: replacing a tail by a smaller value keeps the same length but makes the subsequence easier to extend later, and appending happens exactly when xx extends the longest one. (The array tails\text{tails} itself is not in general an increasing subsequence of the input, only its size is the answer; to print the sequence you store, for every element, the position of the tail before it.)

Longest increasing subsequence by the tails array (binary search): start

Computed by a script, not typed
stepelementindex foundactiontails afterlength
0
1
2
3
4
5
6
7
8
9

Step 0

tails[l] is the smallest possible last element of an increasing subsequence of length l + 1. For each new element, binary search finds the first tail that is not smaller than it and replaces that tail (or appends the element if all tails are smaller). The number of tails is the length of the LIS.

0 / 10
computed by script: the quantities are measured by running C, or evaluated, one row at a time. python3 scripts/gen-algo-dp-traces.py

For non-decreasing subsequences the search is for the first tail strictly larger than xx.

Coin change

Fewest coins. Let f(x)f(x) be the fewest coins for amount xx. The last coin has some value c≤xc \le x, and the rest is an optimal payment of x−cx - c: f(0)=0,f(x)=1+min⁡c≤xf(x−c)(x≥1).f(0) = 0,\qquad f(x) = 1 + \min_{c \le x} f(x - c)\quad (x \ge 1). With TT the amount and kk coin values: time Θ(kT)\Theta(kT), space Θ(T)\Theta(T). This is the dynamic program that does work for every coin system, where greedy (always take the largest coin) fails for some, such as {1,3,4}\{1, 3, 4\} at amount 66 (greedy method): f(6)=2f(6) = 2 (3+33 + 3), while greedy uses 4+1+14 + 1 + 1.

Number of ways (combinations). Let w(x)w(x) be the number of multisets of coins with sum xx. Process one coin value at a time (outer loop over coins, inner loop over amounts): w(0)=1;for each coin c: for x=c,…,T: w(x)+=w(x−c).w(0) = 1;\quad \text{for each coin } c:\ \text{for } x = c, \ldots, T:\ w(x) \mathrel{+}= w(x - c). After the coin cc has been processed, w(x)w(x) counts the combinations using only the coins processed so far. With the loops the other way round (outer loop over amounts, inner over coins) w(x)=∑cw(x−c)w(x) = \sum_c w(x - c) counts ordered sequences, because any coin may come last.

Coin change with coins 1, 3, 4: start

Computed by a script, not typed
amountfewest coinsa best last coinways (combinations)
0
1
2
3
4
5
6
7
8
9
10
11
12

Step 0

For the coins 1, 3 and 4: the fewest coins for each amount (greedy fails for 6), and the number of different sets of coins that make the amount.

0 / 13
computed by script: the quantities are measured by running C, or evaluated, one row at a time. python3 scripts/gen-algo-dp-traces.py

For the coins 1,3,41, 3, 4 the table shows f(6)=2f(6) = 2 and f(12)=3f(12) = 3 (4+4+44 + 4 + 4), and the number of ways to pay 44 is 33 (1+1+1+11 + 1 + 1 + 1, 1+31 + 3, 44); the number of ordered ways to pay 44 is 44 (11111111, 1313, 3131, 44).

Relations. Coin change with unlimited coins is the unbounded knapsack (each item may be taken any number of times, value 11, to be minimised). Deciding whether an amount can be paid at all is the subset-sum style problem with time Θ(kT)\Theta(kT), pseudo-polynomial in TT like the knapsack (knapsack and others).

Worked examples

EX 1LIS table by hand

Easy

a=8,3,7,4,10,5,12,6,11,9a = 8, 3, 7, 4, 10, 5, 12, 6, 11, 9. L[0]=1L[0] = 1 (88), L[1]=1L[1] = 1 (33, nothing smaller before it), L[2]=2L[2] = 2 (77 after 33), L[3]=2L[3] = 2 (44 after 33), L[4]=3L[4] = 3 (1010 after 77 or 44), L[5]=3L[5] = 3 (55 after 3,43, 4), L[6]=4L[6] = 4 (1212 after 1010), L[7]=4L[7] = 4 (66 after 55, whose LL is 33), L[8]=5L[8] = 5 (1111 after 66, whose LL is 44), L[9]=5L[9] = 5 (99 after 66). The maximum is 55. The animation reproduces this and the links give 3,4,5,6,113, 4, 5, 6, 11.

EX 2The tails array step by step

Easy

Same array. 8→[8]8 \to [8]; 33 replaces 88: [3][3]; 77 is larger than every tail: append, [3,7][3, 7]; 44 replaces 77: [3,4][3, 4]; 1010 appends: [3,4,10][3, 4, 10]; 55 replaces 1010: [3,4,5][3, 4, 5]; 1212 appends: [3,4,5,12][3, 4, 5, 12]; 66 replaces 1212: [3,4,5,6][3, 4, 5, 6]; 1111 appends: [3,4,5,6,11][3, 4, 5, 6, 11]; 99 replaces 1111: [3,4,5,6,9][3, 4, 5, 6, 9]. Five tails: LIS length 55. Here the final tails happen to be an increasing subsequence of the input, but in general the array mixes elements of different subsequences: for the input 3,4,13, 4, 1 the tails go [3][3], [3,4][3, 4], [1,4][1, 4], and 1,41, 4 is not a subsequence of the input (the 11 comes after the 44); only the size 22 is meaningful.

EX 3Coin change by hand

Medium

Coins {1,3,4}\{1, 3, 4\}. f(1)=1f(1) = 1, f(2)=2f(2) = 2, f(3)=1f(3) = 1, f(4)=1f(4) = 1, f(5)=1+min⁡(f(4),f(2),f(1))=2f(5) = 1 + \min(f(4), f(2), f(1)) = 2, f(6)=1+min⁡(f(5),f(3),f(2))=1+min⁡(2,1,2)=2f(6) = 1 + \min(f(5), f(3), f(2)) = 1 + \min(2, 1, 2) = 2 (so 3+33 + 3). Number of combinations for 66: 161^6; 1331^3 3; 3 33\,3; 1 1 41\,1\,4: 44 ways, the table's entry. Greedy would give 4+1+14 + 1 + 1, three coins.

EX 4Which loop order?

Medium

Count the ways to make 44 with coins 1,31, 3 (combinations {1111,13}\{1111, 13\}: 22; sequences 1111,13,311111, 13, 31: 33). Outer loop coins: process c=1c = 1: w=1,1,1,1,1w = 1, 1, 1, 1, 1; process c=3c = 3: w(3)=1+w(0)=2w(3) = 1 + w(0) = 2, w(4)=1+w(1)=2w(4) = 1 + w(1) = 2: answer 22 (combinations). Outer loop amounts: w(1)=w(0)=1w(1) = w(0) = 1, w(2)=w(1)=1w(2) = w(1) = 1, w(3)=w(2)+w(0)=2w(3) = w(2) + w(0) = 2, w(4)=w(3)+w(1)=3w(4) = w(3) + w(1) = 3: answer 33 (sequences). A question must say which is meant.

EX 5Typical questions

GATE level

(a) Time of the straightforward LIS DP: Θ(n2)\Theta(n^2); of the best known: Θ(nlog⁡n)\Theta(n \log n). (b) LIS of 5,4,3,2,15, 4, 3, 2, 1: 11 (strictly increasing); LIS of 1,2,3,4,51, 2, 3, 4, 5: 55; of 2,2,22, 2, 2 (strictly): 11, non-decreasing: 33. (c) Fewest coins for 1212 with {1,3,4}\{1, 3, 4\}: 33. (d) Time and space of the coin DP for amount TT and kk coins: Θ(kT)\Theta(kT) and Θ(T)\Theta(T). (e) Is the coin DP polynomial? It is pseudo-polynomial in TT. (f) LIS of a sequence of distinct numbers equals the LCS of the sequence and its sorted version.

Cheat box

Cheat box

  • LIS DP: L[i]=1+max⁡L[j]L[i] = 1 + \max L[j] over j<ij < i with aj<aia_j < a_i; answer max⁡iL[i]\max_i L[i] (not L[n−1]L[n-1]); Θ(n2)\Theta(n^2).
  • LIS Θ(nlog⁡n)\Theta(n \log n): tails array, binary search for the first tail ≥x\ge x (strict) and replace, else append; length == size; the array is not the sequence.
  • Strictly increasing: first tail ≥x\ge x; non-decreasing: first tail >x> x.
  • Fewest coins: f(x)=1+min⁡cf(x−c)f(x) = 1 + \min_c f(x - c), Θ(kT)\Theta(kT); works for every coin system; greedy does not.
  • Number of combinations: coins outer loop, amounts inner; sequences: amounts outer loop.
  • LIS of distinct values == LCS with the sorted array.

Traps and GATE patterns

Connections

Both are instances of dynamic programming with a one-dimensional table; the two-dimensional ones are in LCS, edit distance, knapsack and matrix chain. The coin problem shows where the greedy method fails, and the tails array uses binary search.

PYQs

5 previous year questions from your book. I re-solved each one myself (4 by running or simulating, 1 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2014 · Set 2 · NAT · 2 marksQ6 · page 131 of the book
Consider two strings A = {"qpqrr"} and B =
"pqprqrp". Let x be the length of the longest
common
subsequence
(not
necessarily
contiguous) between A and B and let y be the
number of such longest common subsequences
between A and B. Then x+10y =
.
  • (a)248000
  • (b)44000
  • (b)19000
  • (d)25000 1 otherwise Finally the length of the longest monotonically increasing sequence is Max (L0 , L1 ,....,L n-1 ). Which of the following statements is TRUE?
  • (a)The algorithm uses dynamic programming paradigm
  • (b)The algorithm has a linear complexity and uses branch and bound paradigm
  • (c)The algorithm has a non-linear polynomial complexity and uses branch and bound paradigm.
  • (d)The algorithm uses divide and conquer paradigm.
GATE 2011 · MCQ · 1 markQ7 · page 131 of the book
An algorithm to find the length of the longest
monotonically increasing sequence of numbers
in an array A[0: n - 1] is given below. Let L;
denote the length of the longest monotonically
increasing sequence starting at index i in the
array. Initialize L n-1 = 1. For all i such that 0 ≤
i ≤ n – 2
b) F2 F3 only
L = 1 + Li+1
if A[i] < A[i + 1]
i
{
c) F3 F4 only
d) F1,F2 and F4
GATE 2010 · MCQ · 2 marksQ9 · page 132 of the book
The weight of a sequence a0 , a1 , ..., an–1 of real
numbers is defined as a0 + a1 / 2 + ...+ a n–1 /2n–1 .
A subsequence of a sequence is obtained by
deleting some elements from the sequence,
keeping the order of the remaining elements the
same. Let X denote the maximum possible weight
of a subsequence of a0 , a1 ,..., an-1 . Then X is equal
to
  • (a)max(Y, a0 + Y)
  • (b)max(Y, a0 + Y/2)
  • (c)max(Y, a0 + 2Y)
  • (d)a0 + Y/2
GATE 2009 · MCQ · 2 marksQ10 · page 132 of the book
A sub-sequence of a given sequence is just the
given sequence with some elements (possibly
none or all) left out. We are given two sequences
X[m] and Y[n] of lengths m and n, respectively,
with indexes of X and Y starting from 0.
We wish to find the length of the longest
common sub-sequence (LCS) of x[m] and Y[n]
as l(m,n), where an incomplete recursive
definition for the function l(i,j) to compute the
length of the LCS of x[m] and Y[n] is given
below:

0, if either i = 0 or j = 0
l(i, j) = { expr1, if i, j > 0 and x [i⁡-⁡1] = Y [j -⁡1]⁡
expr2, if i, j > 0 and x [i - 1] ! = Y [j - 1]
Which one of the following options is correct?
  • (a)expr1 = l(i-1, j) + 1
  • (b)expr1 = l(i, j-1)
  • (c)expr2 = max (l(i-1, j), l(i,j-1))
  • (d)expr2 = max(l(i-1, j-1), l(i,j))
GATE 2024 · Set 2 · NATQ2 · page 144 of the book
Let A be an array containing integer values. The
distance of A is defined as the minimum number of
elements in A that must be replaced with another
integer so that the resulting array is sorted in non-
decreasing order. The distance of the array [2, 5, 3,
1, 4, 2, 6] is
.

Self-check

Question 1

Length of the LIS of 10,9,2,5,3,7,101,1810, 9, 2, 5, 3, 7, 101, 18?

Show answer

44, for example 2,3,7,182, 3, 7, 18 (or 2,5,7,1012, 5, 7, 101).

Question 2

Fewest coins for 77 with {1,3,4}\{1, 3, 4\} and the number of combinations?

Show answer

22 (3+43 + 4); combinations: 171^7, 14 31^4\,3, 1 3 31\,3\,3, 13 41^3\,4, 3 43\,4: 55 (the table gives 55).

Question 3

How does the time of the coin DP depend on the amount, and why is it pseudo-polynomial?

Show answer

Θ(kT)\Theta(kT), linear in the value TT, which is exponential in the number of bits of TT.

Question 4

In the tails method, what do you do with an element that is smaller than every tail?

Show answer

Replace the first tail (it becomes the smallest possible end of a subsequence of length 11); the length does not change.

Revision card

One-screen revision

  • LIS: L[i]=1+max⁡L[j]L[i] = 1 + \max L[j] (j<ij < i, aj<aia_j < a_i), Θ(n2)\Theta(n^2); tails + binary search Θ(nlog⁡n)\Theta(n \log n); answer is the max / the size.
  • Coins: f(x)=1+min⁡f(x−c)f(x) = 1 + \min f(x - c), Θ(kT)\Theta(kT); greedy fails for {1,3,4}\{1, 3, 4\}.
  • Ways: coins outer = combinations, amounts outer = sequences.
  • Strict: first tail ≥x\ge x; non-decreasing: first tail >x> x.

References

  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (3rd edition), Problem 15-4 and Exercise 15.4-6 (longest increasing subsequence); Problem 16-1 (coin changing)
  • ·Dasgupta, Papadimitriou, Vazirani, Algorithms, Section 6.1 Shortest paths in dags and longest increasing subsequences; Section 6.4 knapsack (unbounded)
  • ·Kleinberg and Tardos, Algorithm Design, Section 6.1 and 6.4 (weighted interval scheduling, subset sums)

Verification

The longest increasing subsequence was computed by the quadratic dynamic program and by the tails method and both were compared with the maximum over ALL subsets on 400 random arrays (up to 10 elements, with repeated values). The tails array was replayed with an independent bisect implementation, and the LIS of an array with distinct values was confirmed equal to the LCS of the array and its sorted copy. Coin change for the coins 1, 3, 4 was compared, for every amount up to 12, with exhaustive enumeration of all multisets of coins (fewest coins, and number of combinations); the count of ordered sequences, obtained by the other loop order, was also checked. The step-by-step tables come from the page program with prints added. Textbook chapter references are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (loop counts, recurrences, merge and quicksort counts and averages, hash probes and load-factor formulas, DP tables, MST and shortest-path runs, Floyd matrices); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never