动态规划专题(一维动态规划)

16.从递归入手一维动态规划

动态规划:用空间代替重复计算,包含一整套原理和技巧的总和,课程会用非常大的篇幅来全盘介绍

知道怎么算的算法 vs 知道怎么试的算法

有些递归在展开计算时,总是重复调用同一个子问题的解,这种重复调用的递归变成动态规划很有收益 如果每次展开都是不同的解,或者重复调用的现象很少,那么没有改动态规划的必要 下节课会举例,哪些递归没有必要改动态规划的必要

任何动态规划问题都一定对应着一个有重复调用行为的递归

所以任何动态规划的题目都一定可以从递归入手,逐渐实现动态规划的方法

983. 最低票价

已解答

中等

相关标签

相关企业

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1365 的整数。

火车票有 三种不同的销售方式

  • 一张 为期一天 的通行证售价为 costs[0] 美元;

  • 一张 为期七天 的通行证售价为 costs[1] 美元;

  • 一张 为期三十天 的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回 你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。
static int [] day = new int[]{1,7,30};
​
static int [] dp = new int [366];
public static int mincostTickets(int[] days, int[] costs) {
    Arrays.fill(dp,0);
    dp[days.length-1] = Integer.MAX_VALUE;
    for (int i :costs){
        dp[days.length-1] = Math.min(i,dp[days.length-1]);
    }
    for (int i = days.length-2;i>=0;i--){
        int cost = Integer.MAX_VALUE;
        //来到当前i位置,i位置有三种选择,每一种都选,记录最小的那个
        for (int j = 0;j 
  

91. 解码方法

已解答

中等

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)

  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6""06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。
public static void main(String[] args) {
    System.out.println(numDecodings("06"));
}
static char [] str;
static int [] dp;
public static int numDecodings(String s) {
    str = s.toCharArray();
    dp = new int[s.length()+1];
    dp[str.length] = 1;
    if (str[str.length-1]=='0'){
        dp[str.length-1] = 0;
    }else {
        dp[str.length-1] = 1;
    }
    for (int index = str.length-2;index>=0;index--){
        int cha = (str[index]-'0')*10+str[index+1]-'0';
        if (str[index]=='0'){
            continue;
        }else if (cha<27){
            dp[index] = dp[index+1]+dp[index+2];
        }else {
            dp[index] = dp[index+1];
        }
    }
    return dp[0];
}

639. 解码方法 II

已解答

困难

一条包含字母 A-Z 的消息通过以下的方式进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 一条已编码的消息,所有的数字都必须分组,然后按原来的编码方案反向映射回字母(可能存在多种方式)。例如,"11106" 可以映射为:

  • "AAJF" 对应分组 (1 1 10 6)

  • "KJF" 对应分组 (11 10 6)

注意,像 (1 11 06) 这样的分组是无效的,因为 "06" 不可以映射为 'F' ,因为 "6""06" 不同。

除了 上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符,可以表示从 '1''9' 的任一数字(不包括 '0')。例如,编码字符串 "1*" 可以表示 "11""12""13""14""15""16""17""18""19" 中的任意一条消息。对 "1*" 进行解码,相当于解码该字符串可以表示的任何编码消息。

给你一个字符串 s ,由数字和 '*' 字符组成,返回 解码 该字符串的方法 数目

由于答案数目可能非常大,返回 109 + 7

示例 1:

输入:s = "*"
输出:9
解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。
可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。
因此,"*" 总共有 9 种解码方法。

public static long mod = 1000000007;
static long [] dp = new long[100000];
public static int numDecodings(String s) {
    char[] chars = s.toCharArray();
    Arrays.fill(dp,0);
    dp[chars.length]=1;
    for (int index = chars.length-1;index>=0;index--){
        long ans = 0;
        if (chars[index]=='0'){
            dp[index]=0;
            continue;
        }
        if (chars[index]=='*'){
            ans = 9*dp[index+1];
            if (index+1 
  

467. 环绕字符串中唯一的子字符串

已解答

中等

定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串,所以 base 看起来是这样的:

  • "...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....".

思路:统计以每个字符结尾的情况下的最长子字符串是多长,存储在数组中。

例如:abcdef

以f结尾的字符串长度为6,字串右6个,最后将所有的以每个字符结尾的字串加起来就是所有字串的数量。很难想

示例 1:

输入:s = "a"
输出:1
解释:字符串 s 的子字符串 "a" 在 base 中出现
static int [] arr = new int [26];
public int findSubstringInWraproundString(String s) {
    Arrays.fill(arr,0);
    char [] chars = s.toCharArray();
    arr[chars[0]-'a'] = 1;
    int length = 1;
    char cur = chars[0];
    for(int i = 1;i 
  

940. 不同的子序列 II

已解答

困难

给定一个字符串 s,计算 s不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余

字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

  • 例如,"ace""***a***b***c***d***e***" 的一个子序列,但 "aec" 不是。

示例 1:

输入:s = "abc"
输出:7
解释:7 个不同的子序列分别是 "a", "b", "c", "ab", "ac", "bc", 以及 "abc"。
 static int mod = 1000000007;
    //方法1
    public int distinctSubseqII1(String s) {
        long [] arr = new long [26];  // 用于记录以每个字符结尾的子序列数量
        char [] chars = s.toCharArray();
        long ans = 0;  // 最终答案
        for(int i = 0; i < chars.length; i++){
            long num = (ans + 1)%mod ;  // 以当前字符结尾的子序列总数
            long old  = arr[chars[i] - 'a'];// 上一次以当前字符结尾的子序列的总数
            ans = (ans + num - old + mod) % mod; // 因为现在统计以当前字符结尾的子序列的时候会统计之前的,所以要减去旧值
            //例如:abcc      第三个以c结尾的子序列 abc bc ac c  第四个以c结尾的   abcc abc bc ac c cc 会统计重复的,所以要减去旧的值
            arr[chars[i] - 'a'] = num;  // 更新当前字符结尾的子序列数量
        }
        return (int) ans % mod;  // 返回最终结果取模
    }

动态规划的大致过程:

想出设计优良的递归尝试(方法、经验、固定套路很多),有关尝试展开顺序的说明 ->记忆化搜索(从顶到底的动态规划),如果每个状态的计算枚举代价很低,往往到这里就可以了 ->严格位置依赖的动态规划(从底到顶的动态规划),更多是为了下面说的 进一步优化枚举做的准备 ->进一步优化空间(空间压缩),一维、二维、多维动态规划都存在这种优化 ->进一步优化枚举也就是优化时间(本节没有涉及,但是后续巨多内容和这有关)

解决一个问题,可能有很多尝试方法 众多的尝试方法中,可能若干的尝试方法有重复调用的情况,可以转化成动态规划 若干个可以转化成动态规划的方法中,又可能有优劣之分 判定哪个是最优的动态规划方法,依据来自题目具体参数的数据量 最优的动态规划方法实现后,后续又有一整套的优化技巧

本系列课程从【必备】到【扩展】到【挺难】都会讲动态规划,会把这一话题做全面的讲述

你可能感兴趣的:(动态规划,代理模式,算法)