Given an array arr[] of n elements. The task is to find the number of non-empty subsets whose product of elements is less than or equal to a given integer k.
Examples:
Input: arr[] = [2, 4, 5, 3], k = 12
Output: 8
Explanation: All possible subsets whose products are less than 12 are: (2), (4), (5), (3), (2, 4), (2, 5), (2, 3), (4, 3)Input: arr[] = [9, 8, 3], k = 2
Output: 0
Explanation: There are no subsets with products less than or equal to 2.
Table of Content
Using Recursion - O(2^n) Time and O(n) Space
We can identify a recursive pattern in this problem. There are two state variables:
- The current index i in the array arr[].
- The cumulative product currentProduct of the subsets being considered.
We consider two cases for recursion:
Exclude the current element: The current element is not included in the subset, and the product remains unchanged. This corresponds to:
- numOfSubsets(i + 1, currentProduct, k).
Include the current element: The current element is included in the subset, and the product is updated as currentProduct * arr[i]. This corresponds to:
- numOfSubsets(i + 1, currentProduct * arr[i], k)
Recurrence relation:
- numOfSubsets(i, currentProduct, k) = numOfSubsets(i+1, currentProduct, k) + numOfSubsets(i+1, currentProduct*arr[i], k)
Base Case: If i == n (all elements have been processed), return 1 if the currentProduct less than equal to k else 0.
// A C++ program for Number of subsets
// with product less than k using recursion
#include <bits/stdc++.h>
using namespace std;
// Recursive function to count subsets whose product
// is less than or equal to k
int countSubsets(int i, int currentProduct, int k, vector<int> &arr) {
// Get the size of the array
int n = arr.size();
// Base case: if we have considered all elements
// Return 1 if the current product is less than or equal to k,
// otherwise return 0
if (i == n)
return (currentProduct <= k);
// Case 1: Exclude the current element and move to the next
int exclude = countSubsets(i + 1, currentProduct, k, arr);
// Case 2: Include the current element in the subset
int include = 0;
// Only include the current element if the product remains <= k
if ((arr[i] * currentProduct) <= k)
include = countSubsets(i + 1, currentProduct * arr[i], k, arr);
// Return the total count of subsets including both cases
return (include + exclude);
}
int numOfSubsets(vector<int>& arr, int k) {
// Call the recursive function starting from index 0
// Initial product of 1
// Subtract 1 from the result to exclude the empty subset
return countSubsets(0, 1, k, arr) - 1;
}
int main() {
vector<int> arr = {1, 2, 3, 4};
int k = 10;
cout << numOfSubsets(arr, k);
return 0;
}
// A Java program for Number of subsets
// with product less than k using recursion
import java.util.*;
class GfG {
// Recursive function to count subsets whose product is
// less than or equal to k
static int countSubsets(int i, int currentProduct, int k, int[] arr) {
int n = arr.length;
// Base case: if we have considered all elements
if (i == n)
return (currentProduct <= k) ? 1 : 0;
// Case 1: Exclude the current element
int exclude = countSubsets(i + 1, currentProduct, k, arr);
// Case 2: Include the current element
int include = 0;
if ((arr[i] * currentProduct) <= k)
include = countSubsets(i + 1, currentProduct * arr[i], k, arr);
return include + exclude;
}
static int numOfSubsets(int[] arr, int k) {
// Subtract 1 to exclude the empty subset
return countSubsets(0, 1, k, arr) - 1;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
int k = 10;
System.out.println(numOfSubsets(arr, k));
}
}
# A Python program for Number of subsets
# with product less than k using recursion
def countSubsets(i, currentProduct, k, arr):
n = len(arr)
# Base case: if we have considered all elements
if i == n:
return 1 if currentProduct <= k else 0
# Case 1: Exclude the current element
exclude = countSubsets(i + 1, currentProduct, k, arr)
# Case 2: Include the current element
include = 0
if currentProduct * arr[i] <= k:
include = countSubsets(i + 1, currentProduct * arr[i], k, arr)
return include + exclude
def numOfSubsets(arr, k):
# Subtract 1 to exclude the empty subset
return countSubsets(0, 1, k, arr) - 1
if __name__ == "__main__":
arr = [1, 2, 3, 4]
k = 10
print(numOfSubsets(arr, k))
// A C# program for Number of subsets
// with product less than k using recursion
using System;
class GfG {
// Recursive function to count subsets whose
// product is less than or equal to k
static int CountSubsets(int i, int currentProduct, int k, int[] arr){
int n = arr.Length;
// Base case: if we have considered all elements
if (i == n)
return currentProduct <= k ? 1 : 0;
// Case 1: Exclude the current element
int exclude = CountSubsets(i + 1, currentProduct, k, arr);
// Case 2: Include the current element
int include = 0;
if (currentProduct * arr[i] <= k)
include = CountSubsets(i + 1, currentProduct * arr[i], k, arr);
return include + exclude;
}
static int numOfSubsets(int[] arr, int k){
// Subtract 1 to exclude the empty subset
return CountSubsets(0, 1, k, arr) - 1;
}
static void Main(string[] args){
int[] arr = { 1, 2, 3, 4 };
int k = 10;
Console.WriteLine(numOfSubsets(arr, k));
}
}
// A Javascript program for Number of subsets
// with product less than k using recursion
function countSubsets(i, currentProduct, k, arr) {
let n = arr.length;
// Base case: if we have considered all elements
if (i === n) {
return currentProduct <= k ? 1 : 0;
}
// Case 1: Exclude the current element
let exclude = countSubsets(i + 1, currentProduct, k, arr);
// Case 2: Include the current element
let include = 0;
if (currentProduct * arr[i] <= k) {
include = countSubsets(i + 1, currentProduct * arr[i], k, arr);
}
return include + exclude;
}
function numOfSubsets(arr, k) {
// Subtract 1 to exclude the empty subset
return countSubsets(0, 1, k, arr) - 1;
}
let arr = [1, 2, 3, 4];
let k = 10;
console.log(numOfSubsets(arr, k));
Output
11
Using Top - Down Dp (memoization) - O(n*k) Time and O(n*k) Space
If we notice carefully, we can observe that the above recursive solution holds the following two properties of Dynamic Programming:
1.Optimal Substructure: Maximum subsequence length for a given i, j and currentProduct , i.e. numOfSubsets(i + 1, currentProduct, k), depends on the optimal solutions of the subproblems numOfSubsets(i + 1, currentProduct , k) and numOfSubsets(i + 1, currentProduct * arr[i], k). By choosing the total of these optimal substructures, we can efficiently calculate answer.
2. Overlapping Subproblems: While applying a recursive approach in this problem, we notice that certain subproblems are computed multiple times. For example while considering arr = [2, 3, 6, 8] and k = 10, countSubsets(3, 6, 10, arr) computed multiple times from countSubsets(2, 1, 10, arr) and countSubsets(2, 6, 10, arr).
- There are two parameter that change in the recursive solution: i going from 0 to n-1, currentProduct going from 1 to k. So we create a 2D array of size (n+1)*(k+1) for memoization.
- We initialize this array as -1 to indicate nothing is computed initially.
- Now we modify our recursive solution to first check if the value is -1, then only make recursive calls. This way, we avoid re-computations of the same subproblems.
// A C++ program to count the number of subsets
// with a product less than or equal to k memoization
#include <bits/stdc++.h>
using namespace std;
// Recursive function to count subsets whose product is
// less than or equal to k with memoization
int countSubsets(int i, int currentProduct, int k,
vector<int> &arr, vector<vector<int>> &memo) {
int n = arr.size();
// Base case: if we have considered all elements
if (i == n) {
// Return 1 if the current product is less than or
// equal to k, otherwise 0
return (currentProduct <= k);
}
// Check if the current state is already computed
if (memo[i][currentProduct] != -1)
return memo[i][currentProduct];
// Case 1: Exclude the current element and move to the next
int exclude = countSubsets(i + 1, currentProduct, k, arr, memo);
// Case 2: Include the current element in the subset
int include = 0;
if ((arr[i] * currentProduct) <= k)
include = countSubsets(i + 1, currentProduct * arr[i], k, arr, memo);
// Store the result in the memo table and return the total count
return memo[i][currentProduct] = (include + exclude);
}
// Wrapper function to calculate the number of subsets
// whose product is less than or equal to k
int numOfSubsets(vector<int> arr, int k) {
int n = arr.size();
// Memoization table to store intermediate results
// Initialized with -1, indicating uncomputed states
vector<vector<int>> memo(n + 1, vector<int>(k + 1, -1));
// Call the recursive function starting from index 0
// Initial product of 1
// Subtract 1 to exclude the empty subset
return countSubsets(0, 1, k, arr, memo) - 1;
}
int main() {
vector<int> arr = {1, 2, 3, 4};
int k = 10;
cout << numOfSubsets(arr, k);
return 0;
}
// A Java program to count the number of subsets
// with a product less than or equal to k memoization
import java.util.Arrays;
class GfG {
static int countSubsets(int i, int currentProduct,
int k, int[] arr, int[][] memo) {
int n = arr.length;
// Base case: if all elements are considered
if (i == n) {
return currentProduct <= k ? 1 : 0;
}
// Check if result is already computed
if (memo[i][currentProduct] != -1) {
return memo[i][currentProduct];
}
// Exclude current element
int exclude = countSubsets(i + 1, currentProduct, k, arr, memo);
// Include current element if valid
int include = 0;
if (currentProduct * arr[i] <= k) {
include = countSubsets(i + 1, currentProduct * arr[i], k, arr, memo);
}
// Store the result and return
memo[i][currentProduct] = exclude + include;
return memo[i][currentProduct];
}
static int numOfSubsets(int[] arr, int k) {
int n = arr.length;
int[][] memo = new int[n + 1][k + 1];
// Initialize memoization table
for (int[] row : memo) {
Arrays.fill(row, -1);
}
// Compute result, subtracting 1 to
// exclude the empty subset
return countSubsets(0, 1, k, arr, memo) - 1;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
int k = 10;
System.out.println(numOfSubsets(arr, k));
}
}
# A Python program to count the number of subsets
# with a product less than or equal to k memoization
def countSubsets(i, currentProduct, k, arr, memo):
n = len(arr)
# Base case: if all elements are considered
if i == n:
return 1 if currentProduct <= k else 0
# Check if result is already computed
if memo[i][currentProduct] != -1:
return memo[i][currentProduct]
# Exclude current element
exclude = countSubsets(i + 1, currentProduct, k, arr, memo)
# Include current element if valid
include = 0
if currentProduct * arr[i] <= k:
include = countSubsets(i + 1, currentProduct * arr[i], k, arr, memo)
# Store the result and return
memo[i][currentProduct] = exclude + include
return memo[i][currentProduct]
def numOfSubsets(arr, k):
n = len(arr)
# Initialize memoization table
memo = [[-1] * (k + 1) for _ in range(n + 1)]
# Compute result, subtracting 1 to exclude
# the empty subset
return countSubsets(0, 1, k, arr, memo) - 1
arr = [1, 2, 3, 4]
k = 10
print(numOfSubsets(arr, k))
// A C# program to count the number of subsets
// with a product less than or equal to k memoization
using System;
class GfG {
static int CountSubsets(int i, int currentProduct, int k,
int[] arr, int[,] memo) {
int n = arr.Length;
// Base case: if all elements are considered
if (i == n) {
return currentProduct <= k ? 1 : 0;
}
// Check if result is already computed
if (memo[i, currentProduct] != -1) {
return memo[i, currentProduct];
}
// Exclude current element
int exclude = CountSubsets(i + 1, currentProduct, k, arr, memo);
// Include current element if valid
int include = 0;
if (currentProduct * arr[i] <= k) {
include = CountSubsets(i + 1, currentProduct * arr[i], k, arr, memo);
}
// Store the result and return
memo[i, currentProduct] = exclude + include;
return memo[i, currentProduct];
}
static int numOfSubsets(int[] arr, int k) {
int n = arr.Length;
// Initialize memoization table
int[,] memo = new int[n + 1, k + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= k; j++) {
memo[i, j] = -1;
}
}
// Compute result, subtracting 1 to exclude the empty subset
return CountSubsets(0, 1, k, arr, memo) - 1;
}
static void Main(string[] args) {
int[] arr = { 1, 2, 3, 4 };
int k = 10;
Console.WriteLine(numOfSubsets(arr, k));
}
}
// A Javascript program to count the number of subsets
// with a product less than or equal to k memoization
function countSubsets(i, currentProduct, k, arr, memo) {
const n = arr.length;
// Base case: if all elements are considered
if (i === n) {
return currentProduct <= k ? 1 : 0;
}
// Check if result is already computed
if (memo[i][currentProduct] !== -1) {
return memo[i][currentProduct];
}
// Exclude current element
let exclude = countSubsets(i + 1, currentProduct, k, arr, memo);
// Include current element if valid
let include = 0;
if (currentProduct * arr[i] <= k) {
include = countSubsets(i + 1, currentProduct * arr[i], k, arr, memo);
}
// Store the result and return
memo[i][currentProduct] = exclude + include;
return memo[i][currentProduct];
}
function numOfSubsets(arr, k) {
const n = arr.length;
const memo = Array.from({ length: n + 1 }, () => Array(k + 1).fill(-1));
// Compute result, subtracting 1 to exclude the empty subset
return countSubsets(0, 1, k, arr, memo) - 1;
}
const arr = [1, 2, 3, 4];
const k = 10;
console.log(numOfSubsets(arr, k));
Output
11
Using Dynamic Programming (Tabulation) - O(n*k) Time and O(n*k) Space
We create a 2D array dp[n+1][k+1], such that dp[i][j] equals to the number of subsets having product value less than equal to j from subsets of arr[0...i-1].
We fill the dp array as following:
- We initialize all values of dp[i][j] as 1 because we take empty subsequence as our base case.
Iterate over all the values of arr[i] from left to right and for each arr[i], iterate over all the possible values of k i.e. from 1 to k (both inclusive) and fill the dp array as following:
dp[i][j] = dp[i-1][j]
if j>=arr[i-1]
dp[i][j] +=dp[i-1][j/arr[i-1]]
This can be explained as there are only two cases either we take element arr[i] or we don't. We take a element only when it's value is less than or equal to j. Then we look for subsets ending at i-1 such that their product with arr[i] should be atmost k. Product of those subsets will be less than or equal to a j/arr[i-1] so we get this value from dp[i-1][j/arr[i-1]] The number of subsets from set arr[0..n] having product value as less than or equal to k will be dp[n][k].
// A C++ program to count the number of subsets
// with a product less than or equal to k using tabulation
#include <bits/stdc++.h>
using namespace std;
int numOfSubsets(vector<int> &arr, int k) {
int n = arr.size();
// Initialize all values of dp[i][j] to 1 to
// include the empty subset.
vector<vector<int>> dp(n + 1, vector<int>(k + 1, 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
// Case 1: Exclude the current element.
dp[i][j] = dp[i - 1][j];
// Case 2: Include the current element.
// if arr[i-1] is less than equal to j we include it.
if (j >= arr[i - 1]) {
dp[i][j] += dp[i - 1][j / arr[i - 1]];
}
}
}
// Return the total count of subsets with product ≤ k, subtracting 1
// to exclude the empty subset from the result.
return dp[n][k] - 1;
}
int main() {
vector<int> arr = {1, 2, 3, 4};
int k = 10;
cout << numOfSubsets(arr, k);
return 0;
}
// A Java program to count the number of subsets
// with a product less than or equal to k using tabulation
import java.util.*;
class GfG {
static int numOfSubsets(int[] arr, int k) {
int n = arr.length;
// Initialize the DP table with 1s to
// include the empty subset.
int[][] dp = new int[n + 1][k + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(dp[i], 1);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
// Case 1: Exclude the current element
dp[i][j] = dp[i - 1][j];
// Case 2: Include the current element
if (j >= arr[i - 1]) {
dp[i][j] += dp[i - 1][j / arr[i - 1]];
}
}
}
// Return the total count of subsets
// with product ≤ k, subtracting 1
return dp[n][k] - 1;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
int k = 10;
System.out.println(numOfSubsets(arr, k));
}
}
# A Python program to count the number of subsets
# with a product less than or equal to k using tabulation
def numOfSubsets(arr, k):
n = len(arr)
# Initialize the DP table with 1s to
# include the empty subset.
dp = [[1] * (k + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, k + 1):
# Case 1: Exclude the current element
dp[i][j] = dp[i - 1][j]
# Case 2: Include the current element
if j >= arr[i - 1]:
dp[i][j] += dp[i - 1][j // arr[i - 1]]
# Return the total count of subsets
# with product ≤ k, subtracting 1
return dp[n][k] - 1
arr = [1, 2, 3, 4]
k = 10
print(numOfSubsets(arr, k))
// A C# program to count the number of subsets
// with a product less than or equal to k using tabulation
using System;
class GfG {
static int numOfSubsets(int[] arr, int k) {
int n = arr.Length;
// Initialize the DP table with 1s to
// include the empty subset.
int[,] dp = new int[n + 1, k + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= k; j++) {
dp[i, j] = 1;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
// Case 1: Exclude the current element
dp[i, j] = dp[i - 1, j];
// Case 2: Include the current element
if (j >= arr[i - 1]) {
dp[i, j] += dp[i - 1, j / arr[i - 1]];
}
}
}
// Return the total count of subsets with
// product ≤ k, subtracting 1
return dp[n, k] - 1;
}
static void Main(string[] args) {
int[] arr = { 1, 2, 3, 4 };
int k = 10;
Console.WriteLine(numOfSubsets(arr, k));
}
}
// A Javascript program to count the number of subsets
// with a product less than or equal to k using tabulation
function numOfSubsets(arr, k) {
let n = arr.length;
// Initialize the DP table with 1s to include the empty subset.
let dp = Array.from({ length: n + 1 }, () => Array(k + 1).fill(1));
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= k; j++) {
// Case 1: Exclude the current element
dp[i][j] = dp[i - 1][j];
// Case 2: Include the current element
if (j >= arr[i - 1]) {
dp[i][j] += dp[i - 1][Math.floor(j / arr[i - 1])];
}
}
}
// Return the total count of subsets
// with product ≤ k, subtracting 1
return dp[n][k] - 1;
}
let arr = [1, 2, 3, 4];
let k = 10;
console.log(numOfSubsets(arr, k));
Output
11
Using Space Optimised DP - O(n*k) Time and O(k) Space
In previous approach the current value dp[i][j] is only depend upon the current and previous row values of DP. So to optimize the space complexity we use a two 1D array of size k+1 namely prevState and curState to store the computations. The final answer is equal to curState[k]-1;
// A C++ program to count the number of subsets
// with a product less than or equal to k
// using space-optimized tabulation.
#include <bits/stdc++.h>
using namespace std;
// Function to count subsets with product less than or equal to k
int numOfSubsets(vector<int> &arr, int k) {
int n = arr.size();
// Initialize two arrays to store the previous and current state
// Both initialized with 1 to include the empty subset
vector<int> prevState(k + 1, 1), curState(k + 1, 1);
for (int i = 1; i <= n; i++) {
// Copy the previous state to the current state
curState = prevState;
for (int j = 1; j <= k; j++) {
// If the current element can be included in subsets
// Add the count of subsets ending at the previous element
// where the product multiplied by the current element is ≤ k
if (j >= arr[i - 1]) {
curState[j] += prevState[j / arr[i - 1]];
}
}
// Update the previous state for the next iteration
prevState = curState;
}
// Return the total count of subsets with product ≤ k, subtracting 1
// to exclude the empty subset
return curState[k] - 1;
}
int main() {
vector<int> arr = {1, 2, 3, 4};
int k = 10;
cout << numOfSubsets(arr, k);
return 0;
}
// A Java program to count the number of subsets
// with a product less than or equal to k
// using space-optimized tabulation.
import java.util.Arrays;
class GfG {
static int numOfSubsets(int[] arr, int k) {
int n = arr.length;
// Initialize two arrays for previous and current states
int[] prevState = new int[k + 1];
int[] curState = new int[k + 1];
Arrays.fill(prevState, 1);
Arrays.fill(curState, 1);
for (int i = 1; i <= n; i++) {
// Copy previous state to current state
curState = Arrays.copyOf(prevState, k + 1);
for (int j = 1; j <= k; j++) {
// Include current element if valid
if (j >= arr[i - 1]) {
curState[j] += prevState[j / arr[i - 1]];
}
}
// Update previous state
prevState = curState;
}
// Subtract 1 to exclude the empty subset
return curState[k] - 1;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
int k = 10;
System.out.println(numOfSubsets(arr, k));
}
}
# A Python program to count the number of subsets
# with a product less than or equal to k
# using space-optimized tabulation.
def numOfSubsets(arr, k):
n = len(arr)
# Initialize previous and current state arrays
prevState = [1] * (k + 1)
curState = [1] * (k + 1)
for i in range(1, n + 1):
# Copy previous state to current state
curState = prevState[:]
for j in range(1, k + 1):
# Include current element if valid
if j >= arr[i - 1]:
curState[j] += prevState[j // arr[i - 1]]
# Update previous state
prevState = curState
# Subtract 1 to exclude the empty subset
return curState[k] - 1
arr = [1, 2, 3, 4]
k = 10
print(numOfSubsets(arr, k))
// A C# program to count the number of subsets
// with a product less than or equal to k
// using space-optimized tabulation.
using System;
class GfG {
static int numOfSubsets(int[] arr, int k) {
int n = arr.Length;
// Initialize previous and current
// state arrays
int[] prevState = new int[k + 1];
int[] curState = new int[k + 1];
Array.Fill(prevState, 1);
Array.Fill(curState, 0);
for (int i = 0; i < n; i++) {
// Copy previous state to current
// state for this iteration
Array.Copy(prevState, curState, k + 1);
for (int j = 1; j <= k; j++) {
// Include the current element if
// it can be part of a subset
if (j >= arr[i]) {
curState[j] += prevState[j / arr[i]];
}
}
// Update previous state for the
// next iteration
Array.Copy(curState, prevState, k + 1);
}
// Subtract 1 to exclude the empty
// subset from the result
return curState[k] - 1;
}
static void Main(string[] args) {
int[] arr = { 1, 2, 3, 4 };
int k = 10;
Console.WriteLine(numOfSubsets(arr, k));
}
}
// A Javascript program to count the number of subsets
// with a product less than or equal to k
// using space-optimized tabulation.
function numOfSubsets(arr, k) {
let n = arr.length;
// Initialize previous and current state arrays
let prevState = Array(k + 1).fill(1);
let curState = Array(k + 1).fill(1);
for (let i = 1; i <= n; i++) {
// Copy previous state to current state
curState = [...prevState];
for (let j = 1; j <= k; j++) {
// Include current element if valid
if (j >= arr[i - 1]) {
curState[j] += prevState[Math.floor(j / arr[i - 1])];
}
}
// Update previous state
prevState = curState;
}
// Subtract 1 to exclude the empty subset
return curState[k] - 1;
}
let arr = [1, 2, 3, 4];
let k = 10;
console.log(numOfSubsets(arr, k));
Output
11
Related article: