Leetcode題解 Python & C#:六月挑戰DAY13 Largest Divisible Subset

給一組不同的正整數 nums,要找出滿足條件 s[i]%s[j]==0 or s[j]%s[i]==0 之最大的集合
這題要回傳的是「最大的集合」,所以需要先知道最大的,然後再回頭生成。
要滿足條件,大的一定要是次大的乘以某整數,這樣才能歸納於同一組,同時也符合餘數的條件。
我卡了很久,因為我一直想突破 O(N^2) 的障礙,要是想到 O(N^2) 的方法就不會去嘗試,而且對於這樣的題目被太多種DP衍生的可能給卡住。
大概花了二天,才完成我的理想型,也是最初設想的數學理論版。要把想法變成方法就是難度所在,也是樂趣所在。
一個排列過的最大集合,最大的一定是其左一個乘上某數,即「因數」。而一個數的左方會接子集合有最大數目的因數。

將 nums 先 sort() 過,接下來用 for 就會是而小至大的 num 。
比較的是「數目」,因此要保存每個數的最大可能數目,用 count 保存,每個數至少會有 1 即自身。
然而這題要的是最大數目的集合,所以不只要找出數目有多少,也要有能得到最大數目集合有哪些元素的手段。
一種是把所有可能取出來,再從中找到數目最大即可。二是弄成關聯的形式,再從最大數目的某一資訊去回溯出最大的集合。
走方案一比較直觀,但是會用上大量的記憶體,也比方案二慢。
選擇方案二,弄個關聯 path ,如果自身的因數在 nums 裡,那麼會從中選有最大數目集合的因數跟自身組合。
那個因數也是如此進行,直到找不到除了自身之外任何有在 nums 的因數。
如︰[1,2,4,8],對 8 而言是 4 ,對 4 而言是 2 ,對 2 而言是 1 ,對 1 而言沒有,所以就停止,過程回溯了最大數目集合的所有元素。 
因此,在初始化的時候,每個數字的預設即為自身,如果有找到其它的因數在 nums 中,再去選擇有最大數目的因數 。
於是變成,對 1 而言是 1,所以就停止。(預期path = {1:1, 2:1, 4:2, 8:4})
確立了關聯,但要能回溯出來最大集合,一定要有最大數目集合的最大數值。 所以一旦當前 num 的最大數目是比紀錄更高,就把數字保存下來。
可能最大數目相同的集合會有二種以上,但哪一種都沒有關係,取一就好。
這題與其他題不一樣的是,合法的規則都建立在「因數」上,所以用因數的算法是最合理的,而不是從 nums 去一對對試 s[i]%s[j]==0 or s[j]%s[i]==0 。
因數的找法是從左右界找尋,左界預設為 1,右界預設為 nums[i],下一輪,左界 + 1 為 2,右界為 nums[i]/2(左界) ,如果右界沒有整除,左界就繼續 + 1,右界為 nums[i]/左界。
這個找法會用上 O(N**(1/2)) Time ,然後所有元素都這樣用,整體會是 O(N**(3/2)) Time, 比起一對對試的 O(N**2) 是更快速的。

Python

class Solution:
def largestDivisibleSubset(self, nums):
if not nums: return nums
nums.sort()
count = dict([(num, 0) for num in nums])
path = dict([(num, num) for num in nums])
max_num, max_count = nums[0], 0
for num in nums:
l = 1
r = num
while l <= r:
if r in path and count[r]+1 > count[num]:
count[num] = count[r]+1
path[num] = r
if l in path and count[l]+1 > count[num]:
count[num] = count[l]+1
path[num] = l
l += 1
while l <= r and num / l % 1 != 0:
r = num / l
l += 1
r = num // l
if max_count < count[num]:
max_num, max_count = num, count[num]

result = [max_num]
while path[max_num] != max_num:
max_num = path[max_num]
result.append(max_num)
return result

C#

public class Solution {
public IList LargestDivisibleSubset(int[] nums) {
var result = new List();
if(nums.Length == 0){return result;}
Array.Sort(nums);
var path = new Dictionary();
var count = new Dictionary();
for(int i = 0; i < nums.Length; i++)
{
int num = nums[i];
path[num] = num;
count[num] = 0;
//
int l = 1;
int r = num;
while(l <= r)
{
if(path.ContainsKey(l) && count[l] + 1 > count[num])
{
count[num] = count[l] + 1;
path[num] = l;
}
if(path.ContainsKey(r) && count[r] + 1 > count[num] && r != num && r != l)
{
count[num] = count[r] + 1;
path[num] = r;
}
l += 1;
r = num/l;
while(l 0)
{
l += 1;
r = num/l;
}
}
}
int maxNum = count.Aggregate((x, y) => x.Value > y.Value ? x : y).Key;
result.Add(maxNum);
while(path[maxNum] != maxNum)
{
maxNum = path[maxNum];
result.Add(maxNum);
}
return result;
}
}