LeetCode算法学习

LeetCode算法学习

动态规划

DP用法:
1.建立动态方程
2.确定初始状态

Easy

  • 例题1
数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。

每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。

您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。

为什么要使用动态规划:
答:要到达楼层顶部即dp[n]得值最小,建立在使得dp[n-1],dp[n-2]最小的基础上,各自试探1步或者两步,且要保证这两层的代价也是最小,由此符合dp记录所有状态的规则。
由此开始:—>
1.确定动态方程dp[i]=min{dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]}
2.初始状态:dp[0]=0,dp[1]=0 注意这个初始状态的设定很有趣,初始要登上的阶梯没有踩着其他的阶梯
此题可以优化空间复杂度

代码如下:

public class Solution {

    /*
    要统计到楼顶的状态,也即最后一个状态的最小花费,可以到最后一个,或两个的最小花费+当前花费
    由此想到动态规划
    动态方程dp[i]=min{dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]}
    初始状态:dp[0]=0,dp[1]=0 注意这个初始状态的设定很有趣,初始要登上的阶梯没有踩着其他的阶梯
    此题可以优化空间复杂度
     */
    public int minCostClimblingstaits(int[] cost){
        int[] dp=new int[cost.length];
        dp[0]=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 Math.min(dp[cost.length-2]+cost[cost.length-2],dp[cost.length-1]+cost[cost.length-1]);
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] cost = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1};
        int result = solution.minCostClimblingstaits(cost);
        System.out.println(result);
    }
}

MID

  • 例题
    最长回文字符串

问题分析:是否是动规划题?是,当前字串存在中状态回文/非回文,且依赖于字串是回文串/非回文串
动态方程设立:
    回文态:dp[i][j]=dp[i+1][j-1]&&s[i]==s[j]
    非回文态dp[i][j]=dp[i+1][j-1] &&s[i]!=s[j];
初始状态:
单字符:dp[i][i]为回文态
双字符:s[i]=s[j] j-i<3

package 回文字符串;
//给你一个字符串s,找到s中最长的回文子串
//此题寻找最大回文串,回文串最小为2,最大为length,从此处确认左右边界,每次左边从头开始,记录最长的左和长度。
public class Solution {
    public String longestPalindrome(String s){
        //1.建立动态方程
        // dp[i][j]=True:dp[i+1][j-1] && s[i]==s[j]
        //dp[i][j]=false:others
        //2.确定初始状态
        //dp[i][i]=True
        // aba为回文串 aa为回文串,ab不为回文串 也就是 s[i]!=s[j] dp[i][j]=false
        //当前字串一共有两个状态1.是回文串,2.不是回文串

        //初始状态
        int length = s.length();
        boolean[][] dp = new boolean[length+1][length+1];
        for (int i=0;i<length+1;i++){
            dp[i][i]=true;
        }
        int maxLen=1;
        int begin=0;
        char[] chars = s.toCharArray();
        //递推
        //枚举字串长度
        for (int L=2;L<=length;L++){
            //枚举左边界
            for (int i =0;i<length;i++){
                //由L和i可以确定右边界,即j=L+i-1
                int j = L+i-1;
                //如果有边界越界,就可以退出当前循环
                if (j>=length){
                    break;
                }
                if (chars[i]!=chars[j]){
                        dp[i][j]=false;
                }else{
                    if (j-i<3){
                        dp[i][j]=true;
                    }else{
                        dp[i][j]=dp[i+1][j-1];
                    }

                }
                if (dp[i][j]&&(j-i+1)>maxLen){
                    begin=i;
                    maxLen=j-i+1;
                }
            }

        }
        return s.substring(begin,begin+maxLen);
    }

    public static void main(String[] args) {
        String s="cbbd";
        Solution solution = new Solution();
        String s1 = solution.longestPalindrome(s);
        System.out.println(s1);
    }

}

Hard

  • 例题2
    给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
第一眼自然能看出是dp,毕竟和上题差不多
1.首先确定两个状态:当天手里是否持有股票,其中dp[i][0],第i天不持有股票,dp[i][1],第i天持有股票
2.确定状态转移方程
dp[i][0]=max{dp[i-1][1]+prices[i],dp[i-1][0]}当天没有股票的状态来自昨天有今天卖/昨天没有今天也没买
dp[i][1]=max{dp[i-1][1],dp[i-1][0]-prince[1]}当天有股票的状态来自昨天没有今天买/昨天有今天没买
3.确定初始状态:dp[0][0]=0;dp[0][1]=-price[0]

上述是错误的,因为没有看清题干:1.有交易笔数要求2.要求再次购买前需要出售掉之前的股票
什么叫做1笔交易,卖出是完成一笔交易,买入时增加一笔交易
1.第i天一共有两种状态:手里持有一只股票/手里不持有股票
2.确定状态转移方程
 手里持有一支股票,此时为第j笔交易:(1).来自当天购买此时进行第j笔交易,利润为前天不持有股票第j-1笔交易的利润,(2).来自前天遗留,前天的交易为第j笔
state1[i][j]=Math.max(state2[j-1]-price[i],state1[i-1][j])
 手里不持有股票,此时为第j笔交易:(1).当前不购买股票,上一笔交易完成j-1,(2).当天售卖股票获得price[i]利润,售卖的为手里有一只股票且还处于第j笔交易的状态
state2[i][j]=Math.max(state2[i][j],state1[i][j]+price[i])
3.确定初始状态
主要是对第0天和第0笔交易的设置
自己构思一下,下面代码注释有

public class Soulution {
   public int maxProfit1(int k,int[] prices){
       int length=prices.length;
       int[][] dp=new int[length][2];
       dp[0][0]=0;
       dp[0][1]-=prices[0];
       for(int i=1;i<length;i++){
           dp[i][0]=Math.max(dp[i-1][1]+prices[i],dp[i-1][0]);
           dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[1]);
       }
       return dp[length-1][0];
   }
   public int maxProfit(int k,int[] prices){
       int length=prices.length;
       int[][] state1=new int[length][k+1]; //第i天进行第j笔交易时,手里有1支股票
       int[][] state2=new int[length][k+1]; //第i天进行第j笔交易,手里没有股票
       if(length<=0||k==0){//笔数是0,或者是没有价格是无法交易的,也就没有利润
           return 0;
       }

       /*
       第0天,进行1笔交易手里有股票应该为-price[0]
                  >=2笔交易交易手里有股票的情况不存在
              进行>=1笔交易的情况时不存在的
        */
       for (int i=0;i<=k;i++){
           state1[0][i]=state2[0][i]=Integer.MIN_VALUE/2;
       }
       /*
       初始状态,第0天,进行0笔交易,手里有股票的情况??都没有进行交易手里则咋会有股票呢,这种情况应该也是不存在的
       第0天,进行0笔交易,手里无股票的最大利润为0
        */
       state1[0][1]=-prices[0];
       state2[0][0]=0;
       for (int i=1;i<length;i++){
           state1[i][0]=Math.max(state1[i-1][0],state2[i-1][0]-prices[i]);//以此来保证所有天的,当天有股票为当天包括之前没有进行过交易,当天有股票的情况是
           for (int j=1;j<=k;j++){
               //当前进行j笔交易
               state1[i][j]=Math.max(state1[i-1][j],state2[i-1][j-1]-prices[i]);//以买入作为一笔交易,当天在进行第j笔交易,且手里有一支股票,可能原因为前天剩余的股票此时还是第j笔交易,前天没有剩余的j-1笔交易,今天买入
               state2[i][j]=Math.max(state1[i-1][j]+prices[i],state2[i-1][j]);//当天在进行第j笔交易,且手里没有股票,可能原因为前天不剩余股票此时为第j笔交易,前天剩余的第j笔交易,今天卖出
           }
       }
       //最终最大利润肯定为第n-1天,手里不含有股票,进行x笔交易的最大值 函数式编程
       return Arrays.stream(state2[length-1]).max().getAsInt();
   }


   public static void main(String[] args) {
       int k = 2;
       int[] prices={2,4,1};
       Soulution soulution=new Soulution();
       System.out.println(soulution.maxProfit(k,prices));;
   }
}

贪心

HARD

  • 例题1
给定一个已排序的正整数数组 nums,和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用 nums 中某几个数字的和来表示。请输出满足上述要求的最少需要补充的数字个数。

输入: nums = [1,3], n = 6
输出: 1
解释:
根据 nums 里现有的组合 [1], [3], [1,3],可以得出 1, 3, 4。
现在如果我们将 2 添加到 nums 中, 组合变为: [1], [2], [3], [1,3], [2,3], [1,2,3]。
其和可以表示数字 1, 2, 3, 4, 5, 6,能够覆盖 [1, 6] 区间里所有的数。
所以我们最少需要添加一个数字。

输入: nums = [1,5,10], n = 20
输出: 2
解释: 我们需要添加 [2, 4]。
输入: nums = [1,2,2], n = 5
输出: 0

首先读题,感觉有点递归的思想,优先补“集和”缺少的最小的数字,直到满足条件
1.首先要根据数组获取所有可能组合,并计算所有组合的值
2.根据n判断组合缺少的值,可能这也需要一个记录
确定一个最小的缺失的值,通过添加它可以直接将区间从[1,x-1]扩展到[1,2x-1]
问题是怎么确定这个最小的缺失值
无法思考,看题解学习

题解:由此可以提出一个贪心的方案,每次找到未被数组nums覆盖的最小的整数,x在数组中补充x,然后寻找下一个未被覆盖的最小的整数,重复上述步骤直到[1,n]中的所有数字都被覆盖
具体实现方面,任何时候都应该满足[1,x-1]内的所有数组都被覆盖。令x的初始值为1,数组下标index的初始值为0,则初始状态下区间[1,x-1]为空区间,满足区间内的所有数组都被覆盖,进行如下操作
- 如果index在数组nums的下标范围内,且nums[index]<=x,则将nums[index]的值加给x,并将index的值加1(此处是,如果该数组里的值包含在区间内,得到新的区间起始点,并将index的值+1)
    被覆盖的区间从[1,x-1]扩展到[1,x+num[index]-1],对x的值更新以后,被覆盖的区间为【1,x-1】.
- 否则,x没有被覆盖,因此需要在数组中补充x,然后将x的值乘以2.
    在数组中补充x,被覆盖的区间从[1,x-1]扩展到[1,2x-1],对x更新后,被覆盖的区间为[1,x-1]
重复上述操作,直到x的值大于n
由于任何时候都满足区间[1,x-1]内的所有数字都被覆盖,因此上述操作可以保证区间[1,n]内的所有数字都被覆盖。
又由于上述操作,只在x不被覆盖时才在数组中补充x,如果不补充x,则x无法被覆盖,因此可以保证补充的数字字数最少。如果减少补充的数字个数,则无法覆盖区间[1,n]内的所有数字

大致意思为:从x=1开始判断,若第i个数不在当前[以实现1,x-1]的区间内,那我们需要补的最小的数为x,此时将x补入,区间转化为以实现[1,2x-1],若数组第i个数在区间内,即nums[i]<=2x(注意是小于等于,等于表示已经在区间内,不需要补入这个数),则我们实现的区间范围转换为[1,x+num[i]-1],然后我们去找数组内的第2个数。
终止条件为最后一个需要补入的数是<=n的,那么我们不需要补入它,然后x范围*2,超出终止。
最终实现:

public class Soulution {
    public int minPatches(int[] nums ,int n){
        int length=nums.length;
        int index=0;
        int patches=0;
        int x=1;
        while(x<=n){
            if (index<length&&nums[index]<=x){
                x+=nums[index];
                index++;
            }else{
                patches++;
                x*=2;
            }
        }
        return patches;
    }

    public static void main(String[] args) {
        Soulution soulution=new Soulution();
        int[] nums = {};
        int n = 8;
        System.out.println(soulution.minPatches(nums,n));
    }
}

注意x必须为long,否则有个示例是无法通过的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值