【二叉树】【动态规划】1、斐波那契数+2、零钱兑换

1、遍历:在遍历的过程中就能够解决问题,只需要递归函数的参数即可。

2、子树:只有在遍历完成之后才能解决问题,还需要递归函数的返回值。(需要在后序位置写代码)

动态规划:子树 核心思想是穷举求最值
动态规划三要素:

cpp 复制代码
正确的状态转移方程
具有最优子结构
存在重叠子问题(暴力穷举效率很低,需要使用备忘录(dp table)来优化穷举过程)

明确状态,明确选择,定义dp数组

回溯算法:树枝

DFS算法:节点

1、斐波那契数(难度:简单)

该题对应力扣网址

AC方法和对应代码

1、暴力递归穷举

重复计算太多

时间复杂度是O(2的n次方)

cpp 复制代码
class Solution {
public:
    //暴力递归穷举
    int fib(int n) {
        if(n==0 || n==1){
            return n;
        }
        return fib(n-1)+fib(n-2);
    }
};

2、带备忘录的递归解法

使用备忘录数组或者字典(这里用的数组)来记录每次已经算过的fib(n),避免重复计算

时间复杂度是O(n)

cpp 复制代码
class Solution {
public:
    //带备忘录的递归解法
    int fib(int n) {
        int nums[n+1];
        memset(nums,-1,sizeof(nums));
        return dp(nums, n);
    }
    int dp(int nums[], int n){
        if(n==0 || n==1){
            nums[n]=n;
        }
        //备忘录
        if(nums[n]!=-1){
            return nums[n];
        }
        return dp(nums,n-1)+dp(nums,n-2);
    }
};

3、dp数组的迭代(递推)解法(for循环)

前面两种方法都是自顶向下,然后最后通过返回值将答案返回给上级,本质上是自顶向下的思路。

这种方法是自底向上,仍然使用备忘录(数组)来辅助完成推算。

(注意:声明一个n+2的数组int dp[n+2],因为dp[0]=0,dp[1]=1,当n<2的时候,不这么定义会出现数组下标溢出的情况。)

cpp 复制代码
class Solution {
public:
    int fib(int n) {
        int dp[n+2];
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

以上斐波那契数 的题目也不算严格意义上的动态规划题目,只因为涉及到重叠子问题的消除。

暴力解的优化方法是用备忘录或者dp table

斐波那契数还有优化方法,可以将时间复杂度降为o(1)

把dp table的大小从n缩小到n

2、零钱兑换(难度:中等)

(看不懂啊看不懂,狗头保命)

该题对应力扣网址

动态规划递归模板

cpp 复制代码
# 自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
    for 选择 in 所有可能的选择:
        # 此时的状态已经因为做了选择而改变
        result = 求最值(result, dp(状态1, 状态2, ...))
    return result

动态规划迭代模板

cpp 复制代码
# 自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

超出时间限制

由于重复计算,导致超时严重。。

cpp 复制代码
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        return dp(coins,amount);
    }
    //状态是amount,即本题中的变量
    //选择:能够使状态发生变化,本题中指的是不同面值的硬币及个数
    //dp函数,函数参数包含状态,函数返回值是题目需要计算的值
    int dp(vector<int>& coins, int amount){
        if(amount==0){
            return 0;
        }
        if(amount<0){
            return -1;
        }
        int res=INT_MAX;
        //对所有可能的选择
        for(int coin: coins){
            int subsum=dp(coins,amount-coin);
            if(subsum==-1)continue;
            res=min(res,subsum+1);
        }
        return res==INT_MAX?-1:res;
    }
};

AC代码(递归+备忘录)

设置一个dp数组来作为备忘录

写的时候出现了两个问题:

1、备忘录数组的初始化问题,memset(memo,-1,sizeof(memo))用这个初始化方法初始化后不对~~(原理我之后再补)~~

2、一开始加上备忘录之后还是超时,后来发现备忘录数组其实和最后的res是一个值,所以应该放在返回值的地方再确定备忘录数组的值。

cpp 复制代码
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //定义一个备忘录
        // int memo[amount+1]={-2};
        int* memo = new int[amount + 1];
        for (int i = 0; i <= amount; ++i) {
            memo[i] = -2;
        }
        // memset(memo,-1,sizeof(memo));
        // cout<<"尺寸:"<<sizeof(memo)<<endl;
        // cout<<"初始化:"<<memo[0]<<endl;
        return dp(memo,coins,amount);
    }
    //
    int dp(int memo[], vector<int>& coins, int amount){
        if(amount==0){
            return 0;
        }
        if(amount<0){
            return -1;
        }
        if(memo[amount]!=-2){
            return memo[amount];
        }

        int subsum;
        int res=INT_MAX;
        for(int coin: coins){
            // if(amount-coin>=0 && memo[amount-coin]!=-2){
            //     res=memo[amount-coin];
            // }
            subsum=dp(memo,coins,amount-coin);
            if(subsum==-1)continue;
            res=min(res,subsum+1);
        }
        if(res==INT_MAX){
            res=-1;
        }
        memo[amount]=res;
        return res;
    }
};

AC代码(迭代+dp数组)

看完题解了解完主要思路之后,终于把这个代码复现下来了,发现代入具体例子之后,才懂了在什么地方求最小值等等。

做的时候有个地方,就是int+int超出了数据范围报错,于是改成了相减amount-i-coin,解决了超限问题。

cpp 复制代码
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //迭代+dp数组
        int *dp = new int[amount+1];
        //base case dp[0]
        for(int j=1;j<=amount;j++){
            dp[j]=-1;
        }
        dp[0]=0;
        int res=INT_MAX;
        //进行状态转移
        //循环所有的状态1,原递归的参数
        for(int i=0;i<=amount;i++){
            //循环所有状态2
            for(int coin:coins){
                if(dp[i]==-1)continue;
                res=dp[i]+1;
                if((amount-i-coin)<0)continue;
                if(dp[i+coin]!=-1){
                    dp[i+coin]=min(res,dp[i+coin]);
                }
                else{
                    dp[i+coin]=res;
                }

                //dp[1]=dp[0]+1=1
                //dp[2]=dp[0]+1=1
                //dp[5]=dp[0]+1=1
                
                //dp[1+1]=dp[2]=dp[1]+1=2
                //dp[1+2]=dp[3]=dp[1]+1=2
                //dp[1+5]=dp[6]=dp[1]+1=2
                
                //dp[2+1]=dp[3]=dp[2]+1=3
                //dp[2+2]=dp[4]=dp[2]+1=3
                //dp[2+5]=dp[7]=dp[2]+1=3
                
                //...
                
                //dp[6+5]=dp[6]+1=3
                
                //例如:
                //dp[2]=1=2
            }
            
        }
        return dp[amount];
    }
};

从二叉树过来的,听说二叉树的思路可以延伸出来动态规划和回溯等算法,动态规划看了好几天的讲解,现在还是迷糊,费老大劲根据题解写完了两道题,总算有点思路了。

相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海1 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
搬砖的小码农_Sky7 小时前
C语言:数组
c语言·数据结构
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
先鱼鲨生9 小时前
数据结构——栈、队列
数据结构
一念之坤9 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年9 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3