蓝桥杯Java组最后的挣扎【DP总结】

一、前言

按照蓝桥杯的考点,我将DP放在最后来复习(也就是今天,明天则进行简单的IDE熟悉即可了)。对于DP,相信大家也早有听闻,它不是一两天能够提升起来的,而且也需要天赋,因此在此我就简单进行总结一下,培养一下这方面的感觉,希望省赛场上遇到此类题能够有下手方向,最后祝大家蓝桥杯旗开得胜。

二、具体过程

(1)为什么需要DP算法?

  1. DP一般采用使用dp数组存储解,因此在求最优解时能够优化时间复杂度。(即使不能够找到状态方程,也得学会采用空间换时间的思想去优化时间,多骗一点测试用例)
  2. 算法时间效率高,代码量少,多元性强,主要考察思维能力、建模抽象能力、灵活度。

(2)什么情况使用DP

  1. 最优子结构
  2. 子问题重叠
  3. 无后效性

(3)DP的标准步骤

  1. 确定初始状态(从小问题出发,看0,1项情况)
  2. 确立状态方程,如何去存储?
  3. 递推,寻找n与n-1项的关系,建立状态方程
  4. 终止状态,获取最优解

(4)提升DP的一般步骤(长远来看,短期提升很困难)

  1. 分析简单的递归(斐波那,汉诺塔,爬楼梯等)
  2. 开始刷二叉树(弄清楚先中后,层次遍历),N叉树
  3. 开始刷DP较为有难度的题(最长公共子序列,购买股票,背包问题等)
  4. 进阶,在LeetCode上面寻找中等难度甚至困难的题进行刷。

参考书籍:labuladong算法书、算法导论、LeetCode。
看书+刷题(这篇DP题型分类很细)相结合

(5)本次DP简单相应题型总结
蓝桥杯近两次DP题:数字三角形,子串排序、装饰珠。都是有难度的题,考场遇见此类题建议放到最后再动手,先保证基础编程题拿分拿满。

  1. 从斐波那数看DP
    由于斐波那数,我们都知道他的递推公式,因此对于初学者的我来理解DP更为友好,谈到斐波那求解,我们最原始的就直接递归(递推),但是发现稍大了就会超时,因为在递推的过程中就是一颗二叉树在递进,有很多重复部分。于是我们就可以想到采用数组进行存重复部分,于是有了记忆优化。但是发现这样还是时间复杂度降下来了,但是空间复杂度上去了,于是开始想如何降低空间呢?因为我们只需要最后的最优解,所以无需关心前面的情况,因此就可以想到只需一个空间即可存储,采用迭代的思想进行一步步更新。(虽然迭代没有那么明显的使用递推公式,状态方程了,但是根本还是n-1到n的递推,只是表现方式不同罢了,因此最关键的就是从小问题出发寻找递推公式
    //斐波那处理类
 // 斐波那数
    // https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/submissions/

    //法1:最原始的方法直接递归求解
    // //到40求解时就跑不动了
    int method1(int n){
        if(n<2){
            return n;
        }else{
            return method1(n-1)+method1(n-2);
        }
    }
    static int []cache= new int[1000];
    static {
        Arrays.fill(cache,-1);
    }
    // 法2:记忆优化
    int method2(int n){
        if(n<2){
            cache[n]=n;
            return n;
        }else{
            if(cache[n-1]!=-1&&cache[n-2]!=-1){
                return cache[n-1]+cache[n-2];
            }else if(cache[n-2]!=-1){
                return cache[n-2]+method1(n-1);
            }else if(cache[n-1]!=-1){
                return cache[n-1]+method1(n-2);
            }
            return method1(n-1)+method1(n-2);
        }
    }

    // 法3:动规划方法,使用数组进行存储就无需再重复递归

    // 动态方程
    int method3(int n){
        if(n<2){
            cache[n]=n;
        }else {
            cache[0]=0;
            cache[1]=1;
            for(int i =2;i<=n;i++){
                cache[i]=cache[i-1]+cache[i-2];
            }
//            cache[n]=cache[n-1]+cache[n-2];
        }
        return cache[n];
    }

    //空间继续优化,,迭代思想
    int method4(int n){

        int a=0,b=1,sum;
        for(int i=0;i<n;i++){
            sum = a+b;// 如果需要进行数据处理,取余则从此处开始取余这样也肯定让后面的数小于该数,
            a=b;//a进一步迭代到b(即第二数,第一个数废弃)
            b=sum;//b迭代到第三个数
        }
        return a;// 返回第一个数,看起点 n=0不用迭代就为本身,n=1时迭代一次到b也就是第二个数
    }

    void test(){
        for(int i=10;i<1000;i*=2){
            long start = System.currentTimeMillis();
            int val = method4(i);
            long end = System.currentTimeMillis();
            System.out.println(i+"所用时间为:"+(end-start)+";结果为"+val);
        }
    }

    public static void main(String[]args){
        TreeLeetCodePart1 treeLeetCodePart1 = new TreeLeetCodePart1();
        treeLeetCodePart1.test();
    }

利用反射和多线程(单纯为了熟悉Java知识)进行跑一下测试对比。

public class ReflectUtil {
    static int  executeMethod(int kind,int n){

        try{
           Method method =TreeLeetCodePart1.class.getDeclaredMethod("method"+kind, int.class);
          // Object o = new Object();
           Object rs = method.invoke( TreeLeetCodePart1.class.newInstance(),n);
           System.out.println(rs);

           return (int) rs;
        }catch (Exception e){
            e.printStackTrace();
        }
        return -1;
    }

    public static void main(String[]args){
      //  executeMethod(1,20);
    }
}

public class TestFab1 implements Runnable{

    private int method;//方法选择,其实直接判断也行,但为了应用反射,我便写了一个简单的反射调用

    public TestFab1(int method) {
        this.method = method;
    }

    @Override
    public void run() {

        //System.out.println("这里是");
        TreeLeetCodePart1 treeLeetCodePart1 = new TreeLeetCodePart1();
        for(int i=10;i<1000;i*=2){
            long start = System.currentTimeMillis();
            int val = ReflectUtil.executeMethod(method,i);//treeLeetCodePart1.method4(i)
            long end = System.currentTimeMillis();
            System.out.println("方法"+method+"求解"+i+"所用时间为:"+(end-start)+";结果为"+val);
        }

    }


}

public class Main {
    public static void main(String[]args){

        TestFab1 testFab1 = new TestFab1(1);
//        testFab1.run();

        TestFab1 testFab2 = new TestFab1(2);
//        testFab2.run();

        TestFab1 testFab3 = new TestFab1(3);
//        testFab3.run();

        TestFab1 testFab4 = new TestFab1(4);
//        testFab4.run();

        Thread thread = new Thread(testFab1);
        thread.start();
        Thread thread2 = new Thread(testFab2);
        thread2.start();
        Thread thread3 = new Thread(testFab3);
        thread3.start();
        Thread thread4 = new Thread(testFab4);
        thread4.start();

    }

  1. 子序列问题
package lq.questions.consolidate.dp;

import java.util.Arrays;
import java.util.Random;

/**
 * @AUTHOR LYF
 * @DATE 2021/4/16
 * @VERSION 1.0
 * @DESC
 */
public class SimpleDp {

    // 最大子段和
    // 总体思路
    // 1.确定起点(从小问题进行出发考虑)
    // 2.确定最优解是?,如何存储?如何递推(状态转移方程),从小问题类推到n
    // 3.确定终点,递推结束
    // 4.获取最优解,在DP存储中获最优
    // 最大子段和
    // 1 -1 3 -1 3 -2 -4
    // 暴力方式: n!+(n-1)!+..+1

    // dp[]存储以i结尾最大值,当dp[i]为负数,开始转移到该数
    // 可以得到该状态方程为 dp[i+1]=dp[i]>0?dp[i]+arr[i+1]:arr[i];// 若前面为负转移到从本身开始
    void maxSub(){
        int []arr = new int[10];
        int[]dp = new int[10];
        int []status = {-1,1};
        for(int i =0;i<10;i++){
            Random random = new Random();
            arr[i]=random.nextInt(20)*status[random.nextInt(2)];
            System.out.print(arr[i]+"->");
        }
        System.out.println();

        dp[0]=arr[0];
        for(int j =1;j<10;j++){
            dp[j]=dp[j-1]>0?dp[j-1]+arr[j]:arr[j];
        }

        for(int i =0;i<10;i++){
            System.out.print(dp[i]+">");
        }
        Arrays.sort(dp);
        System.out.println("最大值为:"+dp[dp.length-1]);
    }

    // 最长不降子序列
    //  1 3 2 4 5 3 1 0 3 4
    //  https://blog.csdn.net/liu16659/article/details/104091629
    // 1.按照最大子段思路,dp[i]存储在i最长的不降个数
    // 2.确定状态转移,,如何递推? 由于不连续因此,需要在确定dp[i]时,需要遍历i之前符合要求的然后再选取最大的值进行存储
    // dp[0]=arr[0]  ,dp[i]=max(dp,0,i-1)&&arr[i]>=..  // 如果使用list可以使用stream进行条件筛选再进行排序,但是复杂度会多排序,
    // 3.最优解 max dp
    // 时间复杂度n^2
    // 若暴力需要 Cn 1, Cn 2+...Cn n +加上遍历

    void maxSub2(){

        int N =  20;
        int []arr = new int[N];
        // 模拟数据
        for(int i =0;i<N;i++){
            arr[i]=new Random().nextInt(20);
            System.out.print(arr[i]+"->");
        }

        int []dp = new int[N];
        dp[0]=1;

        // 确定DP
        for(int i =1;i<N;i++){

            int maxDpVal = 1;//如该数小于前面所有数则只能为该数起点 个数则为1

            for(int j=0;j<i;j++){
                if(arr[i]>=arr[j]&&dp[j]+1>maxDpVal){// 满足要求,可以考虑是否从该处对接,此处可以进行记录
                    maxDpVal = dp[j]+1;
                }
            }

            dp[i]=maxDpVal;
        }

        Arrays.sort(dp);
        System.out.println();
        System.out.println("最优解:"+dp[dp.length-1]);
    }
    // 最长公共子序列
    //  https://www.cnblogs.com/fengziwei/p/7827959.html
    // 由于是两个字符串比较,因此需要二维数组进行存储DP
    // 行和列分别代表一个字符串

    void maxSub3(){
        String str1=" fjlsdjfklajsdfj";
        String str2=" ofyljfsreoewirwq";//需要进行空位

        int[][]dp = new int[str1.length()][str2.length()];
        //dp[0][0]=0;// 空位

        String str = "";
        for(int i =0;i<str1.length();i++)
        {
            Arrays.fill(dp[i],0);
        }


        // 比较暴力的DP,存储全部
        for(int i =1;i<str1.length();i++){
            for(int j = 1;j<str2.length();j++){

                if(str1.charAt(i)==str2.charAt(j)){
                   dp[i][j]=dp[i-1][j-1]+1;
                   // 有发生变化转移的,记录路径
//                    str=str+str1.charAt(i)+"";
                }else{
                    dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
                }

//                if(i==1){//初始
//                    if(str1.charAt(i)==str2.charAt(j)){
//                        dp[i][j]=1;
//                    }
//                }else{//递推
//
//                    int max=dp[i-1][1];//上一层最大值
//
//                    for(int k=2;k<str2.length();k++){// 时间复杂度n^3
//                        if(dp[i-1][k]>max){
//                            max = dp[i-1][k];
//                        }
//                    }
//                    // 如匹配相等,则dp[i][j]在此之前的最大值+1
//                    // 若不匹配则等于该值
//                    dp[i][j]=str1.charAt(i)==str2.charAt(j)?max+1:max;
//                }
            }
        }

        // 最后一行最大值则是最优解
        Arrays.sort(dp[str1.length()-1]);

        System.out.println("++++++++++");

        for(int i =0;i<str1.length();i++){
            for(int j=0;j<str2.length();j++){
                System.out.print(dp[i][j]+" ");
            }
            System.out.println();
        }


//        int min = Math.min(str1.length(),str2.length());
//        for(int i=1;i<min;i++){
//            if(dp[i][i]>dp[i-1][i-1]){
//                str=str+str2.charAt(i)+"";
//            }
//        }
        for(int i =1;i<str2.length();i++){
            if(dp[str1.length()-1][i]>dp[str1.length()-1][i-1]){
                str=str+str2.charAt(i);
            }
        }

        System.out.println("最优解:"+dp[str1.length()-1][str2.length()-1]+";"+str);




    }



    // 最大子矩阵和

    public static void main(String[]args){
        SimpleDp dp =new SimpleDp();
        dp.maxSub3();
    }



}

  1. 数字三角形
 // 数字三角形
    // 求最大和,且左右相差不超过1

    /**
     *
     * 3
     * 1 2
     * 4 5 6
     * 2 3 4 1
     *
     */
    // 如果没有相差限制,直接倒退贪心即可
    // 但如果有限制则
    private Scanner scanner = new Scanner(System.in);
    private int n =scanner.nextInt();
    private int[][] map = new int[n][n];

    public void inputData(){
        for(int i=0;i<n;i++){
            for(int j =0;j<i+1;j++){
                map[i][j]=scanner.nextInt();
            }
        }

        System.out.println("=========TEST==========");

        for(int i=0;i<n;i++){
            for(int j =0;j<i+1;j++){
                System.out.print(map[i][j]+" ");
            }
            System.out.println();
        }
    }
    // 未进行限制(贪心即可)
    public void test1(){
        for(int i=n-2;i>=0;i--){//从倒数第二开始递推,选择最大的
            for(int j=0;j<i+1;j++){
                map[i][j]+=Math.max(map[i+1][j],map[i+1][j+1]);
            }
        }

        System.out.println("RESULT:"+map[0][0]);
    }

    // 进行相差限制小于等1=>
    // 偶数层 :向左和右一样多  奇数层: 向左或右为n/2,另外为n/2+1
    // dp[n]==每层最大
    // 最原始的思考,遍历所有并记录满足情况的,再选取最大的
    // dp:动态规划,
    //注意,这不是普通的数塔问题,因为向左走的次数与向右走的次数差值不超过1,所以当到达最后一层时
    // ,一定是落在中间位置,如果层数是奇数,那么最后一定落在最后一层的第个元素上,如果层数是偶数,
    // 最后一定是落在第或第个元素上。所以dp只要从最后一层的中间开始向上递推就可以了。
    //作者:yo1ooo
    //链接:https://www.jianshu.com/p/c20b6b9a178a
    //来源:简书

    // 相差的可以从最后的推出。最左边节点即全部往左走,往右走一步,则+1
    // 使用map同时代表DP方程记录
    public void test2(){

        // 讨论奇偶数,确定start,end
        int start,end;// n层范围

        if(n%2==0){
            start=n/2-1;// 0坐标开始
            end = n/2;
        }else{
            start = end = n/2;
        }

       int start0,end0=end;// n-1层范围
        for(int i =n-2;i>=0;i--){

             start0=start-1<0?0:start-1;
             //end0=end+1>n-2?n-2:end+1;
              end0=end>n-2?n-2:end;

            for(int j = start0;j<=end0;j++){
                if(j<start0||j>end0){
                    continue;
                }

                if(j==start0){//左边
                    map[i][j]+=map[i+1][j+1];
                    start--;
                }else if(j==end0){
                    map[i][j]+=map[i+1][j];
                    //end++;//无需加
                }else{
                    map[i][j]+=Math.max(map[i+1][j],map[i+1][j+1]);
                }
            }
        }
        System.out.println("RESULT:"+map[0][0]);
    }



  1. 购买股票以及打劫家舍

看这篇我之前写的

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是蓝桥杯Java做题模板,供参考: 1. 输入输出模板 ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读入一个整数 int n = scanner.nextInt(); // 读入一个字符串 String s = scanner.next(); // 输出结果 System.out.println(n + " " + s); scanner.close(); } } ``` 2. 数模板 ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 定义数 int[] a = new int[n]; // 读入数 for (int i = 0; i < n; i++) { a[i] = scanner.nextInt(); } // 输出数 for (int i = 0; i < n; i++) { System.out.print(a[i] + " "); } scanner.close(); } } ``` 3. 字符串模板 ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String s = scanner.nextLine(); // 字符串长度 int len = s.length(); // 字符串转字符数 char[] ch = s.toCharArray(); // 输出字符串和字符数 System.out.println(s); for (int i = 0; i < len; i++) { System.out.print(ch[i]); } scanner.close(); } } ``` 4. 递归模板 ```java public class Main { public static void main(String[] args) { // 调用递归函数 int result = recursion(10); System.out.println(result); } // 定义递归函数 public static int recursion(int n) { if (n == 1) { return 1; } return n * recursion(n - 1); } } ``` 5. 动态规划模板 ```java public class Main { public static void main(String[] args) { int n = 5; int[] a = {2, 3, 1, 5, 4}; // 定义状态数 int[] dp = new int[n]; // 初始化状态 dp[0] = a[0]; // 状态转移方程 for (int i = 1; i < n; i++) { dp[i] = Math.max(dp[i - 1], 0) + a[i]; } // 输出最终结果 int result = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { result = Math.max(result, dp[i]); } System.out.println(result); } } ``` 以上是蓝桥杯Java做题模板,希望能对大家有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值