算法 复盘

在这里插入图片描述

1. 快速排序

🤠 原题链接
🤠 快速排序+选择性递归
🤠 O( 2n )

import java.util.*;

class Main{
    static int N = 1010;
    static int[] q = new int[N];
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        for(int i =0; i < n; i++)
            q[i] = sc.nextInt();
        
        System.out.println(quickSort(0,n-1,k));    
    }
    
    static int quickSort(int l,int r,int k){
        if(l==r) return q[l];//递归出口
        
        int x = q[l];//分界点
        int i = l-1;//左边界
        int j = r+1;//右边界
        // 快速排序
        while(i<j){
            while(q[++i]<x);//从左到右找到一个大于 x 的数
            while(q[--j]>x);//从右到左找到一个小于 x 的数
            if(i<j){
                int t = q[i];
                q[i] = q[j];
                q[j] = t;
            }
        }
        
        int sl = j-l+1;//左边区间的长度
        if(k <= sl) 
         return quickSort(l,j,k);//递归左区间
        
        return quickSort(j+1,r,k-sl);//递归右区间
        
    }
}

2. 归并排序

🤠 参考链接
🤠 归并求逆序对(分而治之)

	static int N = 100010;
	static int[] q = new int[N], tmp = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for (int i = 0; i < n; i++)
			q[i] = sc.nextInt();

		System.out.println(mergeSort(0, n - 1));
	}

	/**
	 * @param l 区间左边界
	 * @param r 区间右边界
	 * @return long 类型的区间内的逆序对
	 */
	private static long mergeSort(int l, int r)
	{
//		递归出口
		if (l >= r)
			return 0;// 区间只有一个数了,没有逆序队

//		分
		int mid = l + r >> 1;
		long res = mergeSort(l, mid) + mergeSort(mid + 1, r);

//		治 (归并)
		int k = 0;// 临时数组指针
		int i = l;// 左区间指针
		int j = mid + 1;// 右区间指针
		while (i <= mid && j <= r)
		{
			if (q[i] <= q[j])// 无逆序对
				tmp[k++] = q[i++];
			else
			{// 右边区间的数比左边区间的数小,有逆序对
				tmp[k++] = q[j++];
				res += mid - i + 1;
			}
		}
//		断后
		if (i <= mid)
			tmp[k++] = q[i++];
		if (j <= r)
			tmp[k++] = q[j++];

//		物归原主
		for (i = l, j = 0; i <= r; i++, j++)
		{
			q[i] = tmp[j];
		}

		return res;
	}

3. 二分

🤠 参考链接


import java.util.*;

public class 数的三次方根
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		double n = sc.nextDouble();

		double l = -10000, r = 10000;
//		浮点数二分,有可能无穷,所以只要 l r 无穷接近即可,具体的精度按题目所给+2(保险)
		while (r - l > 1e-8)
		{
			double m = (r + l) / 2;// 浮点数 不能用位移
			if (m * m * m > n)
//				边界更新为同一个点
				r = m;
			else
			{
//				边界更新为同一个点
				l = m;
			}
		}
//		保留 6 位小数
		System.out.printf("%.6f", l);
	}
}

4. 前缀和

🤠 原题链接
🤠 避坑:花一天可以浇 n 次,不仅仅是 0 1 2

import java.util.*;
public class Main
{
	static int N = (int) 1e5 + 10;
	static int[] A = new int[N];// 差分数组
	static int[] B = new int[N];// 前缀和数组

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();// 假期天数
		int m = sc.nextInt();// 人数

		for (int i = 0; i < m; i++)
		{
			int a = sc.nextInt();
			int b = sc.nextInt();
			A[a] += 1;
			A[b + 1] -= 1;
		}

		for (int i = 1; i <= n; i++)
		{
			B[i] = A[i] + B[i - 1];
		}
		int k = 1;
		for (k =1; k <= n; k++)
		{
			if (B[k] == 0 || B[k] == 2)
			{
				System.out.print(k + " " + B[k]);
				break;
			}
		}
		if (k == n+1)
			System.out.print("OK");

	}
}

5. 二维前缀和

🤠 参考链接

import java.util.*;

public class 子矩阵的和
{
	static int N = 1010;
//	坐标表示 格子
	static int[][] a = new int[N][N];// 原数组
	static int[][] s = new int[N][N];// 前缀和数组

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		int q = sc.nextInt();
//		下标从 1 开始 ,因为求前缀和数组需要用到 n-1 
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				a[i][j] = sc.nextInt();

//		预处理前缀和数组
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];

		while (q-- > 0)
		{
//			[x1,y1] 左上角的格子 ;[x2,y2] 右下角的格子 
			int x1 = sc.nextInt();
			int y1 = sc.nextInt();
			int x2 = sc.nextInt();
			int y2 = sc.nextInt();
			int res = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
			System.out.println(res);
		}

	}
}

6. 差分

🤠 参考链接

import java.util.*;

public class 差分
{
	static int N = 100010;
	static int[] a = new int[N];// 差分数组

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			insert(i, i, sc.nextInt());
		}

		while (m-- > 0)
		{
			int l = sc.nextInt();
			int r = sc.nextInt();
			int c = sc.nextInt();
			insert(l, r, c);
		}

		for (int i = 1; i <= n; i++)
		{
			a[i] += a[i - 1];
			System.out.print(a[i] + " ");
		}
	}

	public static void insert(int l, int r, int c)
	{
//		在 区间 l~r 上加上 c
		a[l] += c;
		a[r + 1] -= c;
	}

}

7. 二维前缀和

🤠 参考地址

import java.util.Scanner;

public class 差分矩阵
{
	static int N = 1010;
	static int[][] a = new int[N][N];// 前缀和
	static int[][] b = new int[N][N];// 差分数组


	/**
	 * @param x1 左上角横坐标
	 * @param y1 左上角纵坐标
	 * @param x2 右下角横坐标
	 * @param y2 右下角纵坐标
	 * @param c 在该区间内(的前缀和)添加的常数
	 */
	static void insert(int x1, int y1, int x2, int y2, int c)
	{
		b[x1][y1] += c;// 左上角
		b[x1][y2 + 1] -= c;// 右上角
		b[x2 + 1][y1] -= c;// 左下角
		b[x2 + 1][y2 + 1] += c;// 右下角
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();// 行数
		int m = sc.nextInt();// 列数
		int q = sc.nextInt();// 查询数

//		预处理差分数组
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				insert(i, j, i, j, sc.nextInt());

		while (q-- > 0)
		{
			int x1 = sc.nextInt();
			int y1 = sc.nextInt();
			int x2 = sc.nextInt();
			int y2 = sc.nextInt();
			int c = sc.nextInt();
			insert(x1, y1, x2, y2, c);
		}

		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
				System.out.print(a[i][j] + " ");
			}
			System.out.println();
		}
	}

}

8. 双指针

🤠 数组元素的目标和
🤠 写个暴力 ——》 单调性 ——》双指针

import java.util.*;

public class 数组元素的目标和
{
	static int N = 10010;
	static int[] a = new int[N];
	static int[] b = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		int x = sc.nextInt();

		for (int i = 0; i < n; i++)
			a[i] = sc.nextInt();
		for (int i = 0; i < m; i++)
			b[i] = sc.nextInt();

//		双指针:有单调性
		for (int i = 0, j = m - 1; i < n; i++)
		{
			while (a[i] + b[j] > x)
				j--;

			if (a[i] + b[j] == x)
			{
				System.out.println(i + " " + j);
				break;
			}
		}
	}
}

9. 数组实现单链表

🤠 参考链接

import java.util.*;

public class 数组实现单链表
{
	static int N = 100010;
	static int[] e = new int[N];// 节点数组,存储 i 节点的值
	static int[] ne = new int[N];// next 数组,存 i 节点的下一个节点对应的(节点)数组下标
	static int head, idx;// head 是头指针,idx 是节点数组的尾坐标

//	链表的初始化
	static void init()
	{
		head = -1;// 表示链表为空
		idx = 0;
	}

	/**
	 * 头插法
	 * 
	 * @param x 把元素 x 添加到链表中
	 */
	static void add(int x)
	{
		e[idx] = x;// 节点数组存值
		ne[idx] = head;// 对应的 next指针存 head
		head = idx++;// 更新 头指针 为 idx
	}

	/**
	 * @param k 删除第 k 个数后面那个数
	 */
	static void del(int k)
	{
		ne[k] = ne[ne[k]];
	}

	/**
	 * @param k 在第k位后面插入
	 * @param x 值位 x 的元素
	 */
	static void ins(int k, int x)
	{
		e[idx] = x;
		ne[idx] = ne[k];// 让 ne[idx] 代替 k 指向 ne[k]
		ne[k] = idx++;

	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int m = sc.nextInt();
		init();
		while (m-- > 0)
		{
			String s = sc.next();
			switch (s)
			{
			case "H":
				add(sc.nextInt());
				break;
			case "D":
//				注意数组下标从 0 开始,减一(删除头结点得特判)
				int k = sc.nextInt();
				if (k == 0)
				{
					head = ne[head];
				} else
				{
					del(k - 1);
				}
				break;
			case "I":
//				注意数组下标从 0 开始,减一
				ins(sc.nextInt() - 1, sc.nextInt());
				break;
			default:
				break;
			}
		}
		for (int i = head; i != -1; i = ne[i])
		{
			System.out.print(e[i] + " ");
		}
	}
}

10. 数组实现双链表

🤠 参考链接
🤠 0 表示左端点,1表示右端点

import java.util.*;

public class 数组实现双链表
{
	static int N = 10010;
	static int[] e = new int[N];
	static int[] l = new int[N];
	static int[] r = new int[N];
	static int idx;

	/**
	 * 链表初始化
	 */
	static void init()
	{
//		0 表示左端点, 1 表示右端点
		r[0] = 1;
		l[1] = 0;
		idx = 2;// 数组下标从 2 开始
	}

	/**
	 * @param k 在第 k 个节点的后边加上 x
	 * @param x
	 */
	static void add(int k, int x)
	{
		e[idx] = x;// 存值,指针为 idx

		r[idx] = r[k];// 右指针指向 k 的右节点
		l[idx] = k;// 左指针指向 k
		l[r[k]] = idx;// k的右节点的左指针指向 idx
		r[k] = idx;// k的右指针指向 idx
		idx++;
	}

	/**
	 * @param k 删除第 k 个插入的数
	 */
	static void remove(int k)
	{
		r[l[k]] = r[k];// 左节点的右指针指向右节点
		l[r[k]] = l[k];// 右节点的左指针指向左节点

	}

	public static void main(String[] args)
	{
//		数组实现链表,第一步切记初始化
		init();
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();

		while (n-- > 0)
		{
			String op = sc.next();
			if ("L".equals(op))
			{
				int x = sc.nextInt();
				add(0, x);// 0 始终表示 左端点
			} else if ("R".equals(op))
			{
				int x = sc.nextInt();
				add(l[1], x);// 1始终表示 右端点
			} else if ("D".equals(op))
			{
				int k = sc.nextInt();
				remove(k + 1);
			} else if ("IL".equals(op))
			{
				int k = sc.nextInt();
				int x = sc.nextInt();
				add(l[k + 1], x);
			} else if ("IR".equals(op))
			{
				int k = sc.nextInt();
				int x = sc.nextInt();
				add(k + 1, x);
			}
		}
//        从左端点 0 的右指针开始,直到右端点 1 结束
		for (int i = r[0]; i != 1; i = r[i])
		{
			System.out.print(e[i] + " ");
		}
	}
}

11. 单调队列

🤠 参考链接

import java.util.*;

public class 滑动窗口_单调队列
{
	static int N = 1000010;
	static int[] a = new int[N];
	static int[] q = new int[N];// 单调队列,维护 的是a数组的下标
	static int hh, tt;// 队首(先入为首),队尾

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();// 数组长度
		int k = sc.nextInt();// 窗口长度
		for (int i = 0; i < n; i++)
		{
			a[i] = sc.nextInt();
		}

//		队列初始化
		hh = 0;
		tt = -1;

//		求区间 [ i-k+1,i ] 的最小值
		for (int i = 0; i < n; i++)// i 枚举的是窗口的右端点,i-k+1 是左端点
		{
			if (hh <= tt && q[hh] < i - k + 1)
				hh++;// 防止队头滑出窗口

//			维护一个严格单调递增的队列
			while (hh <= tt && a[q[tt]] >= a[i])
				tt--;
			q[++tt] = i;

//			窗口右端点下标 +1 即窗口可维护的最大元素个数
			if (i + 1 >= k)
				System.out.print(a[q[hh]] + " ");
		}
		System.out.println();

//		队列初始化
		hh = 0;
		tt = -1;
//		求区间最大值
		for (int i = 0; i < n; i++)
		{
			if (hh <= tt && q[hh] < i - k + 1)
				hh++;
//			维护严格严格递减的区间,= 也不行
			while (hh <= tt && a[q[tt]] <= a[i])
				tt--;
			q[++tt] = i;

			if (i + 1 >= k)
				System.out.print(a[q[hh]] + " ");
		}

	}
}

12. KMP

🤠 KMP字符串匹配

import java.util.*;

class KMP
{
	static int N = 1000010;

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		String s1 = sc.next();// 源串
		String s2 = sc.next();// 子串

//		求 next 数组
		int[] next = new int[s2.length()];
		next[0] = 0;
//		i 表示第几位,j 表示匹配数量
		for (int i = 1, j = 0; i < next.length; i++)
		{
//			这里让 j 跳回到 j 的前缀 与 i 的前缀相等的位置
			while (j > 0 && s2.charAt(i) != s2.charAt(j))
				j = next[j - 1];//j 更新为前缀已经匹配好的字符个数

//			i 位 于 j 位相匹配,匹配数 +1
			//注意 j 位 表示的是 第 j+1 位字符,前边有 j 位字符已经匹配好了
			if (s2.charAt(i) == s2.charAt(j))
				j++;
//			更新当前位的匹配数量
			next[i] = j;
		}

		for (int i = 0, j = 0; i < s1.length(); i++)
		{
			while (j > 0 && s1.charAt(i) != s2.charAt(j))
				j = next[j - 1];

			if (s1.charAt(i) == s2.charAt(j))
				j++;
			if (j == s2.length())
			{
				System.out.println(i - j + 1 + 1);
				j = next[j - 1];
			}
		}
		System.out.println();
		for (int i = 0; i < s2.length(); i++)
		{
			System.out.print(next[i] + " ");
		}

	}

}

13. 字典树

① 字串匹配

🤠 从前有棵树,树的每个节点有 26 棵子树,存纯字母字符串,可以公用相同前缀节省空间的效果

	static int idx; // 当前 son二维数组中的 一维下标(即第一个[ ](方便开辟空间)
	static int[][] son = new int[10][26]; // 结点数组 一维:树的层数 二维:字符的对应数值(特殊的next指针)
	static int cnt[] = new int[10]; // 记录以该点结束的字符串有多少个

//	trie树的插入操作
	static void insert(char[] str) {
	//所处层数,初始化从 0 开始
		int p = 0;
		for (int i = 0; i < str.length; i++) {
			int u = str[i] - 'a'; // 把字符转换为0到25的数字表示
			if (son[p][u] == 0)
				//以前没有开辟字符,直接新开辟一个一维数组空间,即二维数组的一个下标
				son[p][u] = ++idx; 
			p = son[p][u];
		}
		cnt[p]++;
	}
//	查询操作
	static int query(char str[]) {
		int p = 0;
		for (int i = 0; i < str.length; i++) {
			int u = str[i] - 'a';
			if (son[p][u] == 0) //一个字符不符合,直接pass
				return 0;
			p = son[p][u];
		}
		return cnt[p]; 
	}

② 最大异或对

🤠 核心:二维数组的每一个一维数组都是一个 节点
🤠 最大异或对

import java.util.Scanner;

public class 最大异或对
{
	static int N = 100010;
	static int M = 31 * N;//表示总节点个数
// 存的是子节点的一维地址idx,这里每一个 int[]{0,1} 都表示一个节点
	static int[][] son = new int[M][2];
	static int idx;

	/**
	 * @param x 待插入的数
	 */
	static void insert(int x)
	{
		int p = 0;// p 就是一个 idx ,所以 idx 是从1开始的
		for (int i = 30; i >= 0; i--)
		{
			int k = x >> i & 1;// 取出第 i 位上的数
			if (son[p][k] == 0)// 该位置为空
			{
				son[p][k] = ++idx;//0 已经被使用了,初始化成0,只能先 ++
			}
			p = son[p][k];
		}
	}

	/**
	 * @param x 在trie树中 查找能与 x 亦或 得到最优的结果的数 res
	 * @return 返回该数 res
	 */
	static int query(int x)
	{
		int res = 0;
		int p = 0;
		for (int i = 30; i >= 0; i--)// 用 i 取出 参数 x 的每一位
		{
			int k = x >> i & 1;// x在第 i 位上的数,先从最高位开始取
//			1 0 之间的转换:1+0=1 ; 1-1=0 ,1-0=1  即可实现转换
			if (son[p][1 - k] != 0)// 尽量往不同的分支走,以达到异或值最大
			{
				p = son[p][1 - k];
				res = (res << 1) + (1 - k);
			} else
			{
				p = son[p][k];
				res = (res << 1) + k;
			}
		}
		return res;
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int res = 0;
		for (int i = 0; i < n; i++)
		{
			int x = sc.nextInt();
			int y = query(x);
			res = Math.max(res, x ^ y);
			insert(x);
		}
		System.out.println(res);
	}
}

③ 连续区间最大异或和

🤠 原题地址
🤠 核心:前缀异或和数组:任一个数 s[i] 异或前一个数 s[j] 都是连续区间,即区间【 i ~ j+1 】

import java.util.*;

public class 最大异或和2
{
	static int N = 100010;
//	树的节点数组,有 N 个数,每个数就有 31个节点,每个节点有 2 颗子树
	static int[][] son = new int[N * 32][2];
	static int[] cnt = new int[N];// 当 cnt[i] == 0时,说明没有数经过此点,即不合法
	static int idx, n, m;
	static int[] s = new int[N];// 前缀异或和数组

	/**
	 * @param x 待插入或删除的数
	 * @param f 1是插入,-1是删除
	 */
	static void insert(int x, int f)
	{
		int p = 0;
		for (int i = 30; i >= 0; i--)
		{
			int k = x >> i & 1;
			if (son[p][k] == 0)// 没有可用的适合节点,新建一个,有则复用
			{
				son[p][k] = ++idx;
			}
			p = son[p][k];
			cnt[p] += f;
		}
	}

	/**
	 * @param x
	 * @return 有效数字的最大异或和 (不合法数字已经被删除了,所以可以直接算贪心每一位取最优即可)
	 */
	static int query(int x)
	{
		int p = 0;
		int res = 0;
		for (int i = 30; i >= 0; i--)
		{
			int k = x >> i & 1;
			if (cnt[son[p][1 - k]] > 0)
			{
//				只要有相对的数,异或后的结果当前为必 +1
				res = res * 2 + 1;
				p = son[p][1 - k];
			} else
			{
				res = res * 2;
				p = son[p][k];
			}
		}
		return res;

	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		for (int i = 1; i <= n; i++)
		{
			s[i] = s[i - 1] ^ sc.nextInt();
		}

		insert(0, 1);// 任何数 ^ 0 都等于 它本身
		int res = 0;
		for (int i = 1; i <= n; i++)
		{
			if (i > m)
				insert(s[i - m - 1], -1);// 把超出范围的前缀异或和 删掉

			res = Math.max(res, query(s[i]));
			insert(s[i], 1);
		}
		System.out.println(res);
	}
}

14. 并查集

① 食物链

🤠 食物链
🤠 多维护一个 距离 表示元素间的关系

import java.util.Scanner;

public class Main
{
	static int N = 50010;
	static int[] d = new int[N];// 距离数组,存的是 到父节点的距离
	static int[] p = new int[N];// 并查集数组,存的是 特定点的父节点

	/**
	 * 返回 x 的根节点
	 * 
	 * @param x
	 * @return
	 */
	static int find(int x)
	{
		if (p[x] != x)// 如果 x 不是根节点
		{
			int tmp = find(p[x]);//find过程也会改变 p[x]
			d[x] += d[p[x]];
			p[x] = tmp;		
			
		}
		return p[x];
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
//		初始化,每一个点都是一个独立的集合
		for (int i = 1; i <= n; i++)
			p[i] = i;

		int res = 0;
		while (m-- > 0)
		{
			int t = sc.nextInt();
			int x = sc.nextInt();
			int y = sc.nextInt();

			if (x > n || y > n)
			{
				res++;
				continue;
			}
//			x y 的根节点
			int px = find(x);
			int py = find(y);

			if (t == 1)
			{// 同类

//				px 和 py 在同一集合的情况
				if (px == py && (d[x] - d[y]) % 3 != 0)// 不等于 0 就不是同类
					res++;

//				px 和 py 不在同一个集合的情况
				else if (px != py)
				{
					p[px] = py;// 合并并查集
//					d[px]:px到根 py的距离
//					d[y]:y到根节点 py 的距离
//					d[x]:x到根节点 px 的距离
					d[px] = d[y] - d[x];// x y 同类,得 (d[x]+d[px]-d[y]) % 3 == 0;
				}
			}
//			异类
			else
			{
				// d[x]-d[y] 很可能是负数,为了避免负数取模的多种可能性, -1,就可用 != 0 代替 !=1
				if (px == py && (d[x] - d[y] - 1) % 3 != 0)
					res++;
				else if (px != py)
				{
					p[px] = py;
//					x 吃 y ,按规定,(d[x] + d[px] - d[y] -1 )%3 == 0
					d[px] = d[y] - d[x] + 1;
				}
			}
		}
		System.out.println(res);
	}
}

② 最大度

🤠 原题地址
🤠 要建一个图,使得图中一个节点的度达到最大,那就是 菊花图,度 == 结点数-1

import java.io.*;
import java.util.*;

public class Main
{
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	static int n, d, cnt;// cnt记录多出的边的数量
	static int N = 1010;
	static int[] p = new int[N];
	static int[] s = new int[N];
	static int[] max = new int[N]; // 每个集合中节点的最大度数

	static int find(int x)
	{
		if (p[x] != x)
			p[x] = find(p[x]);
		return p[x];
	}

	public static void main(String[] args) throws IOException
	{
		String[] split = in.readLine().split(" ");
		n = Integer.parseInt(split[0]);
		d = Integer.parseInt(split[1]);

//		初始化并查集
		for (int i = 1; i <= n; i++)
		{
			p[i] = i;
			s[i] = 1;
		}
//		满足需求
		while (d-- > 0)
		{
			String[] split2 = in.readLine().split(" ");
			int x = Integer.parseInt(split2[0]);
			int y = Integer.parseInt(split2[1]);
//			插入一条边,记得先 find 一次,再实现两个集合的合并
			x = find(x);
			y = find(y);
			if (x == y)
				cnt++;
			else
			{
				p[x] = y;
				s[y] += s[x];
			}

//			选 i + cnt + 1 个最大的集合,总和减一就是答案
			int tt = 0;
			for (int j = 1; j <= n; j++)
			{
				if (find(j) == j)
					max[tt++] = s[j];
			}
//			sort 默认就是升序
			Arrays.sort(max, 0, tt);

			int sum = 0;
//			cnt条边,可以连通 cnt+1 个集合
			for (int j = 0; j < tt && j < cnt + 1; j++)
			{
				// 优先取最大值
				sum += max[tt - j - 1];
			}
			System.out.println(sum - 1);
		}
	}
}

15. BFS

🤠 八数码

import java.io.*;
import java.util.*;

public class Main
{
	static int dx[] = { 1, 0, -1, 0 };
	static int[] dy = { 0, 1, 0, -1 };

	public static void main(String[] args) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String[] aa = in.readLine().split(" ");
		String start = "";
		for (int i = 0; i < aa.length; i++)
		{
			start += aa[i];
		}
		int bfs = bfs(start);
		if (bfs == -1)
			System.out.println("dluldrurulldrrulldrrdllurrulddr");
		else
		{
			System.out.println(bfs);
		}
	}

	private static int bfs(String start)
	{
//		string end = "12345678x";表示终点状态
		Queue<String> q = new LinkedList<>();// 维护状态
		HashMap<String, Integer> d = new HashMap<>();// 存到 此状态 的 距离

		q.offer(start);// 入队
		d.put(start, 0);
		String end = "12345678x";

//		宽搜
		while (!q.isEmpty())
		{
			String s = q.poll();
			int distance = d.get(s);
			if (s.equals(end))
			{
				return distance;
			}
			int k = s.indexOf("x");// x 字符串中的位置
			int x = k / 3, y = k % 3;// x 在矩阵中的 二维坐标
			for (int i = 0; i < 4; i++)
			{
				int xx = x + dx[i];
				int yy = x + dy[i];
				if (xx >= 0 && xx < 3 && yy >= 0 && yy < 3)
				{
					char[] arr = s.toCharArray();
					swap(arr, 3 * xx + yy, k);
//					错误实例
//					String ss = arr.toString();//[, C, @, 3, 8, 0, f, b, 4, 3, 4]
//					String ss = Arrays.toString(arr);//[2, 3, 4, 1, 5, 6, 7, x, 8]
					String ss = new String(arr);// 字符数组转成字符串

					if (d.get(ss) == null)// hashmap中没存有 ss 返回 null
					{
						d.put(ss, d.get(s) + 1);
						q.offer(ss);
					}
				}

			}
		}

		return -1;

	}

	public static void swap(char[] a, int i, int j)// 把数组的第i位与第j位互换
	{
		char tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	}
}

16. 高斯消元

🤠 高斯消元解异或线性方程组

import java.io.*;
import java.util.*;

public class 高斯消元
{
	static int N = 4;
	static int[][] a = new int[N][N];
	static int n;

	public static void main(String[] args) throws NumberFormatException, IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		n = Integer.parseInt(in.readLine());

		for (int i = 0; i < n; i++)
		{
			String[] split = in.readLine().split(" ");
			for (int j = 0; j <= n; j++)
			{
				a[i][j] = Integer.parseInt(split[j]);
			}
		}
		int t = gauss();
		if (t == 0)
			for (int i = 0; i < n; i++)
				System.out.println(a[i][n]);
		else if (t == 1)
			System.out.println("Multiple sets of solutions");
		else
		{
			System.out.println("No solution");
		}
	}

	/**
	 * @return 0 表示唯一解 1 表示无穷多组解 2 表示无解
	 */
	static int gauss()
	{
		int r, c;// 表示行列数
		for (r = 0, c = 0; c < n; c++)
		{
			int t = r;// 临时变量记录特定行
//			找出当前列 为 1 的任意一行(靠上面)
			for (int i = r; i < n; i++)// i 枚举行
			{
				if (a[i][c] == 1)
				{
					t = i;
					break;
				}
			}
//			当前列所有数都为 0 
			if (a[t][c] == 0)
				continue;
//			交换 找到的 当前列值为 1 的行 与当前行交换
			for (int i = c; i <= n; i++) // 按列交换
			{
				int tem = a[t][i];
				a[t][i] = a[r][i];
				a[r][i] = tem;
			}
//			对当前行以下的行进行消0操作
			for (int i = r + 1; i < n; i++)
			{
				if (a[i][c] == 1)
				{
//					j 从 c 开始是因为在 当前列 c 之前的数已经被前面的 循环 消 0 了
					for (int j = c; j <= n; j++)
					{
						a[i][j] ^= a[r][j];// 1 ^ 1 = 0;
					}
				}
			}

//			r++处理下一行
			r++;
		}

//		有continue,有某一列都是 0 ,即缺少一个变量 
		if (r < n)
		{
			for (int i = r; i < n; i++)
			{
//				如果常数都不为 0 的话,无解
				if (a[i][n] == 1)
					return 2;
//				不然, k*x = 0; 五穷解方程
				return 1;
			}
		}

//		除去以上两种情况,即是 有唯一解,把举证格式化成正对角线矩阵
		for (int i = n - 1; i >= 0; i--)
		{
			for (int j = i + 1; j < n; j++)
			{
//			下同:a[i][n] ^= a[i][j] & a[j][n]; a[j][n] 是 x[j] 的值 
				a[i][n] ^= a[i][j] & a[j][n];
//			将 a[i][j] 置为 0 的过程
//				a[i][j] ^= a[i][j] * a[i+1][i+1] 
//			所以 a[i][n] 也要这样操作,结果就是 a[i][n]

			}
		}

		return 0;
	}

}

17. 欧几里得求最大公约数

	static long gcd(long a, long b)
	{	
/*  辗转相除法
	求   a,b 的公约数   == 求 a,b+ka 的公约数
	例:求公约数的数   公约数
	   12,18 		   6
	   12,18+12=30	   6
	   30	18		   6
	   12,18+24=42    6   总结可知:a,b 的公约数 ==  a,b%a 的公约数
 */
		return b != 0 ? gcd(b, a % b) : a;

	}

18. 博弈论

① 台阶Nim游戏

🤠 参考地址

import java.util.*;

public class 台阶Nim游戏
{
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int res = 0;
		for (int i = 1; i <= n; i++)//i 枚举台阶数,只考虑 奇数台阶
		{
			int x = sc.nextInt();
			if (i % 2 != 0)
			{
				res ^= x;
			}
		}
		if (res == 0)
		{
			System.out.println("No");
		} else
		{
			System.out.println("Yes");
		}
	}
}

② 拆分Nim游戏

🤠 参考地址

import java.util.*;

public class 拆分Nim游戏
{
	static int N = 110, n;
	static int[] f = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		int res = 0;
		Arrays.fill(f, -1);
		for (int i = 0; i < n; i++)
		{
			res ^= sg(sc.nextInt());
		}
		System.out.println(res == 0 ? "No" : "Yes");
	}

	static int sg(int x)
	{
		if (f[x] != -1)
		{
			return f[x];
		}

		HashSet<Integer> set = new HashSet<>();

		for (int i = 0; i < x; i++)// i 表示分出的 新堆① 的石子数
			for (int j = 0; j <= i; j++)// j 表示 新堆② 的石子数
			{
				// 公式 sg(i,j)=sg(i)^sg(j)
				// 由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和
				set.add(sg(i) ^ sg(j));
			}

//		计算 mex
		for (int i = 0;; i++)
		{
			if (!set.contains(i))
			{
				return f[x] = i;
			}
		}
	}
}

19. 贪心

🤠 最长的上升子序列Ⅱ
🤠 f[i] 存的是最长子序列长度为 i 的 所有序列中末尾元素的 最小值

import java.io.*;

public class 最长上升子序列2
{
	static int N = 100010;
	static int[] a = new int[N];// 原数组
	static int[] f = new int[N];// f[cnt] 存的是 有cnt长度的子序列的所有数 中的最小值, f 是单调递增的
	static int cnt = 0;// cnt 表示最长子序列的长度

	static int find(int x)
	{
		int l = 1;
		int r = cnt;
		while (l < r)
		{
			int mid = l + r >> 1;
			if (f[mid] >= x)
				r = mid;
			else
				l = mid + 1;
		}
		return l;
	}

	public static void main(String[] args) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String[] split = in.readLine().split(" ");
		int n = Integer.parseInt(split[0]);
		String[] split2 = in.readLine().split(" ");
		for (int i = 1; i <= n; i++)
		{
			a[i] = Integer.parseInt(split2[i - 1]);
		}

//		只有一个元素时,最长序列肯定是 1
		f[++cnt] = a[1];
		for (int i = 2; i <= n; i++)
		{
//			后边的元素 大于当前最大子序列的最小末尾值 的时候,子序列长度+1,新元素作为f[cnt++]的最小值
			if (a[i] > f[cnt])
				f[++cnt] = a[i];
			else
			{
//	通过二分查找到大于 a[i] 的最小 f[tem] ,即 在 f 中 tem 前边的数都小于 f[tem], 但f[tem-1] < a[i] < f[tem]
				int tem = find(a[i]);
				f[tem] = a[i];// 把 f[tem] 更新成 a[i],同样长度的子序列,末尾值取最小
			}
		}

		System.out.println(cnt);

	}
}

20. 递归

🤠 原题地址
🤠 类似于语法树,写了个破栈只过了一半案例😭

import java.io.*;
import java.util.*;

public class Main
{
	static StringBuilder sb = new StringBuilder();
	static String[] t;
	static int flag = 1, i = 0;

//	正常情况,每 dfs 一次,  i 就 +1 ,当恰好 i == t.length - 1 时退出循环,则说明 输入 的数据不多不少刚好拼成一个有效字符串
	static void dfs()
	{
		if (flag == 0)
			return;
		if (i < t.length)
		{
			String str = t[i++];
			sb.append(str);
			if (str.equals("pair"))
			{
				sb.append("<");
				dfs();
				sb.append(",");
				dfs();
				sb.append(">");
			}
		} else
		{
			flag = 0;
		}

	}

	public static void main(String[] args) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(in.readLine());
		t = in.readLine().split(" ");
		dfs();
//		没有递归,int 过多的情况
		if (i < t.length)
			flag = 0;

		if (flag == 1)
			System.out.println(sb);
		else
		{
			System.out.println("Error occurred");
		}
	}
}

21. 动态规划

① 最短编辑距离

🤠 参考地址
🤠 f [ i ][ j ]:表示 a[ 1~i ] 变成 b[ 1~j ] 的所有操作次数的最小值

import java.util.*;

public class 最短编辑距离
{
	static int N = 1010;
	static int[][] f = new int[N][N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		String s1 = sc.next();
		char[] a = s1.toCharArray();

		int m = sc.nextInt();
		String s2 = sc.next();
		char[] b = s2.toCharArray();

		for (int i = 0; i < m; i++)
			f[0][i] = 1;

		for (int i = 0; i < n; i++)
			f[i][0] = 1;

		for (int i = 1; i <= n; i++)
			for (int j = 2; j <= m; j++)
			{
//				f[i-1][j]:i-1位与 j 位匹配成功,f[i][j-1]:i位与 j-1 位匹配成功
				f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);// min(删,增)
//				改,同则不用改
				if (a[i - 1] == b[j - 1])
				{
					f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
				} else
				{
					f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
				}
			}
		System.out.println(f[n][m]);
	}
}

② 编辑距离

🤠 参考地址

import java.io.*;
import java.util.*;

public class 编辑距离
{
	static int N = 1010, n, m;
	static int[][] f = new int[N][N];
	static String[] sa = new String[N];
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

	public static void main(String[] args) throws NumberFormatException, IOException
	{
		String[] split = in.readLine().split(" ");
		n = Integer.parseInt(split[0]);
		m = Integer.parseInt(split[1]);

		for (int i = 0; i < n; i++)
			sa[i] = in.readLine();

		for (int i = 0; i < m; i++)
		{
			String[] split2 = in.readLine().split(" ");
			String s = split2[0];
			int limit = Integer.parseInt(split2[1]);
			int ans = 0;
			for (int j = 0; j < n; j++)
			{
				if (limit >= getEdits(sa[j], s))
					ans++;
			}
			out.write(ans+"/n");
		}
		out.flush();
	}

	/**
	 * @param a 起始串
	 * @param b 目标串
	 */
	private static int getEdits(String a, String b)
	{
		for (int i = 0; i <= a.length(); i++)
			f[i][0] = i;

		for (int i = 0; i <= b.length(); i++)
			f[0][i] = i;

		for (int i = 1; i <= a.length(); i++)
			for (int j = 1; j <= b.length(); j++)
			{
				f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1); // min(删,增)
//				改
				if (a.charAt(i - 1) == b.charAt(j - 1))
					f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
				else
				{
					f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
				}
			}
		return f[a.length()][b.length()];
	}
}

③ 整数划分

🤠 类似完全背包
🤠 f[ i ] [ j ]:表示 在(0~i)中的数选出总值为 j 的方案个数,支持重复选
🤠 参考地址

🤠 二维版

import java.util.Scanner;

public class 整数划分
{
	static int N = 1010, mod = (int) 1e9 + 7;
	static int[][] f = new int[N][N];

	public static void main(String[] args)
	{
		int n = new Scanner(System.in).nextInt();

//		容量为 0 时,只有 都不选 一种方案
		for (int i = 0; i <= n; i++)
			f[i][0] = 1;

		for (int i = 1; i <= n; i++)// 枚举在 i 个数中选
			for (int j = 1; j <= n; j++)// j 枚举总和
			{
				f[i][j] = f[i - 1][j];// 处理 i > j 的情况
				if (j >= i)
					f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
			}

		System.out.println(f[n][n]);

	}
}

🤠 一维优化版

import java.util.Scanner;

public class 整数划分plus
{
	static int N = 1010, mod = (int) 1e9 + 7;
	static int[] f = new int[N];// 枚举容量

	public static void main(String[] args)
	{
		int n = new Scanner(System.in).nextInt();

		f[0] = 1;

		for (int i = 1; i <= n; i++)// 枚举在 i 个数中选
			for (int j = i; j <= n; j++)// j 枚举总和,注意 j 从 i 开始
				f[j] = (f[j] + f[j - i]) % mod;

		System.out.println(f[n]);
	}
}

🤠 刁钻角度版
🤠 状态表示: f[ i ][ j ] 所有总和是i,并且恰好可以表示成j个数的和的方案

import java.util.Scanner;

public class 整数划分2
{
	static int N = 1010, mod = (int) 1e9 + 7;
	static int[][] f = new int[N][N];// f[总和][可选数]

	public static void main(String[] args)
	{
		int n = new Scanner(System.in).nextInt();

		f[0][0] = 1;

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= i; j++)
//				分为包含 1 的方案和不包含1的方案
				f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;

		int res = 0;
		for (int i = 0; i <= n; i++)
			res += f[n][i];

		System.out.println(res);
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值