【问题标题】:Minimum total container size Python最小总容器大小 Python
【发布时间】:2020-12-27 21:01:55
【问题描述】:

我正在练习一些亚马逊面试编码练习,但我一直坚持这个练习。

简而言之,给定一个列表l 和一个整数d,我必须将列表拆分为d 个部分,使得每个部分的最大值之和最小,例如l = [10,2,20,5,15,10,1], d = 3应该返回 31,因为最好的选择是 [10,2,20,5,15],[10],[1] -> 20+10+1 = 31

问题链接:Problem

我考虑将列表拆分为 2 并使用第一部分和d-1 递归调用,并取第二部分的最大值(第二部分将开始有一个元素,我们将在此部分中添加一个元素每次迭代)。

我这样做了,但出现“调用 Python 对象时超出最大递归深度”错误。我知道这个问题的解决方案很容易找到,但我想了解为什么这种方法不起作用。

def getmin(l, d):
    # base case, if the list has d elements each element will be a sublist
    if len(l)==d: 
        return sum(l)
    else:
        minn = float("inf")
        for i in range(1, len(l)-d+1):
            val = getmin(l[0:-i], d-1) + max(l[-i:])
            if minn > val:
                minn = val
    return minn

【问题讨论】:

  • 尝试在函数顶部打印ld。我很确定他们没有做你期望的事情。您可能会重载l 作为可能的结果和输入列表。您需要一个单独的结果结构。

标签: python algorithm dynamic-programming


【解决方案1】:

您的列表l 不是作为您的书面示例的二维列表,因此您的算法似乎无法保持结果列表(存储您为到达特定点所做的削减和输入列表直接)。保留一个单独的列表并将结果累积到其中要容易得多,将l 视为只读。更不用说,从性能的角度来看,每次递归调用都复制它是很痛苦的。

在更进一步之前,Python 推荐never using l as a variable name。更喜欢Lnumslst —— 真的很难区分1il。我将在帖子的其余部分使用L

与您类似的蛮力解决方案是运行从 0 到 len(L) 的计数器,并使用递归在每个节点上尝试每种可能性。在每个 i 处,我们可以将当前编号 L[i] 附加到最后一个存储桶,或者以 L[i] 作为第一个元素启动一个新存储桶。一个小的优化是只保留每个桶的最大值而不是所有元素。

既然你有一个蛮力解决方案,你可以在cachememoize 重复工作。

from functools import cache
import random

def min_sum_of_max_elems_from_d_cuts(L, d):
    assert len(L) >= d

    @cache
    def find_best_cut(i, cuts):
        if len(cuts) == d and i == len(L):
            return sum(cuts)
        elif len(cuts) > d or i >= len(L):
            return float("inf")

        best = float("inf")

        if cuts: # try extending the current cut by L[i]
            new_cuts = cuts[:-1] + (max(cuts[-1], L[i]),)
            best = min(best, find_best_cut(i + 1, new_cuts))

        #                try making a new cut at L[i]
        return min(best, find_best_cut(i + 1, cuts + (L[i],)))

    return find_best_cut(i=0, cuts=tuple())

if __name__ == "__main__":
    tests = [
        dict(L=[10,2,20,5,15,10,1], d=3, result=31),
        dict(L=[10,2,20,5,15,10,1], d=5, result=43),
        dict(L=[5,4,2,4,3,4,5,4], d=4, result=16),
        dict(L=[22,12,1,14,17], d=2, result=39),
    ]

    for test in tests:
        assert min_sum_of_max_elems_from_d_cuts(test["L"], test["d"]) == test["result"]
    
    L = random.Random(42).choices(range(200), k=25)
    assert min_sum_of_max_elems_from_d_cuts(L, d=15) == 1056

我将把优化、自下而上的 DP 和重现的问题留给读者。它看起来和LeetCode 1335 一样的问题,所以目标约束是1 <= len(L) <= 3000 <= L[i] <= 10001 <= d <= 10,所以它需要比这更多的火力来避免 TLE。

【讨论】:

  • 感谢您的帮助,解释确实有很大帮助。我很感激。
【解决方案2】:

对于简单的情况,d=1,解就是列表中的最大值

def getmin(l, d):
    if d == 1:
        return max(l)

当 d 大于 1 时,解决方案是返回添加到递归调用结果中的列表的头部或尾部中较小的一个

def getmin(l, d):
    if d == 1:
        return max(l)
    if l[0] < l[-1]:
        return l[0] + getmin(l[1:], d-1)
    else:
        return getmin(l[:-1], d-1) + l[-1]

【讨论】:

  • 嗨,感谢您的帮助,但它不适用于 case l = [5,4,2,4,3,4,5,4], d=4 因为它返回 18当最佳解决方案 i 16: [5,4],[2],[4,3],[4,5,4] -> 16
  • 如果您有问题的链接,这将使人们更容易验证他们的解决方案。顺便说一句,ld 可以解决这个问题有多大?
  • 完成了,我添加了一个指向原始问题的链接,我认为它是通过动态编程和记忆来完成的,但我不确定它是否可以按照我计划的方式完成。跨度>
  • 谢谢。你的策略很好 - 可以从蛮力开始并首先获得正确性,然后在你有工作的时候拍下备忘录或自下而上的 DP。
猜你喜欢
  • 2019-08-07
  • 2010-10-18
  • 2021-09-28
  • 2011-01-21
  • 1970-01-01
  • 2015-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多