对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
JZ42 连续子数组的最大和
思路:五部曲
- 确定dp数组(dp table)以及下标的含义
dp[i]:包括下标i的最长连续子序和 - 确定递推公式
- 加入当前array[i]的子序和 dp[i] = dp[i-1]+array[i]
- 当前元素 array[i]
- dp数组如何初始化
初始化dp[0] = array[0] - 确定遍历顺序
dp的更新需要前一个dp信息,从前往后遍历
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型
*/
public int FindGreatestSumOfSubArray (int[] array) {
// write code here
int result = array[0];//结果
int[] dp = new int[array.length]; //dp数组
dp[0]=array[0];
for(int i=1;i<array.length;i++){
//状态转移方程
dp[i] = Math.max(dp[i-1]+array[i],array[i]);
if(dp[i]>result){
result = dp[i];
}
}
return result;
}
}
JZ85 连续子数组的最大和(二)
思路:动态规划部分和上题一致,该题需要存储最长的子数组。
使用两个指针来记录当前最大和的区间,再用两个指针记录最长的区间,每次更新max的时候更新区间。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] FindGreatestSumOfSubArray (int[] array) {
// write code here
int[] dp = new int[array.length];
dp[0] = array[0];
int max = array[0];
int left=0,right=0;//滑动区间
int resl=0,resr=0;//记录最长区间
for(int i=1;i<array.length;i++){
right++;
dp[i] = Math.max(dp[i-1]+array[i],array[i]);
if(array[i]>(dp[i-1]+array[i])){
left=right;
}
if(dp[i]>max || dp[i]==max && (right-left+1)>(resr-resl+1)){ //记录最大和以及更新区间
max = dp[i];
resl=left;
resr=right;
}
}
int[] res = new int[resr-resl+1];
for(int i=resl;i<=resr;i++){
res[i-resl]=array[i];
}
return res;
}
}
JZ69 跳台阶
思路:
- dp[i]代表上i级台阶的跳法
- dp[i] = dp[i-1]+dp[i-2]
- dp[0]=1; dp[1]=1;
- 从前往后遍历
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param number int整型
* @return int整型
*/
public int jumpFloor (int number) {
// write code here
int[] dp = new int[number+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=number;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[number];
}
}
JZ10 斐波那契数列
[图片]
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @return int整型
*/
public int Fibonacci (int n) {
// write code here
if(n==1){
return 1;
}else if(n==2){
return 1;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
JZ71 跳台阶扩展问题
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param number int整型
* @return int整型
*/
public int jumpFloorII (int number) {
// write code here
int[] dp = new int[number+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=number;i++){
dp[i] = 2*dp[i-1];
}
return dp[number];
}
}
JZ70 矩形覆盖
import java.util.*;
public class Solution {
public int rectCover(int target) {
if(target<=2){
return target;
}
return rectCover(target-1)+rectCover(target-2);
}
}
JZ63 买卖股票的最好时机(一)
思路:暴力双for循环遍历,用二维数组[i,j]记录第i天买入,第j天卖出的利润,max记录当前最大的利润。(超时)
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param prices int整型一维数组
* @return int整型
*/
public int maxProfit (int[] prices) {
// write code here
int n = prices.length;
int[][] dp = new int[n][n];
int max = 0;
for(int i=0;i<n;i++){ // buy
for(int j=i+1;j<n;j++){ // sale
dp[i][j] = prices[j] - prices[i];//利润
if(dp[i][j]>max){
max = dp[i][j];
}
}
}
return max;
}
}
贪心算法:因为股票就买卖一次,那么贪心的想法很自然就是取最左最小值,取最右最大值,那么得到的差值就是最大利润。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param prices int整型一维数组
* @return int整型
*/
public int maxProfit (int[] prices) {
// write code here
int n = prices.length;
if(n==0){
return 0;
}
int res = 0;
int min = prices[0];
for(int i=1;i<n;i++){ // buy
if(prices[i]<min){
min = prices[i];
}
res = Math.max(res,prices[i]-min);
}
return res;
}
}
动态规划:参考牛客网
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param prices int整型一维数组
* @return int整型
*/
public int maxProfit (int[] prices) {
// write code here
int n = prices.length;
if(n==0){
return 0;
}
int[][] dp = new int[n][2];
dp[0][0] = 0; //第一天不持股,收益为0
dp[0][1] = -prices[0]; //第一天持股,收益为-当日股价
for(int i=1;i<n;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);//当前不持股,当日卖掉/没买/卖掉了
dp[i][1] = Math.max(dp[i-1][1],-prices[i]);//当前持股,今日买入/之前买入
}
int res = dp[n-1][0];
return res;
}
}
JZ47 礼物的最大价值
思路:使用二维数组记录从左上角到[i,j]的最大价值,此处的上一位只能是由上面和左边获得。
import java.util.*;
public class Solution {
public int maxValue (int[][] grid) {
int m = grid.length;
int n = grid[0].length;
//第一列只能来自上方
for(int i = 1; i < m; i++)
grid[i][0] += grid[i - 1][0];
//第一行只能来自左边
for(int i = 1; i < n; i++)
grid[0][i] += grid[0][i - 1];
//遍历后续每一个位置
for(int i = 1; i < m; i++)
for(int j = 1; j < n; j++)
//增加来自左边的与上边的之间的较大值
grid[i][j] += Math.max(grid[i - 1][j], grid[i][j - 1]);
return grid[m - 1][n - 1];
}
}
JZ48 最长不含重复字符的子字符串
思路:如果对于某个前面的子串,如果我们新加入一个字符,与前面的都不重复,那么最长无重复子串肯定就是在前面的基础上加1;如果与前面重复了,那就是当前位置减去它重复之前字符出现的位置的长度。因此我们使用动态规划递推。
重复与否的判断,使用哈希表,如果存在于哈希表就是重复。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return int整型
*/
public int lengthOfLongestSubstring (String s) {
// write code here
HashMap<Character,Integer> mp = new HashMap<>();
int res = 0 ;
int[] dp = new int[s.length()+1];
for(int i=1;i<=s.length();i++){
dp[i]=1;
if(!mp.containsKey(s.charAt(i-1))){ //哈希表不存在该字符
dp[i] = dp[i-1]+1;
}else{
dp[i] = Math.min(dp[i-1]+1,i-mp.get(s.charAt(i-1)));
}
mp.put(s.charAt(i-1),i);
res = Math.max(res,dp[i]);
}
return res;
}
}
JZ46 把数字翻译成字符串
思路:
对于普通数组1-9,译码方式只有一种,但是对于11-19,21-26,译码方式有可选择的两种方案,因此我们使用动态规划将两种方案累计。
- dp[i]代表第i个位置时字符串的译码方式数量
- 初始化dp[1] = 1
- nums[i-1,i]是两位数的时候(11-19,21-27),回退到前两位,得出两位译码数量和一位译码数量,dp[i] = dp[i-1]+dp[i-2];
- 否则,比如为10/20/>27,dp[i] =dp[i-1];
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 解码
* @param nums string字符串 数字串
* @return int整型
*/
public int solve (String nums) {
// write code here
if(nums.equals("0")){
return 0;
}
if(nums == "10" && nums =="20"){
return 1;
}
for(int i=1;i<nums.length();i++){
if(nums.charAt(i) == '0'){
if(nums.charAt(i-1)!='1' && nums.charAt(i-1)!='2'){
return 0;
}
}
}
int[] dp = new int[nums.length()+1];
Arrays.fill(dp,1);
for(int i=2;i<=nums.length();i++){
if((nums.charAt(i - 2) == '1' && nums.charAt(i - 1) != '0') || (nums.charAt(i - 2) == '2' && nums.charAt(i - 1) > '0' && nums.charAt(i - 1) < '7')){
dp[i] = dp[i-1]+dp[i-2];
}else{
dp[i] = dp[i-1];
}
}
return dp[nums.length()];
}
}