动态规划(入门级)


动态规划(dynamic programming),以下简称dp,本质是带了记忆+枚举,分析时从上至下(深度搜索),dp的思考方式是从底至上,少数从上至下。

1.整数拆分

给定一个正整数?n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 ×3 ×4 = 36。

思路:
n拆分成至少两个以上的数

f(n) = i*f(n-i) //对n-i继续拆分
f(n) = f(i)(n-i) //对i继续拆分
f(n) = f(i)f(n-i) //对i和n-i继续拆分

i与n-i对称,考虑其中一种,i*(n-i)和if(n-i)包括所有情况,也包括f(i)f(n-i) ,所以f(n) = max(i(n-i),if(n-i))
在这里插入图片描述

递归树解法

int dfs(n){
    if(n<2){
        return 0;
    }
	if(n==2){
		return 1;
	}
	//枚举i
	int res = 0;
	for(int i=1;i<n;i++){
		res = max(res,max(i*(n-i),i*dfs(n-i)));
	} 
	return res;
} 

带记忆的递归

int dfs(n){
    if(n<2){
        return 0;
    }
	if(n==2){
		return 1;
	}

	if(memo[n]!=-1){
		return meno[n];
	} 
    //枚举i
	int res = 0;
	for(int i=1;i<n;i++){
		res = max(res,max(i*(n-i),i*dfs(n-i)));
	} 
	return meno[n] = res;
} 

//动态规划
1、确定状态(子问题) ,拆与不拆,拆,n拆成i,n-i,
2、确定转移方程(原问题与子问题的关系),n-i是否继续拆? 是dp[n-i],否n-i,那么
dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]))

3、确定base(边界条件)
0,1不能拆(原子数),dp[0] = dp[1] = 1;dp[2] = 2;2=1+1, 1*1
初始化dp[i] = 0; i>=2; 未拆,数小于2时,乘积设置为0

dp[2] = max(dp[2],1*(2-1),1*dp[2-1]) = max(0,1,1) = 1;
dp[3] = max(dp[3],1*(3-1),1*dp[3-1]) = max(0,2,1*dp[2]) = 2; 即:3=1+2,1*2=2 

dp代码

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1,0);
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            int cur = 0;
            for(int j=1;j<i;j++){
                cur = max(cur,max(j*(i-j),j*dp[i-j]));
            }
            dp[i] = cur;
        }
        return dp[n];
    }
};

题目连接:

类似的:剪绳子

2.丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

分析:由自底之上的方法,维护三条有序链表,p2,p3,p5分别是2,3,5的倍数,合并三个有序数组,每次取最小的数

nums2 = {1*2, 2*2, 3*2, 4*2, 5*2, 6*2, 8*2...}
nums3 = {1*3, 2*3, 3*3, 4*3, 5*3, 6*3, 8*3...}
nums5 = {1*5, 2*5, 3*5, 4*5, 5*5, 6*5, 8*5...}

那么 dp[i] = min(dp[p2]*2,dp[p3]*3,dp[p5]*5),每次将最小的数的指针++,对应的表格:

index02134
p201,min(1×2,1×3,1×5),p2++12,min(2×2,2×3,1×5),p2++
p3011,min(2×2,1×3,1×5),p3++1
p50000
nums1234
class Solution {
private:
    int min(int a,int b){
        return a<b?a:b;
    }
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n,0);
        int p2=0,p3=0,p5=0;
        dp[0] = 1;

        for(int i=1;i<n;i++){
            int minvalue = min(dp[p2]*2,min(dp[p3]*3,dp[p5]*5));
            dp[i] = minvalue;
            if(minvalue%2==0){
                p2++;
            }
            if(minvalue%3==0){//同时被2和3整除的数会被执行,比如6
                p3++;
            }
            if(minvalue%5==0){
                p5++;
            }
        }
        return dp[n-1];
    }
};

3.完全平方数

题目链接

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

1 <= n <= 10^4

分析:

定义:dp![i]![j] 表示前i个完全平方数组成数字j的最小数量,问题转化为完全背包,并且是恰好装满,状态是第i个完全平方数放或者不放

状态转移方程:

d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − i ∗ i ] + 1 ) , i f ( i ∗ i < j ) d p [ i ] [ j ] = d p [ i − 1 ] [ j ] i f ( i ∗ i > = j ) dp[i][j] = min(dp[i-1][j],dp[i][j-i*i]+1),if(i*i<j) dp[i][j] = dp[i-1][j] if(i*i>=j) dp[i][j]=min(dp[i1][j],dp[i][jii]+1),if(ii<j)dp[i][j]=dp[i1][j]if(ii>=j)
base:

dp![0]![0] = 1;其余dp![i]![j]=INF,因为是求最小

状态压缩:
d p [ j ] = m i n ( d p [ j ] , d p [ j − i ∗ i ] + 1 ) dp[j] = min(dp[j],dp[j-i*i]+1) dp[j]=min(dp[j],dp[jii]+1)
dp代码:

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,65535);
        dp[0] = 0;
        for(int i=0;i<=n;i++){//背包
            for(int j=1;j*j<=i;j++){//物品
                dp[i] = min(dp[i],dp[i-j*j]+1);
            }
        }
        return dp[n];
    }
};

思路2:

BFS+贪心

在这里插入图片描述

构建N叉树

(1) 节点的值(即余数)也是一个完全平方数。
(2) 在满足条件(1)的所有节点中,节点和根之间的距离应该最小。

使用队列将数字n入队列,减去n以内完全平方数,得到余数13-1,13-4,13-9,再将余数入队列,如果遇到剩余队列的数字都是完全平方数,则找到一组解

在这里插入图片描述

需要用set对每层的余数进行去重

class Solution {
public:
    int numSquares(int n) {
        set<int> s;
        s.insert(n);
        vector<int> squreNum;
        for(int i=1;i*i<=n;i++){
            squreNum.push_back(i*i);
        }
        int level=0;
        while(!s.empty()){
            set<int> s1;
            level += 1;
            for(int reminder:s){
                for(int snum : squreNum){
                    if(snum==reminder){
                        return level;
                    }else if(snum>reminder){
                            break;  //不能在减做余数
                    }else{
                        s1.insert(reminder-snum);
                    }
                }
            }
            s = s1;
        }
        return level;
    }
};

4.零钱兑换

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

示例 1:

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

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0
示例 4:

输入:coins = [1], amount = 1
输出:1
示例 5:

输入:coins = [1], amount = 2
输出:2

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
链接:https://leetcode-cn.com/problems/coin-change

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //思考,硬币是无限的,是一个完全背包问题
        //amount是背包的总容量
        //凑成是一个,恰好装满的完全背包问题,初始化dp[0]=0,其他为无穷大,因为是求最小

        //定义,dp[i],容量为i时所需要的最少钱币数
        //dp[i] = min(dp[j],dp[j-nums[i]]+1)//放或不放

        if(amount == 0){
            return 0;
        }

        vector<int> dp(amount+1,0);
       
        for(int i=0;i<=amount;i++){
            dp[i] = 65535;
        }
        
        dp[0] = 0;

        for(int i=0;i<coins.size();i++){//枚举物品
            for(int j=coins[i];j<=amount;j++){//完全背包正向枚举背包容量
                dp[j] = min(dp[j],dp[j-coins[i]]+1);
            }
        }

        return dp[amount]!=65535?dp[amount]:-1;
    }
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值