动态规划:用空间代替重复计算,包含一整套原理和技巧的总和,课程会用非常大的篇幅来全盘介绍
知道怎么算的算法 vs 知道怎么试的算法
有些递归在展开计算时,总是重复调用同一个子问题的解,这种重复调用的递归变成动态规划很有收益 如果每次展开都是不同的解,或者重复调用的现象很少,那么没有改动态规划的必要 下节课会举例,哪些递归没有必要改动态规划的必要
任何动态规划问题都一定对应着一个有重复调用行为的递归
所以任何动态规划的题目都一定可以从递归入手,逐渐实现动态规划的方法
已解答
中等
相关标签
相关企业
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days
的数组给出。每一项是一个从 1
到 365
的整数。
火车票有 三种不同的销售方式 :
一张 为期一天 的通行证售价为 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;j91. 解码方法
已解答
中等
一条包含字母
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+1467. 环绕字符串中唯一的子字符串
已解答
中等
定义字符串
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;i940. 不同的子序列 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; // 返回最终结果取模 }动态规划的大致过程:
想出设计优良的递归尝试(方法、经验、固定套路很多),有关尝试展开顺序的说明 ->记忆化搜索(从顶到底的动态规划),如果每个状态的计算枚举代价很低,往往到这里就可以了 ->严格位置依赖的动态规划(从底到顶的动态规划),更多是为了下面说的 进一步优化枚举做的准备 ->进一步优化空间(空间压缩),一维、二维、多维动态规划都存在这种优化 ->进一步优化枚举也就是优化时间(本节没有涉及,但是后续巨多内容和这有关)
解决一个问题,可能有很多尝试方法 众多的尝试方法中,可能若干的尝试方法有重复调用的情况,可以转化成动态规划 若干个可以转化成动态规划的方法中,又可能有优劣之分 判定哪个是最优的动态规划方法,依据来自题目具体参数的数据量 最优的动态规划方法实现后,后续又有一整套的优化技巧
本系列课程从【必备】到【扩展】到【挺难】都会讲动态规划,会把这一话题做全面的讲述