牛客剑指offer刷题动态规划篇

连续子数组的最大和

题目

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [5,4,-1,7,8]
输出:23
LeetCode链接

思路

以nums = [-2,1,-3,4,-1,2,1,-5,4]数据为例
采用动态规划的思想,将以上问题转化为如下子问题:

  1. 求以-2结尾的连续子数组的最大值;
  2. 求以1结尾的连续子数据的最大值;
  3. 求以-3结尾的连续子数组的最大值;
  4. 求以4结尾的连续子数组的最大值;
  5. 求以-1结尾的连续子数组的最大值;
  6. 求以2结尾的连续子数组的最大值;
  7. 求以1结尾的连续子数组的最大值;
  8. 求以-5结尾的连续子数组的最大值;
  9. 求以4结尾的连续子数组的最大值;
    然后求以上子问题的最大值,即为所得结果;

我们这里再求子问题2:求以1结尾的连续子数据的最大值;
那对应的子数组只有2组,分别为[-2,1]和[1],很明显最大值数组为后者,那对应到公式应该为
Math.max(prev + num[i],num[i]),其中prev表示以-2结尾的连续子数组的最大值,num[i]对应当前下标值;
更加详细讲解可参考:LeetCode 经典动态规划问题

代码实现
   public int maxSubArray(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int prev = 0;
        int result = nums[0];
        for(int num:nums){
            prev = Math.max(prev + num,num);
            result = Math.max(prev,result);
        }
      
        return result;
    }

连续子数组的最大和(二)

题目

描述
输入一个长度为n的整型数组array,数组中的一个或连续多个整数组成一个子数组,找到一个具有最大和的连续子数组。
1.子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组
2.如果存在多个最大和的连续子数组,那么返回其中长度最长的,该题数据保证这个最长的只存在一个
3.该题定义的子数组的最小长度为1,不存在为空的子数组,即不存在[]是某个数组的子数组
4.返回的数组不计入空间复杂度计算

示例1
输入:
[1,-2,3,10,-4,7,2,-5]
返回值:
[3,10,-4,7,2]
说明:
经分析可知,输入数组的子数组[3,10,-4,7,2]可以求得最大和为18,故返回[3,10,-4,7,2]

示例2
输入:
[1]
返回值:
[1]

示例3
输入:
[1,2,-3,4,-1,1,-3,2]
返回值:
[1,2,-3,4,-1,1]
说明:
经分析可知,最大子数组的和为4,有[4],[4,-1,1],[1,2,-3,4],[1,2,-3,4,-1,1],故返回其中长度最长的[1,2,-3,4,-1,1]

思路

和上题整体思路一致,只不过需要在计算子问题的过程中,把子问题解对应的数组开始下标和结束下标记录下来;

代码实现
public int[] FindGreatestSumOfSubArray (int[] array) {
        if(array == null || array.length == 0){
            return new int[0];
        }
        int[] dp = new int[array.length];
        dp[0] = array[0];
        int max = dp[0];
        //start end表示所求数组开始与结束下标
        int start = 0;
        int end = 1;
        //当前一个子问题的最大值小于0时,需要重置开始位置,负责记录开始问题
        int temp = 0;
        for(int i = 1;i<array.length;i++){
            if(dp[i-1] <  0){
                dp[i] = array[i];
                temp = i;
            }else{
                dp[i] = dp[i-1]+array[i];
            }
          
            if(dp[i] >= max){
                max = dp[i];
                start = temp;
                end = i+1;
            }
        }
        
        return Arrays.copyOfRange(array,start,end);
      }

跳台阶

题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
LeetCode链接

思路

满足斐波那契数列性质;

代码实现
  public int climbStairs(int n) {
        if(n <= 0 ){
            return n;
        }
        if(n == 1 || n == 2){
            return n;
        }
        int one = 1;
        int two = 2;
        int result = 0;
        for(int i = 3;i <= n;i++){
            result = one+two;
            one = two;
            two = result;
        }
        return result;
    }

斐波那契数

题目

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。

答案需要取模 1e9+7(1000000007) ,如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:

输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
LeetCode链接

代码实现
public int fib(int n) {
        if(n == 0 || n == 1){
            return n;
        }
        int zero = 0;
        int one = 1;
        int result = 0;
        for(int i =2;i<=n;i++){
            result = (zero + one)%1000000007;
            zero = one;
            one = result;
        }
        return result;
    }

正则表达式匹配【搞不懂、没搞懂、真不会】

题目

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

示例 1:
输入:s = “aa”, p = “a”
输出:false
解释:“a” 无法匹配 “aa” 整个字符串。

示例 2:
输入:s = “aa”, p = “a*”
输出:true
解释:因为 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。

示例 3:
输入:s = “ab”, p = “."
输出:true
解释:".
” 表示可匹配零个或多个(‘*’)任意字符(‘.’)。
LeetCode链接

思路

正则表达式匹配(动态规划,清晰图解)

代码实现
  public boolean isMatch(String s, String p) {
        int m = s.length() + 1, n = p.length() + 1;
        boolean[][] dp = new boolean[m][n];
        dp[0][0] = true;
        // 初始化首行
        for(int j = 2; j < n; j += 2)
            dp[0][j] = dp[0][j - 2] && p.charAt(j - 1) == '*';
        // 状态转移
        for(int i = 1; i < m; i++) {
            for(int j = 1; j < n; j++) {
                if (p.charAt(j - 1) == '*') {
                    if (dp[i][j - 2]) dp[i][j] = true;                                            // 1.
                    else if (dp[i - 1][j] && s.charAt(i - 1) == p.charAt(j - 2)) dp[i][j] = true; // 2.
                    else if (dp[i - 1][j] && p.charAt(j - 2) == '.') dp[i][j] = true;             // 3.
                } else {
                    if (dp[i - 1][j - 1] && s.charAt(i - 1) == p.charAt(j - 1)) dp[i][j] = true;  // 1.
                    else if (dp[i - 1][j - 1] && p.charAt(j - 1) == '.') dp[i][j] = true;         // 2.
                }
            }
        }
        return dp[m - 1][n - 1];
    }

跳台阶扩展问题

题目

描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。

思路

找规律

台阶数跳法总数
11
22
34
48

可以发现,当前台阶跳法 = 前一台阶跳法 * 2;

代码实现
public int jumpFloorII (int number) {
        if(number == 1 || number == 2){
            return number;
        }
        int result = 2;
        for(int i =3;i<=number;i++){
            result = 2*result;
        }
        return result;
    }

矩形覆盖

题目

描述
我们可以用 21 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 21 的小矩形无重叠地覆盖一个 2n 的大矩形,从同一个方向看总共有多少种不同的方法?
注意:约定 n == 0 时,输出 0
比如n=3时,2
3的矩形块有3种不同的覆盖方法(从同一个方向看):
矩形覆盖
示例1
输入:
0
返回值:
0

示例2
输入:
1
返回值:
1

示例3
输入:
4
返回值:
5
牛客链接

思路

同样满足斐波那契数列性质;

代码实现
 public int rectCover(int target) {
        if(target <= 2){
            return target;
        }
        int one = 1;
        int two = 2;
        int result = 0;
        for(int i =3;i<= target;i++){
            result = one+two;
            one = two;
            two = result;
        }
        return result;
    }

买卖股票的最好时机(一)

题目

假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天
2.如果不能获取到任何利润,请返回0
3.假设买入卖出均无手续费
要求:空间复杂度 O(1),时间复杂度 O(n)
牛客链接

思路

假设数组i位置为股票最高点,则我们只需要在[0,i-1]范围内找出股票最低点,即可计算出最大利润;
因此我们需要在遍历数组的过程中,找出股价最低点,然后计算出对应利润比较得出利润最大值;

代码实现
public int maxProfit (int[] prices) {
        if(prices == null || prices.length < 2){
            return 0;
        }
        //假设 0位置为最低值,最大利润为1位置 - 0位置
        int min = prices[0];
        int maxProfit = prices[1] - min;
        for(int i = 2; i < prices.length;i++){
        	//遍历过程中找出i位置前的股票最低值
            if(prices[i-1] < min){
                min = prices[i-1];
            }
            int currentProfit = prices[i] - min;//不断比较利润,得到最大值
            if(currentProfit > maxProfit){
                maxProfit= currentProfit;
            }
        }
        //当最大利润小于0时返回0
        return maxProfit < 0?0:maxProfit;
    }

礼物的最大价值

题目

在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
如输入这样的一个二维数组,
[
[1,3,1],
[1,5,1],
[4,2,1]
]
那么路径 1→3→5→2→1 可以拿到最多价值的礼物,价值为12;
牛客链接

思路

只能方向向右或者向下,因此第m行第n列的值只能来自于m-1行n列或者m行n-1列再加上grid[m][n],所以可知maxSum[i][j] = Math.max(maxSum[i - 1][j], maxSum[i][j - 1]) + grid[i - 1][j - 1];

代码实现
 public int maxValue (int[][] grid) {
       if(grid == null){
        return 0;
       }
       int m = grid.length;
       int n = grid[0].length;
       int[][] max = new int[m+1][n+1];
       for(int i = 1;i<=m;i++){
        for(int j =1;j<=n;j++){
            max[i][j] = Math.max(max[i-1][j],max[i][j-1])+grid[i-1][j-1];
        }
       }
       return max[m][n];
    }

无重复字符的最长子串

题目

给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
LeetCode链接

思路

这里以 s = "abcabcbb"举例
采用动态规划思想,将上述问题分解为以下子问题,然后求出子问题的最大值;
定义j表示下标;

  1. 求j = 0时,不含有重复字符的最长子串的长度记为dp[0],即求s="a"时的情况;
  2. 求j = 1时,不含有重复字符的最长子串的长度dp[1],即求s="ab"时的情况;
  3. 求j = 2时,不含有重复字符的最长子串的长度dp[0],即求s="abc"时的情况;
  4. 求j = 3时,不含有重复字符的最长子串的长度dp[0],即求s="abca"时的情况;
  5. 求j = 4时,不含有重复字符的最长子串的长度dp[0],即求s="abcab"时的情况;
  6. 求j = 7时,不含有重复字符的最长子串的长度dp[7],即求s="abcabcbb"时的情况;

上述所有问题分为如下几种情况:

  1. j位置字符串,往前没有重复字符时,如s = “abc”,则dp[j] = dp[j -1] +1;
  2. j 位置字符串,往前存在重复字符,记录上一次出现相同字符的位置为i,则又应该分为如下情况:
    a. 当 j - i < dp [ j -1]时,说明重复字符串存在于dp[j-1]对应的字符串范围内,则dp[j] = j - i ;
    b.当 j - i > = dp [ j -1]时,说明重复字符串不存在于dp[j-1]对应的字符串范围内,则dp[j] = dp[j -1] +1;

我们设定重复字符上次出现的位置默认为-1;则1情况和2b情况可以统一,如s = "abc"计算;
因此最终状态转移方程如下:
j - i (j - i < dp [ j -1]时)
dp[j] = dp[j -1] +1 (j - i > = dp [ j -1]时)
LeetCode大神解法

代码实现
public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0){
            return 0;
        }
        HashMap<Character,Integer> hashMap = new HashMap<>();
        int res = 0;
        int currentLength = 0;
        for(int i = 0; i < s.length(); i++){
            int index = hashMap.getOrDefault(s.charAt(i),-1); //字符i上一次出现的位置,默认为-1
            currentLength = currentLength < i - index ? currentLength + 1 : i - index; //dp[j-1]和i - index对比
            res = Math.max(res,currentLength); //计算各个dp[i]对应的最大值
            hashMap.put(s.charAt(i),i); //将当前字符存入hash表,便于后续重复字符查找
        }
        return res;
    }

把数字翻译成字符串

题目

现有一串神秘的密文 ciphertext,经调查,密文的特点和规则如下:

密文由非负整数组成
数字 0-25 分别对应字母 a-z
请根据上述规则将密文 ciphertext 解密为字母,并返回共有多少种解密结果。

描述
有一种将字母编码成数字的方式:‘a’->1, ‘b->2’, … , ‘z->26’。

现在给一串数字,返回有多少种可能的译码结果

示例1
输入:
“12”
返回值:
2
说明:
2种可能的译码结果(”ab” 或”l”)

示例2
输入:
“31717126241541717”
返回值:
192
说明:
192种可能的译码结果

代码实现
 public int solve (String nums) {
        //用来保留前面两个字符位置对应的可能译码结果数
        int first = 1, second = 1;
        for(int i = 0;i < nums.length();i++){
            //遇到0不能译码,second清零
            if(nums.charAt(i) == '0'){
                second = 0;
            }
            //更新前两个字符对应的值
            //符合条件则可以译1个或者2个数字
            if(i >= 1 && Integer.parseInt(nums.substring(i-1,i+1)) <= 26){
                second = first + second;
                first = second - first;
            }
            //只能译1个数字
            else{
                first = second;
            }
        }
        return second;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值