本篇博客旨在记录自已的算法刷题练习成长,里面注有详细的代码注释以及和个人的思路想法,希望可以给同道之人些许帮助。本人也是算法小白,水平有限,如果文章中有什么错误或遗漏之处,望各位可以在评论区指正出来,各位共勉💪。
文章目录
- 1、区间移位
- 2、扫雷Ⅱ
- 3、分布式队列
- 4、砍柴
- 5、排列序数
- 6、数位递增的数
1、区间移位
数轴上有几 个闭区间:D1,··D”。
其中区间 Di 用一对整数 [ai,bi] 来描述,满足 ai <= bi。
已知这些区间的长度之和至少有 10^4。
所以,通过适当的移动这些区间,你总可以使得他们的"并"覆盖 [0,10^4],就是说 [0,10^4]这个区间内的每一个点都落于至少一个区间内。
你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。
具体来说,假设你将 Di 移动到 [ai + ci,bi + ci] 这个位置。你希望使得 max |ci|最小。
用例规模: 1 ≤ n ≤ 10^4,0 ≤ ai < bi < 10^4。
解题代码:
import java.util.*;
public class Main {
private static int[][] intervals;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
intervals = new int[n][2];
for (int i = 0; i < n; ++i) {
// 将区间长度翻倍,结果除2
intervals[i][0] = 2*sc.nextInt();
intervals[i][1] = 2*sc.nextInt();
}
sc.close();
// 将二维数组进行升序排列
Arrays.sort(intervals, Comparator.comparingInt(i -> i[1]));
int left = 0, right = (int)2e4;
while (left < right){ // 二分查找,左闭右开
int mid = (left + right) >> 1;
if (check(mid)) right = mid;
else left = mid + 1;
}
// 不是整数,输出带小数
if (right % 2 == 0) System.out.println(right/2);
else System.out.println((double)right/2);
}
private static boolean check(int shift){
int cover = 0;
List<int[]> temp = new ArrayList<>(Arrays.asList(intervals)); // 逐个遍历,并排除掉合格的区间
while (true) {
boolean qualified = false;
for (int i = 0; i < temp.size(); ++i) {
int[] interval = temp.get(i);
if (interval[0] - shift <= cover /* 此时的cover可以理解为前一个区间的覆盖范围,这里检测的是向左移动能不能够到前面的 */ && interval[1] + shift >= cover /* 区间最右点达到的位置不超过区间的最大右偏移量 */) {
qualified = true;
int len = interval[1] - interval[0];
if (interval[0] + shift >= cover) cover += len; // 如果当前区间左移后超过前面区间的覆盖范围,那么此次移动,覆盖范围最多只能增加当前区间本身的长度,否则不连续
else cover = interval[1] + shift; // 若不能超过,覆盖范围最多是当前区间末端+位移量
temp.remove(i);
break; // 删除后立刻到下一个循环,以免Concurrent Modification Exception(贪心循环外面套一个while true的原因)
}
}
if (!qualified || cover >= 2e4) break;
}
return cover >= 2e4;
}
}
2、扫雷Ⅱ
规则如下。
- 地图为 n x m 的矩阵。
- 地图中包含地雷和数字。
- 如果某个点是地雷块,那么用 x表示。
- 如果某个点是数字块,那么用 0~8 表示,具体的数值为该点周围地雷的数量(最少为0个,最多为8个)。
- 如果某个点未知,即可能是地雷或者数字,我们用 * 表示。
周围: 对于(x,y)点来说,周围为(x-1,y),(x + 1,y),(x,y-1),(x,y+ 1),(x-1,y-1),(x-1,y+1),(x+1,y-1),( x+1,y+ 1)八个位置。需要注意的是,超过边界的点不能算作周围的点。
小蓝是扫雷高手,于是他在原来扫雷游戏的基础上改动了一下。
现在给定你一个3 x m 的地图,其中第二行全部都不是地雷,第一行和第三行都是未知,小蓝将第二行的数字全部告诉你,请问这幅地图有多少种不同的可能?
也就是说,假设每个格子有 10 种情况(x或者0~ 8),那么3 x m的地图总共有 10^3m 情况,请你找出满足以下两个要求的地图数量:
-
地图合法,即满足扫雷规则。
-
第二行与给定第二行的值相同,
答案可能很大,请对 998244353 取模。
用例规模: 1 <= n <= 5*10^5,0 <= pi <= 8。
解题代码:
import java.util.Scanner;
public class Main {
static int N = (int)5e5+10,mod = 998244353;
static int n;
static int[][][] f =new int[N][3][3]; //i,j,k: 处理到第i列,第i列的地雷情况j和第i+1列地雷情况k
static int[] p = new int[N]; // 每列地雷数
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 读取列数
if (n == 1){
int x = sc.nextInt();
if (x == 0||x == 2) System.out.println(1);
else System.out.println(2);
return;
}
for (int i = 1; i <= n; i++) {
p[i] = sc.nextInt();
}
// 处理第1列所有情况
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i+j==p[1]){
f[1][i][j] = 1;
}
if (i == 1){
f[1][i][j] *= 2; //当该列地雷数量为1,可以有上下两种情况,故*2
}
if (j == 1) {
f[1][i][j] *= 2;
}
}
}
// 由第i列(已知)递推第i+1列(未知)DP
for (int i = 1; i < n; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
int next = p[i+1]-j-k; // next为第i+2列地雷情况
if (next >= 0&&next <= 2){
f[i+1][k][next] = (f[i+1][k][next] + f[i][j][k])%mod;
}
if (next == 1){
f[i+1][k][next] = (f[i+1][k][next]*2)%mod;
}
}
}
}
// 只要递推到n-1即可,判断n-1列的地雷情况是否满足第n列地雷数量
int res = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i+j==p[n]){
res = (res+f[n-1][i][j])%mod;
}
}
}
System.out.println(res);
}
}
3、分布式队列
小蓝最近学习了一种神奇的队列:分布式队列。简单来说,分布式队列包含 N个节点(编号为0至N-1,其中0号为主节点),其中只有一个主节点,其余为副节点。
主/副节点中都各自维护着一个队列,当往分布式队列中添加元素时都是由主节点完成的(每次都会添加元素到主节点对应的队列的尾部);副节点只负责同步主节点中的队列。可以认为主/副节点中的队列是一个长度无限的一维数组,下标为 0,1,2.3…,同时副节点中的元素的同步顺序和主节点中的元素添加顺序保持一致。
由于副本的同步速度各异,因此为了保障数据的一致性,元素添加到主节点后,需要同步到所有的副节点后,才具有可见性。
给出一个分布式队列的运行状态,所有的操作都按输入顺序执行。你需要回答在某个时刻,队列中有多少个元素具有可见性。
输入格式:
第一行包含一个整数 NN,表示节点个数。
接下来包含多行输入,每一行包含一个操作,操作类型共有以下三种: add、sync 和 query,各自的输入格式如下:
- add:element: 表示这是一个添加操作,将元素 element 添加到队列中;
- sync followerid: 表示这是一个同步操作,followerid 号副节点会从主节点中同步下一个自己缺失的元素;
- query: 查询操作,询问当前分布式队列中有多少个元素具有可见性。
输出格式:
对于每一个 query 操作,输出一行,包含一个整数表示答案。
用例规模:
对于 30% 的评测用例:1 ≤ 输入的操作数 ≤ 100。
对于 100% 的评测用例:1 ≤ 输入的操作数 ≤ 2000,1 ≤ N ≤ 10,1 < followerid ≤ N,0 ≤ element ≤ 10^5。
解题代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
// 用于装下主节点和副节点,长度为n
ArrayList<List<Long>> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(new ArrayList<>());
}
while (sc.hasNext()){
// 获取输入的操作
String cao = sc.next();
switch (cao) {
case "add":
long element = sc.nextLong();
List<Long> headQueue = list.get(0);
headQueue.add(element);
break;
case "sync":
int queueIndex = sc.nextInt(); // 需要同步的队列
syncData(list.get(0), list.get(queueIndex));
case "query":
// 由于队列式是有序的同步数据,我们只需要获取最凶阿德队列的长度即可
int len = list.get(0).size(); // 初始化为最大个数,即头节点拥有元素的个数
for (int j = 1; j < list.size(); j++) {
len = Math.min(list.get(j).size(), len);
}
System.out.println(len);
break;
}
}
sc.close();
}
public static void syncData(List<Long> headQueue, List<Long> fuQueue) {
// 检查出缺失的元素,无需便利判断(副节点与头节点要么完全相等,要么差数据,并不会存在元素不相等的情况)
if (headQueue.size() != fuQueue.size()){
int lackIndex = fuQueue.size() % headQueue.size(); // 利用取模运算,可以得到所缺数据的第一个索引
fuQueue.add(headQueue.get(lackIndex)); // 将头结点这个数据添加到副节点即可
}
}
}
4、砍柴
小蓝和小乔正在森林里砍柴,它们有 T 根长度分别为 n1,n2,⋯,nT 的木头。对于每个初始长度为 n 的木头,小蓝和小乔准备进行交替砍柴,小蓝先出手。
每次砍柴时,若当前木头长度为 x,需要砍下一截长度为 p 的木头,然后换另一个人继续砍,其中 2 ≤p≤x 且 p 必须为质数。当轮到某一方时 x=1 或 x=0,它就没法继续砍柴,它就输了。它们会使用最优策略进行砍柴。请对每根木头判断是小蓝赢还是小乔赢,如果小蓝赢请输出 1(数字 1),如果小乔赢请输出 0(数字 0)。
用例规模:
对于 20% 的评测用例,1 ≤ ni ≤ 10^3;
对于所有评测用例,1 ≤ ni ≤ 10^5,1 ≤ T ≤ 10^4。
解题代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
//先打表列出所有的质数
List<Integer> a=new ArrayList<>();//存2-100000之间的质数
for (int i = 2; i <=100000; i++) {
boolean isright=true;
for (int j = 2; j*j <=i; j++) {
if(i%j==0) {
isright =false;
break;
}
}
if(isright) {
a.add(i);
}
}
//动态规划
int dp[]=new int[100001];//当前状态先手赢为1否则为0
dp[2]=1;
for (int i = 2; i <=100000; i++) {
for (int j = 0; j < a.size(); j++) {
int x=i-a.get(j);//当次砍的长度
if(x<0) {
//防止出现柴长度只有1砍了2的情况,也就是防止超砍情况
break;
}
if(dp[x]==0) {
//说明下一个人来砍必输,所有他赢了,后面就不用循环了,因为他们砍柴采用最优策略
dp[i]=1;
break;
}
}
}
int T=sc.nextInt();//测试的组数
int []trr=new int[T];//值用来存输赢
for (int i = 0; i < T; i++) {
int t=sc.nextInt();
trr[i]=dp[t]==1?1:0;//因为上面动态规划已经模拟了所有情况所以这里直接根据上面动态规划判断即可
}
for (int i = 0; i < T; i++) {
System.out.println(trr[i]);
}
}
}
5、排列序数
如果用 a b c d 这4个字母组成一个串,有 4!=24 种,如果把它们排个序,每个串都对应一个序号:
abcd 0
abdc 1
acbd 2
acdb 3
adbc 4
adcb 5
bacd 6
badc 7
bcad 8
bcda 9
bdac 10
bdca 11
cabd 12
cadb 13
cbad 14
cbda 15
cdab 16
cdba 17
现在有不多于 10 个两两不同的小写字母,给出它们组成的串,你能求出该串在所有排列中的序号吗?
输入描述:
输入一行,一个串。
输出描述:
输出一行,一个整数,表示该串在其字母所有排列生成的串中的序号。注意:最小的序号是 0。
解题代码:
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
static public List<String> list=new ArrayList<>();
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
String s=scan.next();
char[] ch=s.toCharArray();//把字符串转换成数组字符
f(ch,0,s.length()-1);
Collections.sort(list);//排列
for (String s1 : list) {
if(s1.equals(s))//遍历集合匹配
{
System.out.println(list.indexOf(s1));
break;
}
}
}
//全排列,列出所有情况
public static void f(char[] ch, int l, int r)
{
if (l==r)
{
list.add(String.valueOf(ch));
}
else {
for (int i = l; i <= r; i++) {
swap(ch, l, i);
f(ch, l + 1, r);
swap(ch, l, i);
}
}
}
public static void swap(char[] ch,int i,int j)
{
char temp=ch[i];
ch[i]=ch[j];
ch[j]=temp;
}
}
6、数位递增的数
一个正整数如果任何一个数位不大于右边相邻的数位,则称为一个数位递增的数。
例如 1135 是一个数位递增的数,而 1024 不是一个数位递增的数。
给定正整数 n,请问在整数1至 n 中有多少个数位递增的数?
输入描述:
输入的第一行包含一个整数 n(1<n<10^6)。
输出描述:
输出一行包含一个整数,表示答案。
解题代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int count = 0;
sc.close();
for (int i = 11; i <= n; i++) {
// 将数字转换为一个个字符
String before = Integer.toString(i);
char[] c = before.toCharArray();
// 进行排序
Arrays.sort(c);
String ans = "";
// 将字符排序后再添加排列
for (int j = 0; j < c.length; j++) {
ans += c[j];
}
// 将原始数据和排序后的对比,不同的则不为递增数
if (before.equals(ans)){
count++;
}
}
System.out.println(count);
}
}
有帮助的话,希望可以点赞❤️+收藏⭐,谢谢各位大佬~~✨️✨️✨️