【Java算法题】剑指offer_算法之02动态规划

对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

JZ42 连续子数组的最大和

[图片]

思路:五部曲

  1. 确定dp数组(dp table)以及下标的含义
    dp[i]:包括下标i的最长连续子序和
  2. 确定递推公式
  • 加入当前array[i]的子序和 dp[i] = dp[i-1]+array[i]
  • 当前元素 array[i]
  1. dp数组如何初始化
    初始化dp[0] = array[0]
  2. 确定遍历顺序
    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 跳台阶

[图片]

思路:

  1. dp[i]代表上i级台阶的跳法
  2. dp[i] = dp[i-1]+dp[i-2]
  3. dp[0]=1; dp[1]=1;
  4. 从前往后遍历
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()];
    }
}

3. 回溯

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoetu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值