算法基础(可能

蓝桥杯刷题技巧总结

文章目录

  • javaAPI复习
    • BigDecimal
      • 1、简介
      • 2、构造器创建
      • 3、方法描述
    • Integer
    • calendar
    • 字符串格式化
  • java基础复习
    • HashSet
      • 1、HashSet底层机制说明
      • 分析HashSet的添加元素底层是如何实现的(hash()+equals())
      • 2、HashSet的扩容和转成红黑树机制
    • HashMap
    • Map hash表
    • TreeSet
  • 模板
    • 字符串、字符、数字转换
    • *全排列模板
    • 不同子串
    • BFS迷宫
    • 并查集 七段码
    • 联通区间?
      • 水洼数目
    • 最大公共子串?
      • 矩阵法
      • 不要求连续
      • 最长公共子序列2(LCS)
    • 最小生成树
    • 最短路径
    • 快速幂运算
    • GCD
  • 算法很美
    • 数学
    • KMP
    • 快速幂
    • 递归
    • 树+二叉树+堆
      • 1、可以用一个数组表示一个树
      • 2、树的遍历方式
      • 3、堆的概念
    • 贪心
    • DP
      • 01背包
      • 2切钢条
      • 02数字三角
      • 03最大公共子序列问题
    • 尺取法(双指针法)
  • 题目小结总览
    • 1、2013
    • 2、2014
    • 3、2015
    • 4、2016
    • 2017
    • 2018
    • 2019
    • 2020
    • 2021真题
  • 类似题目
  • 做题小技巧
    • 2020
    • 2021真题
  • 类似题目
  • 做题小技巧

java日历类

javaAPI复习

BigDecimal

1、简介

什么是BigDecimal?

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。

双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。

float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。

BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

2、构造器创建

BigDecimal(int)       创建一个具有参数所指定整数值的对象。 
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 //不推荐使用
BigDecimal(long)    创建一个具有参数所指定长整数值的对象。 
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。//推荐使用

3、方法描述

add(BigDecimal)        BigDecimal对象中的值相加,然后返回这个对象。 
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。 
multiply(BigDecimal)  BigDecimal对象中的值相乘,然后返回这个对象。 
divide(BigDecimal)     BigDecimal对象中的值相除,然后返回这个对象。 
toString()BigDecimal对象的数值转换成字符串。 
doubleValue()BigDecimal对象中的值以双精度数返回。 
floatValue()BigDecimal对象中的值以单精度数返回。 
longValue()BigDecimal对象中的值以长整数返回。 
intValue()BigDecimal对象中的值以整数返回。

Integer

Integer.parseInt(s)与Integer.valueOf(s)的联系

Integer.parseInt(s)是把字符串解析成int基本类型,Integer.valueOf(s)是把字符串解析成Integer对象类型,其实int就是Integer解包装,Integer就是int的包装,在jdk8中已经自动实现了自动解包装和自动包装,所以两种方式都能得到想要的整数值。

  1. Integer.parselnt(S):将字符串转换为有符号的int基本类型

  2. Integer.valueOf(s):将字符串转换为integer类型

  • 有什么区别呢?

当使用parselnt()时,如果比较两个相同的数,可以直接用==号

但是如果使用valueOf()时,则不行,当比较内容是否相等时,使用isequal,比较对象是否相等时,使用==

https://blog.csdn.net/u010502101/article/details/79162587

字符串转数字:Integer.parseInt(s)vsInteger.valueOf(s)

​ 转成int基本类型 转成Integer类型

calendar

  • 设置calendar的值求日期
calendar.set(calendar.YEAR, i);//set方法,两个参数(设置的项,甚至的值)
calendar.set(calendar.MONTH, 11);//从0开始记数,1月=0,12月=11
calendar.set(calendar.DAY_OF_MONTH, 31);//设置日期,31号
System.out.println(i+" "+calendar.get(calendar.DAY_OF_WEEK));//验证1999年12月31日 星期五 是不是 6

字符串格式化

System.out.println(String.format("%.2f",2.2222));//2.22
System.out.println(String.format("%02d:%02d:%02d",hour,minutes,second));
//首先字符串格式化用string.format(" ",v1),%d代表整型,%f浮点数,%s字符串

java基础复习

基本数据类型

HashSet

1、HashSet底层机制说明

  • 分析hashset底层是HashMap,hashmap的底层是(数组+链表+红黑树)

算法基础(可能_第1张图片

tips:

算法基础(可能_第2张图片

为什么要设计成数组加链表的形式,主要还是为了提高效率。

  • 分析HashSet的添加元素底层是如何实现的(hash()+equals())

算法基础(可能_第3张图片

结论

1.HashSet底层是HashMap

2.添加一个元素时,会先得到hash值,会转化为索引值

3.找到存储数据表table,看这个索引位置是否已经有存放的元素

4.如果没有,直接加入

5.如果有,则调用equals比较,如果相同就放弃添加,如果不相同则添加到下一个

6.在java8中,如果一条链表的元素个数达到TREEIFY_THRESHOLD(8),并且table的大小大于等于MIN_TREEIFY_CAPACITY(默认64)就会进行树化

2、HashSet的扩容和转成红黑树机制

1.HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16**加载因子(loadFactor)0.75=12.*

2.如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是320.75=24,依次类推

3.在Java8中,如果一条链表的元素个数到达TREEIFY THRESHOLD(默认是8),并且tablel的大小>=]MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

算法基础(可能_第4张图片

HashMap

Map接口实现类的特点[很实用]
注意:这里讲的是JDK8的Map接口特点
1)Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2)Map中的key和value可以是任何引用类型的数据,会封装到HashMaps$Node对象中
3)Map中的key不允许重复,原因和HashSet一样,前面分析过源码.(会替换)
4)Map中的value可以重复
5)Map的key可以为nul,value也可以为null,注意key为null,只能有一个,value为null,可以多个.
6)常用String类作为Map的key
7)key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
8)Map存放数据的key-value示意图,一对k-v是放在一个Node中的,有因为Node实现了Entry接口,有些书上也说一对k-就是一个Entry(如图)[代码演示]

Map hash表

Map cache =new HashMap ()

查询:cache.map(obj key)

插入键值对:cache.put(obj key,V)

删除:cache.remove

TreeSet

sortedSet=new TreeSet()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSDGUZI5-1651584189607)(算法基础(可能Img/image-20220404171646284.png)]

模板

字符串、字符、数字转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VbKS7ZK-1651584189607)(算法基础(可能Img/image-20220403144433632.png)]

数字转换为字符串方法二:

String _a = a + "", _b = b + "", _c = c + "";

*全排列模板

package Data_structures_algorithms;

import11届蓝桥杯JavaB组第1场省赛2020_7_5.Main;

import java.util.HashSet;
import java.util.Set;

public class dfs {//全排列模板
    static Set<String> set=new HashSet<String>();
    public static void dfs1(char[] a,int k){//字符数组,起始位置??
        if (k==a.length){//形成一个排列
            //注意!!这里完成你想要的动作
            String s=new String(a);//用char数组生成一个字符串
                set.add(s);//set去重
        }
        for(int i=k;i<a.length;i++){
            char t=a[k];//交换,确定这一位
            a[k]=a[i];
            a[i]=t;
            dfs1(a,k+1);
            t=a[k];
            a[k]=a[i];//回溯。恢复到探测之前的状态
            a[i]=t;
        }
    }

    public static void main(String[] args) {
        char[] a={'A','B','C'};
        dfs1(a,0);
        for(String x:set){
            System.out.println(x);
        }
    }
}
package provincialGames_04_2013_JavaB;
 
public class A09_全排列 { // 全排列模板——整数数组
 
	// 数字型、字符串、含重复元素的全排列 子集的生成、真子集、集合
	
	public static void main(String[] args) {
		int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		int arr2[] = { 1, 2, 3 };
		f(arr1, 0);
	}
 
	// 确认某一个排列的第k位
	private static void f(int[] arr, int k) {
		if (k == arr.length) { // 全部确认
			for (int x : arr) {
				System.out.print(x);
			}
			System.out.println();
			return;
		}
		for (int i = k; i < arr.length; i++) { // 选定第k位,
			// 将第i位和第k位交换
			int t = arr[i];
			arr[i] = arr[k];
			arr[k] = t;
 
			f(arr, k + 1); // 移交下一层去确认k+1位
 
			// 回溯(换回来)
			t = arr[i];
			arr[i] = arr[k];
			arr[k] = t;
		}
	}
 
}

不同子串

public class 不同字串 {
    public static void main(String[] args) {
        String a="0100110001010001";
        Set<String> set=new HashSet<>();
        int n=a.length();
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                String substring = a.substring(i, j + 1);
                set.add(substring);
            }
        }
        for (String s : set) {
            System.out.println(s);
        }
    }
}

BFS迷宫

package q2019;

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class migong {
    static int N =(int)1e2+10;
    static int dir[][]= {{0,1},{-1,0},{1,0},{0,-1}};
    static int n;
    static int m;
    static boolean f[][]=new boolean[N][N];
    static int sx;
    static int sy;
    static int ex;
    static int ey;
    static int str[][]=new int[N][N];
    static int bfs(int sx,int sy,int ex,int ey) {
        Queue<Node> q=new LinkedList();
        q.offer(new Node(sx,sy,0));
        f[sx][sy]=true;
        while(!q.isEmpty()) {
            int x=q.peek().x;
            int y=q.peek().y;
            int step=q.peek().step;
            q.poll();
            if(x == ex && y == ey)//z终点
                return step;
            for(int i=0;i<4;i++) {
                int xx=x+dir[i][0];
                int yy=y+dir[i][1];
                if(xx >= 0 && xx <n && yy >= 0 && yy < m && str[xx][yy]==1&&!f[xx][yy])
                {
                    f[xx][yy] = true;
                    q.offer(new Node(xx, yy,step+1));
                }
            }
        }
        return -1;
    }

    public static void main(String[] args) throws IOException {
        // TODO 自动生成的方法存根
        Scanner in=new Scanner(System.in);
        n=in.nextInt();
        m=in.nextInt();
        for(int i=0;i<n;i++) {
            for(int j=0;j<m;j++) {
                str[i][j]=in.nextInt();
            }
        }
        sx=in.nextInt();
        sy=in.nextInt();
        ex=in.nextInt();
        ey=in.nextInt();
        System.out.println(bfs(sx-1,sy-1,ex-1,ey-1));
    }
    static class Node {
        int x;
        int y;
        int step;
        public Node(int x, int y,int step) {
            super();
            this.x = x;
            this.y = y;
            this.step=step;
        }

    }

}

并查集 七段码

package q2020;

/*
上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二 极管,分别标记为 a, b, c, d, e, f, g。
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。
例如:b 发光,其他二极管不发光可以用来表达一种字符。
例如:c 发光,其他二极管不发光可以用来表达一种字符。这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。
例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
 */
public class q4七段码 {
    /**
     * @param args
     * 本题的大致思路是将数码管的七段进行全排列(用数字代表数码管字段,0代表a,1代表b,以此类推)然后将这7个数字的所有可能全部排列(从7个数字中取m(1<=m<=7)进行排列)列举出来
     * 得到所有的取值情况,再判断每种情况构成的图是否连通,若连通,sum++
     * 进行排列时需要注意,一定要保证每个排列必须是递增或者递减,这样才能不重复,例如:012,021,210等等,它们都表示数码管中取出ABC这一种情况
     */
    static boolean[][] M;//M是邻接矩阵
    static int[] a;//a代表排列组合的数字
    static int sum = 0;//最后结果

    public static void main(String[] args) {
        /*M是邻接矩阵,根据数码管图像可以得到
         * 例如:a和b,a和f都可以连通,那么代表0和1,0和5连通
         */
        M = new boolean[7][7];
        M[0][1] = M[1][0] = M[0][5] = M[5][0] = true;
        M[1][2] = M[2][1] = M[1][6] = M[6][1] = true;
        M[2][3] = M[3][2] = M[2][6] = M[6][2] = true;
        M[4][3] = M[3][4] = true;
        M[4][5] = M[5][4] = M[4][6] = M[6][4] = true;
        M[5][6] = M[6][5] = true;
        a = new int[7];
        for (int i = 0; i < a.length; i++) {
            a[i] = i;
        }
        //所有排列的可能,深搜
        for (int n = 1; n <= 7; n++) {
            dfs(0, n);
        }
        System.out.println(sum);

    }

    public static void dfs(int k, int n) {
        if (k == n) {
            if (n == 1) {//如果只有一个数,那么这种情况下必然成立
                sum++;
                return;
            }
            //判断,这种情况下的图是否连通。用的是并查集方法
            //初始化
            int[] pre = new int[n];
            for (int i = 0; i < pre.length; i++) {
                pre[i] = i;
            }
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    //两层for穷举所有边的情况
                    //若i和j连通,j加入i
                    //i和j代表的是结点,所以并的时候就是jion(pre, i,j)。但是i代表的结点好j代表的结点是否连通,则需要看a[i]]和[a[j]在邻接矩阵M中是否为真
                    if (M[a[i]][a[j]]) {
                        jion(pre, i, j);
                    }
                }
            }
            //到最后,若所有结点都连通,则所有结点的跟结点应该都一样。否则说明此情况下的图不连通
            boolean flag = true;
            for (int i = 1; i < pre.length; i++) {
                if (find(pre, 0) != find(pre, i)) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                sum++;
            }
            return;
        }
        //dfs,深搜
        for (int i = k; i < a.length; i++) {
            if (k == 0 || a[i] > a[k - 1]) {
                int t = a[i];
                a[i] = a[k];
                a[k] = t;
                dfs(k + 1, n);
                t = a[i];
                a[i] = a[k];
                a[k] = t;
            }
        }
    }

    //查找根节点
    public static int find(int[] pre, int node) {
        int son = node, temp = 0;
        //查找根节点
        while (node != pre[node]) {
            node = pre[node];
        }
        //路径优化
        while (son != node) {
            temp = pre[son];
            //直接通跟
            pre[son] = node;
            //son向上走一格
            son = pre[son];
        }
        return node;
    }

    //两个结点相并
    public static void jion(int[] pre, int x, int y) {
        int fx = find(pre, x);
        int fy = find(pre, y);
        //两个结点属于不同图,相并
        if (fx != fy) {
            pre[fy] = fx;
        }
    }
}

联通区间?

水洼数目

package org.lanqiao.algo.elementary._07_dfs;

import java.util.Scanner;

/*
有一个大小为 N*M 的园子,雨后积起了水。八连通的积水被认为是连接在一起的。请求出
园子里总共有多少水洼?(八连通指的是下图中相对 W 的*的部分)

    ***
    *W*
    ***

限制条件

 N, M ≤ 100

 样例:

 输入
    N=10, M=12

园子如下图('W'表示积水, '.'表示没有积水)

W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出

    3

 */
public class Dfs_3水洼数目 {
  private static int n;
  private static int m;

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    n = sc.nextInt();
    m = sc.nextInt();
    char[][] a = new char[n][];
    for (int i = 0; i < n; i++) {
      a[i] = sc.next().toCharArray();
    }
    int cnt = 0;
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        if (a[i][j] == 'W') {
          dfs(a, i, j);//清除一个水洼
          cnt++;
        }
      }
    }
    System.out.println(cnt);
  }

  private static void dfs(char[][] a, int i, int j) {
    a[i][j] = '.';

    for (int k = -1; k < 2; k++) {//-1,0,1
      for (int l = -1; l < 2; l++) {//-1,0,1
        if (k == 0 && l == 0) continue;

        if (i + k >= 0 && i + k <= n - 1 && j + l >= 0 && j + l <= m - 1) {
          if (a[i + k][j + l] == 'W')
            dfs(a, i + k, j + l);
        }
      }
    }
  }
}

最大公共子串?

矩阵法

    static class Main    {
        static int f(String s1, String s2)//输入两个字符串
        {
            char[] c1 = s1.toCharArray();//将字符串转化为字符数组
            char[] c2 = s2.toCharArray();

            //新建一个二维字符数组
            int[][] a = new int[c1.length+1][c2.length+1];
            //max?最大公共串长度?
            int max = 0;
            for(int i=1; i<a.length; i++){ //将c1的每一个字符和c2的每一个字符一一进行比对
                for(int j=1; j<a[i].length; j++){
                    if(c1[i-1]==c2[j-1]) {//两个字符如果相等,则
                        a[i][j] = a[i-1][j-1]+1 ;  //填空
                        if(a[i][j] > max) max = a[i][j];
                    }
                }
            }

            return max;
        }

        public static void main(String[] args){
            int n = f("abcdkkk", "baabcdadabc");
            System.out.println(n);
        }
    }

不要求连续

package provincialGames_08_2017_JavaB;
 
public class A06_最长公共子序列1 {
	public static int f(String x, String y) {
		if (x.length() == 0)
			return 0;
		if (y.length() == 0)
			return 0;
 
		String x1 = x.substring(1);
		String y1 = y.substring(1);
 
		if (x.charAt(0) == y.charAt(0))
			return f(x1, y1) + 1;
 
		// return ——————————————————————;//填空
		return Math.max(f(x, y1), f(x1, y));
	}
 
	public static void main(String[] args) {
		System.out.println(f("ac", "abcd")); // 2 ac
		System.out.println(f("acebbcde1133", "xya33bc11de")); // 5 abcde
	}
}

最长公共子序列2(LCS)

package provincialGames_08_2017_JavaB;
 
import java.util.Scanner;
 
public class A06_最长公共子序列2 {
	
	public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String str1 = scanner.nextLine().toLowerCase();
            String str2 = scanner.nextLine().toLowerCase();
            System.out.println(findLCS(str1, str1.length(), str2, str2.length()));
        }
    }
 
    public static int findLCS(String A, int n, String B, int m) {
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                dp[i][j] = 0;
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (A.charAt(i - 1) == B.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = dp[i - 1][j] > dp[i][j - 1] ? dp[i - 1][j] : dp[i][j - 1];
                }
            }
        }
        return dp[n][m];
    }
 
}

最小生成树

package Data_structures_algorithms;

import java.util.ArrayList;

public class C2Prim {
    /*
     * 参数G:给定的图,其顶点分别为0~G.length-1,相应权值为具体元素的值
     * 函数功能:返回构造生成的最小生成树,以二维数组形式表示,其中元素为0表示最小生成树的边
     */
    public void getMinTree(int[][] G) {
        int[][] result = G;
        int[] vertix = new int[G.length];   //记录顶点是否被访问,如果已被访问,则置相应顶点的元素值为-2
        for(int i = 0;i < G.length;i++)
            vertix[i] = i;
        ArrayList<Integer> listV = new ArrayList<Integer>(); //保存已经遍历过的顶点
        listV.add(0);      //初始随意选择一个顶点作为起始点,此处选择顶点0
        vertix[0] = -2;    //表示顶点0被访问
        while(listV.size() < G.length) {  //当已被遍历的顶点数等于给定顶点数时,退出循环
            int minDistance = Integer.MAX_VALUE;    //用于寻找最小权值,初始化为int最大值,相当于无穷大的意思
            int minV = -1;   //用于存放未被遍历的顶点中与已被遍历顶点有最小权值的顶点
            int minI = -1;   //用于存放已被遍历的顶点与未被遍历顶点有最小权值的顶点  ;即G[minI][minV]在剩余的权值中最小
            for(int i = 0;i < listV.size();i++) {   //i 表示已被访问的顶点
                int v1 = listV.get(i);
                for(int j = 0;j < G.length;j++) {
                    if(vertix[j] != -2) {    //满足此条件的表示,顶点j未被访问
                        if(G[v1][j] != -1 && G[v1][j] < minDistance) {//G[v1][j]值为-1表示v1和j是非相邻顶点
                            minDistance = G[v1][j];
                            minV = j;
                            minI = v1;
                        }
                    }
                }
            }
            vertix[minV] = -2;
            listV.add(minV);
            result[minI][minV] = 0;
            result[minV][minI] = 0;
        }
        System.out.println("使用Prim算法,对于给定图中的顶点访问顺序为:");
        System.out.println(listV);

    }

    public static void main(String[] args) {
        C2Prim test = new C2Prim();
        int[][] G = {{-1,3,-1,-1,6,5},
                {3,-1,1,-1,-1,4},
                {-1,1,-1,6,-1,4},
                {-1,-1,6,-1,8,5},
                {6,-1,-1,8,-1,2},
                {5,4,4,5,2,-1}};
        test.getMinTree(G);
    }
}

最短路径

package q2021;

public class Q5路径 {


    static int N = 2022;
    static int[][] g = new int[N][N];//NxN的图》》邻接矩阵
    static  int gcd(int a, int b){
        if (b==0)return a;
        return gcd(b,a%b);
    }

    public static void main(String[] args) {
        //最短路径  {{floyd}}  、dijktra、 spfa
        //最短生成树 prim  kruskal

        //初始化
        for (int i = 1; i <= 2021; i++) {
            for (int j = 1; j <= 2021; j++) {
                g[i][j] = 0x3f3f3f3f;//相比于0x7fffffff它不会溢出
            }
        }
//        构图
        for (int i = 1; i <= 2021; i++) {
            for (int j = 1; j <= 2021; j++) {
                if (Math.abs(i - j) <= 21) g[i][j] = i * j / gcd(i, j);//a和b的最小公倍数,就是其乘积/最大公约数(gcd)
            }
        }
        //最短路径模板
        for (int k = 1; k <= 2021; k++) {
            for (int i = 1; i <= 2021; i++) {
                for (int j = 1; j <= 2021; j++) {
                    g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);
                }
            }
        }
        System.out.println(g[1][2021]);
//        System.out.println(0x3f3f3f3f);//1061109567

        /*总结一下:
        我们要计算一个图的最短路径,有很多方法,但是对于本题来说使用floyd算法比较简单
        首先定义一个图(使用邻接矩阵)、初始化它
        接下来构造他,即重新赋值
        最后使用floyd算法求最短路径。
             10266837
             */
    }
}

定义:

最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
    最短路径是从一点出发,到达目的地的路径最小。

总结:

遇到求所有路径之和最小的问题用最小生成树&并查集解决;
    遇到求两点间最短路径问题的用**最短路,**即从一个城市到另一个城市最短的路径问题。

区别:

最小生成树构成后所有的点都被连通,而最短路只要到达目的地走的是最短的路径即可,与所有的点连不连通没有关系。

快速幂运算

GCD

public static void gcd(int a,int b){
	if(b==0){
        return a;
    }    
    return gcd(b,a%b);
}

LGC最小公倍数

x*y/gcd(x,y)

算法很美

数学

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfJDdwn0-1651584189608)(算法基础(可能Img/image-20220405180827502.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wN6Q12w5-1651584189608)(算法基础(可能Img/image-20220405181108225.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lr8lYbry-1651584189608)(算法基础(可能Img/image-20220405181208318.png)]

KMP

package org.lanqiao.algo.elementary._05str;

public class Match02_Kmp {
  public static void main(String[] args) {
    String src = "babababcbabababb";
    int index = indexOf(src, "bababb");
    index = indexOf1(src, "bab");
    System.out.println(index);
  }

  //O(m+n),求count
  private static int indexOf1(String s, String p) {
    if (s.length() == 0 || p.length() == 0) return -1;
    if (p.length() > s.length()) return -1;

    int count = 0;
    int[] next = next(p);
    int i = 0;//s位置
    int j = 0;//p位置
    int sLen = s.length();
    int pLen = p.length();

    while (i < sLen) {
      //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
      //j=-1,因为next[0]=-1,说明p的第一位和i这个位置无法匹配,这时i,j都增加1,i移位,j从0开始
      if (j == -1 || s.charAt(i) == p.charAt(j)) {
        i++;
        j++;
      } else {
        //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
        //next[j]即为j所对应的next值
        j = next[j];
      }
      if (j == pLen) {//匹配成功了
        count++;
        i--;
        j = next[j - 1];
        // return (i - j);
      }
    }
    return count;
  }

  public static int[] next(String ps) {
    int pLength = ps.length();
    int[] next = new int[pLength + 1];
    char[] p = ps.toCharArray();
    next[0] = -1;
    if (ps.length() == 1)
      return next;
    next[1] = 0;

    int j = 1;
    int k = next[j];//看看位置j的最长匹配前缀在哪里

    while (j < pLength) {
      //现在要推出next[j+1],检查j和k位置上的关系即可
      if (k < 0 || p[j] == p[k]) {
        next[++j] = ++k;
      } else {
        k = next[k];
      }
    }
    return next;
  }

  /**
   * 暴力解法
   * @param s
   * @param p
   * @return
   */
  private static int indexOf(String s, String p) {
    int i = 0;
    int sc = i;
    int j = 0;
    while (sc < s.length()) {
      if (s.charAt(sc) == p.charAt(j)) {
        sc++;
        j++;
        if (j == p.length())
          return i;
      } else {
        i++;
        sc = i;//扫描指针以i为起点
        j = 0;//j恢复为0
      }
    }
    return -1;
  }
}

快速幂

package org.lanqiao.algo.elementary._06_math;

/**
 * 以最快速度求a的n次方
 * O(lgn)
 */
public class Case11_NExponent {
  public static int ex(int a, int n) {
    if (n == 0) return 1;
    if (n == 1)
      return a;
    int temp = a; //a 的 1 次方
    int res = 1;
    int exponent = 1;
    while ((exponent << 1) < n) {
      temp = temp * temp;
      exponent = exponent << 1;
    }

    res *= ex(a, n - exponent);

    return res * temp;
  }




  public static void main(String[] args) {
    System.out.println(ex2(2, 10));
  }
}

递归

定义:在方法内部调用方法本身

作用:将一个大型复杂问题转化为一个与原问题相似,规模较小的问题进行求解。

注意事项:在递归中,不能无限调用自己,必须有边界条件,能够让递归结束。否则回栈溢出

package com.lanqiao;

import java.util.Calendar;

public class demo1 {
    //求n的阶乘(递归实现)
    public static  int loop(int n){
        if (n==1){
            return 1;
        }
       return n* loop(n-1);

    }
    public static void main(String[] args) {
        int loop = loop(10);
        System.out.println(loop);
    }
}

树+二叉树+堆

1、可以用一个数组表示一个树

  • 已知父节点求叶子节点: 2i+1,2i+2
  • 已知子节点求父节点:(i-1)/2 (整除啊)

算法基础(可能_第5张图片

2、树的遍历方式

先序遍历(根节点的位置):根左右

中序遍历

后序遍历

代码等一等>__<

3、堆的概念

二叉堆是完全二叉树或者近似完全二叉树

二叉树满足的两个特性:

1.父节点的键值总是大于或者等于(小于或者等于)任意一个子节点的键值。

2.每个节点的左子树和右子树都是一个二叉堆(都是最大堆或者最小堆)

任意节点的值都大于其子节点的值————大顶堆

任意节点的值都小于其子节点的值————小顶堆

二叉树————堆———— 排序

ps:用数组可能是好遍历

算法基础(可能_第6张图片

堆化:

算法基础(可能_第7张图片

贪心

贪心是dp的特例,可以用局部最优解推到全局最优解

一遵循某种规则,不断(贪心地)选取当前最优策略,最终找到最优解
一难占·当前优未必是整体最优

硬币支付问题

package org.lanqiao.algo.elementary._08_dp;
import java.util.Scanner;
import static java.lang.Math.min;

/*
硬币问题
有1元,5元,10元,50元,100元,500元的硬币各c1,c5,c10,c50,c100,c500枚.
现在要用这些硬币来支付A元,最少需要多少枚硬币?
假定本题至少存在一种支付方案.
0≤ci≤10^9
0≤A≤10^9
输入:
第一行有六个数字,分别代表从小到大6种面值的硬币的个数
第二行为A,代表需支付的A元
输入
3 2 1 3 0 2
620
输出
6
*/
public class Case01_硬币支付问题 {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    for (int i = 0; i < 6; i++) {
      cnts[i] = sc.nextInt();
    }
    int A = sc.nextInt();
    int res = f(A, 5);
    System.out.println(res);
  }

  static int[] cnts = new int[6];
  static int[] coins = {1, 5, 10, 50, 100, 500};

  /**
   * 尽量先用大面值,因为不用大面值,将使用更多的小面值硬币,一定得不到最优解
   * @param A
   * @param cur
   * @return
   */
  static int f(int A, int cur) {
    if (A <= 0) return 0;
    if (cur == 0) return A;

    int coinValue = coins[cur];
    int x = A / coinValue;//金额有多少个coinValue
    int cnt = cnts[cur];//当前面值的硬币有cnt个
    int t = min(x, cnt);
    return t + f(A - t * coinValue, cur - 1);//用t个当前面值,剩下的继续处理
  }
}

DP

动态规划方法代表了一类问题(最优子结构or子问题最优性)的一般解法,色设计方法或者策略,不是具体算法。

本质是递推,核心是找到状态状态转移方式,写出dp方程

重叠子问题 dfs --》记忆性递归 带备忘录的递归

形式:

  • 记忆性递归
  • 递推

举例:

  1. 01背包
  2. 钢条切割
  3. 数字三角形问题(滚动数组)
  4. 最长公共子序列
  5. 完全背包
  6. 最长上升子序列问题

总结!

  1. 确定dp数组(dp table)以及下标的含义 dp[i][j】表示背包容量为j时装0~i个东西时的价值
  2. 确定递推公式
  3. dp数组如何初始化 第一行
  4. 确定遍历顺序 先从外面的商品容量开始遍历背包容量
  5. 举例推导dp数组 画图

01背包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0A83Ziu3-1651584189610)(算法基础(可能Img/image-20220405084816468.png)]

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

动态规划-背包问题

这是标准的背包问题,以至于很多同学看了这个自然就会想到背包,甚至都不知道暴力的解法应该怎么解了。

这样其实是没有从底向上去思考,而是习惯性想到了背包,那么暴力的解法应该是怎么样的呢?

每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是 o ( 2 n ) o(2^n) o(2n),这里的n表示物品数量。

所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!

方法一:递归型

方法二:带记忆型的递归

方法三:动态规划

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVMcGxIE-1651584189610)(算法基础(可能Img/image-20220405143743883.png)]

package Data_structures_algorithms.DP;

import java.util.Arrays;

public class _01背包问题 {
    /*
有个重量和价值分别为wi,Vi的物品,从这I物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
1

    static int[] w = new int[]{2, 1, 3, 4};//重量表
    static int[] v = new int[]{3, 2, 4, 2};//价值表
    static int n = 4;//物品数量
    static int W = 5;//背包的承重极限

    static int[][] rec = new int[n][W + 1];

    public static void main(String[] args) {
        int ww = W;//复制一下,不修改之前的值
        //1普通递归
        int ans = dfs(0, 5);
        System.out.println(ans);

        //2带记忆型的递归
        for (int i = 0; i < n; i++) {
            Arrays.fill(rec[i], -1);
        }
        int i = dfs2(0, W);
        System.out.println(i);

        //3、动态规划
        int dp = dp();
        System.out.println(dp);
    }

    //普通递归
    static int dfs(int i, int ww) {//输入是物品数量,背包容量,很明显就这两个在变
        //边界条件
        if (i == n) return 0;//物品数量=4,没东西可选了,所以返回的应该是0价值
        if (ww <= 0) return 0;//装不进了

        int v2 = dfs(i + 1, ww);//不选择当前物品
        if (ww >= w[i]) {//当前背包容量大于选的这个体积
            int v1 = v[i] + dfs(i + 1, ww - w[i]);
            return Math.max(v1, v2);
        } else {
            return v2;
        }
    }

    //当然我们也可以改进这个递归,使用记忆型递归
    //使用【i】【j】二维数组存放已经出现的值
    static int dfs2(int i, int ww) {
        //整体先拷贝没优化之前的
        if (i == n) return 0;
        if (ww <= 0) return 0;

        //1、计算之前先查询,如果有则说明之前查到了
        if (rec[i][ww] >= 0) {
            return rec[i][ww];
        }
        int v2 = dfs(i + 1, ww);

        int ans = 0;

        if (ww >= w[i]) {
            int v1 = v[i] + dfs(i + 1, ww - w[i]);
            ans = Math.max(v1, v2);
        } else {
            ans = v2;
        }
        rec[i][ww] = ans;
        return ans;
    }


    static int dp() {
        int[][] dp = new int[n][W + 1];
        //初始化dp表的第一行
        for (int i = 0; i < W + 1; i++) {//i代表的是列号!!!,背包的容量
            if (i >= w[0]) {
                dp[0][i] = v[0];//dp的0这一行的每一个列等于多少 呢?
            } else {
                dp[0][i] = 0;
            }
        }
        //其他行
        for (int i = 1; i < n; i++) {
            //j是列,也是背包的剩余容量
            for (int j = 0; j < W + 1; j++) {
                if (j > w[i]) {//要的起
                    int i1 = v[i] + dp[i - 1][j - w[i]];//选择当前物品,以及返回剩余容量
                    int i2 = dp[i - 1][j];//要不起,容量不变,只不过选取的物品范围减一
                    dp[i][j] = Math.max(i1, i2);
                } else {
                    dp[i][j] = dp[i - 1][j];//直接装不下
                }
            }
        }
        return dp[n - 1][W];
    }
}

做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!

总结!

  1. 确定dp数组(dp table)以及下标的含义 dp[i][j】表示背包容量为j时装0~i个东西时的价值
  2. 确定递推公式
  3. dp数组如何初始化 第一行
  4. 确定遍历顺序 先从外面的商品容量开始遍历背包容量
  5. 举例推导dp数组 画图

2切钢条

/*
Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。

| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| - | - | - | - | - | - | - | - | - | - |
价格pi | 1 | 5 | 8 | 16 | 10 | 17 | 17 | 20 | 24 | 30 |

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。
注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

 */
package Data_structures_algorithms.DP;

import java.util.Arrays;

public class _2切钢条 {
    /*
Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。

| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| - | - | - | - | - | - | - | - | - | - |
价格pi | 1 | 5 | 8 | 16 | 10 | 17 | 17 | 20 | 24 | 30 |

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。
注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

 */
    //切割钢条,分两部分,1保留的长度,继续分割的长度
    //1、递归方法
    static int n = 10;//长度
    static int[] vs = new int[n + 1];//带记忆型递归的存储空间
    static int[] p = {1, 5, 8, 16, 10, 17, 17, 20, 24, 30};//价格

    static int dfs(int x) {//输入钢条的长度,output:最大价值
        if (x == 0) return 0;
        int ans = 0;
        for (int i = 1; i <= x; i++) {
            //i是钢条的保留长度
            //记忆递归
            if (vs[x - i] == -1) {
                vs[x - i] = dfs(x - i);//都存好了,方便以后取
            }
            int v = p[i - 1] + vs[x - i];
            ans = Math.max(ans, v);//每一次选择保留长度为i的钢条的最大价值
        }
//        System.out.print(ans+" ");//1 5 8 16 17 21 24 32 33 37 37
        return ans;
    }

    //基础版递归
    static int r(int x) {//什么在变就将其作为输入,很明显这题是钢管的长度在变
        if (x == 0) return 0;
        int ans = 0;
        for (int i = 1; i <= x; i++) {
            int v = p[i] + r(x - i);
            ans = Math.max(ans, v);
        }
        return ans;
    }

    //dp
    static int dp(){//?为什么不要传参
        vs[0]=0;//初始化dp数组?
        for (int i = 1; i <= n; i++) {//外层从拥有的钢条长度开始循环
            for (int j = 1; j <=i; j++) {//保留的j为整段
                vs[i]=Math.max(p[j-1]+vs[i-j],vs[i]);
            }
        }
        return vs[n];
    }

    public static void main(String[] args) {
        Arrays.fill(vs, -1);
        System.out.println(dfs(10));

        Arrays.fill(vs, 0);
        System.out.println(dp());
    }
}

02数字三角

* 数字三角形(POJ1163)
*

* 在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
* 路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。
* 三角形的行数大于1小于等于100,数字为 0 - 99
* 输入格式:
* 5 //表示三角形的行数 接下来输入三角形
* 7
* 3 8
* 8 1 0
* 2 7 4 4
* 4 5 2 6 5
* 要求输出最大和

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JuIRMvcc-1651584189611)(算法基础(可能Img/image-20220405162451681.png)]

      7
    3 8
    8 1 0
   2 7 4 4
  4 5 2 6 5
package org.lanqiao.algo.elementary._08_dp;

import java.util.Scanner;

import static java.lang.Math.max;

/**
 * 数字三角形(POJ1163)
*

* 在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
* 路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。
* 三角形的行数大于1小于等于100,数字为 0 - 99
* 输入格式:
* 5 //表示三角形的行数 接下来输入三角形
* 7
* 3 8
* 8 1 0
* 2 7 4 4
* 4 5 2 6 5
* 要求输出最大和
* * @author zhengwei */ public class Case12_POJ1163_数字三角形 { public static void main(String[] args) { // int[][] triangle = { // {7}, // {3, 8}, // {8, 1, 0}, // {2, 7, 4, 4}, // {4, 5, 2, 6, 5}, // {4, 5, 2, 6, 5, 7}, // {4, 13, 12, 88, 6, 6, 5}, // {3, 8, 7, 11, 9, 22, 66, 3}, // }; // Instant now = Instant.now(); // System.out.println(maxSumUsingRecursive(triangle, 0, 0)); // System.out.println("持续时间为:" + Duration.ofMillis(Instant.now().toEpochMilli() - now.toEpochMilli()).getSeconds()); // now = Instant.now(); // System.out.println(maxSumUsingMemory(triangle, 0, 0, new int[8][8])); // System.out.println("持续时间为:" + Duration.ofMillis(Instant.now().toEpochMilli() - now.toEpochMilli()).getSeconds()); Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[][] triangle = new int[n][]; for (int i = 0; i < n; i++) { triangle[i] = new int[i + 1]; for (int j = 0; j < i + 1; j++) { triangle[i][j] = sc.nextInt(); } } System.out.println(maxSumUsingDp(triangle, 0, 0)); System.out.println(maxSumUsingRecursive(triangle, 0, 0)); } /** * @param triangle 数字三角形 * @param i 起点行号 * @param j 起点列号 * @return 计算出的最大和 */ public static int maxSumUsingRecursive(int[][] triangle, int i, int j) { int rowIndex = triangle.length; if (i == rowIndex - 1) { return triangle[i][j]; } else { //顶点的值+max(左侧支线的最大值,右侧支路的最大值) return triangle[i][j] + max(maxSumUsingRecursive(triangle, i + 1, j), maxSumUsingRecursive(triangle, i + 1, j + 1)); } } /** * 记忆型递归 * * @param triangle * @param i * @param j * @return */ public static int maxSumUsingMemory(int[][] triangle, int i, int j, int[][] map) { int rowIndex = triangle.length; int value = triangle[i][j]; if (i == rowIndex - 1) { } else { //缓存有值,便不递归 int v1 = map[i + 1][j]; if (v1 == 0) { v1 = maxSumUsingMemory(triangle, i + 1, j, map); } //缓存有值,便不递归 int v2 = map[i + 1][j + 1]; if (v2 == 0) { v2 = maxSumUsingMemory(triangle, i + 1, j + 1, map); } value = value + max(v1, v2); } //放入缓存 map[i][j] = value; return value; } public static int maxSumUsingDp(int[][] triangle, int i, int j) { int rowCount = triangle.length;//行数 int columnCount = triangle[rowCount - 1].length;//最后一行的列数 int[] dp = new int[columnCount]; for (int k = 0; k < columnCount; k++) { dp[k] = triangle[rowCount - 1][k];//初始化最后一行 } for (int k = rowCount - 2; k >= 0; k--) { for (int l = 0; l <= k; l++) { dp[l] = triangle[k][l] + max(dp[l], dp[l + 1]); } } return dp[0]; } }

03最大公共子序列问题

/**
 * 求最大公共子序列问题
 * AB34C
 * A1BC2 结果为 ABC
 * 更多案例请看测试用例
 * */
package org.lanqiao.algo.elementary._08_dp;

import org.assertj.core.api.Assertions;

import java.util.ArrayList;

/**
 * 求最大公共子序列问题
 * AB34C
 * A1BC2 结果为 ABC
 * 更多案例请看测试用例
 * */
public class Case13_LCS {
  public static void main(String[] args) {
    Case13_LCS obj = new Case13_LCS();
    // System.out.println(obj.solution("AB34C", "A1BC2"));
    // Assertions.assertThat(obj.solution("3563243", "513141")).isEqualTo("534");
    // Assertions.assertThat(obj.solution("3069248", "513164318")).isEqualTo("3648");
    ArrayList ans = obj.dfs("AB34C", "A1BC2");
    System.out.println(ans);
    System.out.println(obj.dfs("3563243", "513141"));
    System.out.println(obj.dfs("3069248", "513164318"));
    System.out.println(obj.dfs("123", "456"));

  }

  ArrayList<Character> dfs(String s1, String s2) {
    int len1 = s1.length();
    int len2 = s2.length();
    ArrayList<Character> ans = new ArrayList<>();
    for (int i = 0; i < len1; i++) {
      //求以i字符开头的公共子序列
      ArrayList<Character> list = new ArrayList<>();
      //和s2的每个字符比较
      for (int j = 0; j < len2; j++) {
        if (s1.charAt(i) == s2.charAt(j)) {//如果相同
          list.add(s1.charAt(i));
          list.addAll(dfs(s1.substring(i + 1), s2.substring(j + 1)));
          break;
        }
      }
      if (list.size() > ans.size()) {
        ans = list;
      }
    }
    return ans;
  }
  /**
   * 生成动规表
   * @param s1
   * @param s2
   * @return
   */
  String solution(String s1, String s2) {
    int len1 = s1.length();
    int len2 = s2.length();
    int[][] dp = new int[len1 + 1][len2 + 1]; // 动规数组
    int flag = 0;
    // 初始化第一列
    //O(M)
    for (int i = 1; i <= len1; i++) {
      if (flag == 1) {
        dp[i][1] = 1;
      } else if (s1.charAt(i - 1) == s2.charAt(0)) {
        dp[i][1] = 1;
        flag = 1;
      } else {
        dp[i][1] = 0;
      }
    }

    flag = 0;
    //初始化第一行
    //O(N)
    for (int j = 1; j <= len2; j++) {
      if (flag == 1) {
        dp[1][j] = 1;
      } else if (s2.charAt(j - 1) == s1.charAt(0)) {
        dp[1][j] = 1;
        flag = 1;
      } else {
        dp[1][j] = 0;
      }
    }
    //O(M*N)
    for (int i = 2; i <= len1; i++) {  // M
      for (int j = 2; j <= len2; j++) {  // N
        int maxOfLeftAndUp = Math.max(dp[i - 1][j], dp[i][j - 1]);
        if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
          // dp[i][j] = Math.max(maxOfLeftAndUp, dp[i - 1][j - 1] + 1);
          dp[i][j] = dp[i - 1][j - 1] + 1;//这样也是对的……
        } else {
          dp[i][j] = maxOfLeftAndUp;
        }
      }
    }
    // Util.printMatrix(dp);
    return parseDp(dp, s1, s2);
  }

  /**
   * 解析动态规划表,得到最长公共子序列
   * @param dp
   * @param s1
   * @param s2
   * @return
   */
  private String parseDp(int[][] dp, String s1, String s2) {
    int M = s1.length();
    int N = s2.length();
    StringBuilder sb = new StringBuilder();
    while (M > 0 && N > 0) {
      // 比左和上大,一定是当前位置的字符相等
      if (dp[M][N] > Math.max(dp[M - 1][N], dp[M][N - 1])) {
        sb.insert(0, s1.charAt(M - 1));
        M--;
        N--;
      } else {  // 一定选择的是左边和上边的大者
        if (dp[M - 1][N] > dp[M][N - 1]) {
          M--;  //往上移
        } else {
          N--; // 往左移
        }
      }
    }

    return sb.toString();
  }


}

尺取法(双指针法)

根据题目要求,定义头尾两个指针,扫描一个区间,看看是否满足要求,记录头尾指针的位置

尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,通俗地说,在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目前所选取的区间,但却无法确定所要求解的区间如何进一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。

总的来说尺取法处理是问题是一段连续的区间,区间是单调的

尺取法通常是指对数组保存一对下标(起点、终点),然后根据实际情况交替推进区间左右端点以得出答案。
尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以说尺取法是一种高效的枚举区间的方法,是一种技巧,一般用于求取有一定限制的区间个数或最短的区间等等。而且很少会出现纯模板题,更多的还是在复杂的问题中运用蕴含在该技巧中的思想,很多看似复杂的题(包括时间复杂度要求高的题),运用这些小技巧会大有裨益。
当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案,所以要先自己判断好是否可以使用尺取法再开始来coding。

总的来说尺取法处理是问题是一段连续的区间,区间是单调的

例如:

1、 Poj3061

题意:给定一个序列,找出最短的子序列长度,使得其和大于或等于S。

分析:很明显区间是连续递增的,而且处理的问题也是连续的区间,因此可以用上述方法。我们定义两个指针,头指针和尾指针,开始的时候两个指针都指向数值的开头,然后不断让尾指针增加,判断两个指针的区间和是否大于S, 如果是那么我们就可以固定尾指针,让头指针不断加1直到两段区间和小于S那么首位指针之间的长度+1就是一个以尾指针结尾的最短长度,然后再固定头指针,尾指针+1不断重复上述动作直到数组变量完毕答案就找到了

题目小结总览

1、2013

01 世纪末的星期 枚举每个世纪末,判断是否星期天 Calendar
02 马虎的算式 枚举每个位上的数字,组合判断
03 振兴中华 找重复中的变化,找边界
04 黄金连分数 1.识别问题等价于斐波那契的n项和n+1项的比值,2.n要多少才够,3.怎么处理很大的数和精度要求很高的浮点数

05 有理数类 面向对象+分数加法(通分与约分)
06 三部排序 快速排序(三指针区间法)变体;极限思维(考虑全部是0)

07 错误票据 输入比较特殊,排序,迭代(获取断号和重号),输出
08 幸运数 模拟筛选过程,枚举计数
09 带分数 1-9用递归做全排列,对每一个排列,都枚举+和/的位置,然后进行检查
10 连号区间数 枚举所有区间,检查这个区间里面的数排序后是否连号

2、2014

01 武功秘籍 书的构造方式,思维题
02 切面条 发现规律,思维题
03 猜字母 数组中元素的挪动和挤压
04 大衍数列 考察奇偶数判断
05 圆周率 细心,极限思维
06 奇怪的分式 枚举abcd,分数运算,最大公约数
07 扑克排序 带重复元素的全排列
08 分糖果 模拟
**09 地宫取宝 搜索->记忆型递归,因为子问题重复求解
****10 矩阵翻硬币 数学;字符,字符串,BigInteger的互相转化=

3、2015

01 三角形面积 热身 不用编程
02 立方变自身 简单枚举 方法1:相当于水仙花数,求每个位置上的数 2将数字转化为字符串,再利用charAt()计算其于‘0’的差值
03 三羊献瑞 简单枚举 小技巧 根据公式可知,“三”+“祥”进了一位“三”,所以“三”=1 剩下的遍历吧><
*04 循环节长度 有坑 逻辑 模拟,多试几个数,找到规律即可
05 九数组分数 全排列 带分数
06 加法变乘法 简单枚举 小技巧 但是这个题目不需要,将+号变为*号,我们可以只关注修改的部分
07 牌型种数 递归 暴力
08 饮料换购 模拟
9 垒骰子 递归-动规-矩阵快速幂
10 生命之树 Java中递归最多1万层

4、2016

01 煤球数目 找规律 简单计算
02 生日蜡烛 枚举: 1两个年龄 2过生日的次数,等差数列求和的公式
03 凑算式 全排列+check
04 分小组 逻辑
05 抽签递归,搞清楚参数的含义和参数的变化方向
06 方格填数 全排列+check
07**** 剪邮票 全排列(带重复元素的全排列,要用套路全排套路1) +标注(二维数组上标注01) +dfs求连通块数目
08 四平方和 枚举优化(hash缓存)
09*** 取球博弈 典型的博弈框架(带平局)
10**** 压缩变换 hash查找+标注(一维数组上标注01便于区间求和) +区间树(线段树)

2017

小结
01 购物单 简单计算 excel
02 纸牌三角形 全排列+特殊去重
*03 承压计算 数组 注意计量单位,要精确就先放大2^30来做除法
**04 魔方状态 模拟+去重
05 取数位 递归 搞清楚参数的含义及参数变化的方向
06 最大公共子串 经典dp
07 日期问题 常规日期运算,细心,考虑闰年;字符串处理
***08 包子凑数 扩展欧几里得 + 完全背包问题(dp)
09 分巧克力 二分枚举
****10 k倍区间 前缀和+组合数学

2018

01 第几天 简单计算

02 方格记数 for循环寻找满足的点,然后答案++,题目说实话也没怎么看懂

03 复数幂 计算虚数运算方法(a+bi)x(c+di)=(ac-bd)+(bc+ad)i 使用BigInteger, new PrintStrem(new FileOutStrem())

04 测试次数 不会

05 快速排序 递归一般测试极限情况,看看状态的转移,实在不行多写写,测试几种可能

06 递增三元组 只会暴力,先排序,在计算情况

07 螺旋折线 不会

08 日志统计 先定义一个类存放数据(对象数组) //2.匿名内部类 定义,排序器,自定义 比较器

//用于存储答案(各id),因为答案要求输出有序,这里直接用TreeSet
SortedSet ans=new TreeSet<>();

09 全球变暖 连通块+记数 使用宽搜bfs 使用queue Queue queue = new LinkedList();

10 不会

2019

01 组队 暴力,只不过要注意约束条件,不能重复选一个人

02 不同字串 使用双指针+字符串截取(注意左闭右开,j+1)

03 数列求和 保留几位就%几位 例如保留4位就%10000,然后就是斐波那契数列

04 数的分解 for循环爆力解||首先我们规定a

05 迷宫 bfs +走路的套路 上下左右+queue保存点+stack保存走过的路径 套路bfs

06 特别数的和 就是分别判断每个数是否满足要求,求和即可

07 外卖订单 模拟、仔细的模拟

08 人物相关性分析 split分割(“\\s|\,”)正则表达式,

2020

01 门派制作 注意值的引用,小心细心

02 寻找2020 遍历整个二维数组 判断满足条件(注意极限情况)

03 蛇形填数 找规律(多找几个)

04 七段码 全排列+并查集 模板?

05 排序 逻辑推理

06 成绩分析

07 单词分析 26个字母 \n 换行 数字变字母可以用 System.out.println((char) (‘a’ + index) + “\n” + max);

08 数字三角形 递归(参数1 i,j 参数2 left right 记录情况 到达底部判断条件满不满足满足则选择最大值

09 子串分值和 生成子串 看上面的模板

2021真题

01 ASC “A”.hashCode()

02 卡片 遍历一个数的每一位

while (x>0){
   a[x%10]--;
   x/=10;
}

03 直线 首先找到所有的点,根据y=kx+b公式 1想到斜率无穷大的,2想到要通分,要想到斜率可能会重,x>0&&y<0||x=Math.abs(x);y=Math.abs(y); 最后由于进度问题,我们只能放在字符串里面,用分数表示。set存字符串去重!

04 货物摆放 通过题目可知本质是求其最大公约数 ,通过将最小公约数全排列,然后一一判断是否满足,最后得到结果

gcd(最大公约数)——》 最小公倍数=x*y/gcd(x,y)

05 路径 floyd最短路径 首先要懂得如何构图,其次要会最短路径算法 最短路径vs最小生成树 g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);

06 时间显示 转换根据题意 要仔细细心

System.out.println(String.format("%02d:%02d:%02d",hour,minutes,second));
       //首先字符串格式化用string.format(" ",v1),%d代表整型,%f浮点数,%s字符串

07 最小砝码 换一种思维,找规律,多想想,能骗分就偏分吧>=<

08 杨辉三角 递推

09 双向排序 对数组进行排序可以使用Arrays.sort(b,0,3);可以设置起始区间,默认是升序,如果要降序则要自己手写 两层for循环

10 括号序列

类似题目

选或者不选

地宫取宝

做题小技巧

小贴士:

在遇到这类的填空题时,我们可以从边界条件开始,取极限值,如n=0,n=1,看看我们的取值,仔细,细心即可

  • 递归嘛,查看参数的含义不是k增大,就是x减少,但是k应该不会变,k是位数,x减少,x向上取,去掉最末一位,进行递归,知道长度满足k,得到结果

//计算是否是闰年
static boolean isLeap(int year) { // 闰年:2月29天;平年:2月28天
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;//能够被400整除或者是4的倍数但是不能被100整除
}

  • 打印输出到文本中

PrintStream ps = new PrintStream(new FileOutputStream(“text.txt”));
System.setOut(ps);

  • 字符串格式化

string.format(%.2f)//保留两位小数,且四舍五入、

String.format(“%.2f”, xxx):自动四舍五入 xxx要是double\float类型,int不行

保留4位就%10000,然后就是斐波那契数列

04 数的分解 for循环爆力解||首先我们规定a

05 迷宫 bfs +走路的套路 上下左右+queue保存点+stack保存走过的路径 套路bfs

06 特别数的和 就是分别判断每个数是否满足要求,求和即可

07 外卖订单 模拟、仔细的模拟

08 人物相关性分析 split分割(“\\s|\,”)正则表达式,

2020

01 门派制作 注意值的引用,小心细心

02 寻找2020 遍历整个二维数组 判断满足条件(注意极限情况)

03 蛇形填数 找规律(多找几个)

04 七段码 全排列+并查集 模板?

05 排序 逻辑推理

06 成绩分析

07 单词分析 26个字母 \n 换行 数字变字母可以用 System.out.println((char) (‘a’ + index) + “\n” + max);

08 数字三角形 递归(参数1 i,j 参数2 left right 记录情况 到达底部判断条件满不满足满足则选择最大值

09 子串分值和 生成子串 看上面的模板

2021真题

01 ASC “A”.hashCode()

02 卡片 遍历一个数的每一位

while (x>0){
   a[x%10]--;
   x/=10;
}

03 直线 首先找到所有的点,根据y=kx+b公式 1想到斜率无穷大的,2想到要通分,要想到斜率可能会重,x>0&&y<0||x=Math.abs(x);y=Math.abs(y); 最后由于进度问题,我们只能放在字符串里面,用分数表示。set存字符串去重!

04 货物摆放 通过题目可知本质是求其最大公约数 ,通过将最小公约数全排列,然后一一判断是否满足,最后得到结果

gcd(最大公约数)——》 最小公倍数=x*y/gcd(x,y)

05 路径 floyd最短路径 首先要懂得如何构图,其次要会最短路径算法 最短路径vs最小生成树 g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);

06 时间显示 转换根据题意 要仔细细心

System.out.println(String.format("%02d:%02d:%02d",hour,minutes,second));
       //首先字符串格式化用string.format(" ",v1),%d代表整型,%f浮点数,%s字符串

07 最小砝码 换一种思维,找规律,多想想,能骗分就偏分吧>=<

08 杨辉三角 递推

09 双向排序 对数组进行排序可以使用Arrays.sort(b,0,3);可以设置起始区间,默认是升序,如果要降序则要自己手写 两层for循环

10 括号序列

类似题目

选或者不选

地宫取宝

做题小技巧

小贴士:

在遇到这类的填空题时,我们可以从边界条件开始,取极限值,如n=0,n=1,看看我们的取值,仔细,细心即可

  • 递归嘛,查看参数的含义不是k增大,就是x减少,但是k应该不会变,k是位数,x减少,x向上取,去掉最末一位,进行递归,知道长度满足k,得到结果

//计算是否是闰年
static boolean isLeap(int year) { // 闰年:2月29天;平年:2月28天
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;//能够被400整除或者是4的倍数但是不能被100整除
}

  • 打印输出到文本中

PrintStream ps = new PrintStream(new FileOutputStream(“text.txt”));
System.setOut(ps);

  • 字符串格式化

string.format(%.2f)//保留两位小数,且四舍五入、

String.format(“%.2f”, xxx):自动四舍五入 xxx要是double\float类型,int不行

你可能感兴趣的:(蓝桥杯,算法,java)