文章目录
动态规划
主要思想
若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。动态规划往往用于优化递归问题,例如斐波那契数列,如果运用递归的方式来求解会重复计算很多相同的子问题,利用动态规划的思想可以减少计算量。
动态规划法仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量,
一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。
解题步骤
- 确定动态规划状态
- 写出状态转移方程(画出状态转移表)
- 考虑初始化条件
- 考虑输出状态
- 考虑对时间,空间复杂度的优化(Bonus)
算法应用
1、leetcode674.最长连续递增序列
题目描述:
给定一个未经排序的整数数组,找到最长且连续的的递增序列,并返回该序列的长度。
示例 1:
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
示例 2:
输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。
注意:数组长度不会超过10000。
程序代码(Java版)
class Solution {
public static int findLengthOfLCIS(int[] nums) {
if(nums.length == 0) return 0;
int [] dp = new int[nums.length];
dp[0] = 1;
int res = dp[0];
for(int i=1; i<nums.length; i++){
if(nums[i] > nums[i-1]){
dp[i] = dp[i-1] + 1;
}else {
dp[i] = 1;
}
res = Math.max(res, dp[i]);
}
return res;
}
}
2、leetcode5. 最长回文子串
题目描述:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
解题思路
首先定义字符串 s 从下标 i 到下标 j 的字串为 P(i, j),若 s 从下标 i 到下标 j 的字串为回文串,则 P(i, j)=true,否则 P(i, j) = false;
当字符串长度小于2时,肯定是回文;
想知道 P(i,j)的情况,只需要知道 P(i+1,j−1)的情况,再结合s[i] == s[j]进行判断;
当有回文时,就需要判断最长的回文了,比较每次得到的回文长度,赋值得到最大的回文和起始的索引。
程序代码(Java版)
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n < 2) return s;
int max_len = 1;
int start = 0;
boolean dp[][]=new boolean[n][n];
for(int i=0; i<n; i++){
dp[i][i] = true;
}
for(int j=1; j<n; j++){
for(int i=0; i<j; i++){
if(s.charAt(i) == s.charAt(j)){
if(j-i<3){
dp[i][j] = true;
}else{
dp[i][j] = dp[i+1][j-1];
}
}else{
dp[i][j] = false;
}
if(dp[i][j]){
int length = j-i+1;
if(length > max_len){
max_len = length;
start = i;
}
}
}
}
return s.substring(start, start+max_len);
}
}
3、leetcode516. 最长回文子序列
题目描述:
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1:
输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 "bbbb"。
示例 2:
输入:
"cbbd"
输出:
2
一个可能的最长回文子序列为 "bb"。
解题思路:
初始状态确定:dp[i][j]表示字符串s最长回文子序列的长度,子问题即是求每个回文子字符串的长度。
状态转移方程:当s[i] = s[j]时,就说明在原先的基础上又增加了回文子序列的长度,dp[i][j] = dp[i+1][j-1] + 2;当s[i]不等于 s[j]时,就说明s[i]、s[j]至少有一个不在回文子序列中,即我们只需要取s[i-1,j-1]加上s[i]或者s[j]的数值中较大的,即dp[i][j] = max(dp[i][j-1],dp[i+1][j]);
由题意可知:一个字符就能构成一个回文子序列,且长度为1,故dp[i][j] = 1,此时i一直是小于j的,不存在i大于j的情况。所以我们的dp表如下表:
由上可知,我们需要从下往上的遍历顺序。
程序代码(Java版)
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
if(n == 0) return 0;
int [][] dp = new int[n][n];
for(int i=0; i<n; i++){
dp[i][i] = 1;
}
//从下向上遍历
for(int i=n-1; i>=0; i--){
for(int j=i+1; j<n; j++){
if(s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}
}
}
//返回右上角位置的状态就是最大长度
return dp[0][n-1];
}
}
4、leetcode72.编辑距离
题目描述:
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
程序代码(Java版)
class Solution {
public int minDistance(String word1, String word2) {
int row = word1.length();
int col = word2.length();
//dp[i][j]表示word1[0...i]变为word2[0...j]所使用的最少操作数
int[][] dp = new int[row+1][col+1];
for(int j = 0; j <= col; j++){
dp[0][j] = j; //插入字符
}
for(int i = 0; i <= row; i++){
dp[i][0] = i; //删除字符
}
for(int i = 1; i <= row ; i++){
for(int j = 1; j <= col; j++){
//末尾字符相同,不需要编辑.(对应字符相等,不操作.)(注意下标,dp[][]下标为从1开始,字符串下标从0开始)
if(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
//需要操作,对应操作分别为:替换,删除,插入
dp[i][j] = 1 + Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1]));
}
}
}
return dp[row][col];
}
}