【问题标题】:Memoized solution to Combination IV on Leetcode gives TLE when an array is used for caching当数组用于缓存时,Leetcode 上组合 IV 的记忆化解决方案提供 TLE
【发布时间】:2022-01-09 10:29:31
【问题描述】:

在尝试解决 Leetcode 上的 Combination IV 时,我想出了这个记忆化的解决方案:

def recurse(nums, target, dp):
        
    if dp[target]!=0:
        return dp[target]
    
    if target==0:
        return dp[0]

    for n in nums:
        if n<=target:
            dp[target] += recurse(nums, target-n, dp)
            
    return dp[target]

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        
        dp = [0]*(target+1)
        dp[0] = 1

        return recurse(nums, target, dp)

但这给了我一个超出时间限制的错误。

另一个使用字典而不是 dp 数组来缓存值的记忆化解决方案运行良好并且不超过时间限制。解决方法如下:

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        memo = {}
        def dfs(nums, t, memo):
            if t in memo:
                return memo[t]
            if t == 0:
                return 1
            if t < 0:
                return 0
            res = 0
            for i in nums:
                res += dfs(nums, t-i, memo)
            memo[t] = res
            return res
        return (dfs(nums, target, memo)) 

为什么使用 dict 代替数组可以提高运行时间?这不像我们在遍历数组或 dict,我们只是使用它们来存储和访问值。

编辑:我的代码崩溃的测试用例如下:

nums = [10,20,30,40,50,60,70,80,90,100,110,120,130,140,​​150,160,170,180,190,200,210,220,230,240,250,260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,410,420,430,440,450,460,470,480,490,500,510,520,530,540,550,560,570,580,590,600,610,620,630,640,650,660,670,680,690,700,710,720,730,740,750,760,770,780,790,800,810,820,830,840,850,860,870,880,890,900,910,920,930,940,950,960,970,980,990,111]

目标 = 999

【问题讨论】:

  • 乍一看,我觉得这两个程序之间的区别不仅仅是用数组替换字典。
  • 我不这么认为。如果你仔细看,它是一样的。虽然如果您发现任何其他可能有助于程序 2 运行得更快的差异,请告诉我。
  • 你能以一种不需要仔细查看的方式来准备代码,但要清楚地表明它们是相同的,除了用数组交换字典吗?
  • 一个区别,例如,在上面的代码中,dp[target] += 发生在循环内部,而在下面的代码中,memo[t] = 在循环之外发生。
  • 其中一个测试用例可能有一个巨大的目标值,这将使基于 dict 的代码更快。

标签: python python-3.x memoization


【解决方案1】:

两个版本的代码相同。在列表版本中,如果“缓存”值为 0,则继续递归。在 dict 版本中,如果当前键不在缓存中,则继续递归。当结果为 0 时,这会有所不同。例如,如果您尝试使用 nums=[2, 4, 6, 8, 10] 和 total=1001 的示例,则列表版本中没有进行有用的缓存(因为每个结果为 0)。

您可以通过将每个条目初始化为 None 而不是 0 并使用 None 作为标记值来确定结果是否未缓存来改进您的列表版本。

放弃缓存的想法,直接使用动态编程表也更容易。例如:

def ways(total, nums):
    r = [1] + [0] * total
    for i in range(1, total+1):
        r[i] = sum(r[i-n] for n in nums if i-n>=0)
    return r[total]

这显然需要 O(total * len(nums)) 时间。

这里没有必要(因为问题中的总数最多为 1000),但原则上您可以在迭代时只保留表的最后 N 行(其中 N 是 nums 的最大值)。这将使用的空间从 O(total) 减少到 O(max(nums))。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-11
    相关资源
    最近更新 更多