Given a set of distinct positive integers nums, return the largest subset answer such that every pair (answer[i], answer[j]) of elements in this subset satisfies:
answer[i] % answer[j] == 0, or
answer[j] % answer[i] == 0
If there are multiple solutions, return any of them.
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Dynamic Programming (Memoization) - The problem requires caching recursive results to avoid recomputation
Sorting - Elements must be sorted first to establish the divisibility chain property
Divisibility and Modulo Operations - Understanding when one number divides another evenly
Recursion - Top-down approaches use recursive function calls with state tracking
1. Dynamic Programming (Top-Down)
Intuition
A divisible subset has a special property: if we sort the numbers, then for any pair in the subset, the larger number must be divisible by the smaller one. This means if we sort the array and pick elements in order, we only need to check divisibility with the most recently picked element. We can use recursion with memoization to try including or skipping each number.
Algorithm
Sort the array in ascending order.
Define a recursive function dfs(i, prevIndex) that returns the largest divisible subset starting from index i, where prevIndex is the index of the last included element (or -1 if none).
At each index:
Option 1: Skip the current number.
Option 2: If no previous element exists or the current number is divisible by the previous one, include it and recurse.
Memoize results based on (i, prevIndex) to avoid recomputation.
publicclassSolution{privateList<int>[][] cache;privateint[] nums;publicList<int>LargestDivisibleSubset(int[] nums){
Array.Sort(nums);this.nums = nums;int n = nums.Length;
cache =newList<int>[n +1][];for(int i =0; i <= n; i++){
cache[i]=newList<int>[n +1];}returnDfs(0,-1);}privateList<int>Dfs(int i,int prevIndex){if(i == nums.Length)returnnewList<int>();if(cache[i][prevIndex +1]!=null)return cache[i][prevIndex +1];List<int> res =Dfs(i +1, prevIndex);if(prevIndex ==-1|| nums[i]% nums[prevIndex]==0){List<int> tmp =newList<int>{ nums[i]};
tmp.AddRange(Dfs(i +1, i));if(tmp.Count > res.Count) res = tmp;}
cache[i][prevIndex +1]= res;return res;}}
funclargestDivisibleSubset(nums []int)[]int{
sort.Ints(nums)
n :=len(nums)
cache :=make([][][]int, n)for i :=range cache {
cache[i]=make([][]int, n+1)}var dfs func(i, prevIndex int)[]int
dfs =func(i, prevIndex int)[]int{if i == n {return[]int{}}if cache[i][prevIndex+1]!=nil{return cache[i][prevIndex+1]}
res :=dfs(i+1, prevIndex)if prevIndex ==-1|| nums[i]%nums[prevIndex]==0{
tmp :=append([]int{nums[i]},dfs(i+1, i)...)iflen(tmp)>len(res){
res = tmp
}}
cache[i][prevIndex+1]= res
return res
}returndfs(0,-1)}
class Solution {privatelateinitvar cache: Array<Array<List<Int>?>>privatelateinitvar nums: IntArray
funlargestDivisibleSubset(nums: IntArray): List<Int>{
nums.sort()this.nums = nums
val n = nums.size
cache =Array(n){ arrayOfNulls<List<Int>>(n +1)}returndfs(0,-1)}privatefundfs(i: Int, prevIndex: Int): List<Int>{if(i == nums.size)returnemptyList()
cache[i][prevIndex +1]?.let{return it }var res =dfs(i +1, prevIndex)if(prevIndex ==-1|| nums[i]% nums[prevIndex]==0){val tmp =listOf(nums[i])+dfs(i +1, i)if(tmp.size > res.size) res = tmp
}
cache[i][prevIndex +1]= res
return res
}}
classSolution{funclargestDivisibleSubset(_ nums:[Int])->[Int]{var nums = nums.sorted()let n = nums.count
var cache =[[Int]?](repeating:nil, count: n *(n +1))funcdfs(_ i:Int,_ prevIndex:Int)->[Int]{if i == n {return[]}let key = i *(n +1)+(prevIndex +1)iflet cached = cache[key]{return cached }var res =dfs(i +1, prevIndex)if prevIndex ==-1|| nums[i]% nums[prevIndex]==0{let tmp =[nums[i]]+dfs(i +1, i)if tmp.count > res.count { res = tmp }}
cache[key]= res
return res
}returndfs(0,-1)}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n2)
2. Dynamic Programming (Top-Down) Space Optimized
Intuition
We can simplify the state by observing that we only need to track the starting index, not the previous index. For each starting position, we find the longest divisible subset that begins there. When building the subset from index i, we look at all later indices j where nums[j] is divisible by nums[i] and take the best result.
Algorithm
Sort the array in ascending order.
Define dfs(i) that returns the largest divisible subset starting at index i.
At each index i:
Initialize the result with just nums[i].
For each later index j where nums[j] % nums[i] == 0, recursively get the subset starting at j.
Prepend nums[i] to the best result and keep the longest.
Memoize results for each starting index.
Try all starting positions and return the longest subset found.
classSolution:deflargestDivisibleSubset(self, nums: List[int])-> List[int]:
nums.sort()
cache ={}defdfs(i):if i in cache:return cache[i]
res =[nums[i]]for j inrange(i +1,len(nums)):if nums[j]% nums[i]==0:
tmp =[nums[i]]+ dfs(j)iflen(tmp)>len(res):
res = tmp
cache[i]= res
return res
res =[]for i inrange(len(nums)):
tmp = dfs(i)iflen(tmp)>len(res):
res = tmp
return res
publicclassSolution{privateList<Integer>[] cache;publicList<Integer>largestDivisibleSubset(int[] nums){Arrays.sort(nums);int n = nums.length;
cache =newArrayList[n];List<Integer> res =newArrayList<>();for(int i =0; i < n; i++){List<Integer> tmp =dfs(i, nums);if(tmp.size()> res.size()){
res = tmp;}}return res;}privateList<Integer>dfs(int i,int[] nums){if(cache[i]!=null)return cache[i];List<Integer> res =newArrayList<>();
res.add(nums[i]);for(int j = i +1; j < nums.length; j++){if(nums[j]% nums[i]==0){List<Integer> tmp =newArrayList<>();
tmp.add(nums[i]);
tmp.addAll(dfs(j, nums));if(tmp.size()> res.size()){
res = tmp;}}}return cache[i]= res;}}
classSolution{private:
vector<vector<int>> cache;public:
vector<int>largestDivisibleSubset(vector<int>& nums){sort(nums.begin(), nums.end());int n = nums.size();
cache.resize(n,vector<int>());
vector<int> res;for(int i =0; i < n; i++){
vector<int> tmp =dfs(i, nums);if(tmp.size()> res.size()){
res = tmp;}}return res;}
vector<int>dfs(int i, vector<int>& nums){if(!cache[i].empty())return cache[i];
vector<int> res ={nums[i]};for(int j = i +1; j < nums.size(); j++){if(nums[j]% nums[i]==0){
vector<int> tmp ={nums[i]};
vector<int> next =dfs(j, nums);
tmp.insert(tmp.end(), next.begin(), next.end());if(tmp.size()> res.size()){
res = tmp;}}}return cache[i]= res;}};
classSolution{/**
* @param {number[]} nums
* @return {number[]}
*/largestDivisibleSubset(nums){
nums.sort((a, b)=> a - b);const n = nums.length;const cache =newArray(n).fill(null);constdfs=(i)=>{if(cache[i]!==null)return cache[i];let res =[nums[i]];for(let j = i +1; j < n; j++){if(nums[j]% nums[i]===0){let tmp =[nums[i],...dfs(j)];if(tmp.length > res.length){
res = tmp;}}}return(cache[i]= res);};let res =[];for(let i =0; i < n; i++){let tmp =dfs(i);if(tmp.length > res.length){
res = tmp;}}return res;}}
publicclassSolution{privateDictionary<int, List<int>> cache;privateint[] nums;publicList<int>LargestDivisibleSubset(int[] nums){
Array.Sort(nums);this.nums = nums;
cache =newDictionary<int, List<int>>();List<int> res =newList<int>();for(int i =0; i < nums.Length; i++){List<int> tmp =Dfs(i);if(tmp.Count > res.Count){
res = tmp;}}return res;}privateList<int>Dfs(int i){if(cache.ContainsKey(i))return cache[i];List<int> res =newList<int>{ nums[i]};for(int j = i +1; j < nums.Length; j++){if(nums[j]% nums[i]==0){List<int> tmp =newList<int>{ nums[i]};
tmp.AddRange(Dfs(j));if(tmp.Count > res.Count){
res = tmp;}}}
cache[i]= res;return res;}}
funclargestDivisibleSubset(nums []int)[]int{
sort.Ints(nums)
n :=len(nums)
cache :=make([][]int, n)var dfs func(i int)[]int
dfs =func(i int)[]int{if cache[i]!=nil{return cache[i]}
res :=[]int{nums[i]}for j := i +1; j < n; j++{if nums[j]%nums[i]==0{
tmp :=append([]int{nums[i]},dfs(j)...)iflen(tmp)>len(res){
res = tmp
}}}
cache[i]= res
return res
}
res :=[]int{}for i :=0; i < n; i++{
tmp :=dfs(i)iflen(tmp)>len(res){
res = tmp
}}return res
}
class Solution {privatelateinitvar cache: Array<List<Int>?>privatelateinitvar nums: IntArray
funlargestDivisibleSubset(nums: IntArray): List<Int>{
nums.sort()this.nums = nums
cache =arrayOfNulls(nums.size)var res = emptyList<Int>()for(i in nums.indices){val tmp =dfs(i)if(tmp.size > res.size){
res = tmp
}}return res
}privatefundfs(i: Int): List<Int>{
cache[i]?.let{return it }var res =listOf(nums[i])for(j in i +1 until nums.size){if(nums[j]% nums[i]==0){val tmp =listOf(nums[i])+dfs(j)if(tmp.size > res.size){
res = tmp
}}}
cache[i]= res
return res
}}
classSolution{funclargestDivisibleSubset(_ nums:[Int])->[Int]{var nums = nums.sorted()let n = nums.count
var cache =[[Int]?](repeating:nil, count: n)funcdfs(_ i:Int)->[Int]{iflet cached = cache[i]{return cached }var res =[nums[i]]for j in(i +1)..<n {if nums[j]% nums[i]==0{let tmp =[nums[i]]+dfs(j)if tmp.count > res.count {
res = tmp
}}}
cache[i]= res
return res
}var res =[Int]()for i in0..<n {let tmp =dfs(i)if tmp.count > res.count {
res = tmp
}}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
3. Dynamic Programming (Bottom-Up)
Intuition
We can convert the top-down approach to bottom-up. Processing from right to left, for each index we compute the longest divisible subset starting from that position. At each step, we check all later indices for valid extensions and build upon the precomputed results.
Algorithm
Sort the array in ascending order.
Create a DP array where dp[i] stores the longest divisible subset starting at index i.
Initialize each dp[i] with just the element at that index.
Iterate from right to left:
For each later index j, if nums[j] % nums[i] == 0, check if prepending nums[i] to dp[j] gives a longer subset.
classSolution:deflargestDivisibleSubset(self, nums: List[int])-> List[int]:
nums.sort()
dp =[[num]for num in nums]# dp[i] = longest start at i
res =[]for i inrange(len(nums)-1,-1,-1):for j inrange(i +1,len(nums)):if nums[j]% nums[i]==0:
tmp =[nums[i]]+ dp[j]
dp[i]= tmp iflen(tmp)>len(dp[i])else dp[i]
res = dp[i]iflen(dp[i])>len(res)else res
return res
publicclassSolution{publicList<Integer>largestDivisibleSubset(int[] nums){Arrays.sort(nums);int n = nums.length;List<Integer>[] dp =newArrayList[n];List<Integer> res =newArrayList<>();for(int i = n -1; i >=0; i--){
dp[i]=newArrayList<>();
dp[i].add(nums[i]);for(int j = i +1; j < n; j++){if(nums[j]% nums[i]==0){List<Integer> tmp =newArrayList<>();
tmp.add(nums[i]);
tmp.addAll(dp[j]);if(tmp.size()> dp[i].size()){
dp[i]= tmp;}}}if(dp[i].size()> res.size()){
res = dp[i];}}return res;}}
classSolution{public:
vector<int>largestDivisibleSubset(vector<int>& nums){sort(nums.begin(), nums.end());int n = nums.size();
vector<vector<int>>dp(n);
vector<int> res;for(int i = n -1; i >=0; i--){
dp[i].push_back(nums[i]);for(int j = i +1; j < n; j++){if(nums[j]% nums[i]==0){
vector<int> tmp = dp[j];
tmp.insert(tmp.begin(), nums[i]);if(tmp.size()> dp[i].size()){
dp[i]= tmp;}}}if(dp[i].size()> res.size()){
res = dp[i];}}return res;}};
classSolution{/**
* @param {number[]} nums
* @return {number[]}
*/largestDivisibleSubset(nums){
nums.sort((a, b)=> a - b);const n = nums.length;const dp =newArray(n).fill(0).map(()=>[]);let res =[];for(let i = n -1; i >=0; i--){
dp[i]=[nums[i]];for(let j = i +1; j < n; j++){if(nums[j]% nums[i]===0){let tmp =[nums[i],...dp[j]];if(tmp.length > dp[i].length){
dp[i]= tmp;}}}if(dp[i].length > res.length){
res = dp[i];}}return res;}}
publicclassSolution{publicList<int>LargestDivisibleSubset(int[] nums){
Array.Sort(nums);List<List<int>> dp =newList<List<int>>();foreach(int num in nums){
dp.Add(newList<int>{ num });}List<int> res =newList<int>();for(int i = nums.Length -1; i >=0; i--){for(int j = i +1; j < nums.Length; j++){if(nums[j]% nums[i]==0){List<int> tmp =newList<int>{ nums[i]};
tmp.AddRange(dp[j]);if(tmp.Count > dp[i].Count) dp[i]= tmp;}}if(dp[i].Count > res.Count) res = dp[i];}return res;}}
funclargestDivisibleSubset(nums []int)[]int{
sort.Ints(nums)
n :=len(nums)
dp :=make([][]int, n)for i :=range dp {
dp[i]=[]int{nums[i]}}
res :=[]int{}for i := n -1; i >=0; i--{for j := i +1; j < n; j++{if nums[j]%nums[i]==0{
tmp :=append([]int{nums[i]}, dp[j]...)iflen(tmp)>len(dp[i]){
dp[i]= tmp
}}}iflen(dp[i])>len(res){
res = dp[i]}}return res
}
class Solution {funlargestDivisibleSubset(nums: IntArray): List<Int>{
nums.sort()val n = nums.size
val dp =Array(n){mutableListOf(nums[it])}var res = emptyList<Int>()for(i in n -1 downTo 0){for(j in i +1 until n){if(nums[j]% nums[i]==0){val tmp =listOf(nums[i])+ dp[j]if(tmp.size > dp[i].size){
dp[i]= tmp.toMutableList()}}}if(dp[i].size > res.size){
res = dp[i]}}return res
}}
classSolution{funclargestDivisibleSubset(_ nums:[Int])->[Int]{var nums = nums.sorted()let n = nums.count
var dp = nums.map {[$0]}var res =[Int]()for i instride(from: n -1, through:0, by:-1){for j in(i +1)..<n {if nums[j]% nums[i]==0{let tmp =[nums[i]]+ dp[j]if tmp.count > dp[i].count {
dp[i]= tmp
}}}if dp[i].count > res.count {
res = dp[i]}}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
4. Dynamic Programming (Top-Down) + Tracing
Intuition
Instead of storing entire subsets in the DP table (which uses extra memory), we can store just two values per index: the length of the longest subset starting there, and the next index in that subset. After computing all lengths, we trace through the indices to reconstruct the actual subset.
Algorithm
Sort the array in ascending order.
Create a DP array where dp[i] = [maxLen, nextIndex].
Define dfs(i) that returns the length of the longest subset starting at index i:
For each later index j where nums[j] % nums[i] == 0, compute the length via dfs(j) + 1.
Track which j gave the best length for tracing.
Find the starting index with the maximum length.
Reconstruct the subset by following the nextIndex pointers.
This is the iterative version of the tracing approach. We process indices from left to right, looking backward for valid predecessors. For each position, we store the length of the longest subset ending there and a pointer to the previous index. This is similar to the classic Longest Increasing Subsequence pattern.
Algorithm
Sort the array in ascending order.
Create a DP array where dp[i] = [maxLen, prevIndex], initialized with length 1 and no predecessor.
For each index i, check all earlier indices j:
If nums[i] % nums[j] == 0 and extending from j gives a longer subset, update dp[i].
Track the index with the overall maximum length.
Reconstruct the subset by following the prevIndex pointers backward from the best ending index.
The divisibility property only guarantees transitivity when elements are sorted. If a divides b and b divides c, then a divides c, but this chain only works reliably when processing elements in ascending order. Skipping the sort step means you might miss valid subset extensions or incorrectly reject valid ones.
Checking Divisibility in the Wrong Direction
After sorting in ascending order, when comparing nums[i] and nums[j] where i < j, you must check if nums[j] % nums[i] == 0 (larger divisible by smaller). A common mistake is checking nums[i] % nums[j] == 0, which will always be false for distinct positive integers when nums[i] < nums[j].
Only Returning the Length Instead of the Actual Subset
The problem asks for the subset itself, not just its size. When using DP with length tracking only, you must also maintain a way to reconstruct the path, typically through parent pointers or by storing the actual subsets. Forgetting this reconstruction step means you can compute the correct length but cannot output the required elements.