LeetCode|动态规划入门三题

一、爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

解题思路

本题是动态规划里面最简单的题目了,印象中第一次见到这个题目还是在蓝桥杯的练习题中。
虽然本篇是动态规划的入门文章,但是我并不会说任何书面上写的动态规划算法的定义。这是因为动态规划的概念还是很复杂的,看完动态规划算法的思路和特性估计你就被动态规划吓到了。
关于动态规划的定义,我只说一句个人看法——动态规划求的是全局最优解。全局意味着动态规划是要全盘考虑问题的。话不多说,我们先从题目解答中开始体会。

本题中,我们看到,爬1阶梯只有1种方法,爬2阶梯有2种方法。那我们就能想到,爬3阶梯有3种,这是因为爬3阶等于爬2阶的基础上再爬1阶。同理爬4阶等于在3阶的基础上爬1阶和2阶的基础上爬2阶;

这种“站在巨人的肩膀上”的思路就是动态规划的思想。

实现代码

首先我们能想到的是使用递归的方法。递归方法跟我们上面的思路正好反过来。递归的思路是爬n阶楼梯的方式等于爬n-1阶楼梯的次数加上n-2阶楼梯的次数。但是递归方法不会保存计算结果,导致有很多重复的计算,因此超时也就不可避免了。

1. 递归
class Solution {
    public int climbStairs(int n) {
        if(n==1) return 1;
        if(n==2) return 2;
        return climbStairs(n-1)+climbStairs(n-2);
    }
}

运行结果:超时

2. 动态规划

动态规划的整体思路跟递归很像,但是会通过一个dp数组保存计算结果。这样计算速度就会快很多。

class Solution {
    public int climbStairs(int n) {
        int[] steps=new int[n];
        if(n==1){
            return 1;
        }
        steps[0]=1;
        steps[1]=2;
        for(int i=2;i<n;i++){
            steps[i]=steps[i-1]+steps[i-2];
        }
        return steps[n-1];
    }
}

二、连续子数组的最大和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1:

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

示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [0]
输出:0

示例 4:

输入:nums = [-1]
输出:-1

示例 5:

输入:nums = [-100000]
输出:-100000

提示:

1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105

进阶: 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

解题思路

动态规划是本题的最优解。动态规划的思路也很清晰。假设有一个数组[-2,1],那么我们可以看到[-2,1]两数的和比数组第二个元素1要小。因此连续子数组的最大和为1。当数组变为[-2,1,-3]的时候,我们已经知道[-2,1]这部分数组的连续子数组的最大和为1。那么1跟[-3]比较是1比较大,因此这个数组的连续子数组的最大和依然为1。

简而言之,每次数组新增一个元素,都需要跟增加前的数组的连续子数组的最大和相比较,结果较大的那个就是新增后数组的连续子数组的最大和。代码实现如下:

实现代码

class Solution {
    public int maxSubArray(int[] nums) {
        int dp[]=new int[nums.length];
        dp[0]=nums[0];
        int max=dp[0];
        for(int i=1;i<dp.length;i++){
            //compare the num last status plus current num and current num 
            dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
            //compare max and dp[i],it will update max
            max=Math.max(max,dp[i]);
        }
        return max;
    }
}

三、游戏币组合

硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)

示例1:

输入: n = 5
输出:2
解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1

示例2:

输入: n = 10
输出:4
解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

说明:

注意:

你可以假设:

0 <= n (总金额) <= 1000000

解题思路

这道题的状态转移方程似乎不那么容易看出来,不过我们可以列一个表格来观察一下,看能不能发现一点什么规律。

硬币数\总面值 1 2 3 4 5 6 7 8 9 10
1 1 0 0 0 1 0 0 0 0 1
2 0 1 0 0 0 1 0 0 0 1
3 0 0 1 0 0 0 1 0 0 0
4 0 0 0 1 0 0 0 1 0 0
5 0 0 0 0 1 0 0 0 1 0
6 0 0 0 0 0 1 0 0 0 1
7 0 0 0 0 0 0 1 0 0 0
8 0 0 0 0 0 0 0 1 0 0
9 0 0 0 0 0 0 0 0 1 0
10 0 0 0 0 0 0 0 0 0 1
f(n) 1 1 1 1 2 2 2 2 2 4

首先我们定义一个dp数组和所有的硬币数组

 int[] dp = new int[n + 1];
 int[] coins = {1,5,10,25};

并且设置dp[0]=1,为边界的条件,作为完美能被一个硬币表示的情况为 1。
dp[i]+=dp[i-coin]当i-coin为0时结果为1,表示一个硬币能表示的情况。

我们可以简单的得出状态转移方程

// 题目中需求对结果取模1000000007
dp[i] = dp[i] + dp[i - coin] 

代码实现

class Solution {
    public int waysToChange(int n) {
       int dp[]=new int[n+1];
       int coins[]={1,5,10,25};
       dp[0]=1;

        for(int coin : coins) {
            for(int i = coin; i <= n; i++) {
                dp[i] = (dp[i] + dp[i - coin]) % 1000000007;
            }
        }
        return dp[n];
    }
}
posted @ 2021-07-13 16:43  六层楼  阅读(168)  评论(0编辑  收藏  举报