leetcode:求最后一块石头的重量

题目描述:
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-stone-weight-ii

解题思路:
assume:现可将所有石头重量分为2部分:pos 和 neg,pos-neg,即为所剩石头最小的可能重量remain。其中石头总重量为sum = pos + neg。
remain = pos - neg = sum - neg - neg = sum - 2*neg
因为sum为恒定值,所以,要求remain最小值,只要求出 neg可能的最大值即可;
由上等式可知,neg最大不得超过 sum/2;

如果将neg看作是一个袋子,其最大承重为sum/2,则我们现在要做的就是从stones数组中,选出若干元素,使得neg能够装下尽可能重的物品。
这个问题可以进一步转化为背包问题,用动态规划的思路解决:
assume:我们现在依次从stones中取出石头,累加,判断其重量之和 与 neg容量 j 的大小,并将判断结果记录在一个二维数组dp[][] 中,二维数组dp的row number为 stones的长度,即:石头个数,column number为stones的neg容量的可取值,从:from 1 to sum/2。
我们将 dp[i+1][j] 定义为:前i个石头的总重量 刚好等于 neg的某个可取容量值 j。
根据上述定义,dp[0][0] 可以解释为,不取出任何石头时的状态(总重量=0),这在任何情况下都是成立的,所以将 dp[0][0] = 1,表示这种state为真。

通过上述定义,我们可以将求解neg最大值的问题,转化为求解 dp[n][?] 的问题,即:n个石头可以装进neg中的最大重量为多少(given that: neg最大容量为 sum/2)

dp[i+1][j] 的取值可以分两种情况来分析:
situation1:
当 j<stones[i] 时,则 可知,要判断 能否从前 i 个石头中,挑选出若干石头,刚好装满 容量为 j的背包neg,我们只需要判断 dp[i][j] 是否成立即可(即:从前 i-1 个 石头中 能否挑出若干石头,使其重量刚好等于 j),因为,很明显,第i个石头,其重量stones[i]已超过背包容量,不可能放进neg之中。
situation2:
当j >= stones[i]时,则关于 是否将第 i 个石头挑出,放入背包neg中,取决于:
1)前 i-1 个石头 ,是否已经能够 形成一个 石头堆,使其重量刚好 等于 j,如果这种情况为 False,则我们考虑第2)种情况
2)前 i-1 个石头,是否 能够挑出一些石头,使得 dp[i][j-stones[i]] 为True,如果存在这一石头堆,则我们可以将第 i 个石头 放入这些石头中,从而使这堆石头的总重量刚好等于 j,即:dp[i+1][j] = True;
dp[i+1][j] = dp[i][j] or dp[i][j-stones[i]]

dp[n][?] 的求解问题,进而转化为:
dp[i][j] j<stones[i]
dp[i+1][j] =
dp[i][j] or dp[i][j-stones[i]] j>=stones[i]
initialize dp[][] = 0 , except dp[0][0] = 1。

python code
```python
class Solution: #定义类
    def lastStoneWeightII(self,  stones: List[int]) -> int: #定义函数:stones为 int 列表,返回值为int
    sum = sum(stones)  #求出所有石头的总重量
    neg = sum//2  # neg可取最大值为 sum//2
    length = len(stones)  #求出数组长度
    dp = [[0]*neg for _ in length] #定义二维数组,行数为石头的数量,列数为 neg 可能取得整数值,且初始化所有得dp[][]为false
    dp[0][0] = 1 #没有石头时,重量为0,任何情况都成立,所以 将其 set 为1

    #开始循环验证dp各个元素得True or False
    for i in length+1:  # from 0 to length+1, 因为 dp[length+1][j]为length个石头重量可为j得 真假
        for j in range(1, neg+1, 1): #from 1 to neg
            if j<stones[i]:
                dp[i+1][j] = dp[i][j]
            else:
                dp[i+1][j] = dp[i][j] or dp[i][j-stones[i]]
    res = None #???
    n = length
    for j in range(neg, -1, -1): #以倒置得方式列举neg得可能取值   j的取值为 from neg to 0
        if dp[n][j]: #一旦发现n个石头,可以挑选出若干石头,堆积成尽可能大得neg,即计算 石头消除的最小可能值,并退出循环
            res = sum - 2*j
            break
    return res

在思考这一题时,最初看不明白 上述 动态规划公式 ,是因为,不理解 dp[i+1][j] ,是指 从前i个石头中,挑选 出若干石头,使其重量之和为 j,这里“若干石头”可以指 i个石头,也可以指 前i个石头中的 任意 k(k<i) 个石头。

猜你喜欢

转载自blog.csdn.net/u014765410/article/details/117713109