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
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.
- Start with an empty group and product 1.
- For each later number, multiply it in.
- If the product is below
k, count it and keep growing from there. - If not, do not grow it. All numbers are at least 1, so the product can only stay or grow.
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.
- Start with all jars at 0.
- For each number
x, make a copynextof the jars (every group can skipx). - For every product
pwithcnt[p] > 0andp * x < k, addcnt[p]tonext[p * x](those groups usex). - If
x < k, add 1 tonext[x](the group with onlyx). Thencnt = next. - The answer is the sum of all jars.
Count groups by their exact product, one number at a time
01 / 06
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
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 updatecntin place while reading it, a group can use the samextwice. (Updating in place while walkingpfrom high to low also works.) p * xcan overflow anintwhenkis large. Multiply aslong.- 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 addsright - left + 1. That isO(n). - What if
kis huge, like10^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 about2 * sqrt(b)different values ofb / somethingexist. - Sum less than
kinstead 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.