概览
先找到一个比较舒服的开始模板,然后逐渐适应 java,然后去使用和之前 c++平替的操作,若这个操作过于复杂,则可以记录下来。
若对于 c++这里的代码有一些比较相似的可以使用的可以列出。
这里说明一下,对于新拿到的一个 eclipse 软件,需要做哪些设置:eclipse的下载和 jdk的配置
- (若需要配置 JDK,否则略过)搜索 jre,选择 1.8 那个
- workspace 中选择 UTF-8
- compiler 选 1.8
- Editor 中的 Content Assist 写入
.abcdefghijklmnopqrstuvwxyz
(代码提示),再勾选Disable insertion triggers except "Enter
选项(取消按空格时自动填入代码) - 搜索 keys,设置 control+D copyLine
- 搜索 font,更换字体大概率没有
JetBrain
字体,选择Consolas
即可。
对于还需要学习的算法可以进行学习,对于没有必要学习的则不学习
由下面的总结可以知道,着重的点在于:DP+搜索+图论+数据结构+字符串+计算几何
在这之前,先总结一下蓝桥杯的大纲(重点的需要特殊标注,感觉不是特别有必要的可以斜体标注):
- BFS/DFS(即搜索:需要会普通的搜索,基本的剪枝,双向搜索,记忆化搜索),对于迭代加深搜索,启发式搜索,个人感觉不用学
- DP(重中之重):
- 背包 DP,树形 DP,状压 DP,数位 DP,DP 的常见优化
- 贪心,模拟,二分,链表(这个我并不熟悉,使用数组进行模拟实现)
- 高精度:java 自带高精度,个人感觉是几乎不会考的,就算考了,用大数即可。
- 字符串(个人感觉需要学,补):
- hash,KMP,manacher
- 图论:
- 必学:图的连通性,LCA,最小生成树,最短路,拓扑排序
- 选学:二分图匹配,dfs 序
- 数学:个人感觉每场最多一道数学类的题目(涉及逆元的不算)
- 逆元,二项式定理,容斥原理,排列组合,矩阵运算,高斯消元
- 数据结构:有一说一,这里给出的都不简单,线段树类型的大块模板型不考虑
- ST 表,堆,树状数组,线段树,字典树,并查集,平衡树
- 计算几何
- 会基本的判定和比较基础的题目
- 会较为简单的博弈论题目
对于需要学习的地方
- 对于快速 I/O 而言,先不必了,感觉有点恼火。...‘
- 算法讲解046【必备】构建前缀信息的技巧-解决子数组相关问题 我居然不知道前缀和+Hash 的方法。确实是见识少了。/或者也有其他解法?确实是孤陋寡闻了
- 题目 1:构建前缀和数组。快速解决子数组范围求和的问题
- 题目 2:构建前缀和最早出现的位置。返回无序数组中累加和为给定值的最长子数组长度
- 题目 3:构建前缀和出现的次数。返回无序数组中累加和为给定值的子数组数量
- 题目 4:构建前缀和最早出现的位置。返回无序数组中正数和负数个数相等的最长子数组长度
- 题目 5:构建前缀和最早出现的位置。表现良好的最长时间段问题
- 题目 6:构建前缀和余数最早出现的位置。移除的最短子数组长度,使得剩余元素的累加和能被 p 整除
- 题目 7:构建前缀奇偶状态最早出现的位置。每个元音包含偶数次的最长子串长度
对应关系:
格式 :<cpp的容器/函数>: java 的形式/替代形式
其中比较重要的有:与 cpp 对应的具体方式,遍历方式,输入输出方式,还有一些 api
还有一些比较重要的类的使用方法,例如:Collections,Arrays 等等。需要详细了解。
需要将常用的算法模板都自己写一写:比如快速幂,逆元,等常见的模板
对应
这里给出 cpp->java 可以相互替换的部分:
#define int long long
在 java 中直接全部 long/Long- vector 在 java 中使用普通数组/ArrayList
- Set
- unordered_set->HashSet
- set->TreeSet
- Map
- unordered_map->HashMap
- map->TreeMap
IO
关于读入:(大数据量)
Scanner sc = new Scanner(System.in);//或者也可以: Scanner scanner = new Scanner(new BufferedInputStream(System.in));
int x = sc.nextInt();
System.out.println(x);
// 更快的IO sync_with_stdio(false)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
关于输出:(大数据量)
PrintWriter pw = new PrintWriter(System.out));
pw.println(x); // 比System.out快
pw.flush(); // 记得刷新缓冲区
// 读取单个整数
int a = Integer.parseInt(st.nextToken());
// 读取单个长整数
long b = Long.parseLong(st.nextToken());
// 读取单个双精度浮点数
double c = Double.parseDouble(st.nextToken());
// 读取单个字符串
String s = st.nextToken();
// 读取单字符
char ch = st.nextToken().charAt(0);
//当需要读取整行时(比如包含空格的字符串):
String line = br.readLine();
st = new StringTokenizer(line); // 如果需要分割可以重新创建st
数组
vector :普通数组/ArrayList
/** cpp **/
// vector<int> v;
// v.push_back(1);
// v.size();
// v[i];
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.size();
list.get(i);
pair <int,int>
迪杰斯特拉算法的两种语言实现:
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
vector<vector<pair<int, int>>> adj;
vector<int> dijkstra(int start) {
vector<int> dist(adj.size(), INF);
priority_queue<pair<int, int>> pq;
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
d = -d;
if (d > dist[u]) continue;
for (auto [v, w] : adj[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({-dist[v], v});
}
}
}
return dist;
}
import java.util.*;
public class Main {
static final int INF = (int)1e9;
static List<List<Pair>> adj;
static class Pair {
int first, second;
Pair(int f, int s) { first = f; second = s; }
}
static int[] dijkstra(int start) {
int[] dist = new int[adj.size()];
Arrays.fill(dist, INF);
PriorityQueue<Pair> pq = new PriorityQueue<>((a, b) -> a.first - b.first);
dist[start] = 0;
pq.add(new Pair(0, start));
while (!pq.isEmpty()) {
Pair p = pq.poll();
int d = p.first, u = p.second;
if (d > dist[u]) continue;
for (Pair edge : adj.get(u)) {
int v = edge.first, w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.add(new Pair(dist[v], v));
}
}
}
return dist;
}
}
栈,队列,堆
这里还需要对应的 api 函数的使用方式
stack<int> s;
queue<int> q;
priority_queue<int> pq; // 最大堆
Stack<Integer> stack = new Stack<>();
Queue<Integer> queue = new LinkedList<>();
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 最小堆
// 最大堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
//PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
set&map
set<int> s;
unordered_set<int> us;
map<int, int> m;
unordered_map<int, int> um;
TreeSet<Integer> set = new TreeSet<>(); // 有序
HashSet<Integer> hashSet = new HashSet<>(); // 无序
TreeMap<Integer, Integer> map = new TreeMap<>(); // 有序
HashMap<Integer, Integer> hashMap = new HashMap<>(); // 无序
排序
sort(v.begin(), v.end());
Collections.sort(list); // 对List排序
Arrays.sort(arr); // 对数组排序
lambda 表达式: 更加推荐 Integer.compare(a, b)
的写法
List<Integer> list = Arrays.asList(5, 2, 8, 1);
list.sort((a, b) -> b - a); // 降序排序 [8, 5, 2, 1]
这里对于非数值类型的排序:
// 字符串长度排序
List<String> strings = Arrays.asList("apple", "banana", "pear");
strings.sort((s1, s2) -> s1.length() - s2.length());
若规则更加复杂:
// 先按年龄降序,年龄相同按姓名升序
people.sort((p1, p2) -> {
int ageCompare = p2.age - p1.age;
if (ageCompare != 0) return ageCompare;
return p1.name.compareTo(p2.name);
});
对于 lambda 的其他优化(感觉不算特别有用):
// 等价于 (a, b) -> a - b
Comparator<Integer> natural = Comparator.comparingInt(a -> a);
// 等价于 (a, b) -> b - a
Comparator<Integer> reversed = Comparator.comparingInt(a -> -a);
// 按字符串长度排序
List<String> strings = Arrays.asList("apple", "banana", "pear");
strings.sortlength);
strings.sortlength).reversed();
自定义排序
class Person {
String name;
int age;
// 构造方法等
}
List<Person> people = ...;
people.sort((p1, p2) -> p1.age - p2.age); // 按年龄升序
people.sort((p1, p2) -> p2.age - p1.age); // 按年龄降序
遍历
普通数组:
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
for (int num : arr) {
System.out.println(num);
}
Arrays.streamprintln;//性能较差
List 的遍历:
List<Integer> list = Arrays.asList(1, 2, 3);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//java中的增强for为只读视图,无法修改元素
for (Integer num : list) {
System.out.println(num);
}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Set 遍历:
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
for (Integer num : set) {
System.out.println(num);
}
Iterator<Integer> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Map 的遍历
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
//类似C++17的for(auto& [key, value] : map)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
//不是特别建议
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
map.forEach((k, v) -> System.out.println(k + ": " + v));
Queue 的遍历
Queue<Integer> q = new LinkedList<>(Arrays.asList(1, 2, 3));
while (!q.isEmpty()) {
System.out.println(q.poll());
}
// PriorityQueue同样适用
PriorityQueue<Integer> pq = new PriorityQueue<>(Arrays.asList(3, 1, 2));
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 按优先级顺序
}
二分
lower_bound(v.begin(), v.end(), x);
upper_bound(v.begin(), v.end(), x);
binary_search(v.begin(), v.end(), x);
Collections.binarySearch(list, x); // 基本二分查找
// 需要自己实现lower_bound和upper_bound
int lowerBound(List<Integer> list, int x) {
int l = 0, r = list.size();
while (l < r) {
int m = l + (r - l) / 2;
if (list.get(m) < x) l = m + 1;
else r = m;
}
return l;
}
// 必须先排序!
Arrays.sort(arr);
int pos = Arrays.binarySearch(arr, 5);
// 返回值:
// 找到则返回索引
// 未找到则返回 -(插入点) - 1
字符串处理
string s = "hello";
s += " world";
s.substr(1, 3);
s.find("ll");
String s = "hello";
s += " world";
s.substring(1, 4); // 注意Java是[1,4)而不是长度
s.indexOf("ll");
大数处理
java 内置高精度模板
BigInteger bigInt = new BigInteger("12345678901234567890");
BigDecimal bigDec = new BigDecimal("123.456");
java 标准库函数
Math.abs(x);
Math.max(a, b);
Math.min(a, b);
// swap需要手动实现
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
//java特有的:>>>无符号右移
boolean isOdd = (x & 1) != 0; // 比x%2==1更快更安全
a ^= b; b ^= a; a ^= b;// 交换两个变量(不需要临时变量)
存放一些问题
-
- 需要使用的是 while 和 poll 进行遍历
-
- 详情见遍历部分
-
(a, b) -> b - a
是降序排列,反之为升序- 这里当 b 很大,a 很小的情况下,可能会出现溢出的情况,所以更加推荐使用
(a, b) -> Integer.compare(a, b);
- 更多详情参见排序的 lambda 表达式部分
//...
IO 总结
写了太多了,太乱了,这里总结:(前提为输入输出数据量大的情况,否则
stdio
即可)
关于标准输入输出:
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
//Scanner scanner = new Scanner(System.in);
scanner.nextInt();
scanner.next();
scanner.nextDouble();
scanner.nextLine();
输出需要了解保留小数等特殊的情况
输入推荐:
- 如果需要更复杂的输入处理,使用
BufferedReader/StreamTokenizer
(大量数据的推荐)。
若使用 BufferedReader/StreamTokenizer
的注意事项:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
in.nextToken();//每个都要加
//整数
in.nextToken();
int n = (int) in.nval;
//
in.nextToken();
double num = in.nval
//
in.nextToken();
String str = in.sval
//读取数组,ArrayList类似
in.nextToken();
int size = (int)in.nval; // 读取数组大小
int[] array = new int[size];
for (int i = 0; i < size; i++) {
in.nextToken();
array[i] = (int)in.nval;
}
//br.close()
读取自定义对象:
class Person {
String name;
int age;
static Person readPerson(StreamTokenizer in) throws Exception {
in.nextToken();
String name = in.sval; // 假设第一个是名字
in.nextToken();
int age = (int)in.nval; // 然后是年龄
return new Person(name, age);
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// 使用
in.nextToken();
int size = (int)in.nval; // 读取Person数组大小
ArrayList<Person> people = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
people.add(Person.readPerson(in));
}
输出推荐
- (若需要输出例如
个)推荐使用 PrintWriter
,API 更多,更易于使用
若使用 PrintWriter
的注意事项:
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
out.println(compute());
//还有更多API需要补充
//结束处理:(算法题也可以不处理)
out.flush();//这个不太清楚是每次输出都带还是最后带?
out.close();
IO
那么使用 BufferedReader
和 BufferedWriter
来处理数据可不可以做到不被时间卡住呢?
输入为
级别时,建议使用 BufferedReader
/StreamTokenizer
?BufferedReader
/String.split
呢?
输出为级别时,建议使用 BufferedWriter
?PrintWriter
稍慢
输入推荐:
- 如果数据格式简单,推荐使用
BufferedReader/String.split
。 - 如果需要更复杂的输入处理,使用
BufferedReader/StreamTokenizer
。
若使用 BufferedReader/String.split
的注意事项:(感觉挺麻烦的,而且相对较慢!)略
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
//读入int
String s=in.readLine();
int n=Integer.parseInt(s);
//若是一行读入多个,空格间隔,读入到ArrayList
ArrayList<String> list = new ArrayList<>();
String line = in.readLine();
String[] items = line.split("\\s+");
for (String item : items) {
list.add(item);
}
//in.close()
输出(若需要输出很多东西)推荐使用 PrintWriter
,API 更多,更易于使用
若使用 PrintWriter
实际上,对于 IO 的哪种方式好,还没有想好!平时使用一个较为简单的,能处理大量数据的即可(能做到不被时间卡住即可!)。
输出: BufferedWriter
vs PrintWriter
BufferedWriter
BufferedWriter
是一个用于缓冲的字符输出流,可以通过增加一个缓冲区来减少对底层输出的写操作。- 主要用于提高字符输出的效率。
- 不提供自动刷新功能,需要手动调用
flush()
确保所有数据被写出。
PrintWriter
PrintWriter
同样是字符输出流,更注重便捷性,提供了自动换行和自动刷新等功能。- 支持
print
和println
等方法,可以更方便地格式化输出。 - 适合需要频繁格式化输出的场景。
- 可以通过构造函数参数打开自动刷新功能(如在每次调用
println
后自动刷新)。
区别总结
BufferedWriter
注重效率,是处理大量字符输出时的好选择。PrintWriter
注重使用的便捷性,适合需要格式化输出的场景。- 在算法竞赛中,如果你需要频繁且复杂的格式化输出,
PrintWriter
可能更适合;否则,BufferedWriter
可能会更高效。
输入: BufferedReader
/ StreamTokenizer
vs BufferedReader
/ String.split
BufferedReader
with StreamTokenizer
StreamTokenizer
是一种工具类,用于解析输入流为一系列标记(tokens),适合处理结构化的输入数据。- 提供了一些内建的方法来解析各类标记(如数字、单词等)。
BufferedReader
with String.split
- 直接使用
BufferedReader
逐行读取输入,然后使用String.split()
方法来将读取的数据分割成数组。 - 更加灵活但需要自己管理数据转换。
区别总结
StreamTokenizer
提供了更高级的数据解析功能,更适合处理特定格式的数据,但相比手动解析略显繁琐。- 简单的行读取配合
String.split()
会更直接和灵活,适合大多数简单结构的数据。
推荐
- 输出: 如果你的算法题对输出格式有特定要求,且需要处理大量格式化输出,建议使用
PrintWriter
。否则,BufferedWriter
是一个高效的选择。 - 输入: 在算法题中,使用
BufferedReader
和行读取,然后用String.split()
在性能和灵活性上提供了一个平衡,是较为普遍的选择。StreamTokenizer
不太常用,除非掌握熟练,面临非常复杂的输入结构时才考虑使用。
在高效处理大数据的竞争环境中,选择合适的工具并了解其性能特征会对提升整体效率有显著帮助。
//对于大数据比较好,那就用这个
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
OR
//Scanner scanner = new Scanner(System.in);
接收数据(有新的东西补充即可)
scanner.nextInt();
scanner.next();
scanner.nextDouble();
scanner.nextLine();
需要寻找一个尽量 FAST_IO
的方式!(适用于数据量很大的情况)
需要抛出 throws IOException
// 创建一个 BufferedReader,用于从标准输入流(System.in)读取数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 创建一个 StreamTokenizer,用于将输入流解析为标记(tokens)
StreamTokenizer in = new StreamTokenizer(br);
// 创建一个 PrintWriter,用于向标准输出(System.out)写入数据(结果)
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
//结束位置->
// 刷新输出流,确保所有数据都被写出
out.flush();
// 关闭流,进行资源释放
out.close();
br.close();
若要读入(这里的读入还是相对麻烦的!)
//读入一个int
in.nextToken();
int n = (int) in.nval;
所以在数据量不大的情况下,尽量用 Scanner
即可。
普通数组
和 c++很像
int[] array = new int[5]; // 创建一个长度为5的int数组,初始值为0
//复制整个数组:
int[] copy = array.clone();
Arrays.fill(array, value);
int[][] twoDimArray = new int[3][2]; // 创建一个3x2的二维数组
//0 1 0 0 0 ->
//[0, 1, 0, 0, 0]
String arrayString = Arrays.toString(array);
vector
对于 c++的 vector
,在 java 中用 ArrayList
替代即可。
//vector<int> a(10,0);
ArrayList<Integer> a=new ArrayList<>(Collections.nCopies(10, 0));
//a[0]=1;
a.set(0, 1);
//快速遍历方式
a.forEachprintln;
//vector<vector<int>> a(26);
ArrayList<ArrayList<Integer>> a = new ArrayList<>(26);
for(int i=0;i<26;i++)a.add(new ArrayList<>());
//这种方式不推荐,所有数组都引用的同一个对象!会出问题!
//ArrayList<ArrayList<Integer>> a = new ArrayList<>(Collections.nCopies(26, new ArrayList<>()));
// 向指定位置插入元素
a.get(0).add(1); //a[0].push_back(1);
a.get(0).add(2);
// 获取指定位置的元素
int element = a.get(0).get(1);//a[0][1];
对于排序
- 推荐
Collections.sort
和lambda
方式进行排序(总之,我认为越简单越好!) a.sort((x,y)->x-y);
深得我心
//sort(a.begin(),a.end());
a.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;//反过来则为降序排序
}
});
//lambda<->等价
a.sort((x,y)->x-y);
Collections.sort(a,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
二分:
//int x=lower_bound(ans.all(),k)-ans.begin();
int x=Collections.binarySearch(ans, k);
String
//s[i]
s.charAt(i);