LeetCode--动态规划基础概念总结和经典题目解析汇总

目录

Divide&Conquer + optimal substructure

动态规划关键点

题目一:斐波那契数

题目二:不同路径

题目三:最长公共子序列

题目四:三角形最小路径和

题目五:最大子序和

题目六:零钱兑换


Divide&Conquer + optimal substructure

1.动态规划和递归或者分治没有根本上的区别(关键看有无最优子结构)

2.共性:找到重复子问题

3.差异性:最优子结构,中途可以淘汰次优解

动态规划关键点

1.最优子结构

2.存储中间状态

3.递推公式(美其名曰:状态转移方程或者dp方程)

题目一:斐波那契数

(题目链接:https://leetcode-cn.com/problems/fibonacci-number/

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 

思路:使用动态规划即动态递推

class Solution {
    public int fib(int n) {
        if(n < 2){return n;}
        int a = 0;
        int b = 1;
        int sum = 0;
        for(int i =2;i<=n;i++){
            sum = a+b;
            a = b;
            b = sum;
        }
        return sum;
    }
}

题目二:不同路径

(题目链接:https://leetcode-cn.com/problems/unique-paths/

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

思路:从出发点有两种方案推进:1)要么往右走一步;2)要么往下走一步

也就是说每一个格子有多少种方案等于它右边格子的方案+它下面格子方案之和

显然最下面一行每个格子的方案都是1,最右边一列每个格子的方案也都是1.

class Solution {
    public int uniquePaths(int m, int n) {
        int f[][] = new int[m][n];
        for(int j =0;j<n;j++){
            f[m-1][j] = 1;
        }
        for(int i =0;i<m;i++){
            f[i][n-1] = 1;
        }
        for(int i =m-2;i>=0;i--){
            for(int j = n-2;j>=0;j--){
                f[i][j] = f[i+1][j]+f[i][j+1];
            }
        }
        return f[0][0];
    }
}

题目三:最长公共子序列

(题目链接:https://leetcode-cn.com/problems/longest-common-subsequence/

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace",它的长度为 3

思路:最长公共子序列问题,经验来讲,把两个字符串当成二维数组的行和列。

二维数组中每个点对应两个字符串此点之前对应的公共字串的长度

新建一个多一行多一列的数组,一个是为了初始化为0,一个不需要单独讨论索引为0的行和列的空串

递推公式:

if(c1 == c2){
    dp[i+1][j+1] = dp[i][j]+1;
}else{
   dp[i+1][j+1] = Math.max(dp[i+1][j],dp[i][j+1]);
}

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        for(int i=0;i<m;i++){
            for(int j = 0;j<n;j++){
                char c1 = text1.charAt(i);
                char c2 = text2.charAt(j);
                if(c1 == c2){
                    dp[i+1][j+1] = dp[i][j]+1;
                }else{
                    dp[i+1][j+1] = Math.max(dp[i+1][j],dp[i][j+1]);
                }
            }
        }
        return dp[m][n];
    }
}

题目四:三角形最小路径和

(题目链接:https://leetcode-cn.com/problems/triangle/

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标

相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

示例 1:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2
  3 4
 6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

思路:采用动态规划,自底向上,逐层上升。

每一层取相应的最小路径。

动态递推方程为:dp[j]= Math.min(dp[j],dp[j+1])+triangle.get(i).get(j)

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int[] dp = new int[triangle.size()+1];
        for(int i=triangle.size()-1;i>=0;i--){
            for(int j =0;j<triangle.get(i).size();j++){
                dp[j]= Math.min(dp[j],dp[j+1])+triangle.get(i).get(j);
            }
        }
        return dp[0];
    }
}

题目五:最大子序和

(题目链接:https://leetcode-cn.com/problems/maximum-subarray/

给定一个整数数组 nums ,找到一个具有最大和的

连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 

思路:找到递推方程:

最大子序和 = 当前元素自身值 或者 包含之前元素后的值。

dp方程为:f[i] = max( f [i-1] , 0) + a[i]

class Solution {
    public int maxSubArray(int[] nums) {
        int maxsum=nums[0];
        for(int i =1;i<nums.length;i++){
            nums[i] = nums[i]+Math.max(0,nums[i-1]);
            if(nums[i]>maxsum){
                maxsum = nums[i];
            }
        }
        return maxsum;
    }
}

题目六:零钱兑换

(题目链接:https://leetcode-cn.com/problems/coin-change/

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。

如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。

示例 1:

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

思路:采用动态规划。

dp方程为f(n) = min {f(n-k) , for k  in 硬币数组} +1 

编程思路:

先初始化dp table各个元素为amount +1(Integer.MAX_VALUE也行),若最后dp[amount]的值还是初始化的值代表不存在兑换的情况

在变量dp时候,如果金额可以凑出,则进行次数更新,若凑不出,则不更新。

class Solution {
    public int coinChange(int[] coins, int amount) {
        int maxvalue = amount+1;
        int[] dp = new int[amount+1];
        Arrays.fill(dp,maxvalue);
        dp[0] = 0;
        for(int i=1;i<amount+1;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 ? -1 : dp[amount];
    }
}

猜你喜欢

转载自blog.csdn.net/yezonghui/article/details/112055518