Java算法每日一题

leetcode_70_爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

一开始解这道题时,列了前5阶楼梯的走法,[1, 2, 3, 5, 8],观察得到是类似于斐波那契数列的形式。
F(n) = F(n-1) + F(n-2) n>=2
但是因为是做动态规划算法思想的联系,所以思考如何分解出子问题。
思考许久············································!!!
如果我想踏上第n级台阶,那么我要不就是从第n-1级台阶处跨一级上来的,要不就是从n-2级台阶处跨两级上来的。
那么我跨上第n级台阶的方法,就是跨上第n-1级台阶和n-2级台阶方法的和,这样就分解出了两个子问题。
这样的思路思考应该符合动态规划的思路吧我觉得。

class Solution {
    public int climbStairs(int n) {
        int[] arr = new int[n];
        if(n<3){
        	return n;
        }
        arr[0] = 1;
        arr[1] = 2;
        for (int i = 2; i < n; i++){
            arr[i] = arr[i-1] + arr[i-2];
        }
        return arr[n-1];
    }
}
leetcode_198_打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,
影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

今天再更新一道简单的动态规划题目,尝试以动态规划的思路去解决这个问题
那么首先分析题目可知不能联系偷两家,也就是 n和n-1之间只能选一家拿钱
和昨天的走台阶类似,我偷第n家的时候,有两种情况:
1、我已经拿了n-1家的钱,那么n家的钱我不能拿
2、我已经拿了n-2家的钱,那么n家的钱我可以拿
现在就是要比较 哪一种拿钱的方案拿的钱多,也就是要取 max(情况1, 情况2)
这样就分解出了子问题,偷到n-1家最多拿的钱和偷到n-2家最多拿到的钱
这些子问题都可以自底向上的求解,当n>2的时候

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        else if (nums.length == 1) {
            return nums[0];
        }
        if (nums.length == 2) {
            return nums[0] > nums[1] ? nums[0] : nums[1];
        }
        
        nums[1] = nums[0] > nums[1] ? nums[0] : nums[1];
        for (int i = 2; i < nums.length; i++) {
            nums[i] = Math.max(nums[i - 1], nums[i - 2] + nums[i]);
        }
        return nums[nums.length - 1];
    }
}
leetcode_91_解码方法
一条包含字母 A-Z 的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

构造了一个dp数组,数组的长度为字符串长度加1
dp数组中,dp[i]表示长度为i的字符串解码数
先不考虑边界条件,字符串长度为i的字符串,其解码数与i-1和i-2长度的字符串解码数有关
以为 1 ~ 26 均可以被解码,所以最后长度为i的字符串的最后一位数字可以单独为一个编码,或者与前一位组合成为另一个编码
所以 dp[i] = dp[i-1] + dp[i-2]
现在考虑边界条件:
1、第一个数字为0时无法编码
2、若第i位数字和第i-1位数字组成的两位数等于 0 也就是出现了 “00”连续的多个0时,无法编码,直接返回0
3、当第i位不为0时:

① 当第i位数字和第i-1位数字组成的两位数在 [10, 26]之间时和dp[i-1]和 dp[i-2]相关
② 若第i位数字和第i-1位数字组成的两位数在(0, 9]和[27, 99]之间时只能将第i位单独编码,至于dp[i-1]有关

4、当第i位为0时,

① 第i位数字和第i-1位数字组成的两位数在 [10, 26] (也就是10和20)之间可以成功编码,但只与dp[i-2]相关
② 如果不满足①则返回0

public int numDecodings(String s) {
        // 边界条件1:当第一个字符为0时,无法编码
        if(s.charAt(0) == '0'){
            return 0;
        }
        //dp状态数组中,第前i位字符所能构成的编码方式数量位dp[i]
        int[] dp = new int[s.length() + 1];
        //初始条件
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= s.length(); i++){
            // i表示前i位字符,在字符串中的实际索引为 i-1
            // numI表示当前添加的字符 numII表示当前添加的字符与前一位字符组成的二位数
            String numI = s.substring(i-1, i);
            String numII = s.substring(i-2, i);
            // 边界条件2:当出现连续的两个0时无法编码
            if(Integer.parseInt(numII) == 0){
                return 0;
            }
            // 边界条件3:第i位数字不等于0
            if(Integer.parseInt(numI) != 0 ){
                if(Integer.parseInt(numII) <= 26 && Integer.parseInt(numII) >= 10){
                    dp[i] = dp[i-1] + dp[i-2];
                }
                else{
                    dp[i] = dp[i-1];
                }
            }
            // 边界条件4:第i位数字等于0
            else{
                if (Integer.parseInt(numII) <= 26){
                    dp[i] = dp[i-2];
                }
                else{
                    return 0;
                }
            }
        }
        return dp[s.length()];
    }
leetcode_96_不同的二叉搜索树
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:
	输入: 3
	输出: 5
	解释:
	给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
	
	   1         3     3      2      1
	    \       /     /      / \      \
	     3     2     1      1   3      2
	    /     /       \                 \
	   2     1         2                 3

智商不够,题解来凑,多多学习吧
在一开始自己尝试解的时候,没有走到正确的解题思路上来,并且没有想到最最重要的一点
那就是:对于任何长度为n的单调递增数列,其能构成的二叉搜索树的结构数量是一样的
也就是说:12345 和 13579 可以构成的二叉搜索数的结构数量一致
题目中还更好理解一点 单调递增数列的公差为1
接下来进行动态规划的思考
1、创建一个dp数组,其中dp[i]表示长度为i的数列可以构造的搜索二叉树的结构数量

上面这一步十分重要,因为如果这里如果想成 n=i时的搜索二叉树的结构数量那就无法解决问题了。

2、状态转换方程

设给定的数为i 那么我们可以遍历1到i,依次取一个数作为根节点,将根节点前的序列定义为左子树,后半部分定义为右子树
对于左子树和右子树来说,我们可以再用上面的思路,遍历所有结构,又因为根节点是不同的,所以可以保证结构不重复
这就将问题拆分为更小的子问题,并且每个子问题都会被多次解决
dp[i] = {以1为根 + 以2为根 + ······ + 以i为根}
举例 以1为根的搜索二叉树,左子树为长度为0 右子树长度为 i-1 那么就是 dp[0] × dp[i-1]
那么dp[i] = {dp[0] × dp[i-1] + dp[1] × dp[i-2] ······ + dp[i-1] × dp[0]}
所以最重要的就是要把 dp[i] 定义为长度为i的序列所能构成的搜索二叉树结构数量

3、边界条件

当n等于0时,输出1

public int numTrees(int n) {
        int[] dp = new int[n + 1];
        if (n < 2) {
            return 1;
        }
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
leetcode_213_打家劫舍Ⅱ
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2, 因为他们是相邻的。
示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4

这题是198题的进阶,多了一个限制条件就是第一个房子和最后一个房子地理上连接,也就是一个循环队列
其实从索引 2到n-1,其得到的最高金额算法和198是一样的,基于动态规划来算
根据题目给出的条件我们可知 如果偷取了索引为0的房间,那么就不能偷取索引为n-1的房间
也就是说,这两个房间是对立的
那么我们就可以将问题划分为两个,从 0到n-1 和从 1到n这两个规模为n-1的问题

public int rob(int[] nums) {
		// 边界条件
        if (nums.length == 0){
            return 0;
        }
        if (nums.length == 1){
            return nums[0];
        }
        if (nums.length == 2){
            return Math.max(nums[0], nums[1]);
        }
		// 定义一个二维数组,第一维为 n-1 第二维为 2
		// [i][0]表示第一个子问题索引为i的最高金额 [i][1]表示第二个子问题索引为i+1的最高金额
        int[][] ls = new int[nums.length - 1][2];
        ls[0][0] = nums[0];
        ls[0][1] = nums[1];
        ls[0][1] = Math.max(nums[0], nums[1]);
        ls[0][2] = Math.max(nums[1], nums[2]);
        for (int i = 2; i < nums.length - 1; i++){
            ls[i][0] = Math.max(ls[i-1][0], ls[i-2][0] + nums[i]);
        }
        for (int i = 3; i < nums.length; i++){
            ls[i][1] = Math.max(ls[i-1][0], ls[i-2][0] + nums[i]);
        }
		// 比较两个子问题最后的结果大小
        return Math.max(ls[nums.length - 1][0], ls[nums.length - 1][1]);
    }
leetcode_32_ 最长有效括号
给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:

输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

字符串下的动态规划题目
我发现了,动态规划题目最难的还是状态转换方程的得出,看了题解以后,拿到状态转换方程,就可以很快写出
这个题暴力法的话就是以最大偶数窗口(s.length() / 2 * 2)进行滑动,看里面的字串是不是有效括号字串,如果是直接输出窗口长度
这题的状态转换方程是

如果当前字符是’)’ :
如果前一个字符是 ‘(’ 那么的dp[x] = dp [x - 2] + 2; 前提是x - 2 没有越界,如果越界则直接等于2
也就是说,如果前一个字符和自己刚好组成一对括号,那么算上自己的最长有效字串为 前一个括号前的最长有效子串长度 + 2
如果前一个字符是 ‘)’ 那么就需要查找前一个括号所代表的最长有效子串的前一个字符是否为 ‘(‘
如果是,那么dp[x] = dp[x - 1] + 2,如果所配对字符前还有其他字符,那么也需要加上所配对字符前已经存在的最长有效字串
dp[x] = dp[x - 1] + 2 + dp[x - dp[x - 1] - 1]

使用一个max变量记录dp数组中出现的最大值。

public int longestValidParentheses(String s) {
        int max = 0;
        int[] dp = new int[s.length()];
        for (int x = 1; x < s.length(); x++){
            if (s.charAt(x) == ')'){
                if (s.charAt(x - 1) == '('){
                    dp[x] = 2 + (x - 2 >= 0 ? dp[x - 2] : 0);
                }
                else{
                    if (x - dp[x - 1] - 1 >= 0){
                        if (s.charAt(x - dp[x - 1] - 1) == '('){
                            dp[x] = 2 + dp[x - 1] + (x - dp[x - 1] - 2 >= 0 ? dp[x - dp[x - 1] - 2] : 0);
                        }
                    }
                }
            }
            max = Math.max(max, dp[x]);
        }
        return max;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值