Skip to content
dsa450for five year olds

Dynamic Programming · 20 of 60

Count all subsequences having product less than K

Count the groups of numbers you can pick from a list, keeping their order, whose product stays below k.

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

The problem, simply

You get a list of positive whole numbers arr and a number k. Pick any non-empty group of them (each position at most once). Multiply the group together. Count how many groups have a product strictly less than k.

Two groups are different if they use different positions, even if the numbers look the same.

A story to remember it

Picture it

treegroups of [1, 2, 3, 4] with product under 10
x1x2x3x4x4x3x4x4x2x3x4x4x3x4x4start1262483124262483124
arrayarr
01231234

Every group, built by adding numbers from left to right. The number on a branch is the one you multiply in. Circles show the product so far. 11 groups stay below 10. Crossed out circles reached 10 or more, and are never grown further.

  • done
  • rejected

Examples

A normal case

In
arr = [1, 2, 3, 4], k = 10
Out
11

The groups 3 x 4, 1 x 3 x 4, 2 x 3 x 4 and 1 x 2 x 3 x 4 reach 10 or more. The other 11 of the 15 groups stay below 10.

A bigger k

In
arr = [4, 8, 7, 2], k = 50
Out
9

The four singles, plus 4 x 8, 4 x 7, 4 x 2, 8 x 2 and 7 x 2. Every group of three is 56 or more.

Exactly k does not count

In
arr = [2, 5], k = 10
Out
2

2 and 5 count. 2 x 5 is exactly 10, which is not less than 10.

Every number too big

In
arr = [5, 6], k = 5
Out
0

Even the single numbers reach 5. Nothing counts.

Lots of ones

In
arr = [1, 1, 1], k = 2
Out
7

Every group has product 1, and there are 2^3 - 1 = 7 groups.

Way 1: the simple way

Build groups from left to right, as in the picture. From a group, try adding each later number. Count a group the moment it is made if its product is still below k.

  1. Start with an empty group and product 1.
  2. For each later number, multiply it in.
  3. If the product is below k, count it and keep growing from there.
  4. If not, do not grow it. All numbers are at least 1, so the product can only stay or grow.
java
class Solution {
    long count = 0;

    void grow(int[] arr, int start, long product, int k) {
        for (int i = start; i < arr.length; i++) {
            long p = product * arr[i];            // add arr[i] to the group
            if (p < k) {
                count++;                          // this group counts
                grow(arr, i + 1, p, k);           // try adding later numbers
            }                                     // else: too big, never grow it
        }
    }

    public long countSubsequences(int[] arr, int k) {
        count = 0;
        grow(arr, 0, 1, k);
        return count;
    }
}

How long it takes: with many small numbers (like lots of 1s), almost every group is valid, so it can visit all 2^n groups, O(2^n). Extra room: the chain of calls, O(n).

Way 2: the clever way

We never need to know which group it is, only its product. And only products from 1 to k - 1 matter. So keep cnt[p]: how many groups so far have product exactly p.

  1. Start with all jars at 0.
  2. For each number x, make a copy next of the jars (every group can skip x).
  3. For every product p with cnt[p] > 0 and p * x < k, add cnt[p] to next[p * x] (those groups use x).
  4. If x < k, add 1 to next[x] (the group with only x). Then cnt = next.
  5. The answer is the sum of all jars.

Count groups by their exact product, one number at a time

01 / 06

gridgroups with product exactly p
123456789startwith 1with 2with 3with 4000000000
arrayarr
01231234

Column p will count the groups whose product is exactly p, for p from 1 to 9. Products of 10 or more do not matter, so they get no column. At the start there are no groups.

  • looking now
  • checking
  • answer
  • just added
java
class Solution {
    public long countSubsequences(int[] arr, int k) {
        if (k <= 1) return 0;                      // no product of positives is below 1
        long[] cnt = new long[k];                  // cnt[p]: groups with product exactly p
        for (int x : arr) {
            long[] next = cnt.clone();             // every old group may skip x
            for (int p = 1; p < k; p++) {
                if (cnt[p] > 0 && (long) p * x < k) {
                    next[p * x] += cnt[p];         // old groups that use x
                }
            }
            if (x < k) next[x]++;                  // the group with only x
            cnt = next;
        }
        long total = 0;
        for (int p = 1; p < k; p++) total += cnt[p];
        return total;
    }
}

How long it takes: n numbers, and for each we walk k jars, O(n * k). Extra room: two rows of k jars, O(k). This is fast when k is small (up to about a million). For a huge k, Way 1 with its early stops can be better.

Watch out for

  • Use the copy next. If you update cnt in place while reading it, a group can use the same x twice. (Updating in place while walking p from high to low also works.)
  • p * x can overflow an int when k is large. Multiply as long.
  • If the list may contain 0, every group with a 0 has product 0, which is below any positive k. Count those separately.

What interviewers ask next

  • Contiguous pieces only (subarrays)? Use a sliding window: grow the right end, and while the product is at least k, divide out the left end. Each right end adds right - left + 1. That is O(n).
  • What if k is huge, like 10^9? Keep jars only for products that appear, in a hash map. Or count by "budget": ways(b, j) = ways(b, j-1) + 1 + ways(b / arr[j], j-1), where only about 2 * sqrt(b) different values of b / something exist.
  • Sum less than k instead of product? Same idea with jars indexed by sum: that is the subset sum counting problem.
  • Why must numbers be positive? With fractions or zeros, a big product can shrink later, so "never grow a too big group" stops being safe.

Remember this

Grown-up words: a group kept in order is a subsequence, the jars are a DP array indexed by product, the copy is a rolling array, and not growing a too big group is pruning.