0

The goal is to find the largest divisible subset. A divisible subset is a subset s.t. for every pair of elements i, j, either i is divisible by j or j is divisible by i. The general approach to solve this is to sort the numbers and realize that if a is divisible by b and b by c, that a must also be divisible by c. Here's the unmemoized recursion:

def largestDivisibleSubset0(self, nums: List[int]) -> List[int]:
    def backtrack(candidate, end):
        if len(candidate) > len(self.best):
            self.best = candidate[:]

        if end == n - 1:
            return

        for new_end in range(end+1, n):
            if not candidate or candidate[-1] % nums[new_end] == 0:
                backtrack(candidate+[nums[new_end]], new_end)

    nums.sort(reverse=True)
    n = len(nums)
    self.best = []
    backtrack([], -1)
    return self.best

Next, I tried to memoize. Forgive the poor style where self.best and memo are both tracked. I know it's redundant and that I'm not actually caching the local best but instead the global (side question: what is the best way to memoize this?)

def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
    def backtrack(candidate, end):
        if (len(candidate), end) in memo:
            return memo[(len(candidate), end)]

        if len(candidate) > len(self.best):
            self.best = candidate[:]

        if end == n - 1:
            return

        for new_end in range(end+1, n):
            if not candidate or candidate[-1] % nums[new_end] == 0:
                backtrack(candidate+[nums[new_end]], new_end)

        memo[(len(candidate), end)] = self.best

    nums.sort(reverse=True)
    n = len(nums)
    self.best = []
    memo = {}
    backtrack([], -1)
    return self.best

Here's what I don't understand. How is it an accurate representation of the state to simply consider the length of the current candidate, and not the last element? What if a subsequence ending in 5 has length x, equal to a subsequence ending in 4 — isn't it possible the latter one gets pruned from the search space, even if it might result in a longer divisible subset down the line?

8
  • What does your end parameter mean? Commented Feb 22, 2024 at 4:04
  • End is the current position being considered to include or not. When end = x, only the first x characters have been considered Commented Feb 22, 2024 at 4:06
  • The current one? Shouldn't it try range(end, n) then instead of range(end+1, n)? Commented Feb 22, 2024 at 4:07
  • you need range(end+1, n) or you'd just be repeating the same index again Commented Feb 22, 2024 at 4:14
  • So what does end really tell you? Commented Feb 22, 2024 at 4:15

1 Answer 1

0

end tells you the candidate subsequence ending.

What if a subsequence ending in 5 has length x, equal to a subsequence ending in 4

Then their end differs and thus their memo key differs and thus they don't prune each other.

Sign up to request clarification or add additional context in comments.

2 Comments

Nah this isn't correct. End is not the last index that was used, it's the last index we've considered. If we've considered up to x elements, a length of 1 could represent any of the x elements
@Alec See your call: backtrack(candidate+[nums[new_end]], new_end). The same value new_end is used for both the candidate's last element and the end value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.