Problem Statement
Given an array of positive integers arr[] and an integer k, determine whether there exists at least one non-empty subset whose sum is divisible by k.
Example 1
Input:
arr = [3, 1, 7, 5]
k = 6
Output:
true
Explanation
Subset {7, 5} has sum:
7 + 5 = 12
12 % 6 = 0
Hence, the answer is true.
Example 2
Input:
arr = [1, 2, 6]
k = 5
Output:
false
No subset produces a sum divisible by 5.
Why Not Brute Force?
For an array of size n, every element can either be included or excluded.
Therefore, the total number of subsets is:
2^n
For each subset, we calculate the sum and check whether it is divisible by k.
Complexity
Time: O(2^n × n)
Space: O(1)
This approach is impractical for large values of n.
We need something better.
Key Observation
We are not interested in the exact subset sum.
We only care about:
sum % k
For example, when:
k = 6
Sum = 8
8 % 6 = 2
Sum = 20
20 % 6 = 2
Although the sums are different, their remainder is the same.
This means we only need to remember the k possible remainders:
Instead of storing large sums, we store only their remainder.
Dynamic Programming State
Create an array:
boolean[] dp = new boolean[k];
Meaning
dp[r] = true
There exists a non-empty subset whose sum gives remainder r.
Example
Suppose:
k = 5
Index : 0 1 2 3 4
Value : F T F T F
This means:
Initial State
Initially, no subset exists.
Index : 0 1 2 3 4
Value : F F F F F
Processing Every Number
Suppose the current number is:
num
There are two choices.
Choice 1: Start a New Subset
Take only the current element.
Example:
num = 13
k = 5
13 % 5 = 3
So,
dp[3] = true
because the subset:
{13}
exists.
Choice 2: Extend Existing Subsets
Suppose we already have:
dp[2] = true
Meaning:
Some subset has remainder 2.
Now add the current number.
Suppose:
num = 7
7 % 5 = 2
The new remainder becomes:
(2 + 7) % 5
= 9 % 5
= 4
Therefore:
dp[4] = true
General Formula
newRemainder = (oldRemainder + num) % k
Why Do We Clone the DP Array?
Suppose:
dp =
F T F F F
Current number:
4
If we directly update the same array,
dp[0] becomes true.
Later in the same iteration, that new value might again be used, meaning we accidentally use the same element multiple times.
To avoid this, we first copy the array:
boolean[] next = dp.clone();
We update next.
After finishing all updates:
dp = next;
Now every element is used exactly once.
Dry Run
Input
arr = [3, 1, 7, 5]
k = 6
Initially
Remainder: 0 1 2 3 4 5
DP: F F F F F F
Process 3
Start a new subset:
3 % 6 = 3
DP becomes:
Remainder: 0 1 2 3 4 5
DP: F F F T F F
Possible remainders:
{3}
Process 1
Start a new subset:
1 % 6 = 1
Current remainder:
{1}
Extend the previous subset:
3 + 1 = 4
Possible remainders become:
{1, 3, 4}
DP:
Remainder: 0 1 2 3 4 5
DP: F T F T T F
Process 7
Start a new subset:
7 % 6 = 1
Already exists.
Extend existing subsets:
1 + 7 = 8
8 % 6 = 2
3 + 7 = 10
10 % 6 = 4
4 + 7 = 11
11 % 6 = 5
Possible remainders:
{1, 2, 3, 4, 5}
Process 5
Start:
5 % 6 = 5
Extend:
1 + 5 = 6
6 % 6 = 0
We obtained remainder:
0
This means a subset sum is divisible by 6.
Answer:
true
Algorithm
For every element:
Clone the current DP array.
Mark the remainder of the current element.
Extend all existing subsets.
Replace the old DP array with the updated one.
If dp[0] == true, return true.
If every element is processed and remainder 0 never appears, return false.
Java Implementation
class Solution {
public boolean divisibleByK(int[] arr, int k) {
boolean[] dp = new boolean[k];
for (int num : arr) {
boolean[] next = dp.clone();
// Start a new subset
next[num % k] = true;
// Extend existing subsets
for (int r = 0; r < k; r++) {
if (dp[r]) {
next[(r + num) % k] = true;
}
}
dp = next;
// Found a subset divisible by k
if (dp[0]) {
return true;
}
}
return false;
}
}
Complexity Analysis
Time Complexity
For every element, we iterate over all k possible remainders.
O(n × k)
Space Complexity
We maintain two arrays of size k.
O(k)
Key Takeaways
We don't need the actual subset sums—only their remainders modulo k.
The DP array dp[r] records whether a non-empty subset with remainder r is achievable.
For each element, we either start a new subset or extend every previously reachable remainder.
Cloning the DP array ensures each element is used at most once, which is essential for subset problems.
This optimization reduces the solution from O(2ⁿ) brute force to O(n × k) time with O(k) space.
Summary
Instead of exploring every possible subset, this dynamic programming approach tracks only the possible remainders obtained after dividing subset sums by k. Since there are only k distinct remainders, the algorithm efficiently builds all reachable states by either starting a new subset or extending existing ones. This reduces the time complexity from exponential O(2ⁿ) to O(n × k) while using only O(k) additional space.