力扣练习4.11

文章介绍了如何使用动态规划方法解决一系列与区间、背包问题以及路径计数相关的问题,包括合并重叠区间、最大化卡车装载箱数、计算目标和组合以及计算出界的路径数,展示了将这些问题转化为经典动态规划问题的解决方案。
摘要由CSDN通过智能技术生成

452. 用最少数量的箭引爆气球

不考虑y轴,可以将其转换为重叠区间的问题。将同属于一个重叠区间的小区间合并为一个区间,加上不重叠的区间,即是所求数量。
更加简化:如果是非重叠区间才加1,因为两个大的重叠区间间肯定是非重叠的。

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        if not points:
            return 0
        # 根据右区间排序
        points.sort(key=lambda x: x[1])
        # 结果,所需数量
        res = 1 # 初始化为1,这样当仅有一个区间时也能正确返回
        # 区间末尾
        end = points[0][1]
        # 遍历剩余区间
        for i in range(1, len(points)):
            # 如果当前区间左界超过了区间末尾,说明不重叠
            if points[i][0] > end:
                res += 1 # 结果加一
                end = points[i][1] # 更新区间末尾
        return res
        

1710. 卡车上的最大单元数

贪婪算法,每次优先使用最能装的箱子,也要判断箱子数和该类箱子数的大小。

class Solution:
    def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
        # 结果数
        res = 0
        # 根据单元数倒排
        boxTypes.sort(key=lambda x: x[1], reverse=True)
        # 索引
        i = 0
        # 限定循环条件
        while truckSize > 0 and i < len(boxTypes):
            # 如果说箱子数比该类箱子多,就使用全部该箱子
            if boxTypes[i][0] <=  truckSize:
                res += boxTypes[i][0] * boxTypes[i][1]
                truckSize -= boxTypes[i][0] # 更新箱子数
            # 否则,只能使用全部箱子数
            else:
                res += truckSize * boxTypes[i][1]
                truckSize = 0 # 更新箱子数
            i += 1 # 查找下一类箱子
        return res

494. 目标和

问题转化为0-1背包问题

题目要求我们计算通过在nums数组中的每个数字前添加+-,使得它们的总和等于给定的目标值target的方法数。实际上,这个问题可以转化为一个经典的0-1背包问题。

首先,假设我们把所有选用+的数字放入一个背包A中,所有选用-的数字放入另一个背包B中。那么,背包A的总和减去背包B的总和应该等于target。同时,背包A和背包B中数字的总和加起来就是nums数组中所有数字的总和,记为sum。基于这两个条件,我们可以得到以下两个等式:

  1. sumA - sumB = target
  2. sumA + sumB = sum

通过解这两个等式,我们可以得到 sumA = (target + sum) / 2。这意味着问题转化为了寻找nums数组中有多少种组合,使得这些组合的和等于(target + sum) / 2

解决0-1背包问题

这个转化后的问题是一个标准的0-1背包问题:给定一个容量为(target + sum) / 2的背包,nums数组中的每个数字都可以选择放入或不放入背包,求有多少种方法可以恰好填满背包。

我们可以使用动态规划来解决这个问题。定义一个一维数组dp,其中dp[j]表示用nums数组中的数字填满容量为j的背包的方法数。初始化dp[0] = 1,表示容量为0的背包有一种填法(即不放入任何数字)。

然后,对于nums数组中的每个数字num,我们更新dp数组:对于每个容量j(从(target + sum) / 2num),如果j >= num,则dp[j] += dp[j - num]。这表示如果当前数字可以放入容量为j的背包中,那么填满容量为j的背包的方法数就等于之前填满容量为j的方法数(不放入当前数字)加上填满容量为j - num的方法数(放入当前数字)。

代码实现
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        total_sum = sum(nums)
        if abs(target) > total_sum or (target + total_sum) % 2 != 0:
            return 0
        package_a = (target + total_sum) // 2
        dp = [0] * (package_a + 1)
        dp[0] = 1
        
        for num in nums:
            for j in range(package_a, num - 1, -1):
                dp[j] += dp[j - num]
        
        return dp[package_a]
示例

输入: nums = [1, 1, 1, 1, 1], target = 3 输出: 5 解释: 有五种方法使得和为3:

  • +1+1+1-1-1
  • +1+1-1+1-1
  • +1-1+1+1-1
  • -1+1+1+1+1
  • +1-1-1+1+1

通过将问题转化为0-1背包问题,并应用动态规划方法,我们可以高效地求解出结果。

1137. 第 N 个泰波那契数

使用动态规划,下面是直接规划

class Solution:
    def tribonacci(self, n: int) -> int:
        # 直接在状态数组dp中初始化前三个Tribonacci数
        dp = [0, 1, 1] + [0] * (n - 2)
        
        # 从第4个数开始计算(索引3),因为前三个数已经初始化
        for i in range(3, n + 1):
            dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1]
        
        return dp[n]

规避了要判断n是否小于3的操作,因为直接对dp的前三个进行赋值。
dp数组的前三个值被初始化为[0, 1, 1],这对应于n = 0、n = 1和n = 2的情况。如果n小于3,dp[n]将直接返回正确的结果,无需额外的条件判断。对于n >= 3的情况,for循环将按照Tribonacci数的定义计算每个数值,并存储在dp数组中。最后,直接返回dp[n]作为结果。

记忆化搜索方法:

class Solution:
    def tribonacci(self, n: int) -> int:
        # 保存记忆
        memo = [0 for _ in range(n+1)]
        return self.do(n, memo)

    def do(self, n, memo):
        if n == 0:
            return 0
        elif n < 3:
            return 1
        # 检查是否已经计算过
        if memo[n] != 0:
            return memo[n]
        # 如果没有被计算过
        memo[n] = self.do(n-3, memo) + self.do(n-2, memo) + self.do(n-1, memo)

        return memo[n]

576. 出界的路径数

题目概述

题目“576.
出界的路径数”要求计算一个点从网格中的特定位置开始,最多移动N步后,有多少种路径可以使它移出网格的边界。每一步,这个点可以向上、下、左、右四个方向之一移动。

解题思路

解决这个问题的关键是理解我们如何使用动态规划(DP)来跟踪每一步移动后各个位置的路径数量,并最终计算出所有导致出界的路径。

动态规划

  1. 定义状态: 我们定义一个二维数组dp[x][y]来表示当前步骤中从起始位置到达(x, y)的路径数。
  2. 状态转移: 对于每一步,我们根据前一步的dp数组计算当前步的dp数组。如果从(x, y)出发的下一步移动会导致出界,则将这部分路径数累加到最终的计数中。
  3. 初始化: 在第0步,我们将起始位置(i, j)的路径数设为1,因为在没有移动之前,存在一条路径停留在起点。

为了避免重复计算和节省空间,我们可以使用滚动数组技巧,即只维护两个二维数组交替表示当前步和前一步的状态。

边界处理

由于每一步移动都有可能导致点移出边界,我们需要在每一步检查四个方向的移动是否会导致出界。如果是,我们就把这次移动对应的路径数加到最终的计数中。

取模操作

由于路径数可能非常大,题目要求返回结果对10^9 + 7取模的值。这意味着每次累加路径数后,我们都应该对结果进行取模,以保证结果在合法的整数范围内。

代码实现

MOD = 10**9 + 7  # 定义模数,用于取模操作,防止整数溢出

class Solution:
    def findPaths(self, m: int, n: int, N: int, i: int, j: int) -> int:
        if N == 0: return 0  # 如果没有移动次数,直接返回0
        dp = [[0] * n for _ in range(m)]  # 初始化dp数组,存储每个位置的路径数
        dp[i][j] = 1  # 将起始位置的路径数设为1
        
        count = 0  # 初始化出界路径的计数器
        for step in range(1, N+1):  # 遍历每一步
            temp = [[0] * n for _ in range(m)]  # 初始化临时数组,用于计算下一步的路径数
            for x in range(m):  # 遍历每个位置
                for y in range(n):
                    for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:  # 遍历四个方向
                        nx, ny = x + dx, y + dy  # 计算新位置
                        if 0 <= nx < m and 0 <= ny < n:  # 如果新位置在网格内
                            temp[x][y] += dp[nx][ny]  # 累加路径数到临时数组
                        else:  # 如果新位置出界
                            count += dp[x][y]  # 累加当前位置的路径数到出界计数器
                        temp[x][y] %= MOD  # 对每一步的结果取模,防止溢出
            dp = temp  # 将临时数组的结果赋值给dp,为下一步做准备
        return count % MOD  # 返回最终的出界路径数,并对结果取模

示例

假设m = 2, n = 2, N = 2, i = 0, j = 0,则有6种路径可以在2步内从(0, 0)移出网格边界。

通过逐步应用动态规划并在每一步累加出界的路径数,我们可以找到从给定起始位置移动N步后出界的所有路径数。这种方法通过避免重复计算并优化空间使用,有效解决了问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

灵海之森

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

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

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

打赏作者

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

抵扣说明:

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

余额充值