Skip to content
dsa450for five year olds

Dynamic Programming · 4 of 60

Permutation Coefficient Problem

Count the ways to hand out k different prizes to n kids, one prize per kid, where who gets which prize matters.

Simple way
time O(2^n)
room O(n)
Clever way
time O(k)
room O(1)

The problem, simply

You get two numbers, n and k. Count the ways to pick k things out of n and put them in order. This number is called P(n, k).

The formula is P(n, k) = n! / (n - k)!, which is the same as n x (n - 1) x ... x (n - k + 1), a product of k numbers. If k > n the answer is 0. Many versions ask for the answer modulo 1,000,000,007.

A story to remember it

Picture it

treegold first, then silver
GSSGSSGSSgoABCBACCAB

Three kids A, B and C and two prizes. Gold can go to 3 kids, then silver to 2 kids. That is 3 x 2 = 6 ways, the 6 bottom circles.

Examples

Two prizes, ten kids

In
n = 10, k = 2
Out
90

10 choices for gold, then 9 for silver: 10 x 9.

The race

In
n = 5, k = 3
Out
60

5 x 4 x 3.

Everyone gets a prize

In
n = 4, k = 4
Out
24

When k = n this is n!, every way to line up all the kids.

No prizes

In
n = 6, k = 0
Out
1

There is exactly one way to hand out nothing.

More prizes than kids

In
n = 3, k = 5
Out
0

You cannot give 5 different kids a prize when there are only 3.

Way 1: the simple way

Think about the last kid, kid number n.

  • Either that kid gets no prize. Then all k prizes go to the other n - 1 kids: P(n - 1, k) ways.
  • Or that kid gets one of the k prizes (k choices), and the other k - 1 prizes go to the other n - 1 kids: k x P(n - 1, k - 1) ways.

So P(n, k) = P(n - 1, k) + k x P(n - 1, k - 1), with P(n, 0) = 1 and P(n, k) = 0 when k > n. Write it as a recursive function.

treeP(n, k) calls
4,23,22,21,21,10,10,02,11,10,10,01,03,12,11,10,10,01,02,0001again011againagain0111

The calls for P(4, 2). Left child: the last kid gets no prize. Right child: the last kid gets a prize. Small numbers under the leaves are their answers. (2,1) and (1,1) are worked out more than once.

  • checking
java
class Solution {
    static final long MOD = 1_000_000_007L;

    public long nPr(int n, int k) {
        if (k == 0) return 1;           // no prizes: one way
        if (k > n) return 0;            // more prizes than kids
        long lastSkipped = nPr(n - 1, k);           // last kid gets nothing
        long lastWins = k * nPr(n - 1, k - 1) % MOD; // last kid gets one of k prizes
        return (lastSkipped + lastWins) % MOD;
    }
}

How long it takes: each call makes two calls, so up to O(2^n) calls. Extra room: the call chain, O(n).

Way 2: the clever way

Hand out the prizes one at a time and keep a running answer. P(n, j) = P(n, j - 1) x (n - j + 1): the jth prize can go to any kid who does not have a prize yet, and there are n - j + 1 of them.

  1. Start with ways = 1 (zero prizes).
  2. For j = 1 to k: ways = ways x (n - j + 1).
  3. Return ways.

Each step fills the next box of a small row of answers, and only the last box is needed, so one number is enough.

P(5, 3) one prize at a time

01 / 05

arrayP(5, j) for j = 0..3
01231

P(5, 0) = 1. Handing out zero prizes can be done in exactly one way: do nothing.

kids left
5
  • looking now
  • checking
  • answer
  • just added
java
class Solution {
    static final long MOD = 1_000_000_007L;

    public long nPr(int n, int k) {
        if (k > n) return 0;
        long ways = 1;                          // zero prizes handed out
        for (int j = 1; j <= k; j++) {
            ways = ways * (n - j + 1) % MOD;    // kids still without a prize
        }
        return ways;
    }
}

How long it takes: k multiplications, O(k). Extra room: one number, O(1).

Way 3: the full table

Some interviewers ask for the textbook table version, which turns Way 1 into a table. Row i holds P(i, j) for every j, and each cell uses the cell above it and the cell above and to the left.

gridP[i][j]
j=0j=1j=2j=3i=0i=1i=2i=3i=4i=51000110012201366141224152060

P(5, 3) = P(4, 3) + 3 x P(4, 2) = 24 + 3 x 12 = 60. Cells where j is bigger than i are 0.

  • checking
  • answer
java
class Solution {
    static final long MOD = 1_000_000_007L;

    public long nPr(int n, int k) {
        if (k > n) return 0;
        long[][] P = new long[n + 1][k + 1];
        for (int i = 0; i <= n; i++) {
            P[i][0] = 1;                                   // no prizes
            for (int j = 1; j <= Math.min(i, k); j++) {
                P[i][j] = (P[i - 1][j] + j * P[i - 1][j - 1]) % MOD;
            }
        }
        return P[n][k];
    }
}

This takes O(n * k) time and O(n * k) room (or O(k) with one row filled from right to left). It is slower than Way 2, but it gives every P(i, j) at once, which helps when you have many questions.

Watch out for

  • k > n must return 0, and k = 0 must return 1.
  • Take the modulo after each multiply, not only at the end.
  • Do not mix it up with C(n, k), which ignores order. P(n, k) = C(n, k) x k!.

What interviewers ask next

  • How is it related to combinations? Choosing the group ignores order, C(n, k), then arranging the group gives k! orders. So P = C x k!.
  • Answer many questions P(n, k) mod p fast. Precompute factorials and inverse factorials up to the largest n once. Then each answer is fact[n] x invFact[n - k], which is O(1).
  • What if p is small, smaller than n? Then n! is 0 modulo p and the inverse trick breaks. The plain product from Way 2 still works, because it never divides.

Remember this

Grown-up words: handing out prizes in order is a permutation, P(n, k) is the permutation coefficient, the recursive rule is a Pascal style recurrence, the running answer is 1D dynamic programming, and the fast many question trick uses modular inverses.