Leetcode 368. How To Find The Largest Divisible Subset of a Given Integer Set
Find the largest subset of distinct positive integers where each element is a multiple of another, represented using index mapping for optimal performance.
Problem statement
You have a collection of positive integers called nums
, where each integer is distinct. Your task is to find the largest subset answer
from this collection, such that for every pair of elements (answer[i], answer[j])
within this subset:
- Either
answer[i]
is a multiple ofanswer[j]
(i.e.,answer[i] % answer[j] == 0
), or answer[j]
is a multiple ofanswer[i]
(i.e.,answer[j] % answer[i] == 0
).
If there are multiple possible solutions, you can return any of them.
Example 1
Input: nums = [1,2,3]
Output: [1,2]
Explanation: [1,3] is also accepted.
Example 2
Input: nums = [1,2,4,8]
Output: [1,2,4,8]
Constraints
1 <= nums.length <= 1000
.1 <= nums[i] <= 2 * 10^9
.- All the integers in
nums
are unique.
Solution 1: Bruteforce with Dynamic programming
Note that the condition a % b == 0
is called a
is divisible by b
. In mathematics, it can also be called b
divides a
and be written as b | a
.
The symmetry of the divisibility criteria means it does not count the ordering of the answer
. You could sort the vector nums
before trying to find the longest subset answer = [answer[0], answer[1], ..., answer[m]]
where answer[i] | answer[j]
with all 0 <= i <= j <= m
.
Now assuming the nums
were sorted. For each i
, you need to find the largest subset maxSubset[i]
starting from nums[i]
. And the final answer is the largest one among them.
Example 3
Input: nums = [2, 4, 3, 9, 8].
Sorted nums = [2, 3, 4, 8, 9].
maxSubset[0] = [2, 4, 8].
maxSubset[1] = [3, 9].
maxSubset[2] = [4, 8].
maxSubset[3] = [8].
maxSubset[4] = [9].
Output: [2, 4, 8].
Note that for a sorted nums
, if nums[i] | nums[j]
for some i < j
, then maxSubset[j]
is a subset of maxSubset[i]
.
For example, maxSubset[2]
is a subset of maxSubset[0]
in Example 3 because nums[0] = 2 | 4 = nums[2]
.
That might lead to some unnecessary recomputing. To avoid it, you could use dynamic programming to store the maxSubset[j]
you have already computed.
Code
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
//! @return the max divisible subset starting from nums[i]
//! and store it to _map[i]
//! @param nums a sorted array of unique positive integers
vector<int> largestDivisibleSubsetOf(vector<int>& nums,
int i, unordered_map<int, vector<int> >& _map) {
if (_map.find(i) != _map.end()) {
// already computed!
return _map[i];
}
vector<int> maxSubset{nums[i]}; // start with nums[i]
if (i == nums.size() - 1) {
// largest value in nums
_map.insert({i, maxSubset});
return maxSubset;
}
for (int j = i + 1; j < nums.size(); j++) {
if (nums[j] % nums[i] == 0) {
// compute the max divisble subset starting from nums[j]
auto subset = largestDivisibleSubsetOf(nums, j, _map);
// add nums[i] to subset as it might become maxSubset
subset.push_back(nums[i]);
if (maxSubset.size() < subset.size()) {
// update maxSubset
maxSubset = subset;
}
}
}
// store what have been calculated in _map
_map.insert({i, maxSubset});
return maxSubset;
}
vector<int> largestDivisibleSubset(vector<int>& nums) {
if (nums.size() <= 1) {
return nums;
}
unordered_map<int, vector<int> > _map;
sort(nums.begin(), nums.end());
vector<int> answer;
for (int i = 0; i < nums.size(); i++) {
auto maxSubset = largestDivisibleSubsetOf(nums, i, _map);
if (answer.size() < maxSubset.size()) {
// update the maximal subset
answer = maxSubset;
}
}
return answer;
}
void printSolution(const vector<int>& result) {
cout << "[";
for (auto& v : result) {
cout << v << ",";
}
cout << "]" << endl;
}
int main() {
vector<int> nums{2,1,3};
auto answer = largestDivisibleSubset(nums);
printSolution(answer);
nums = {1,2,4,8};
answer = largestDivisibleSubset(nums);
printSolution(answer);
}
Output:
[2,1,]
[8,4,2,1,]
This solution uses dynamic programming with memoization to find the largest divisible subset of a given set of numbers.
The largestDivisibleSubsetOf
function recursively computes the largest divisible subset starting from a particular index i
in the sorted array nums
. It memoizes the computed subsets in an unordered map _map
to avoid redundant computations. By iteratively calling largestDivisibleSubsetOf
for each index i
in the sorted array and updating the answer
with the largest subset found so far, the largestDivisibleSubset
function computes the largest divisible subset of the input array nums
.
This approach optimizes the computation by avoiding repeated calculations and leveraging dynamic programming techniques to efficiently explore the solution space.
Complexity
- Runtime:
O(n^2)
, wheren
is the number of elements in thenums
vector. - Extra space:
O(n^2)
.
Solution 2: Store only the representative of the maxSubset
In the brute-force solution above, you used a big map
to log all maxSubset[i]
though you need only the largest one at the end.
One way to save memory (and eventually improve performance) is just storing the representative of the chain relationship between the values nums[i]
of the maxSubset
through their indices mapping.
That means if maxSubset[i] = [nums[i0] | nums[i1] | ... | nums[iN1]] | nums[iN]]
, you could log pre[iN] = iN1
, ..., prev[i1] = i0
.
Then all you need to find is only the last index iN
of the largest maxSubset
.
Example 3
Input: nums = [2, 4, 3, 9, 8].
sorted nums = [2, 3, 4, 8, 9].
pre[0] = -1 (there is no nums[i] | nums[0]).
pre[1] = -1 (there is no nums[i] | nums[1]).
pre[2] = 0 (nums[0] is the only divisor of nums[2]).
pre[3] = 2 (for the largest subset though nums[0] and nums[2] are both divisors of nums[3]).
pre[4] = 1 (nums[1] is the only divisor of nums[4]).
iN = 3 ([2 | 4 | 8] is the largest maxSubset).
Output: [8, 4, 2].
Code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> largestDivisibleSubset(vector<int>& nums) {
if (nums.size() <= 1) {
return nums;
}
sort(nums.begin(), nums.end());
// the size of the resulting subset
int maxSize = 0;
// nums[maxindex] is the largest value of the resulting subset
int maxindex = 0;
// subsetSize[i] stores the size of the largest subset
// having the biggest number nums[i]
vector<int> subsetSize(nums.size(), 1);
// pre[i] stores the previous index of i in their largest subset
vector<int> pre(nums.size(), -1);
for (int i = 0; i < nums.size(); i++) {
// find the previous nums[j] that make subsetSize[i] largest
for (int j = i - 1; j >= 0; j--) {
if (nums[i] % nums[j] == 0 &&
subsetSize[j] + 1 > subsetSize[i])
{
subsetSize[i] = subsetSize[j] + 1;
pre[i] = j;
}
}
// update the largest subset
if (maxSize < subsetSize[i]) {
maxSize = subsetSize[i];
maxindex = i;
}
}
vector<int> result;
while (maxindex != -1) {
result.push_back(nums[maxindex]);
maxindex = pre[maxindex];
}
return result;
}
void printSolution(const vector<int>& result) {
cout << "[";
for (auto& v : result) {
cout << v << ",";
}
cout << "]" << endl;
}
int main() {
vector<int> nums{2,1,3};
auto result = largestDivisibleSubset(nums);
printSolution(result);
nums = {1,2,4,8};
result = largestDivisibleSubset(nums);
printSolution(result);
}
Output:
[2,1,]
[8,4,2,1,]
This solution finds the largest divisible subset of a given set of numbers by dynamically updating the size of the subsets and maintaining the previous index of each element in their largest subset.
It iterates through the sorted array of numbers, updating the size of the largest subset that ends with each element by considering the previous elements that are factors of the current element. By keeping track of the maximum subset size and the index of the largest element in the subset, it constructs the largest divisible subset.
This approach optimizes the computation by avoiding redundant calculations and leveraging dynamic programming techniques to efficiently explore the solution space.
Complexity
- Runtime:
O(n^2)
, wheren
is the number of elements in thenums
vector. The nested loop searches for previous elements with divisibility relationships, which may lead to quadratic time complexity in the worst case. However, it maintains information about subset sizes and elements, reducing redundant calculations and improving performance. - Extra space:
O(n)
.
Key takeaway
In this interesting problem, we use index mapping to simplify everything. That improves the performance in both runtime and memory.
Thanks for reading!
I hope you enjoyed this content. Don't keep it to yourself! Share it with your network and help each other improve your coding skills and advance your career!
Nhut Nguyen, the author of The Problem Solver's Guide To Coding.