算法汇总啊

算法思想-----数据结构

  • 数据结构的存储方式 : 顺序存储(数组) , 链式存储(链表)

      顺序存储(数组) : 在内存中的存储空间是连续的 , 所以可以通过索引来获取存储的元素
      链式存储(链表) : 不是连续存储的 , 可能是这一个那一个的 .
      				 通常是由 数据域和指针域组成-->也就是data和next指针 (next指针指向下一个节点的地址)
    
  • 数据结构底层逻辑

      所以啊 , 数据结构的那些东西(数组,链表,栈,队列,图,树,散列表等等)--->其实底层逻辑 都是数组或链表
    
  • 数组和链表优缺点

    数组--->可以随机访问(通过索引) , 但是 需要考虑存储容量的问题
    链表--->没有存储容量的问题 , 但是不能随机访问元素
    
  • 数据结构存在的意义

      数据结构存在的意义---->就是为了处理数据啊(增删改查)--->怎么增删改查呢?---->遍历+递归
      那为什么会有那么多种数据结构呢 ? ---->因为每种数据结构的应用场景不一样(灵活应用,优化代码嘛) 
    

动态规划(DP)

0.题目特点

  • 1.计数(问:how many ways。。。)
    • a.有多少种方式 走到右下角
    • b.有多少种方法 选出k个数使得和是sum
  • 2.求最大值、最小值(最大的一个解题类型)
    • a.从左上角走到右下角 路径的最大数字和
    • b.最长上升子序列的长度
  • 3.求存在性
    • a.取石子游戏,先手是否必胜
    • b.能不能选出k个数 使得和是sum

1.【重点】经典例题(简单一维dp)

1.斐波那契数列

1 1 2 3 5 8 …

这是最经典的递归问题,
但 如果用递归求解,会重复计算一些子问题。
那如何用 动态规划 求解呢。

题目描述:求斐波那契数列的第n项,n<39。
在这里插入图片描述

  • 递归法
    根据递推公式:f(n) = f(n-1)+f(n-2)
int fib(int n){
	if(n<2) return n;
	return fib(n-1)+fib(n-2);
}
  • dp
    • 1.状态 : 最后一步是求f[n]
    • 2.转移方程:f[n] = f[n-1]+f[n-2]
    • 3.初始化:f[1]=1 ;边界条件:n<=1
    • 4.计算顺序:1—>n
public int Fibonacci(int n){
	if(n <= 1) return n;	//边界条件
	int[] fib = new int[n+1];
	fib[1] = 1;		//初始化
	fib[2] = 1;
	for(int i=2;i<=n;i++){	//计算顺序
		 fib[i] = fib[i-1] + fib[n-2];	//状态方程
	return fib[n];
}

2.矩形覆盖

题目描述:我们可以用2*1的小矩形横着或竖着去覆盖更大的矩形。请问用n2*1的小矩形无重叠的覆盖一个2*n的大矩形,总共有多少种方法?

  • 分析:dp[1] = 1 ; dp[2] = 2

      要覆盖2*n的大矩形,
      可以先覆盖一个2*1的矩形,再覆盖2*(n-1)的矩形;
      也可以先覆盖两个个2*2的矩形,再覆盖2*(n-2)的矩形。
      而覆盖2*(n-1)和2*(n-2) 可以看做是子问题,传递下去
    

在这里插入图片描述

- 最后一步:求 dp[n]
- 初始化:dp[1] = 1 ; dp[2] = 2;  边界条件:n<=2
- 转移方程(递归表达式):dp[n] = dp[n-1] + dp[n-2]
- 计算顺序:1-->n
  • 递归法
public int rectCover(int n){
	if(n<=2) return n;
	return rectCover(n-1)+rectCover(n-2);
}
  • dp算法
public int rectCover(int n){
	if(n<=2) return n;
	int[] dp = new int[n+1];
	dp[1] = 1;
	dp[2] = 2;
	for(int i=3;i<=n;i++){
		dp[i] = dp[i-1]+dp[i-2];
	}
	return dp[n];
}

3.跳台阶

题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级台阶总共有多少种跳法。
在这里插入图片描述

  • 分析

      int[] dp = new int[n]; //dp[i]表示跳到第i级台阶有多少种跳法
      
      状态(最后一步):d[n]
      初始化:dp[1] = 1 ; dp[2] = 1 ;
      边界条件:n<=2;
      状态转移方程:dp[i] = dp[i-1]+dp[i-2]; 	//dp[i]的状态,要么从i-1的台阶跳1级到i	; 要么从i-2级台阶一次跳2级到i
      计算顺序:1-->n 	//计算 dp[i] 需要先计算 dp[i-1] 和 dp[i-2]
    
public int jumpFloor(int n){
	if(n<=2) return n;
	int[] dp = new int[n+1];
	dp[1] = 1;
	dp[2] = 1;
	for(int i=3;i<=n;i++{
		dp[i] = dp[i-1]+dp[i-2];
	}
	return dp[n];
}

4.变态跳台阶

题目描述:一只青蛙可以跳上1级台阶,也可以跳上2级…它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
在这里插入图片描述

  • 分析

      最后一步:求 dp[n] //跳上n级台阶的方案数
      初始化:dp[1] = 1,dp[2] = 1,...,dp[n] = 1
      状态转移方程:dp[i] = dp[i-1]+dp[i-2]+...+dp[1]	//从所有台阶上都可以调到i级台阶上去
      计算顺序:1-->n
    
  • 代码实现

public int jumpFloorII(int n){
	int[] dp = new int[n+1];
	Arrays.fill(dp,1);	//把dp数组中所有元素初始化为1
	//对于每一级台阶,方案数都是前面所有台阶的方案数的和
	for(int i=1;i<=n;i++){	
		for(int j=1;j<i;j++){
			dp[i] += dp[j];
		}
	}
	return dp[n];
}

2.我的日常练习汇总(DP)

1.蓝桥真题-----路径

蓝桥真题:路径
在这里插入图片描述

import java.util.Arrays;
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        //在此输入您的代码...
        //最短路径-->dp
        //最小公倍数 = 两数乘积/最大公约数
        System.out.println(check());
        scan.close();
    }

    public static int check(){
      int[] dp = new int[2021+1];
      //结束条件 
      //初始化
      Arrays.fill(dp,Integer.MAX_VALUE);
      dp[1] = 0;
      dp[2] = 2;
      
      for(int i=3;i<=2021;i++){
        for(int x=i-21;x<i;x++){
          if(x<=0) continue;
          dp[i] = Math.min((dp[x] + lcm(i,x,gcb(i,x))) , dp[i]);
        }
      }
      return dp[2021];
    }

    public static int gcb(int a,int b){
      if(b == 0) return a;
      return gcb(b,a%b);
    }
    public static int lcm(int a,int b,int gcb){
      return (a*b)/gcb;
    }
}

贪心

蓝桥真题–买二赠一
在这里插入图片描述

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main{
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] a = new int[n];
		for(int i=0;i<n;i++) 
			a[i] = scan.nextInt();
		
		Arrays.sort(a);
		
		//创建一个列表,存放p/2,每次取对头进行比较
		Queue<Integer> q = new LinkedList<Integer>();
		//标记是否取了第二个
		boolean t = false;
		long sum = 0L;
		
		int i = n-1;
		//对每个商品进行分析
		while(i>=0) {
			//如果能免费就免费
			if(!q.isEmpty() && a[i] <= q.peek()) {
				q.poll();
			}else {
				//不能免费就买了
				sum+=a[i];
				//如果是第二件商品,那就加price/2到q中
				if(t == true) {
					q.add(a[i]/2);
					t = false;
				}else {
					t = true;
				}
			}
			i--;
		}
		System.out.println(sum);
	}
}

优先队列(堆)

蓝桥杯–最大开支
在这里插入图片描述
在这里插入图片描述

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Scanner;

//计算出每个项目增加一人,总收益会增加多少
//每次放入一人,这个人要在所有项目中,选择商家收益最高的那个项目

public class Main{
	//写一个pojo,实现随着人数增加 , 而计算总收益(因为在后面写到k,b,i++的时候,我获取不到对应的k,b值)
	static class Game{
		public int k;
		public int b;
		public int people;
		
		public Game(int k,int b) {
			this.k = k;
			this.b = b;
			people = 0;
		}
		
		//每增加一人,项目的总收益变化
		public int earn() {
			return k*(2*people+1)+b;	//(k(x+1)+b)(x+1)-(kx+b)x
		}
		
	}
	
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
//		List<Game> list = new LinkedList<>();
		PriorityQueue<Game> pQueue = new PriorityQueue<>((a,b) -> b.earn() - a.earn());

		int n = scan.nextInt();	//n个
		int m = scan.nextInt();	//m个项目
		for(int i=0;i<m;i++) {
			int k = scan.nextInt();
			int b = scan.nextInt();
//			list.add(new Game(k, b));
			pQueue.add(new Game(k, b));
		}
		
		//每个人开始选择项目	
		//注:我用的earn是x+1 - x	所以是从i=0开始
		//如果用的是 x - x-1 那么应从i=1开始
		long sum = 0L;
		for(int i=0;i<n;i++) {
//			list.sort((a,b)->b.earn()-a.earn());//降序排列
			//如果项目赚不到钱,那就直接退出
//			if(list.get(0).earn() <= 0) {
//				break;
//			}
//			sum += list.get(0).earn();
//			list.get(0).people++;
			
			if(pQueue.peek().earn() <= 0) {
				break;
			}
			//这样不行--->这样的话 堆不会自动更新排序-->需要把队头拿出来,再放回队尾-->让堆重新排序
//			sum += pQueue.peek().earn();
//			pQueue.peek().people++;
			
			Game cur = pQueue.poll();
			sum += cur.earn();
			cur.people++;
			pQueue.add(cur);	//重新加入队列以触发排序
		}
		System.out.println(sum);
	}
	
	
}

前缀和 和 差分

前缀和

一维前缀和的预处理

其实就是创建一个一维数组 , 只是让这个数组的值为 a[1] + … + a[i] 而已

int n = in.nextInt();
int[] sum = new int[n];
int[] a = new int[n];

for(int i=1;i<=n;i++){
	sum[i] = sum[i-1] + a[i];	//sum[i] = a[1] + ... a[i-1] + a[i]
}

用来解决什么问题?

  • 例 :
    题目描述:
    输入一个长度为n的整数序列 , 接下来输入m个询问 , 每个询问输入一对 l , r 。对于每个询问 , 输出原序列中从第l个数到第r个数的和 。

    分析:
    对于数据量小的 当然可以用暴力循环去求和
    可是 , 不管数据量大还是小 , 暴力求和的时间复杂度都挺大的
    所以我们考虑优化-----前缀和

import java.util.Scanner;

public class Main{
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int m = scan.nextInt();
		int[] a = new int[n+1];
		int[] s = new int[n+1];
		
		for(int i=1;i<=n;i++) {
			a[i] = scan.nextInt();
			s[i] = s[i-1] + a[i];	//前缀和数组
		}
		
		for(int i=0;i<m;i++) {
			int l = scan.nextInt();
			int r = scan.nextInt();
			System.out.println(s[r] - s[l-1]);	//区间和的计算
		}
	}
}

二维前缀和的预处理

其实就是用来处理二维数组 的 前缀和 叫做二维前缀和

s[i][j] = s[i-1][j] + s[i][j-1] + a[i][j] - s[i-1][j-1]

用来处理什么问题?


  • 给定一个二维数组的两个坐标点 (x1,y1) , (x2,y2)
    求 这两个坐标点之间的数组的和
    分析:
    暴力循环当然可解 , 但和上面同样的问题 时间复杂复大
    所以优化 : 前缀和
import java.util.Scanner;

public class Main{
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();	//二维数组的行数
		int m = scan.nextInt();	//二维数组的列数
		int q = scan.nextInt();	//输入的询问数
		
		int[][] a = new int[n+1][m+1];
		int[][] s = new int[n+1][m+1];
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				a[i][j] = scan.nextInt();
				s[i][j] = s[i-1][j] + s[i][j-1] + a[i][j] - s[i-1][j-1];	//预处理 得到前缀和数组
			}
		}
		
		for(int i=0;i<q;i++) {
			int x1 = scan.nextInt();
			int y1 = scan.nextInt();
			int x2 = scan.nextInt();
			int y2 = scan.nextInt();
			
			System.out.println(s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]);	//按输入进行计算
			
		}
	}
}

差分

差分其实思路和前缀和差不太多

例题: 先来看下区别

  • 题目描述:
    输入一个长度为n的整数序列
    接下来输入m个操作 , 每个操作包含三个整数l,r,c , 表示将序列中[l,r]之间的每个数加上c.
    请你输出进行完所有操作后的序列.
  • 输入格式:
    第一行包含两个整数nm
    第二行包含n个整数,表示整数序列
    接下来m行,每行包含三个整数l,r,c , 表示一个操作
  • 输出格式:
    共一行 , 包含n个整数 , 表示最终序列
    !!! a数组其实就是b数组的前缀和数组 !!!
import java.util.Scanner;

public class Main{
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int m = scan.nextInt();
		
		int[] a = new int[n+1];	//原数组
		int[] b = new int[n+1];	//差分数组
		
		for(int i=1;i<=n;i++) {
			a[i] = scan.nextInt();
			b[i] = a[i] - a[i-1];	//构建差分数组
		}
		
		for(int i=0;i<m;i++) {
			int l = scan.nextInt();	//左边索引
			int r = scan.nextInt();	//右边索引
			int c = scan.nextInt();	//要进行运算的数
			
			//进行差分处理
			b[l] += c;	
			b[r+1] -= c;
		}
		
		for(int i=1;i<=n;i++) {
			b[i] += b[i-1];		//答案
			System.out.print(b[i]+" ");
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值