动态规划总结(2)

总结下今天学习的题目

1.LeetCode 221 最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4

在这里插入图片描述

这道题目还是比较好理解的
这里借用别人一张图片,就是说,当前位置最大的正方形,
由左上 左面 上面 三个位置所限制,所能拓展的最大正方形也就是三个中的最小值+1
因此 代码如下

class Solution {
    public int maximalSquare(char[][] matrix) {
        if(matrix.length ==0 || matrix[0].length == 0)return 0;
        int n=matrix.length,m=matrix[0].length;
        int [][] dp=new int [n+1][m+1];
        int res =0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                if(matrix[i-1][j-1]=='1')
                    dp[i][j]=Math.min(dp[i][j-1],Math.min(dp[i-1][j-1],dp[i-1][j]))+1;
                res = Math.max(res,dp[i][j]);
            }
        return res*res;
    }
}

2.LeetCode 576 出界的路径数

给定一个 m × n 的网格和一个球。球的起始坐标为 (i,j) ,你可以将球移到相邻的单元格内,或者往上、下、左、右四个方向上移动使球穿过网格边界。但是,你最多可以移动 N 次。找出可以将球移出边界的路径数量。答案可能非常大,返回 结果 mod 109 + 7 的值。
输入: m = 2, n = 2, N = 2, i = 0, j = 0
输出: 6
解释:
在这里插入图片描述

这道题还是要仔细思考一下,写的时候遇到了很多的问题,所以记录一下
首先
需要遍历球的上下左右,
这个过程,需要求出上面距离出界有多少种方案,以及下,左右。
这里要注意的一个问题,一般我们在写深搜的时候都要防止他走回原来走过的路,但是这道题,他是可以走曾经走过的路,所以dp数组里面还需要一个剩余多少步的变量,因为同一个位置,他们之间的方案并不相同。所以定义的是dp[n][n][k] ,当时用了两个参数,导致遇到了很多的问题。代码如下

class Solution {
    int [][][]dp;
    int []dx={0,1,0,-1},dy={1,0,-1,0};
    int mod =1000000007;
    public int findPaths(int m, int n, int N, int i, int j) {
        dp=new int [m][n][N+1];
        for(int q=0;q<m;q++)
            for(int p=0;p<n;p++)
                Arrays.fill(dp[q][p],-1);
        return dfs(m,n,N,i,j);
    }
    public int dfs(int m, int n, int N, int i, int j){
        if(dp[i][j][N] != -1)return dp[i][j][N];
        dp[i][j][N]=0;
        if(N==0)return dp[i][j][N];
        for(int q=0;q<4;q++){
            int a=i+dx[q],b=j+dy[q];
            if(a<0 || a>=m || b<0 || b>=n)dp[i][j][N]++;
            else dp[i][j][N]+=dfs(m,n,N-1,a,b);
            dp[i][j][N]%=mod;
        }
        return dp[i][j][N];
    }
}

3.LeetCode 91 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
输入: “12”
输出: 2
解释: 它可以解码为 “AB”(1 2)或者 “L”(12)。

这道题也比较好理解
就是遍历字符串,站在当前的位置,查看前一位,和自己是是否可以构成一个大于等于10,小于等于26的数字,如果符合这个范围,那么,dp即为前一种方案和前两个方案的和,否则等于前一个方案。
但是,这道题有个小诀窍,在字符串初始位置加一个空格,这样就防止了溢出,也减少了处理特殊情况。

class Solution {
    public int numDecodings(String s) {
        if(s.length()<1)return s.length();
        int [] dp = new int [s.length()+1];
        s="0"+s;
        dp[0]=1;
        for(int i=1;i<s.length();i++){
            if(s.charAt(i)!='0')dp[i]=dp[i-1];
            int x=(s.charAt(i)-'0')+(s.charAt(i-1)-'0')*10;
            if(x>=10 && x<=26)dp[i]+=dp[i-2];
        }
        return dp[s.length()-1];
    }
}

4.LeetCode 264 丑数 II

丑数这个名字真的是经常看到了。。。为什么要叫这个名字。。。

丑数就是只包含质因数 2, 3, 5 的正整数。
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

思考:丑数除了1之外,他表示只包含2,3,5的正整数。
那么 我们手动数一下丑数都有什么
2的倍数:2,4,6,8,10,12,14
3的倍数:3,6,9,12,15…
5的倍数:5,10,15,20
然后把它们按顺序整理出来,2,3,4,5,6,8,9,10,12,14,15,
大家是不是很容易想到,可以设置三个不同的游标(初始值为1)来进行下面这样的尝试
2先开始扩大12=2,13=3 15=5,2最小 保存下来。然后2的游标往后移一位。
2开始扩大 2
2=4 13=3 15=5,3最小,保存下来,3的游标往后移一位。
2开始扩大 22=4 23=6 1*5=5,2最小,保存下来,2的游标往后移一位。
。。。。
代码如下:

class Solution {
    public int nthUglyNumber(int n) {
        if(n<1)return 0;
        int [] dp = new int [n];
        dp[0]=1;
        for(int q=1,i=0,j=0,k=0;q<n;q++){
            int min=Math.min(dp[i]*2,Math.min(dp[j]*3,dp[k]*5));
            if(dp[i]*2==min)i++;
            if(dp[j]*3==min)j++;
            if(dp[k]*5==min)k++;
            dp[q]=min;
        }
        return dp[n-1];
    }
}

5.LeetCode 486. 预测赢家

给定一个表示分数的非负整数数组。 玩家1从数组任意一端拿取一个分数,随后玩家2继续从剩余数组任意一端拿取分数,然后玩家1拿,……。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。如果最终两个玩家的分数相等,那么玩家1仍为赢家。
输入: [1, 5, 2]
输出: False

刚看到的时候,懵了一下,这也能用动规,在我的理解里博弈论不都是找规律的返回吗(属实太菜,不会推导,只能找规律)

首先题目说假设每个玩家玩法默认最优,然后我就想,都最优了,那不是先手必胜吗
后来看到样例,1 5 2 这种情况下先手无论如何都赢不了。然后老老实实学动规。
但是,我们发现,当给出的个数为偶数的情况下,那么不就是先手必胜吗
可以这样想,反正他们都是最优的玩法,并且即使相等也是玩家一胜利,所以无论怎样,都是玩家1胜利,但是奇数情况下,老老实实学动规。

这道题动态规划有些不好想(反正我是没想到),dp[i][j]用来表示i-j区间分数差(因为有负数)。为了好理解,这里换一种说法,dp[i][j]用来表示i-j区间玩家1比玩家2多出来的分数(也可能是负数)。
那么如果玩家1取了第i个数,那么他就比对手多了nums[i]-dp[i+1][j]分,
如果玩家1取了第j个数,那么他就比对手多了nums[j]-dp[i][j-1]分,
那么状态转移方程为dp[i][j] = Math.max(nums[i]-dp[i+1][j],nums[j]-dp[i][j-1])
最后判断这个数如果为正数,那么玩家1赢,否则玩家2赢

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        if(nums.length ==0 || (nums.length&1)==0)return true;
        int [][]dp=new int [nums.length][nums.length];
        for(int i=nums.length-1;i>=0;i--){
            for(int j=i+1;j<nums.length;j++){
                dp[i][j]=Math.max(nums[i]-dp[i+1][j],nums[j]-dp[i][j-1]);
            }
        }
        return dp[0][nums.length-1]>=0;
    }
}

6.LeetCode 115 不同的子序列

给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

输入: S = “babgbag”, T = “bag”
输出: 5
解释:
如下图所示, 有 5 种可以从 S 中得到 “bag” 的方案。
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^

思考:
这道题可以先思考S 的第一个字母 和T的第一个字母
用dp[i][j]来表示 S前 j 字符串可以组成最多T 前 i 字符串个数
(这里为什么调换i,j顺序 因为等会我们写dp的时候,外层循环T 子序列,内层循环大字符串)
这里借用一张图
在这里插入图片描述
这里可以清晰的看到,如果T[i]与S[j]不相等,那么当前所能组成最多字符串个数就是左面,也就是前一个
如果相等,那么就是左面的加上左上方的
也就是dp[i][j-1]和dp[i-1][j-1]转换而来
所以状态我们可以写出代码了

class Solution {
    public int numDistinct(String s, String t) {
        if(s.length()<1 || t.length()<1)return 0;
        int n=t.length(),m=s.length();
        int [][] dp = new int [n+1][m+1];
        Arrays.fill(dp[0],1);
        for(int i=1;i<n+1;i++)
            for(int j=1;j<m+1;j++){
                if(t.charAt(i-1)==s.charAt(j-1))dp[i][j]=dp[i][j-1]+dp[i-1][j-1];
                else dp[i][j]=dp[i][j-1];
            }
        return dp[n][m];
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值