蓝桥杯爪哇速通
简单题:枚举、找规律、模拟
复杂题:DP、图论、数论、二分、贪心
数据结构
1. 哈希表
分为HashSet和HashMap
Set<Integer> set=new HashSet<Integer>();
set.add(1);//添加元素
set.remove(1);//删除元素
if(set.contains(1)) {...}//判断是否包含某个元素
int len=set.size();//获得集合长度
12345
Map<Character,Integer> map=new HashMap<Character,Integer>();
map.put('a', 1);
int num1=map.get('a');
int num2=map.getOrDefault('a',0);
map.replace('a',2);
map.remove('a');
123456
2. 堆
使用优先队列(PriorityQueue)实现,默认是小根堆
Queue<Integer> q=new PriorityQueue<Integer>();//创建一个小根堆
Queue<Integer> q=new PriorityQueue<Integer>((e1,e2)->e2-e1);//创建一个大根堆
//(e1,e2)->e2-e1代表升序,可以自定义其他排序规则
时间相关
1. String转Date
import java.text.ParseException;
import java.text.SimpleDateFormat;
String s1 = "2021-7-24";
String s2 = "2022-7-24";
//yyyy-MM-dd,注意MM必须大写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 转换成date1需要抛出异常
try {
Date date1 = sdf.parse(s1);
} catch (ParseException e) {
e.printStackTrace();
}
123456789101112
2. Date转String(标准格式化)
Date date = new Date(120000000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date));
123
3. Calender类(日历,星期)
Calender的月份MONTH是从0开始,也就是1-12月对应 0-11,但日期和年份是从1开始的。DAY_OF_WEEK从1开始,也就是周日到周六对应 1-7。
周日是1,周一是2,周六是7。1月是0,12月是11。
// 创建Calendar实例
Calendar c = Calendar.getInstance();
// 用日期赋值2021年7月1日,或者是用Date赋值c.setTime(Date date);
c.set(2021, 7 - 1, 1);
System.out.println(c.get(Calendar.YEAR));//年
System.out.println(c.get(Calendar.MONTH) + 1);//月
System.out.println(c.get(Calendar.DATE));//日
System.out.println(c.get(Calendar.DAY_OF_WEEK));// 星期几,1是星期日
// 这年的第几天,从1开始算
System.out.println(c.get(Calendar.DAY_OF_YEAR));
// 这个月的第几天,从1开始算
System.out.println(c.get(Calendar.DAY_OF_MONTH));
// 这个月的第几周,从1开始算
System.out.println(c.get(Calendar.DAY_OF_WEEK_IN_MONTH));
4. 计算时间间隔
主要是通过使用SimpleDateFormat,先把时间写成String,然后再转成Date, 用getTime函数转成毫秒,相减得到相差的毫秒数。注意1s = 1000ms,SimpleDateFormat中,HH代表24小时制,hh是12小时制,MM是月份,mm是分钟。
String start = "2021-7-13 13:14:20";
String end = "2021-7-10 13:14:20";
// 注意HH是24小时,hh是12小时
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date1 = sdf.parse(start);
Date date2 = sdf.parse(end);
// 因为不知道谁大谁小,所以要用abs
long diff = Math.abs(date1.getTime() - date2.getTime());
System.out.println("相差" + diff + "毫米");
// 注意1s = 1000ms
long seconds = diff / 1000;//秒
long minutes = diff / 1000 / 60;//分钟
long hours = diff / 1000 / 60 / 60;//小时
long day = diff / 1000 / 60 / 60 / 24;//天
字符串
1.int和String的互相转换
//int转String
int i=1; String s=""+i:
//String转int
String s=”3”;
int i=Integer.parseInt(s);
12345
2.判断一个字符串是否是回文
方法一:判断字符串的i到j位是否是回文
public static boolean isPalindrome(String s, int i, int j){
while(i<j){
if(s.charAt(i)!=s.charAt(j)){
return false;
}
i++;j--;
}
return true;
}
123456789
方法二:快速判断,一个数字是否是回文
class Solution {
public boolean isPalindrome(int x) {
StringBuffer s=new StringBuffer(Integer.toString(x));
String a=s.toString();//创建一个新字符串记录原来的值
return a.equals(s.reverse().toString())?true:false;
}
}
BigInteger与BigDecimal
1.BigInteger
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 传入字符串才能形成大数,默认是把字符串当成十进制
BigInteger bs = new BigInteger("15666666666666666");
BigInteger bs1 = new BigInteger("100002123123123");
//加减乘除
bs.add(bs1);bs.subtract(bs1);bs.multiply(bs1);bs.divide(bs1);//取商
// 取余
bs.mod(bs1);bs.remainder(bs1);
// 返回大整数的double、float类型
bs.doubleValue();bs.floatValue();
// 求最大公约数
bs.gcd(bs1);
// 9、将当前大整数转成十进制字符串形式
System.out.println(bs.toString());
// 也可以把字符串当成二进制传入,生成十进制大数
BigInteger bs2 = new BigInteger("100000", 2);
System.out.println(bs2);
}
}
1234567891011121314151617181920212223
2.BigDecimal
对BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断。
package Chapter_5;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 大小数
BigDecimal bs = scan.nextBigDecimal();
// 获取小数位数,如果是整数则输出负数,表示末尾0的个数
System.out.println(bs.scale());
// 去掉小数末尾无用的0
System.out.println(bs.stripTrailingZeros());
// 设置小数位数,可以选择四舍五入或者直接截断
System.out.println(bs.setScale(4, RoundingMode.HALF_UP)); // 四舍五入
// 对BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断。
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); //保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
//比较两个BigDecimal,不能用equals()因为小数的个数问题,要用compareTo()
//它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于
d1.compareTo(d2)
}
}
质数和公约数
1.判断一个数是否是质数
假设该数为n, 我们只需要判断[2,sqrt{n}]内是否有n的因子。如果有,则n为合数,否则,n为质数。这种方法被称为试除法, 即试着除一下所有可能的因子。
public static Boolean isprime(int n){
if(n == 1) return false;
for(int i = 2; i<= n/i ; i++){
if(n % i == 0){
return false;
}
}
return true;
}
123456789
2.求两个数的最大公约数
//递归(返回最大公约数)
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
3.求两个数最小公倍数
//递归(返回最大公约数)
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
// 借用gcd
int lcm(int a, int b){
return a * b / gcd(a,b);
}
4.分解质因数
设一个质数为p.如果n%p == 0,那么p就是n的一个质因数,接下来就是求p的指数,我们让n = n/p, 这样就从n中剔除了一个p,接着重复上述两步,直到n%p != 0
public static void prime(int n){
for(int i = 2; i <= n/i ; i++){//判断条件用n/i,以防i*i<=n发生溢出
int a = 0;//每次循环都要清零
while(n % i == 0){
n /= i;
a++;
}
if(a > 0)
System.out.println(i + "" + a);//i的a次方
}
//若该数是质数,那么应该将自身(n)也加入
if(n > 1) System.out.println(n + " " + 1);
BFS和回溯DFS框架
回溯DFS
回溯算法框架。解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
代码方面,回溯算法的框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
BFS
本质上就是一幅「图」,让你从一个起点,走到终点,问最短路径,这就是 BFS 的本质。
图可能会走回头路,所以要用visited数组存储已经访问过的节点。
/ 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
HashSet<Node> visited; // 保存已经访问过的节点,避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (!q.isEmpty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
进制转换
/*
* 十进制转其他进制
* Integer.toBinaryString 返回 int 的 二进制 String
* Integer.toOctalString 返回 int 的 八进制 String
* Integer.toHexString 返回 int 的 十六进制 String
* Integer.toString 返回 int 的 X进制 String(两个参数,第一个对应 int 第二个对应X)
* */
String s1 = Integer.toBinaryString(16);
System.out.println(s1);
String s2 = Integer.toOctalString(16);
System.out.println(s2);
String s3 = Integer.toHexString(16);
System.out.println(s3);
String s4 = Integer.toString(16, 9);
System.out.println(s4);
/*
* 其他进制转十进制
* Integer.parseInt 返回 X进制的String 的 十进制 Integer(两个参数, 第一个对应String 第二个对应X)
* Integer.valueOf 返回 X进制的String 的 十进制 int (两个参数, 第一个对应String 第二个对应X)
* */
String s5 = "10010";
System.out.println(Integer.parseInt(s5, 2));
String s6 = "761";
System.out.println(Integer.valueOf(s6, 8));
快速幂
- 快速幂
用处: O(log n)复杂度计算a^n的值(常用于n可能很大时) - 原理:分治
- 如果n是奇数,则计算a^floor(n/2),然后将这个数平方,并乘多一个a。
- 如果n是偶数,则计算a^(n/2),然后将这个数平方。
- 不断递归下去
- 复杂度低,不会爆栈。
// 递归版本
long long fastpow2(long long a,int n){
if(n==1)return a;
long long tmp = fastpow2(a,n: n/2);
if(n%2==1) return tmp*tmp*a;
else return tmp*tmp;
}
// 非递归版本 更优
long long fastpow1 (long long a,int n){
long long ans = 1;
while(n){
if(n&1) ans*=a;
a*=a;
n>>=1;
}
return ans;
}
线性筛法求质数
private static int cnt;
private static int[] primes = new int[10010];
private static boolean[] st = new boolean[10010];
public static void get_primes(int n){
for(int i = 2; i <= n; i++){
if(!st[i]) primes[cnt++] = i;
for(int j = 0; primes[j] <= n / i; j++){
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
算术基本定理
又称为正整数的唯一分解定理,即:每个大于1
的自然数,若不是本身就是质数,就是可写为2
个以上的质数的积
这个定理又叫做算术基本定理(或者叫正整数的唯一分解定理),是数论中非常重要的一个定理。它的表述是:
任何一个大于1的正整数n,都可以唯一地写成n=p1α1*p2α2...pk^αk的形式,其中p1,p2,...,pk是不同的质数,α1,α2,...,αk是正整数。
这个定理的意思是,任何一个大于1的正整数n,都可以写成若干个质数的乘积的形式,并且这种分解方式是唯一的。例如,60可以分解为2^2 * 3^1 * 5^1,其中2、3、5是不同的质数,2的指数为2,3的指数为1,5的指数为1。
对于一个大于1正整数n可以分解质因数:n=p1^α1*p2^α2...*pk^αk
则n
的正约数的个数就是(a1+1)(a2+1)(a3+1)…(ak+1)
。
其中a1、a2、a3…ak
是p1、p2、p3,…pk
的指数。
证明:n
可以分解质因数:n=p1^α1*p2^α2...*pk^αk
由约数定义可知p1^α1
的约数有:p1^0, p1^1, p1^2, ...,p1^α1
,共(a1+1)
个,同理p2
的约数有(a2+1)
个;……;pk^αk
的约数有(ak+1)
个。
根据乘法原理:n的约数的个数就是 (a1+1)(a2+1)(a3+1)…(ak+1)
。
例:
将378000
分解质因数378000=2^4×3^3×5^3×7^1
由约数个数定理可知378000
共有正约数(4+1)×(3+1)×(3+1)×(1+1)=160
个。
知道约数个数,然后就可以求约数之和
一个数所有约数之和等于:先把每个质因数从0
次幂一直加到其最高次幂,再把每个相应质因数幂的和相乘.
若一个数分解为(a^m)*(b^n)
,则这个数所有约数的和为:
(a^0+a^1+a^2+a^3+…+a^m)(b^0+b^1+b^2+b^3+…+b^n)
.
例如:(1)12=2²*3
,则12
所有约数的和为:(2^0+2^1+2^2)*(3^0+3^1)=7*4=28
;
(2)60=2²*3*5=(2^0+2^1+2^2)*(3^0+3^1)*(5^0+5^1)=7*4*6=168
.
Java Lambda 表达式
Java Lambda 表达式是 Java 8 中引入的一种新特性,它可以看作是一种简化了的函数定义形式,可以使代码更加简洁、易读。
Lambda 表达式的基本语法如下:
(parameters) -> expression
或
(parameters) -> { statements; }
其中,parameters 表示 Lambda 表达式的参数列表,可以为空或包含一个或多个参数,多个参数之间用逗号隔开。expression 或 statements 表示 Lambda 表达式的函数体,可以是一个表达式或一个语句块。
下面是一个使用 Lambda 表达式的例子,假设有一个列表 List
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
Collections.sort(numbers, (a, b) -> a.compareTo(b));
System.out.println(numbers);
这里使用了 Collections.sort() 方法对列表进行排序,第二个参数使用了 Lambda 表达式,其中 (a, b) -> a.compareTo(b) 表示一个比较函数,它的作用是比较两个数的大小。这个比较函数使用了箭头符号 ->,箭头符号左侧是参数列表,右侧是函数体,这个比较函数的函数体是 a.compareTo(b),它表示比较 a 和 b 的大小,并返回比较结果。
Lambda 表达式可以用在很多地方,例如函数式接口、Stream API、并发编程等。它可以使代码更加简洁、易读,同时也可以提高代码的性能和可维护性。
Collection
Collection
是 Java 中的接口,被多个泛型容器接口所实现。在这里,Collection
是指代存放对象类型的数据结构。
Java 中的 Collection
元素类型定义时必须为对象,不能为基本数据类型。
以下内容用法均基于 Java 里多态的性质,均是以实现接口的形式出现。
常用的接口包括 List
、Queue
、Set
和 Map
。
容器定义
- 当定义泛型容器类时,需要在定义时指定数据类型。
例如:
List<Integer> list1 = new LinkedList<>();
倘若不指定数据类型,而当成 Object
类型随意添加数据,在 Java 8 中虽能编译通过,但会有很多警告风险。
如果是明确了类型如 List<Integer>
,此时编译器会检查放入的数据类型,只能放入整数的数据。声明集合变量时只能使用包装类型 List<Integer>
或者自定义的 Class
,而不能是基本类型如 List<int>
。
List
ArrayList
ArrayList
是支持可以根据需求动态生长的数组,初始长度默认为 10。如果超出当前长度便扩容 。
初始化
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(); // 创建一个名字为 list1 的可自增数组,初始长度为默认值(10)
List<Integer> list2 = new ArrayList<>(30); // 创建一个名字为list2的可自增数组,初始长度为 30
List<Integer> list3 = new ArrayList<>(list2); // 创建一个名字为 list3 的可自增数组,使用 list2 里的元素和 size 作为自己的初始值
}
}
LinkedList
LinkedList
是双链表。
初始化
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
public static void main(String[] args) {
List<Integer> list1 = new LinkedList<>(); // 创建一个名字为 list1 的双链表
List<Integer> list2 = new LinkedList<>(list1); // 创建一个名字为 list2 的双链表,将 list1 内所有元素加入进来
}
}
常用方法
以下均用 this
代替当前 List<Integer>
:
函数名 | 功能 |
---|---|
size() |
返回 this 的长度 |
add(Integer val) |
在 this 尾部插入一个元素 |
add(int idx, Integer e) |
在 this 指定位置插入一个元素 |
get(int idx) |
返回 this 中第 idx 位置的值,若越界则抛出异常 |
set(int idx, Integer e) |
修改 this 中第 idx 位置的值 |
使用案例及区别对比:
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static List<Integer> array = new ArrayList<>();
static List<Integer> linked = new LinkedList<>();
static void add() {
array.add(1); // 时间复杂度为 O(1)
linked.add(1); // 时间复杂度为 O(1)
}
static void get() {
array.get(10); // 时间复杂度为 O(1)
linked.get(10); // 时间复杂度为 O(11)
}
static void addIdx() {
array.add(0, 2); // 最坏情况下时间复杂度为 O(n)
linked.add(0, 2); // 最坏情况下时间复杂度为 O(n)
}
static void size() {
array.size(); // 时间复杂度为 O(1)
linked.size(); // 时间复杂度为 O(1)
}
static void set() { // 该方法返回值为原本该位置元素的值
array.set(0, 1); // 时间复杂度为 O(1)
linked.set(0, 1); // 最坏时间复杂度为 O(n)
}
}
遍历
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static List<Integer> array = new ArrayList<>();
static List<Integer> linked = new LinkedList<>();
static void function1() { // 朴素遍历
for (int i = 0; i < array.size(); i++) {
out.println(array.get(i)); // 遍历自增数组,复杂度为 O(n)
}
for (int i = 0; i < linked.size(); i++) {
out.println(linked.get(i)); // 遍历双链表,复杂度为 O(n^2),因为 LinkedList 的 get(i) 复杂度是 O(i)
}
}
static void function2() { // 增强 for 循环遍历
for (int e : array) {
out.println(e);
}
for (int e : linked) {
out.println(e); // 复杂度均为 O(n)
}
}
static void function3() { // 迭代器遍历
Iterator<Integer> iterator1 = array.iterator();
Iterator<Integer> iterator2 = linked.iterator();
while (iterator1.hasNext()) {
out.println(iterator1.next());
}
while (iterator2.hasNext()) {
out.println(iterator2.next());
} // 复杂度均为 O(n)
}
}
注意
不要在
for/foreach
遍历List
的过程中删除其中的元素,否则会抛出异常。原因也很简单,
list.size()
改变了,但在循环中已循环的次数却是没有随之变化。原来预计在下一个index
的数据因为删除的操作变成了当前index
的数据,运行下一个循环时操作的会变为原来预计在下下个index
的数据,最终会导致操作的数据不符合预期。
Queue
LinkedList
可以使用 LinkedList
实现普通队列,底层是链表模拟队列。
初始化
Queue<Integer> q = new LinkedList<>();
LinkedList
底层实现了 List
接口与 Deque
接口,而 Deque
接口继承自 Queue
接口,所以 LinkedList
可以同时实现 List
与 Queue
。
ArrayDeque
可以使用 ArrayDeque
实现普通队列,底层是数组模拟队列。
初始化
Queue<Integer> q = new ArrayDeque<>();
ArrayDeque
底层实现了 Deque
接口,而 Deque
接口继承自 Queue
接口,所以 ArrayDeque
可以实现 Queue
。
LinkedList 与 ArrayDeque 在实现 Queue 接口上的区别
- 数据结构:在数据结构上,
ArrayDeque
和LinkedList
都实现了 Java Deque 双端队列接口。但ArrayDeque
没有实现了 Java List 列表接口,所以不具备根据索引位置操作的行为。 - 线程安全:
ArrayDeque
和LinkedList
都不考虑线程同步,不保证线程安全。 - 底层实现:在底层实现上,
ArrayDeque
是基于动态数组的,而LinkedList
是基于双向链表的。 - 在遍历速度上:
ArrayDeque
是一块连续内存空间,基于局部性原理能够更好地命中 CPU 缓存行,而LinkedList
是离散的内存空间对缓存行不友好。 - 在操作速度上:
ArrayDeque
和LinkedList
的栈和队列行为都是 时间复杂度,ArrayDeque
的入栈和入队有可能会触发扩容,但从均摊分析上看依然是 时间复杂度。 - 额外内存消耗上:
ArrayDeque
在数组的头指针和尾指针外部有闲置空间,而LinkedList
在节点上增加了前驱和后继指针。
PriorityQueue
PriorityQueue
是优先队列,默认是小根堆。
初始化
Queue<Integer> q1 = new PriorityQueue<>(); // 小根堆
Queue<Integer> q2 = new PriorityQueue<>((x, y) -> {return y - x;}); // 大根堆 `
常用方法
以下均用 this
代替当前 Queue<Integer>
:
函数名 | 功能 |
---|---|
size() |
返回 this 的长度 |
add(Integer val) |
入队 |
offer(Integer val) |
入队 |
isEmpty() |
判断队列是否为空,为空则返回 true |
peek() |
返回队头元素 |
poll() |
返回队头元素并删除 |
使用案例及区别对比:
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Queue<Integer> q1 = new LinkedList<>();
static Queue<Integer> q2 = new PriorityQueue<>();
static void add() { // add 和 offer 功能上没有差距,区别是是否会抛出异常
q1.add(1); // 时间复杂度为 O(1)
q2.add(1); // 时间复杂度为 O(logn)
}
static void isEmpty() {
q1.isEmpty(); // 时间复杂度为 O(1)
q2.isEmpty(); // 空间复杂度为 O(1)
}
static void size() {
q1.size(); // 时间复杂度为 O(1)
q2.size(); // 返回 q2 的长度
}
static void peek() {
q1.peek(); // 时间复杂度为 O(1)
q2.peek(); // 时间复杂度为 O(logn)
}
static void poll() {
q1.poll(); // 时间复杂度为 O(1)
q2.poll(); // 时间复杂度为 O(logn)
}
}
遍历
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Queue<Integer> q1 = new LinkedList<>();
static Queue<Integer> q2 = new PriorityQueue<>();
static void test() {
while (!q1.isEmpty()) { // 复杂度为 O(n)
out.println(q1.poll());
}
while (!q2.isEmpty()) { // 复杂度为 O(nlogn)
out.println(q2.poll());
}
}
}
实现一个泛型为Pair<Integer,Integer>的PriorityQueue,以Pair的第二个元素大小进行排序
import java.util.Comparator;
import java.util.PriorityQueue;
public class PairPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Pair<Integer, Integer>> pq = new PriorityQueue<>(new Comparator<Pair<Integer, Integer>>() {
@Override
public int compare(Pair<Integer, Integer> p1, Pair<Integer, Integer> p2) {
return p1.getValue().compareTo(p2.getValue());
}
});
pq.offer(new Pair<>(1, 3));
pq.offer(new Pair<>(2, 2));
pq.offer(new Pair<>(3, 1));
while (!pq.isEmpty()) {
System.out.println(pq.poll());
}
}
static class Pair<K, V> {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public String toString() {
return "[" + key + ", " + value + "]";
}
}
}
或者实现如下:
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
// 创建一个泛型为<String, Integer>的PriorityQueue,并自定义Comparator
PriorityQueue<Map.Entry<String, Integer>> pq = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
// 添加测试数据
pq.add(new AbstractMap.SimpleEntry<>("hello", 2));
pq.add(new AbstractMap.SimpleEntry<>("world", 3));
pq.add(new AbstractMap.SimpleEntry<>("java", 1));
pq.add(new AbstractMap.SimpleEntry<>("programming", 4));
// 输出队列中的元素
while (!pq.isEmpty()) {
Map.Entry<String, Integer> entry = pq.poll();
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
Set
Set
是保持容器中的元素不重复的一种数据结构。
HashSet
随机位置插入的 Set
。
初始化
Set<Integer> s1 = new HashSet<>();
LinkedHashSet
保持插入顺序的 Set
。
初始化
Set<Integer> s2 = new LinkedHashSet<>();
TreeSet
保持容器中元素有序的 Set
,默认为升序。
初始化
Set<Integer> s3 = new TreeSet<>();
Set<Integer> s4 = new TreeSet<>((x, y) -> {return y - x;}); // 降序
常用方法
以下均用 this
代替当前 Set<Integer>
:
函数名 | 功能 |
---|---|
size() |
返回 this 的长度 |
add(Integer val) |
插入一个元素进 this |
contains(Integer val) |
判断 this 中是否有元素 val |
addAll(Collection e) |
将一个容器里的所有元素添加进 this |
retainAll(Collection e) |
将 this 改为两个容器内相同的元素 |
removeAll(Collection e) |
将 this 中与 e 相同的元素删除 |
使用案例:求并集、交集、差集。
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Set<Integer> s1 = new HashSet<>();
static Set<Integer> s2 = new LinkedHashSet<>();
static void add() {
s1.add(1);
}
static void contains() { // 判断 set 中是否有元素值为 2,有则返回 true,否则返回 false
s1.contains(2);
}
static void test1() { // s1 与 s2 的并集
Set<Integer> res = new HashSet<>();
res.addAll(s1);
res.addAll(s2);
}
static void test2() { // s1 与 s2 的交集
Set<Integer> res = new HashSet<>();
res.addAll(s1);
res.retainAll(s2);
}
static void test3() { // 差集:s1 - s2
Set<Integer> res = new HashSet<>();
res.addAll(s1);
res.removeAll(s2);
}
}
遍历
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Set<Integer> s1 = new HashSet<>();
static Set<Integer> s2 = new LinkedHashSet<>();
static void test() {
for (int key : s1) {
out.println(key);
}
out.close();
}
}
实现一个泛型为Pair<Integer,Integer>的TreeSet,以Pair的第二个元素大小进行排序
import java.util.Comparator;
import java.util.TreeSet;
public class PairTreeSet {
public static void main(String[] args) {
Comparator<Pair<Integer, Integer>> comparator = new Comparator<Pair<Integer, Integer>>() {
@Override
public int compare(Pair<Integer, Integer> p1, Pair<Integer, Integer> p2) {
return p1.getValue().compareTo(p2.getValue());
}
};
TreeSet<Pair<Integer, Integer>> set = new TreeSet<>(comparator);
set.add(new Pair<>(1, 3));
set.add(new Pair<>(2, 2));
set.add(new Pair<>(3, 1));
for (Pair<Integer, Integer> pair : set) {
System.out.println(pair.getKey() + " " + pair.getValue());
}
}
}
class Pair<K, V> {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
Map
Map
是维护键值对 <Key, Value>
的一种数据结构,其中 Key
唯一。
HashMap
随机位置插入的 Map
。
初始化
Map<Integer, Integer> map1 = new HashMap<>();
LinkedHashMap
保持插入顺序的 Map
。
初始化
Map<Integer, Integer> map2 = new LinkedHashMap<>();
TreeMap
保持 key
有序的 Map
,默认升序。
初始化
Map<Integer, Integer> map3 = new TreeMap<>();
Map<Integer, Integer> map4 = new TreeMap<>((x, y) -> {return y - x;}); // 降序
常用方法
以下均用 this
代替当前 Map<Integer, Integer>
:
函数名 | 功能 |
---|---|
put(Integer key, Integer value) |
插入一个元素进 this |
size() |
返回 this 的长度 |
containsKey(Integer val) |
判断 this 中是否有元素 key 为 val |
get(Integer key) |
将 this 中对应的 key 的 value 返回 |
keySet |
将 this 中所有元素的 key 作为集合返回 |
使用案例:
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Map<Integer, Integer> map1 = new HashMap<>();
static Map<Integer, Integer> map2 = new LinkedHashMap<>();
static Map<Integer, Integer> map3 = new TreeMap<>();
static Map<Integer, Integer> map4 = new TreeMap<>((x,y)->{return y-x;});
static void put(){ // 将 key 为 1、value 为 1 的元素返回
map1.put(1, 1);
}
static void get(){ // 将 key 为 1 的 value 返回
map1.get(1);
}
static void containsKey(){ // 判断是否有 key 为 1 的键值对
map1.containsKey(1);
}
static void KeySet(){
map1.keySet();
}
}
遍历
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
public class Main {
static PrintWriter out = new PrintWriter(System.out);
static Map<Integer, Integer> map1 = new HashMap<>();
static void print() {
for (int key : map1.keySet()) {
out.println(key + " " + map1.get(key));
}
}
}
当然,在面向对象的世界里,你的参数是什么都可以,包括 Collection
与自定义类型。
例如 Map
也可以定义为:
Map<String, Set<Integer>> map = new HashMap<>();
泛型为<String, Integer>,按第String大小进行降序排序
import java.util.*;
public class TreeMap {
public static void main(String[] args) {
// 创建一个泛型为<String, Integer>的TreeMap,并自定义Comparator
TreeMap<String, Integer> map = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2); // 降序排序
}
});
}
}
}
Arrays
Arrays
是 java.util
中对数组操作的一个工具类。方法均为静态方法,可使用类名直接调用。
Arrays.sort()
Arrays.sort()
是对数组进行的排序的方法,主要重载方法如下:
import java.util.Arrays;
import java.util.Comparator;
public class Main {
static int a[] = new int[10];
static Integer b[] = new Integer[10];
static int firstIdx, lastIdx;
public static void main(String[] args) {
Arrays.sort(a); // 1
Arrays.sort(a, firstIdx, lastIdx); // 2
Arrays.sort(b, new Comparator<Integer>() { // 3
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
Arrays.sort(b, firstIdx, lastIdx, new Comparator<Integer>() { // 4
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
// 由于 Java 8 后有 Lambda 表达式,第三个重载及第四个重载亦可写为
Arrays.sort(b, (x, y) -> { // 5
return y - x;
});
Arrays.sort(b, (x, y) -> { // 6
return y - x;
});
}
}
序号所对应的重载方法含义:
- 对数组 a 进行排序,默认升序。
- 对数组 a 的指定位置进行排序,默认升序,排序区间为左闭右开
[firstIdx, lastIdx)
。 - 对数组 a 以自定义的形式排序,第二个参数
-
第一个参数为降序,第一个参数-
第二个参数为升序,当自定义排序比较器时,数组元素类型必须为对象类型。 - 对数组 a 的指定位置进行自定义排序,排序区间为左闭右开
[firstIdx, lastIdx)
,当自定义排序比较器时,数组元素类型必须为对象类型。 - 和 3 同理,用 Lambda 表达式优化了代码长度。
- 和 4 同理,用 Lambda 表达式优化了代码长度。
Arrays.sort()
底层函数:
- 当你
Arrays.sort
的参数数组元素类型为基本数据类型(byte
、short
、char
、int
、long
、double
、float
)时,默认为DualPivotQuicksort
(双轴快排),复杂度最坏可以达到 。 - 当你
Arrays.sort
的参数数组元素类型为非基本数据类型时),则默认为legacyMergeSort
和TimSort
(归并排序),复杂度为。
可以通过如下代码验证:
Codeforces 1646B - Quality vs Quantity
题意概要:有n个数,你需要将其分为 2 组,是否能存在 1 组的长度小于另 1 组的同时和大于它。
例题代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
static class FastReader {
StringTokenizer st;
BufferedReader br;
public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
long nextLong() {
return Long.parseLong(next());
}
double nextDouble() {
return Double.parseDouble(next());
}
String nextLine() {
String str = "";
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
}
static PrintWriter out = new PrintWriter(System.out);
static FastReader in = new FastReader();
static void solve() {
int n = in.nextInt();
Integer a[] = new Integer[n + 10];
for (int i = 1; i <= n; i++) {
a[i] = in.nextInt();
}
Arrays.sort(a, 1, n + 1);
long left = a[1];
long right = 0;
int x = n;
for (int i = 2; i < x; i++, x--) {
left = left + a[i];
right = right + a[x];
if (right > left) {
out.println("YES");
return;
}
}
out.println("NO");
}
public static void main(String[] args) {
int t = in.nextInt();
while (t-- > 0) {
solve();
}
out.close();
}
}
如果你将以上代码的 a 数组类型由 Integer
修改为 int
,会导致 TLE。
Arrays.binarySearch()
Arrays.binarySearch()
是对数组连续区间进行二分搜索的方法,前提是数组必须有序,时间复杂度为 ,主要重载方法如下:
import java.util.Arrays;
public class Main {
static int a[] = new int[10];
static Integer b[] = new Integer[10];
static int firstIdx, lastIdx;
static int key;
public static void main(String[] args) {
Arrays.binarySearch(a, key); // 1
Arrays.binarySearch(a, firstIdx, lastIdx, key); // 2
}
}
源码如下:
private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
序号所对应的重载方法含义:
- 从数组 a 中二分查找是否存在
key
,如果存在,便返回其下标。若不存在,则返回一个负数。 - 从数组 a 中二分查找是否存在
key
,如果存在,便返回其下标,搜索区间为左闭右开[firstIdx,lastIdx)
。若不存在,则返回一个负数。
Arrays.fill()
Arrays.fill()
方法将数组中连续位置的元素赋值为统一元素。其接受的参数为数组、fromIndex
、toIndex
和需要填充的数。方法执行后,数组左闭右开区间 [firstIdx,lastIdx)
内的所有元素的值均为需要填充的数。
Collections
Collections
是 java.util
中对集合操作的一个工具类。方法均为静态方法,可使用类名直接调用。
Collections.sort()
Collections.sort()
底层原理为将其中所有元素转化为数组调用 Arrays.sort()
,完成排序后再赋值给原本的集合。又因为 Java 中 Collection
的元素类型均为对象类型,所以始终是归并排序去处理。
该方法无法对集合指定区间排序。
底层源码:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
Collections.binarySearch()
Collections.binarySearch()
是对集合中指定区间进行二分搜索,功能与 Arrays.binarySearch()
相同。
Collections.binarySearch(list, key);
该方法无法对指定区间进行搜索。
Collections.swap()
Collections.swap()
的功能是交换集合中指定二个位置的元素。
Collections.swap(list, i, j);