建造纸牌屋的方法数

Submission

运行时间: 155 ms

内存: 0.0 MB

@cache
def dp(k, n, lo):
    if lo > n: return 0
    if k == 1: return 1
    return sum(dp(k - 1, n - lo, lo + 1) for lo in range(lo, n // 2 + 1))

class Solution:
    def houseOfCards(self, n: int) -> int:
        return sum(dp(l, (n - 2 * l) // 3, 0) for l in range(3 - n % 3, round(sqrt(2 * n / 3 + 1 / 36) - 1 / 6) + 1, 3))

Explain

题解的思路基于递归和动态规划。解题思路中,定义了一个递归函数 dp(k, n, lo),其中 k 代表层级数,n 代表剩余可用的卡片数,lo 为当前层起始最小卡片数。函数的目的是计算能够构建 k 层塔且使用至少 lo 张卡片的方法数。如果当前层使用的卡片数超过剩余卡片数 n,则返回 0。如果仅剩一层 (k == 1),则只有一种构建方式。对于多层情况,递归地计算所有可能的构建方式的总和。外层函数 houseOfCards 求解具体的 n 张卡片可以构建的不同高度的塔的总数,通过迭代每一可能的层高 l,并根据剩余卡片调用 dp 函数。起始层高 l 从 3 - n % 3 开始,到 n 张卡片能支持的最大层数结束,步长为 3。

时间复杂度: O(k * n^2/2)

空间复杂度: O(k * n^2/2)

from functools import cache
import math

class Solution:
    @cache
    def dp(self, k, n, lo):
        # 如果当前层使用的卡片数超过剩余卡片数,返回 0
        if lo > n: return 0
        # 如果只剩下一层,返回 1 种方法
        if k == 1: return 1
        # 计算所有可能的构建方式的总和
        return sum(self.dp(k - 1, n - lo, lo + 1) for lo in range(lo, n // 2 + 1))

    def houseOfCards(self, n: int) -> int:
        # 计算可以构建的不同高度的塔的总数
        return sum(self.dp(l, (n - 2 * l) // 3, 0) for l in range(3 - n % 3, round(math.sqrt(2 * n / 3 + 1 / 36) - 1 / 6) + 1, 3))

Explore

起始层高`l`的计算方式`3 - n % 3`是为了确保构建每一层至少需要3张卡片的基础上,剩余的卡片数能够被3整除,这样才能保证在接下来的每一层都至少使用3张卡片。因此,`3 - n % 3`是为了找出最小的对3整除的剩余卡片数,从而确定能够开始构建的最低层数。这种方式确保了在给定卡片数量下,可以从最小合理的层高开始构建,同时尽可能高效地利用每张卡片。

在递归函数`dp`中,当`k == 1`时直接返回1是因为,如果只剩下一层要建造,那么无论剩下多少张卡片(只要至少为最小层数的卡片数),都只有一种方法来构建这最后一层。`n`在这里代表剩余的卡片数,只要`n`大于等于当前层所需的最小卡片数`lo`,就能构建出这一层。因此,当`k == 1`时意味着在卡片数量允许的情况下,总是有一种方法可以完成最后一层的构建。

在递归调用中,`lo`的范围从当前层的最小卡片数`lo`开始,到`n // 2 + 1`结束,是基于这样的考虑:每一层都应至少比上一层多使用一张卡片,从而保持塔的稳定性和递增的结构特性。范围的上限`n // 2 + 1`确保了在当前层至少留有足够的卡片数供下一层使用。这种设置帮助确保了每一层不仅满足最小卡片数的需求,而且考虑到了剩余卡片数,从而使构建过程中每一层的卡片使用都是合理和可行的。