LeetCode 剑指 Offer II 动态规划(五) 专题总结(完结,附动态规划经典公式)

前言:动态规划专题最后一篇,已完结,附动态规划经典公式

往期文章 :

动态规划常见问题:
*************************************************************************************************************************************************

1、组合问题:
377. 组合总和 Ⅳ
494. 目标和
518. 零钱兑换 II

2、True、False问题:
139. 单词拆分
416. 分割等和子集

3、最大最小问题:
474. 一和零
322. 零钱兑换

组合问题公式

dp[i] += dp[i-num]

True、False问题公式

dp[i] = dp[i] || dp[i-num]

最大最小问题公式

dp[i] = min(dp[i], dp[i-num]+1) 或 dp[i] = max(dp[i], dp[i-num]+1)

当然拿到问题后,需要做到以下几个步骤:

  1. 分析是否为背包问题。
  2. 是以上三种背包问题中的哪一种。
  3. 是0-1背包问题还是完全背包问题。也就是题目给的nums数组中的元素是否可以重复使用。
  4. 如果是组合问题,是否需要考虑元素之间的顺序。需要考虑顺序有顺序的解法,不需要考虑顺序又有对应的解法。

背包问题技巧:

  1. 如果是0-1背包,即数组中的元素不可重复使用,nums放在外循环,target在内循环,且内循环倒序;
for(int i = 0; i < nums.size(); i++) {
	for(int j = target; j >= 0; j--) {
	
	}
}
  1. 如果是完全背包,即数组中的元素可重复使用,nums放在外循环,target在内循环。且内循环正序。
for(int i = 0; i < nums.size(); i++) {
	for(int j = nums[i]; j <= target; j++) {
		
	}
}
  1. 如果组合问题需考虑元素之间的顺序,需将target放在外循环,将nums放在内循环。
for(int i = 1; i <= target; i++) {
			for(int j = 0; j < nums.size(); j++) {
				
			}
		}

*************************************************************************************************************************************************

103. 最少的硬币数目

题目:

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

思路:

完全背包:取任意n种东西无限个,使结果为最大(最小)
我们采用自下而上的方式进行思考。定义 f(i) 为组成金额 i 所需最少的硬币数量,假设在计算 f(i) 之前,我们已经计算出 f(0)−f(i−1) 的答案。 则 f(i)对应的转移方程应为
f[j] = min(f[j], f[j - coins[i]] + 1);
其中coins[i]] 代表的是第 i 枚硬币的面值,即我们枚举最后一枚硬币面额是 coins[i]] ,那么需要从 j - coins[i] 这个金额的状态 f[j - coins[i]] 转移过来,再算上枚举的这枚硬币数量 1 的贡献,由于要硬币数量最少,所以 f(i) 为前面能转移过来的状态的最小值加上枚举的硬币数量 1。
假设 coins = [1, 2, 3], amount = 6
在这里插入图片描述

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();
        vector<int> dp(amount+1, amount+1);
        dp[0] = 0;
        for(int i = 0; i < n; i++) {
            for(int j = coins[i]; j <= amount; j++) {
                dp[j] = min(dp[j], dp[j - coins[i]] + 1);
            }
        }
        return dp[amount] == amount+1? -1 : dp[amount];
    }
};

104. 排列的数目

题目:

给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。

题目数据保证答案符合 32 位整数范围。

示例:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

进阶:如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

思路:

爬楼梯问题
楼梯的阶数一共为target,一次可以走的步数为nums[i]。 一共有多少种走法?
转化成这种思想直接搞定,要注意的点就是C++数据会溢出
两种解决方案:

  1. dp改成unsigned类型
  2. dp时增加判断条件:两数之和不超过32位整数
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        // 当不选取任何元素时,元素之和才为 0,因此只有 1 种方案
        dp[0] = 1;
        for(int i = 1; i <= target; i++) {
            for(int j = 0; j < nums.size(); j++) {
                // 如果不加第二个判断条件就会溢出,
                // 解决方法:1. dp改成unsigned类型
                // 2. 加上下面第二个判断条件,两数之和不超过32位整数,题目已经保证不超过32位整数范围
                if(i >= nums[j] && dp[i] < INT_MAX - dp[i - nums[j]])
                    dp[i] = dp[i] + dp[i - nums[j]];
            }
        }
        return dp[target];
    }
};
  • 39
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 37
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 37
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只小逸白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值