算法学习|动态规划

学习材料声明

所有知识点都来自互联网,进行总结和梳理,侵权必删。
引用来源:计算机算法与设计分析(第5版)|山景城一姐|力扣动态规划|灵茶山艾府|灵茶山艾府-最长公共子序列|包教包会~最长公共子序列|灵茶山艾府-买卖股票的最佳时机【基础算法精讲 21】
动态规划找到子状态之间的关系很重要!| LeetCode:96.不同的二叉搜索树|

学习目的

算法考试怕不及格,以及长久以来对算法的恐惧。
因为算法问题(数学一直是自己的噩梦),失去了很多信心。
想要找到好工作。

什么是动态规划?

代码随想录|动规五部曲

  1. dp数组以及下标的含义
  2. 递推公式
  3. dp数组如何初始化
  4. 遍历顺序
  5. 打印dp数组

面向问题

  1. 动态基础
  2. 背包问题
  3. 打家劫舍
  4. 股票问题
  5. 子序列问题

1.自己叨叨叨

DP数组!
最小子问题-》初始化!
赋值规则-》遍历方向(从山景城一姐那里悟出来的!)。
Q1:到目前为止解题思路是:我知道这题是动态规划—》用动态规划的几个步骤解题。那么我要怎么在不知情情况下也能选择动态规划呢?
Q2:习题1和2,分别使用了二维和一维的dp数组,那么怎么确定用几维的动态数组呢?
A2:是状态决定的嘛?

2.回溯

例题| 17. 电话号码的字母组合

从灵茶山艾府老师那里学习到的。(我目前水平听up讲解,只是听个热闹。。思维完全跟不上。。)

  1. 回溯三问:当前操作?子问题是什么?下一个子问题是什么?
    算法学习|动态规划_第1张图片
  2. dfs(i)的意思是考虑下标>=i的元素的情况。

3.状态转移机

算法学习|动态规划_第2张图片

3.0-1背包

4.完全背包问题(物品可以无限次使用)|学习参考《代码随想录》

4.1问最大价值?

算法学习|动态规划_第3张图片

4.2问多少种方法?(遍历顺序决定是组合数还是排列数)

11|518. 零钱兑换 II(组合数)
12377. 组合总和 Ⅳ(排列数)
算法学习|动态规划_第4张图片

习题

1.动规基础

1.1|509. 斐波那契数

class Solution {
public:
    int fib(int n) {
        int dp[n+1];
        memset(dp, 0, sizeof(dp));
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2; i<=n; i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];

    }
};

1.2|70. 爬楼梯

这一题,经常在各种算法课堂是听到,自己也做过。但是总是忘记。每一次听人提起,都心虚不已。似乎是一题每个人都得知道的题目。是一题能够把我推离算法远远的题目。

class Solution {
public:
    int climbStairs(int n) {
        int dp[n+1];
        memset(dp, 0, sizeof(dp));
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        if(n==2){
            return 2;
        }
        dp[1]=1;
        dp[2]=2;
        for(int i=3; i<=n; i++){
            dp[i] = dp[i-1]+dp[i-2];//到达第i阶只有两种方式,跨1上来的,或者跨2上来的。
        }
        return dp[n];
    }
};

这里拓展一个问题,一次可以上1-m个台阶,问有几种方法?

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) { // 把m换成2,就可以AC爬楼梯这道题
                if (i - j >= 0) dp[i] += dp[i - j];
            }
        }
        return dp[n];
    }
};

1.3|746. 使用最小花费爬楼梯

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        int dp[n+1];
        memset(dp, 0, sizeof(dp));
        for(int i=2; i<=n; i++){
            dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]);
        }
        for(int i=0; i<=n; i++){
            cout<<dp[i]<<" ";
        }
        return dp[n];
    }
};

1.4|62. 不同路径|用时12min

class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[m][n];
        memset(dp, 0, sizeof(dp));
        for(int i=0; i<m; i++){
            dp[i][0] = 1;
        }
        for(int i=0; i<n; i++){
            dp[0][i] = 1;
        }
        
        for(int i=1; i<n; i++){
            for(int j=1; j<m; j++){
                dp[j][i] = dp[j-1][i] + dp[j][i-1];
            }
        }
        return dp[m-1][n-1];
    }
};

非常基础的动态规划问题,初始化条件和更新公式都在题目鲜明地给出了。就是动态顺序记住行列顺序!双重循环,以后还是用r,c比较合适。

1.6|63. 不同路径 II

与上一题唯一的区别在于有障碍物的话,路线次数直接为0;

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int rnum = obstacleGrid.size();
        int cnum = obstacleGrid[0].size();
        cout<<rnum<<" "<<cnum<<endl;
        int dp[rnum+1][cnum+1];
        memset(dp, 0, sizeof(dp));
        for(int i=0; i<rnum; i++){
            if(obstacleGrid[i][0]==1){
                break;
            }
            dp[i][0]=1;
        }
        for(int i=0; i<cnum; i++){
            if(obstacleGrid[0][i]==1){
                break;
            }
            dp[0][i]=1;
        }
        for(int i=1; i<rnum; i++){
            for(int j=1; j<cnum; j++){
                if(obstacleGrid[i][j]==1){
                    dp[i][j]=0;
                }else{
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }               
            }
        }
        return dp[rnum-1][cnum-1];

    }
};

1|5. 最长回文子串|用时42min

class Solution {
public:
    string longestPalindrome(string s) {
        //先初始化dp, 0-false, 1-true
        int len = s.length();
        int dp[len][len];
        string res;
        memset(dp, 0, sizeof(dp));
        for(int i=0;i<len;i++){
            dp[i][i] = 1; 
            res = s.substr(i ,1);
        }
        for(int i=0;i<len-1;i++){
           if(s[i] == s[i+1]){
               dp[i][i+1] = 1;
               res = s.substr(i ,2);
           } 
        }
        //动态赋值条件 dp[i][j] = (s[i] == s[j]) && (dp[i+1][j-1] == 1)
        for(int j=2; j<len; j++){
            for(int i=0; i<j-2+1; i++){
                // cout<
                if(s[i] == s[j] && dp[i+1][j-1] == 1){
                    dp[i][j] = 1;
                    
                } 
                if(dp[i][j] == 1 && abs(j-i+1)>res.length()){
                    res = s.substr(i, abs(j-i+1));

                }
            }
        }
        return res;
    }
};

想要小结一下,对于C++的确非常不熟悉,一直想要不要改用Java。可能二刷会用Java吧,毕竟Java才是自己的主力语言。
明明看过一姐的视频后刷题,但是因为最长子串的记录和C++的字符串语法问题,耽搁了很久。

2|198. 打家劫舍|用时11min

class Solution {
    public int rob(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 0);
        //初始化
        dp[0] = nums[0];
       
        for(int i=1; i<nums.length; i++){
            if(i==1){
                dp[1] = Math.max(nums[1], dp[i-1]);
            }else{
                dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
            }
        }
        return dp[nums.length-1];
    }
}

比较简单的动态规划问题,很像背包问题,给出的限制引导了赋值规则。
----------------------------------------------------------------------------2023年10月16日----------------------------------------------------------

4|300. 最长递增子序列

10min思考,没有合适的思路。找不到合适的更新公式。
子集型回溯思路 :(1)选或者不选?(2)枚举选哪个?算法学习|动态规划_第5张图片
官方题解给出的是枚举选哪个?

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 0);
        dp[0] = 1;
        int res = dp[0], ma;
        for(int i=1; i<n; i++){
            ma = 0;
            for(int j=i-1; j>=0; j--){
                if(nums[j]<nums[i]&&ma<dp[j]){
                    ma = dp[j];
                }
            }
            dp[i] = ma + 1;
            if(dp[i]>res)
                res=dp[i];
        }
        return res;
    }
}

----------------------------------------------------------------------------2023年10月17日----------------------------------------------------------

5|1143. 最长公共子序列|8min

一拿到题目,感觉和之前做的题目有些没办法套思路。去看了灵茶山艾府的视频,在递推公式和证明的部分没看懂(看了三遍),又去找了其他up主的视频,最后才弄明白。
其实思路还是很简单的,弄懂dp[i][j]表示的状态(s中前i个字母和t中前j个字母,的 最长公共子序列)。
递推公式,dp[i][j],根据s[i]和t[j]相等与否设计了两个递推公式,相等dp[i-1][j-1] +1,不相等,max(dp[i-1][j], dp[i][j-1])。四种状态,选其中一个,两不选,两都选。艾府的视频中介绍并证明了。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.length();
        int n2 = text2.length();
        int dp[n1+1][n2+1];
        memset(dp, 0, sizeof(dp));
        for(int r=1; r<=n1; r++)
        {
            for(int c=1; c<=n2; c++){
                if(text1[r-1] == text2[c-1]){
                    dp[r][c] = dp[r-1][c-1] + 1;
                }else{
                    dp[r][c] = max(dp[r-1][c],  dp[r][c-1]);
                }
            }
        }
        return dp[n1][n2];

    }
};

6|309. 买卖股票的最佳时机含冷冻期

依旧是思考10min出不来,怎么这么菜呀!
灵茶山艾府:状态转移进行分析。
一共两个状态,四个转移。

class Solution {
    public int maxProfit(int[] prices) {
        int n =prices.length;
        int[][] dp = new int[n+1][2];
        for(int i=0; i<=n; i++){
            Arrays.fill(dp[i], 0);
        }  
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[1][1] = -prices[0];
        dp[1][0] = 0;
        for(int i=2; i<=n; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i-1]);
        }
        // for(int i=0; i<=n; i++){
        //     System.out.println(dp[i][0]+" "+dp[i][1]);
        // }
        return Math.max(dp[n][0], dp[n][1]);
    }
}

算法学习|动态规划_第6张图片
----------------------------------------------------------------------------2023年10月19日----------------------------------------------------------

7|96. 不同的二叉搜索树

拿到题目,感觉和不同路径那题的走格子很像。非常明显的动态规划题。
二叉搜索树的构建。左<根<右;
但是我依旧没有想到怎么做,10min过去了,满脑子都是糊里糊涂的回溯。
每次都在纠结用一维数组还是二维数组(不能明确dp数组含义),递推公式是啥?
看了视频才知道dp[i]表示以i个数组成的搜索二叉树个数。这题不在乎每个结点具体数值的!切记!

class Solution {
public:
    int numTrees(int n) {
        int dp[n+1];
        memset(dp, 0, sizeof(dp));
        dp[0] = 1;
        for(int i=1; i<=n; i++){
            for(int j=1; j<=i; j++){
                dp[i] += (dp[j-1] * dp[i-j]);//左子树*右子树
            }
        }
        return dp[n];

    }
};

8|279. 完全平方数

class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n+1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2; i<=n; i++){   
            int a = (int)Math.sqrt(i);
            if(a*a==i){
                dp[i] = 1;
            }else{     
                for(int j=1; j<(int)i/2+1; j++){
                    int temp = dp[i-j] + dp[j];
                    if(temp<dp[i]){
                        dp[i] = temp;
                    }
                }
            }
        }
        return dp[n];
    }
}

----------------------------------------------------------------------------2023年10月20日----------------------------------------------------------

9|2140. 解决智力问题|40min

这题就差把动态规划四个字写在题目里了。子集型的问题,枚举还是选不选呢?
选择枚举法,时间复杂度很容易算出来是O(n^2),超时,代码如下:

class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        int dp[n];
        memset(dp, 0, sizeof(dp));
        int maxAns=0;
        dp[0] = questions[0][0];
        maxAns = dp[0];
        for(int i=1; i<n; i++){
            int maxFore = 0;
            for(int j=0; j<i; j++){
                if(questions[j][1]+j<i&&maxFore<dp[j])
                {
                    maxFore = dp[j];
                }
            }
            dp[i] = maxFore + questions[i][0];
            if(dp[i]>maxAns){
                maxAns = dp[i];
            }
        }
        return maxAns;
    }
};

随后按照官方题解,反向思考,状态转移方程变成

class Solution {
public:
    long long mostPoints(vector<vector<int>>& questions) {
        int n = questions.size();
        long long dp[n];
        memset(dp, 0, sizeof(dp));
        long long maxAns=0;
        dp[n-1] = questions[n-1][0];
        maxAns = dp[n-1];
        for(int i=n-2; i>=0; i--){
            long long  temp = questions[i][1]+i+1;
            if(temp>n-1){
                temp=0;
            }else{
                temp=dp[temp];
            }
            dp[i] = max(dp[i+1], questions[i][0]+ temp);
            if(dp[i]>maxAns){
                maxAns = dp[i];
            }
        }
        for(int i=0; i<n; i++){
            cout<<dp[i]<<" ";
        }
        return maxAns;
    }
};

10|322. 零钱兑换|50min

主要花费递归方程的编写上。

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
        Arrays.fill(dp, -1);
        dp[0] = 0;
        Arrays.sort(coins);
        for(int i=0; i<coins.length; i++){
            if(coins[i]<=amount)
                dp[coins[i]] = 1;
        }
        for(int i=coins[0]; i<=amount; i++){
            if(dp[i]==1){
                continue;
            }
            int temp = Integer.MAX_VALUE, flag=0;
            for(int j=0; j<coins.length; j++)
            {
                int cur = i - coins[j];
                if(cur<0)
                {
                    break;
                }
                if(dp[cur]>0){
                    flag=1;
                    if(temp>dp[cur]+1){
                        temp = dp[cur]+1;
                    }
                    dp[i] = temp;
                }

            }
            if(flag==0){
                dp[i] = -1;
            }

        }
        for(int i=1; i<=amount; i++){
            System.out.print(dp[i]+ " ");
        }
        return dp[amount];
    }
}

官方的代码更加简单:

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

----------------------------------------------------------------------------2023年10月21日----------------------------------------------------------

11|518. 零钱兑换 II

以为很简单的问题,找不到转移方程。。。看了代码随想录的视频之后才知道,自己懂得知识太少了,只是套了一个浅层的模版,稍微深层一点的知识就会难倒自己。

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int dp[amount+1];
        memset(dp, 0, sizeof(dp));
        dp[0] = 1;
        for(int j=0; j<coins.size(); j++){
            for(int i=coins[j]; i<=amount; i++){
                dp[i] += dp[i-coins[j]];
            }
        }
        for(int i=1; i<=amount; i++){
            cout<<dp[i]<<" "; 
        }
        return dp[amount];
    }
};

12377. 组合总和 Ⅳ

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target+1];
        Arrays.fill(dp, 0);
        dp[0] = 1;
        for(int i=0; i<=target; i++){
            for(int j=0; j<nums.length; j++){
                if(nums[j]<=i)
                    dp[i] += dp[i-nums[j]];
            }
        }
        return dp[target];
    }
}

13|474. 一和零

感觉是0-1问题又像是子集问题。0-1问题,先物品再背包,倒序。

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[] ones = new int[len];
        int[] zeros = new int[len];
        int[][] dp = new int[m+1][n+1];
        for(int i=1; i<=m; i++){
            Arrays.fill(dp[i], 0);
        }
        for(int i=0; i<len; i++){
            ones[i] = (int) strs[i].chars().filter(c -> c == '1').count();
            zeros[i] = (int) strs[i].chars().filter(c -> c == '0').count();
        }
        for(int s=0; s<len; s++){
            for(int i=m; i>=zeros[s]; i--){
                for(int j=n; j>=ones[s]; j--){
                    dp[i][j] = Math.max(dp[i][j], dp[i-zeros[s]][j-ones[s]]+1);
                }
            }
        }
        for(int i=0; i<=m; i++){
            for(int j=0; j<=n; j++){
                System.out.print(dp[i][j] + " ");
            }
            System.out.print("\n");
        } 
        return dp[m][n];
    }
}

----------------------------------------------------------------------------2023年10月22日----------------------------------------------------------

你可能感兴趣的:(算法学习,算法,学习,动态规划)