[java]——leetcode刷题(动态规划)(简单8中等11)(TBC)

目录

前言

妈的,深搜都刷不会.

简单

1. 1025. 除数博弈

题目描述:

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:
选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。
只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 False。假设两个玩家都以最佳状态参与游戏。

思路1:数学
当N=1时,输
当N=2时,赢
当N=3时,输
当N=4时,赢
当N=5时,输
猜想结论:奇数输,偶数赢
证明:
N=1和N=2时结论成立
N>2时,假设N<=k结论成立,则当N=k+1时
1, 如果k是奇数,那么k+1是偶数,此时的x可以是奇数也可以是偶数,那么x可以选择奇数,让k+1-x是奇数,让bob输. 偶数赢的结论成立.
2. 如果k是偶数,那么k+1是奇数,此时的x只能是奇数,选择x之后,k+1-x是偶数,此时bob必赢. 奇数输的结论也成立.

代码1:

class Solution {
    public boolean divisorGame(int N) {
        return N % 2 == 0;
    }
}

思路2:动态规划(重点)
当N时,我们选择一个x,那么f(N)就取决于f(N-x)了. 具体的,当 存在1<=x<N,f(N-x) = false,则f(N)=true, 否则f(N) = false

代码2:

class Solution {
    public boolean divisorGame(int N) {
        boolean[] f = new boolean[1001];
        f[1] = false;
        f[2] = true;
        for(int i=3;i<=N;i++){
            for(int j=1;j<i;j++){
                if(i%j==0&&f[i-j]==false){
                    f[i] = true;
                    break;
                }
            }
        }
        return f[N];
    }
}

2. 303. 区域和检索 - 数组不可变

题目描述:

给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。
实现 NumArray 类:
NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], … , nums[j]))

思路:啊,果断又看了题解,是一种"前缀和"的题目.状态转移方程是:sum(i,j) = pre_sum(j+1) pre_sum(i),即,pre_sum(i)中存储了0到i-1的和.

代码:

class NumArray {
    int[] pre_sum;
    public NumArray(int[] nums) {
        pre_sum = new int[nums.length+1];
        for(int i = 1;i<=nums.length;i++){
            pre_sum[i] = pre_sum[i-1] + nums[i-1];
        }
    }
    
    public int sumRange(int i, int j) {
        return pre_sum[j+1] - pre_sum[i];
    }
}

3. 剑指 Offer 42. 连续子数组的最大和

题目描述:

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。

思路:max[i] = Math.max(max[i], max[i]+max[i-1])(max[i]表示以第i个元素为结尾的最大连续和)
代码:

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0];
        for(int i = 1;i<nums.length;i++){
            nums[i] = Math.max(nums[i],nums[i]+nums[i-1]);
            ans = Math.max(ans, nums[i]);
        }
        return ans;
    }
}

4. 面试题 16.17. 连续数列

思路1:同3. 剑指 Offer 42. 连续子数组的最大和

思路2:分治

5. 746. 使用最小花费爬楼梯

题目描述:

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。
请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

思路:维护一个dp数组,dp[i]表示在第i个台阶的最低花费.由于第i个台阶只能从i-1或者i-2过来,所以dp[i]=Math.min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]).

代码:

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[1001];
        dp[0] = dp[1] = 0;
        for(int i = 2; i <=cost.length; i++){
            dp[i] = Math.min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]);
        }
        return dp[cost.length];
    }
}

6.面试题 17.16. 按摩师

题目描述:

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
注意:本题相对原题稍作改动

思路:dp[i][0] = max(dp[i-1][0], dp[i-1][1]); dp[i][1] = nums[i] + dp[i-1][0];

代码:

class Solution {
    public int massage(int[] nums) {
        if(nums.length==0) return 0;
        int dp0 = 0;
        int dp1 = nums[0];
        for(int i = 1; i < nums.length; i++){
            int ndp0 = Math.max(dp0, dp1);
            int ndp1 = nums[i] + dp0;

            dp0 = ndp0;
            dp1 = ndp1;
        }
        return Math.max(dp0, dp1);
    }
}

7. 392. 判断子序列

题目描述:

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

思路1:双指针
代码1:

class Solution {
    public boolean isSubsequence(String s, String t) {
        int s_len = s.length();
        int t_len = t.length();
        int i = 0;
        int j = 0;
        while(i<s_len && j<t_len){
            if(s.charAt(i) == t.charAt(j)) i++;
            j++;
        }
        return i == s_len;
    }
}

思路2:动态规划,dp[i][j]=i if j对应的字符正好第一次出现在字符串t的i这个位置上;dp[i][j]=dp[i+1][j] if 前面的条件不成立.即,dp[idx][j]保存了:在字符串t中,第idx位及其后面的子串中,dp[j]+'a’所对应的字符,第一次出现的位置. 所以每一次得到一个新的ss = s.charAt(i),只要去查看dp[idx][ss-‘a’]对应的转移下标.参考:https://leetcode-cn.com/problems/is-subsequence/solution/pan-duan-zi-xu-lie-by-leetcode-solution/
代码2:

class Solution {
    public boolean isSubsequence(String s, String t) {
        int s_len = s.length();
        int t_len = t.length();
        int[][] dp = new int[t_len+1][26];
        for(int j = 0; j < 26;j++){
            dp[t_len][j] = t_len;
        }
        for(int i = t_len - 1; i >= 0 ; i--){
            for(int j = 0; j < 26; j++){
                if(t.charAt(i) - 'a' == j){
                    dp[i][j] = i;
                }else{
                    dp[i][j] = dp[i+1][j];
                }
            }
        }
        int idx = 0;
        for(int i = 0; i < s_len; i++){
            if(dp[idx][s.charAt(i) - 'a']==t_len){
                return false;
            }
            idx = dp[idx][s.charAt(i) - 'a'] + 1;
        }
        return true;
    }
}

8.面试题 08.01. 三步问题

题目描述:

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

思路:
f(n) = f(n-1) + f(n-2) + f(n-3)

代码:

class Solution {
    public int waysToStep(int n) {
        int ans = 0;
        int a = 1;
        int b = 2;
        int c = 4;
        if(n == 1){
            return a;
        }else if(n==2){
            return b;
        }else if(n==3){
            return c;
        }else{
            for(int i = 4; i <= n; i++){
                ans = ((a + b ) % 1000000007 + c) % 1000000007;
                a = b;
                b = c;
                c = ans;
            }
        }
        return ans;
    }
}

中等

1. 338. 比特位计数

题目描述:

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

思路1:硬算
代码1:

class Solution {
    public int[] countBits(int num) {
        int[] all_ans = new int[num+1];
        for(int j = 0; j <=num; j++){
            all_ans[j] = cal(j);
        }
        return all_ans;
    }
    public int cal(int i){
        int ans = 0;
        while(i > 0){
            i = i&(i-1);
            ans ++;
        }
        return ans;
    }
}

思路2:bit(n) = bit(n - 最高有效数) + 1 主要的思想就是,对于bit(n),如何用bit(比n更小的数)来表示,方法一:保存最高有效位,比如8的二进制数是1000,9的二进制是1001,那么bit(9) = bit(8) + 1,10的二进制是1010,因为 10 - 8 = 2,2的二进制,正好是10的二进制减去了最高有效数8的最高位,所以bit(10) = bit(2) + 1。如何得到最高有效数?—— if i&(i-1) == 0 then i是最高有效数.
代码2:

class Solution {
    public int[] countBits(int num) {
        int[] ans = new int[num+1];
        int top = 0;
        for(int i = 1; i <= num; i++){
            if((i&(i-1))==0) top = i;
            ans[i] = ans[i-top] + 1;
        }
        return ans;
    }
}

思路3:bit(n) = bit(n >> 1) + n % 2 或者 bit(n) = bit(n >> 1) + n & 1 对于数字n,把n右移一位得到数字n’,分两种情况:1. n是偶数,则bit(n) = bit(n’) 2. n是奇数,则bit(n) = bit(n’) + 1。其中,右移可以使用n>>1来实现.n>>1 小于 n 一定成立.
代码3:

class Solution {
    public int[] countBits(int num) {
        int[] ans = new int[num+1];
        for(int i = 1; i <= num; i++){
            ans[i] = ans[i>>1] + i % 2;
        }
        return ans;
    }
}

思路4:bit(n) = bit(n&(n-1)) + 1对于数n,n&(n-1)表示把n的最低为1的那一位,变成0,n&(n-1)小于n一定成立.
代码4:

class Solution {
    public int[] countBits(int num) {
        int[] ans = new int[num+1];
        for(int i = 1; i <= num; i++){
            ans[i] = ans[i & (i-1)] + 1;
        }
        return ans;
    }
}

2.1641. 统计字典序元音字符串的数目

题目描述:

给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。
字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。

思路:
定义aeiou数组,aeiou[0]表示以’a’为结尾的情况数,aeiou[1]表示以’e’为结尾的情况数,aeiou[2]表示以’i’为结尾的情况数,aeiou[3]表示以’o’为结尾的情况数,aeiou[4]表示以’u’为结尾的情况数. 以aeiou[0]为例,它一定是上一个长度的aeiou[0],再在末尾加上一个’a’,得到的.

代码:

class Solution {
    public int countVowelStrings(int n) {
        int[] aeiou = new int[5];
        for(int i = 0;i < 5;i++){
            aeiou[i] = 1;
        }
        for (int j=2;j<=n;j++){
            aeiou[4] = aeiou[4] + aeiou[3] + aeiou[2] + aeiou[1] + aeiou[0];
            aeiou[3] = aeiou[3] + aeiou[2] + aeiou[1] + aeiou[0];
            aeiou[2] = aeiou[2] + aeiou[1] + aeiou[0];
            aeiou[1] = aeiou[1] + aeiou[0];
            aeiou[0] = aeiou[0];    
        }
        return aeiou[0] + aeiou[1] + aeiou[2] + aeiou[3] + aeiou[4];
    }
}

3. 877. 石子游戏

题目描述:

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

思路1:

用i和j定义石子堆的范围,dp[i][j]表示,当还剩i~j堆石头时,当前玩家与另外一位玩家的差值最大,这符合常理:dp[i][j]表示一位玩家,那么dp[i-1][j]和dp[i+1][j]等就表示的是另外一位玩家;考虑dp[0][length-1]表示最终两个人的最大差值,这与题目中"发挥出最佳水平"一致.状态转移方程dp[i][j]
= max(piles[i] - dp[i+1][j],piles[j] - dp[i][j-1])
.特别的,1. i要从大往前 2. j要从小往后.且只需要填满i<=j的部分.

代码1:

class Solution {
    public boolean stoneGame(int[] piles) {
        int[][] dp = new int[piles.length][piles.length];
        for(int i = 0; i < piles.length; i++){
            dp[i][i] = piles[i]; // 只有一堆石头
        }
        for(int i = piles.length - 2; i >= 0; i--){
            for(int j = i + 1; j < piles.length; j ++){
                dp[i][j] = Math.max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1]);
            }
        }
        return dp[0][piles.length-1] > 0;
    }
}

思路2:
一维数组的版本

代码2:

class Solution {
    public boolean stoneGame(int[] piles) {
        int[] dp = new int[piles.length];
        for(int i = 0; i < piles.length; i++){
            dp[i] = piles[i]; // 只有一堆石头
        }
        for(int i = piles.length - 2; i >= 0; i--){
            for(int j = i + 1; j < piles.length; j ++){
                dp[j] = Math.max(piles[i] - dp[j], piles[j] - dp[j-1]);
            }
        }
        return dp[piles.length-1] > 0;
    }
}

思路3:
假设一共n堆石头,将其下标标为0,1,2,3…n-1,奇数下标为一组(1),偶数下标为一组(2).初始化后,第0堆属于第二组,第n-1堆属于第一组. 1. 如果亚历克斯选择第0堆,那么剩下的下标为:1,2,3…n-1,此时可以发现,李要么选第1堆,要么选第n-1堆,两者都属于第一组. 选完之后,剩下的可能是:2(第二组),3,…n-1 (第一组)或者 1(第一组),2,…n-2(第二组),那么,亚历克斯又可以有选择第一组和第二组两种选择. 所以,在一开始我们计算是第一组的和更大,还是第二组,然后从一开始,亚历克斯就选择和更大的那一组中的石头堆,就一定可以取得胜利.

代码3:

class Solution {
    public boolean stoneGame(int[] piles) {
        return true;
    }
}

4.1314. 矩阵区域和

题目描述:

给你一个 m * n 的矩阵 mat 和一个整数 K ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:
i - K <= r <= i + K, j - K <= c <= j + K
(r, c) 在矩阵内。

预备知识:

设二维数组 A 的大小为 m * n,行下标的范围为 [1, m],列下标的范围为 [1, n]。
数组 P 是 A 的前缀和数组,等价于 P 中的每个元素 P[i][j]:
如果 i 和 j 均大于 0,那么 P[i][j] 表示 A 中以 (1, 1) 为左上角,(i, j) 为右下角的矩形区域的元素之和;
如果 i 和 j 中至少有一个等于 0,那么 P[i][j] 也等于 0。
数组 P 可以帮助我们在 O(1)O(1) 的时间内求出任意一个矩形区域的元素之和。具体地,设我们需要求和的矩形区域的左上角为 (x1, y1),右下角为 (x2, y2),则该矩形区域的元素之和可以表示为:
sum = A[x1…x2][y1…y2] = P[x2][y2] - P[x1 - 1][y2] - P[x2][y1 - 1] + P[x1 - 1][y1 - 1]

思路:左上角(x1,y1)和右下角(x2,y2)区域和的状态公式: sum = p(x2,y2)-p(x2,y1-1)-p(x1-1,y2)+p(x1-1,y1-1).特别的:mat[i][j](左上角(i,j),右下角(i,j)) = p(i,j) - p(i, j-1) - p(i-1, j) + p(i-1, j-1). 所以p(i,j)的转移公式为: p(i,j) = mat[i][j] + p(i, j-1) + p(i-1, j) - p(i-1, j-1). 所以,我们只需要正常的从小到大遍历就可以了.

代码:

class Solution {
    public int[][] matrixBlockSum(int[][] mat, int K) {
        // p(i,j) = mat[i][j] + p(i, j-1) + p(i-1, j) - p(i-1, j-1)
        int N = mat.length;
        int M = mat[0].length;
        int[][] p = new int[N+1][M+1];
        for(int i = 1; i <= N; i++){
            for(int j = 1; j <= M; j++){
                p[i][j] = mat[i-1][j-1] + p[i][j-1] + p[i-1][j] - p[i-1][j-1];
            }
        }
        int[][] answer = new int[N][M];
        for(int i = 0; i < N; i ++){
            for(int j = 0; j < M; j++){
                int x1 = Math.max(i - K, 0) + 1;
                int y1 = Math.max(j - K, 0) + 1;
                int x2 = Math.min(i + K, N-1) + 1;
                int y2 = Math.min(j + K, M-1) + 1;
                // sum = p(x2,y2)-p(x2,y1-1)-p(x1-1,y2)+p(x1-1,y1-1).
                answer[i][j] = p[x2][y2] - p[x2][y1-1] - p[x1-1][y2] + p[x1-1][y1-1];
            }
        }
        return answer;
    }
}

5. 1277. 统计全为 1 的正方形子矩阵

题目描述:

给你一个 m * n 的矩阵,矩阵中的元素不是 0 就是 1,请你统计并返回其中完全由 1 组成的 正方形 子矩阵的个数。

思路1:

按照**p(i,j) = mat[i][j] + p(i, j-1) + p(i-1, j) - p(i-1, j-1)**的公式构建前缀和数组p,按照 **sum = p(x2,y2)-p(x2,y1-1)-p(x1-1,y2)+p(x1-1,y1-1)**的公式,计算所有正方形的和. 如果和=(x2-x1)的平方,说明元素全都是1. 如何遍历所有正方形?——三重循环,变量1:c(1到min(M,N)),变量2:i(0到M-c),变量3:j(0到N-c),特别注意: int x2 = i + c - 1 + 1; int y2 = j + c - 1 + 1;

代码1:

class Solution {
    public int countSquares(int[][] matrix) {
        //p(i,j) = mat[i][j] + p(i, j-1) + p(i-1, j) - p(i-1, j-1)
        int N = matrix.length;
        int M = matrix[0].length;
        int[][] p = new int[N+1][M+1];
        for(int i = 1; i <= N; i++){
            for(int j = 1; j <= M; j++){
                p[i][j] = matrix[i-1][j-1] + p[i][j-1] + p[i-1][j] - p[i-1][j-1];
            }
        }
        int ans = 0;
        //sum = p(x2,y2)-p(x2,y1-1)-p(x1-1,y2)+p(x1-1,y1-1)
        int max_c = Math.min(N,M);
        for(int c = 1; c <= max_c; c++){
            for(int i = 0; i <= N-c; i++){
                for(int j = 0; j <= M-c; j++){
                    int x1 = i + 1;
                    int y1 = j + 1;
                    int x2 = i + c - 1 + 1;
                    int y2 = j + c - 1 + 1;
                    int sum = p[x2][y2] - p[x2][y1-1] - p[x1-1][y2] + p[x1-1][y1-1];
                    if(sum == c*c){
                        ans++;
                    }
                }
            }
        }
        return ans;
    }
}

思路2:https://leetcode-cn.com/problems/count-square-submatrices-with-all-ones/solution/tong-ji-quan-wei-1-de-zheng-fang-xing-zi-ju-zhen-f/

6. 714. 买卖股票的最佳时机含手续费

题目描述:

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

思路:

dp[i][0] 表示第i天结束后,手里没有股票的最大利润
没有股票,1.前一天也没有dp[i-1][0] 2.前一天有,今天卖掉了dp[i-1][1] + prices[i] - fee
dp[i][1] 表示第i天结束后,手里有股票的最大利润
有股票,1.前一天也有dp[i-1][1] 2.前一天没有,今天刚买dp[i-1][0] - prices[i]

代码:

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i = 1; i < prices.length; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i] - fee);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
        }
        return dp[prices.length-1][0];
    }
}

7. 96. 不同的二叉搜索树

题目描述:

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

思路:

   //F[i][n]:以i为根,长度为n的序列对应的二叉搜索树个数
    //G[n]:长度为n的序列对应的二叉搜索树个数
    //G[n] = /sum_i=1_n(F[i][n])
    //F[i][n] = G[i-1] * G[n-i]
    // G[n] = /sum_i=1_n(G[i-1] * G[n-i])
    // G[i] = /sum_j=1_i(G[j-1] * G[i-j])

代码:

class Solution {
    public int numTrees(int n) {
        int[] G = new int[n+1];
        G[0] = 1;
        G[1] = 1;
        for(int i = 2; i <= n ; i++){
            for(int j = 1; j <= i; j++){
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    }
}

思路2:

G[n] = /sum_i=1_n(G[i-1] * G[n-i])是卡塔兰数
得到: G[n+1] = G(n) * (4*n+2) / (n+2)

代码2:

class Solution {
    public int numTrees(int n) {
        long  C = 1;
        for(int i = 0; i < n; i++){
            C = C * (4 * i + 2) / (i + 2);
        }
        return (int)C;
    }
}

8. 剑指 Offer 47. 礼物的最大价值

题目描述:

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

思路:1

还是没有理解动态规划,我是这样想:对于每一个位置,可以从左边一个位置向右到达,可以从上面一个位置向下到达。然后我又傻逼地考虑到:左边或者上面那个位置,也可能有两种被到达的方式,所以我定义了dp[i][j][2]。

ans[i][j][0] 表示从上一个位置向右移动到达 = max(ans[i][j-1][0], ans[i][j-1][1]) + grid[i][j];ans[i][j][1] 表示从上一个位置向下移动到达 = max(ans[i-1][j][0], ans[i-1][j][1]) + grid[i][j];特殊位置:1. 上一个位置无法向右移动: 没有左一个位置,你自己的j就是0–>初始化 2. 上一个位置无法向下移动: 没有上一个位置,你自己的i就是0–>初始化。答案: max0([0],[1]);

代码1:

class Solution {
    public int maxValue(int[][] grid) {
        int[][][] ans = new int[grid.length][grid[0].length][2];
        ans[0][0][0] = grid[0][0];
        ans[0][0][1] = grid[0][0];
        // 一直向右:
        for(int j = 1; j < grid[0].length; j++){
            ans[0][j][0] = ans[0][j-1][0] + grid[0][j];
            ans[0][j][1] = ans[0][j][0];
        }
        // 一直向下:
        for(int i = 1; i < grid.length; i++){
            ans[i][0][0] = ans[i-1][0][1] + grid[i][0];
            ans[i][0][1] = ans[i][0][0];
        }
        for(int i = 1; i < grid.length; i++){
            for(int j = 1; j < grid[0].length; j++){
                ans[i][j][0] = Math.max(ans[i][j-1][0], ans[i][j-1][1]) + grid[i][j];
                ans[i][j][1] = Math.max(ans[i-1][j][0], ans[i-1][j][1]) + grid[i][j];
            }
        }
        return Math.max(ans[grid.length-1][grid[0].length-1][0], ans[grid.length-1][grid[0].length-1][1]);
    }
}

思路2:

事实上,很简单。如果i,j是从左边一个位置:i-1,j到达,那么dp[i-1][j]本身就一定是一个最优解,而不存在说:还要去考虑dp[i-1][j]是从它的上还是它的左进一步到达的。这是正确的最优子结构。
状态转移方程: dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j]

代码2:

class Solution {
    public int maxValue(int[][] grid) {
        for(int j = 1; j < grid[0].length; j++){
            grid[0][j] += grid[0][j-1];
        }
        for(int i = 1; i < grid.length; i++){
            grid[i][0] += grid[i-1][0];
        }
        for(int i = 1; i < grid.length; i++){
            for(int j = 1; j < grid[0].length; j++){
                grid[i][j] += Math.max(grid[i-1][j], grid[i][j-1]);
            }
        }
        return grid[grid.length-1][grid[0].length-1];
    }
}

9. 64. 最小路径和

题目描述:

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

思路:
和上一题一摸一样~

代码:

class Solution {
    public int minPathSum(int[][] grid) {
        for(int i = 1; i < grid.length; i++){
            grid[i][0] += grid[i-1][0];
        }
        for(int j = 1; j < grid[0].length; j++){
            grid[0][j] += grid[0][j-1];
        }
        for(int i = 1; i < grid.length; i++){
            for(int j = 1; j < grid[0].length; j++){
                grid[i][j] += Math.min(grid[i-1][j], grid[i][j-1]);
            }
        }
        return grid[grid.length-1][grid[0].length-1];
    }
}

10. 1043. 分隔数组以得到最大和

题目描述:

给你一个整数数组 arr,请你将该数组分隔为长度最多为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。
返回将数组分隔变换后能够得到的元素最大和。
注意,原数组和分隔后的数组对应顺序应当一致,也就是说,你只能选择分隔数组的位置而不能调整数组中的顺序。

思路:
状态转移方程:dp[i] = max_j=i_j=i-k+1(dp[j] + max(A[j:i+1]*(i-j+1))), dp[i]代表以第i个位置为结尾的数组,它的最大值,如果我们把A[i]单独拎出来,那么前面的最优解是dp[i-1],如果把A[i-1]和A[i]这两个拎出来组成一个子数组,那么前面的最优解是dp[i-2]…另外,我们要一直计算A[i-…]到A[i]这些元素中的最大值。

代码:

class Solution {
        public int maxSumAfterPartitioning(int[] A, int K) {
            int n = A.length;
            int[] dp = new int[n+1];
            for(int i = 0; i <= n; i++){
                int j = i - 1;
                int max = 0;
                while(i-j <= K && j>=0){
                    max = Math.max(max, A[j]);
                    dp[i] = Math.max(dp[i], dp[j] + max * (i-j));
                    j --;
                }
            }
            return dp[n];
        }   
}

11.95. 不同的二叉搜索树 II

题目描述:

给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。

思路1:
递归?额,怎么又叫回溯?反正不叫DP。

代码1:

class Solution {
    public List<TreeNode> generateTrees(int n) {
        if(n==0){
            return new ArrayList<TreeNode>();
        }else{
            return dfs(1, n);
        }
    }
    public List<TreeNode> dfs(int start, int end){
        List<TreeNode> tmp_result = new ArrayList<TreeNode>();
        if(start > end){
            tmp_result.add(null);
            return tmp_result;
        }
        for(int root = start ; root <= end; root ++){
            List<TreeNode> left_result = dfs(start, root-1);
            List<TreeNode> right_result = dfs(root+1, end);
            for(TreeNode left:left_result){
                for(TreeNode right:right_result){
                    TreeNode tmp_root = new TreeNode(root);
                    tmp_root.left = left;
                    tmp_root.right = right;
                    tmp_result.add(tmp_root);
                }
            }
        }
        return tmp_result;
    }
}

思路2:
动态规划,dp[len].add(tmp_root),考虑tmp_root_val = tmp_root.val,则tmp_root.left = dp[tmp_root_val - 1]中的一个,tmp_root.right = dp[n-tmp_root_val]中的一个(里面的每一个元素需要+tmp_root_val)

代码2:

class Solution {
    public List<TreeNode> generateTrees(int n) {
        List<TreeNode>[] dp = new ArrayList[n+1];
        dp[0] = new ArrayList<TreeNode>();
        if(n == 0) return dp[0];
        dp[0].add(null);
        for(int len = 1; len <=n; len++){
            dp[len] = new ArrayList<TreeNode>();
            for(int root = 1; root <= len; root++){
                int left_num = root - 1;
                int right_num = len - root;
                for(TreeNode left_child:dp[left_num]){
                    for(TreeNode right_child:dp[right_num]){
                        TreeNode tmp_root = new TreeNode(root);
                        tmp_root.left = left_child;
                        tmp_root.right = clone(right_child, root);
                        dp[len].add(tmp_root);
                    }
                }
            }
        }
        return dp[n];
    }
    public TreeNode clone(TreeNode child, int bias){
        if(child==null){
            return null;
        }
        TreeNode clone_root = new TreeNode(child.val + bias);
        clone_root.left = clone(child.left, bias);
        clone_root.right = clone(child.right, bias);
        return clone_root;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值