LeetCode算法心得——零数组变换IV(0-1背包)

大家好,我是晴天学长,很久很久没有写算法题解了,今天开始转python了。💪💪💪


1)统计打字方案数

在这里插入图片描述
给你一个长度为 n 的整数数组 nums 和一个二维数组 queries ,其中 queries[i] = [li, ri, vali]。
每个 queries[i] 表示以下操作在 nums 上执行:
从数组 nums 中选择范围 [li, ri] 内的一个下标子集。 将每个选中下标处的值减去 正好 vali。
零数组 是指所有元素都等于 0 的数组。
返回使得经过前 k 个查询(按顺序执行)后,nums 转变为 零数组 的最小可能 非负 值 k。如果不存在这样的 k,返回 -1。
数组的 子集 是指从数组中选择的一些元素(可能为空)。


示例 1:

输入: nums = [2,0,2], queries = [[0,2,1],[0,2,1],[1,1,3]]

输出: 2

解释:

对于查询 0 (l = 0, r = 2, val = 1):
将下标 [0, 2] 的值减 1。
数组变为 [1, 0, 1]。
对于查询 1 (l = 0, r = 2, val = 1):
将下标 [0, 2] 的值减 1。
数组变为 [0, 0, 0],这就是一个零数组。因此,最小的 k 值为 2

示例 2:

输入: nums = [4,3,2,1], queries = [[1,3,2],[0,2,1]]

输出: -1

解释:

即使执行完所有查询,也无法使 nums 变为零数组。

示例 3:

输入: nums = [1,2,3,2,1], queries = [[0,1,1],[1,2,1],[2,3,2],[3,4,1],[4,4,1]]

输出: 4

解释:

对于查询 0 (l = 0, r = 1, val = 1):
将下标 [0, 1] 的值减 1。
数组变为 [0, 1, 3, 2, 1]。
对于查询 1 (l = 1, r = 2, val = 1):
将下标 [1, 2] 的值减 1。
数组变为 [0, 0, 2, 2, 1]。
对于查询 2 (l = 2, r = 3, val = 2):
将下标 [2, 3] 的值减 2。
数组变为 [0, 0, 0, 0, 1]。
对于查询 3 (l = 3, r = 4, val = 1):
将下标 4 的值减 1。
数组变为 [0, 0, 0, 0, 0]。因此,最小的 k 值为 4

示例 4:

输入: nums = [1,2,3,2,6], queries = [[0,1,1],[0,2,1],[1,4,2],[4,4,4],[3,4,1],[4,4,5]]

输出: 4

提示:
1 <= nums.length <= 10
0 <= nums[i] <= 1000
1 <= queries.length <= 1000
queries[i] = [li, ri, vali]
0 <= li <= ri < nums.length
1 <= vali <= 10

2) .算法思路

我们首先注意到nums长度最长才只有10,我们可以枚举每一个询问,处理出一个arr[i],arr[i]也是一个数组,代表了的位置i有哪些询问可以使用,并且是有序的。然后对于每一个询问可以处理的位置,加到对应的位置的数组中即可。

然后对于每一个位置,就是判断选择这些询问,是否能够凑出这个位置的值,并且是在用到的询问最小的情况下,那么这就是01背包,对每一个位置我们都求一个01背包,定义f[i][j]代表了前i个询问是否能够凑出j。然后对于每一个位置求出的最少询问数求最大值。

需要注意的是,这道题如果num所有元素为0的时候,是直接返回0,需要判断一下,有点坑。


3) .算法步骤

1.创建一个二维数组arr,用于记录每个点能够被哪些区间覆盖。对于每个查询,遍历区间中的点,将该点与对应的查询值存储在arr中。

2.对于每个点,遍历其覆盖的区间,使用动态规划来尝试凑出该点的值。定义一个二维数组f,其中f[i][j]表示在考虑前i个区间的情况下,是否可以凑出值为j。

3.初始化f[0][0] = True,表示不选取任何区间时,可以凑出值为0。

4.对于每个区间,考虑选取或不选取该区间。如果选取该区间,则需要判断是否可以凑出值为j - arr[k][i][1]。

5.如果最终在考虑完所有区间后,可以凑出该点的值,则记录下使用的区间索引,并更新cur的值。

6.最终返回mx + 1,其中mx表示使用的区间索引的最大值。


4).代码示例

class Solution:
    def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:
        if max(nums) == 0:
            return 0
        n = len(nums)
        m = len(queries)
        arr = [[] for _ in range(n)]
    
        #每个点能够被哪一些区间覆盖
        for i in range(m):
            l, r, val = queries[i]
            for j in range(l, r + 1):
                arr[j].append((i, val))
        #对于每一个点,问题转换为了在尽量少用的情况下,凑出nums[i]
        mx = -inf
        for k,x in enumerate(nums):
            f = [[False] * (x + 1) for _ in range(len(arr[k]) + 1)]
            f[0][0] = True
            cur = inf
            for i in range(len(arr[k])):
                for j in range(x + 1):
                    #不选
                    f[i + 1][j] = f[i][j]
                    #选
                    if j - arr[k][i][1] >= 0:
                        f[i + 1][j] |= f[i][j - arr[k][i][1]] 
                if f[i + 1][x]:
                    cur = min(cur,arr[k][i][0])
                    break
            else:
                return -1
            mx = max(mx,cur)
        return mx + 1

5).总结

这个算法主要是通过动态规划来解决问题。首先,对于每个点,算法找出它能够被哪些区间覆盖。然后,问题转化为对于每个点,如何在尽量少的情况下凑出该点的值。


试题链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴天学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值