【刷题日记】Java 快速热身——基础篇
由于各种原因,我在平时写代码或刷题时,每隔一定时期的就要进行语言切换(C、C++、Python、JavaScript、Java),导致一些常用的语法或写题技巧老是记混,每次卡壳都要去搜一搜。
本文将随着刷题进度持续记录一些使用 Java 语言刷题时必须记住的要素,快速解决在语法方面的障碍。如果你打算开始使用 Java 语言进行刷题时,可以通篇浏览实现快速热身。
本文主要内容:
- 必背写法与重要函数用法
- 集合增删改查与字符串操作速查
- 语言无关的通用技巧
ACM 模式下的输入输出详看:站内文章【刷题日记】Java 快速热身——ACM 模式下的输入输出
本文题目难度标识:🟩简单,🟨中等,🟥困难。
Java 语法基础
代码风格
看看爪哇代码长啥样先,试图唤起尘封的记忆 😅😅😅
1 | public class Solution{ // 惯例一个文件一个类,文件名与类名相同 |
讨论类的定义和静态上下文的问题:
1 | // 一个文件可以有多个类,但只能有一个与文件名同名的public类 |
操作符
一些注意点:
操作 | 说明 | 备注 |
---|---|---|
右移操作 | 算数右移:>> 逻辑右移: >>> |
记得和别的语言进行区分 |
取模运算 | 由于 Java 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正:int modulus = (sum % k + k) % k; |
相关题目:🟨 974. 和可被 K 整除的子数组 - 力扣(LeetCode) |
优先级 | 恒等运算符优先级比位运算符更大,因此正确写法为:(num&mask)==0x01 |
这是语言之间普遍的优先级 |
值传递
有时候搞不清 Java 到底是引用传递还是值传递,根本原因是「值传递」没有理解透。
Java 中的函数传递本质是值传递的。
函数中如果传递的参数是对象 Object
,那么传递的只是对象的引用。我们可以利用这个引用去修改对象,但如果在函数内重新 new Object()
,函数返回时,主函数不会发生变化。
1 | public class Test { |
1 | class Node { |
此外需要注意的是 Java 在传递参数给方法前,都会计算参数的值。后文聊到 Map
的 getOrDefault
方法时会提到。
数组 Arrays
声明与初始化
1 | // 一维数组 |
排序 Arrays.sort
流 Arrays.stream
Arrays.stream()
可以将数组转换为流,以使用更多的操作。
1 | int[] arr1 = {1, 2, 3, 4, 5}; |
其他
使用 Arrays.equals(array1, array2);
比较两个数组的元素是否相等。
1 | // 比较两个数组是否相等 |
Arrays.copyOf()
方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。Arrays.copyOf()
的第二个参数指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。
1 | int[] arr1 = {1, 2, 3, 4, 5}; |
更多关于
Arrays.copyOf()
的源码解读详见文章:站内文章搞清楚 Java 中 List 的 toArray 方法。
Arrays.fill()
将指定的 int 值分配给指定 int 型数组的每个元素。
1 | int[] arr1 = {1, 2, 3, 4, 5}; |
字符串 String
1 | // 字符串相等判断 |
操作 | String |
StringBuffer /StringBuilder |
---|---|---|
增 | + 操作符。注意从左到右结合性 |
sb.append(str); sb.insert(int offset, str); |
删 | 不可变类 | sb.delete(start, end) sb.deleteCharAt() |
改(替换字符) | replace(char oldChar, char newChar) :返回新的字符串,原字符串不改变。replaceAll(String regex, String replacement) :返回新的字符串,原字符串不改变。replaceFirst(String regex, String replacement) :返回新的字符串,原字符串不改变。 |
sb.replace(start,end, str); :将起始位置为 start,结束位置为 end-1 的子串替换为 str。不生成新的 StringBuilder 对象,在原来的 StringBuilder 对象上修改。sb.setCharAt(index,c) |
查 | str.charAt(index); |
相同 |
长度 | str.length(); |
StringBuilder
还提供以下函数:
sb.reverse()
:反转字符串sb.toString()
:返回字符串sb.capacity()
:返回当前容量。注意,和返回有效长度sb.length()
不一样!
String
子串的获取:
str.substring(int beginIndex)
str.substring(int beginIndex,int endIndex)
。不含endIndex
。
String
大小写转换:
str.toUpperCase()
str.toLowerCase()
String
查字符、字符串第一次出现的位置:
str.indexOf(c)
字符、字符串与数字之间的转换
单步骤转换:
一招静态方法 valueOf
可以解决大部分转换难题。被转的放里面。
Character[]
char[]
互转详见:站内文章【刷题日记】Java 快速热身——排序。
我们在处理输入输出字符串题目时的连招:
1 | // 把字符串转换成字符数组处理,更丝滑。不然只能疯狂 s.charAt(i) 咯 |
StringBuffer
和 StringBuilder
中的构造方法和其他方法几乎是完全一样的。StringBuilder
非线程安全,但速度更快。一般情况下(比如刷题)推荐使用 StringBuilder
。
String
保留小数精度的处理可看 站内文章【刷题日记】Java 快速热身——ACM 模式下的输入输出。
基础函数
1 | // 输出 |
集合 Collection
/Map
集合增删改查速览:
接口 | Collection |
List |
Queue |
Deque |
---|---|---|---|---|
增 | add(E e) |
add(int index,E element) |
抛异常:add(e) 返回特殊值: offer(e) |
抛异常:addFirst(e) 、addLast(e) 返回特殊值: offerFirst(e) 、offerLast(e) |
删 | remove(Object o) 移除指定对象clear() 清空所有内容 |
抛异常:remove() 返回特殊值: poll() |
抛异常:removeFirst() 、removeLast() 返回特殊值: pollFirst() 、pollLast() |
|
改 | set(int index, E element) 请避免对空列表调用该函数 |
|||
查 | get(int index) |
抛异常:element() 返回特殊值: peek() |
抛异常:getFirst() 、getLast() 返回特殊值: peekFirst() 、peekLast() |
|
判断 | contains(Object o) isEmpty() |
栈结构的使用:
- 队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留
Stack
类。 - List 也可用于简单的栈。
ls.add(e)
方法作为入栈,ls.remove(ls.size() - 1)
作为出栈。
变长数组可以使用 ArrayList 进行实现。
接口 | Map |
---|---|
增 | put(key,value) |
删 | remove(key) 删除 key 对应的键值对clear() 清空 map |
改 | put(key,newValue) |
查 | get(key) getOrDefault(key,defaultValue) |
判断 | containsKey(key) containsValue(value) |
集合 | entrySet() keySet() |
getOrDefault
的使用有时候我们会编写这样的代码:map.getOrDefault(key,func());
,我们会误以为只有 map
不存在 key
时才会调用 func()
。事实上,Java 在传递参数给方法前,都会计算参数的值。因此即使 key 存在,func()
还是会执行一次。
如果我们需要 map
不存在时才执行 func()
,可以使用 computeIfAbsent
函数:map.computeIfAbsent(key,e->func());
或者使用三元运算符:int result = map.containsKey(key)? map.get(key) : func();
Map
的遍历
1 | // Map 遍历元组对 |
哈希算法
有时候我们需要在集合中存储一个元素的二维坐标,我们不需要自己定义二元组存储,我们只需要存储第几个格子即可:i*n+j
。或者将其组合成独一无二的 String
来作为 Key。
我们可以自己实现简单字符串的哈希算法。
不同哈希算法的粗略分析
下面列举出的算法是在通过例题 🟨 3137. K 周期字符串需要的最少操作次数 - 力扣(LeetCode) 前提下,对计算时间和复杂性权衡后的结果。
Hash 算法 | 时间 (ms) | 内存 (MB) | 时间击败比率 (%) |
---|---|---|---|
简易位运算 Hash | 23 | 45.2 | 100 |
简易乘法 Hash | 28 | 88.37 | |
Java Map hash() |
43 | 44.85 | 11.63 |
1 | // 乘法 Hash |
Array
与 List
之间的转换
两者之间的转换除了 for 循环外,还有以下方式。
Array2List:
1 | String[] array = {"A","B","C"}; // 基本类型数组的话必须是包装类型数组 |
【强制】使用工具类 Arrays.asList()
把数组转换成集合时, 不能使用其修改集合相关的方法, 它的 add
/ remove
/ clear
方法会抛出 UnsupportedOperationException
异常。
说明: asList
的返回对象是一个 Arrays
内部类, 并没有实现集合的修改方法。 Arrays.asList
体现的是适配器模式, 只是转换接口, 后台的数据仍是数组。
List2Array:
1 | List<String> ls = new ArrayList<String>(){{add("aa");add("bb");add("cc");}}; |
注意 String[] array = (String [])ls.toArray();
是不对的。因为 ls.toArray()
返回的 Object[]
类不能向下转型为 String[]
。有关 List
的 toArray
方法详看:站内文章这篇文章。
【强制】 使用集合转数组的方法, 必须使用集合的 toArray(T[] array)
, 传入的是类型完全一致、 长度为 0 的空数组。
List
的复制
1 | // 底层源码涉及到了 Arrays.toArray() |
集合的排序
集合的排序详见:站内文章【刷题日记】Java 快速热身——排序
TreeSet
TreeMap
的一些常用方法
以下函数在与「线段树」有关的题目的题解下见得多。可以稍微了解下,毕竟玩 API 玩得好能节省时间。
tset.floor(E e)
方法返回在这个集合中小于或者等于给定元素的最大元素,如果不存在这样的元素,返回null
tset.ceiling(E e)
方法返回在这个集合中大于或者等于给定元素的最小元素,如果不存在这样的元素,返回null
- tset.last()
- tset.lower()
- tmap.floorEntry(E e)
- tmap.higherEntry(E e)
- tmap.firstEntry()
- tmap.subMap(E fromKey, E toKey): 返回一个视图,其键范围从 fromKey(包括)到 toKey(不包括)。注意,子 Map 中的更改会反映在原始 Map 中。
相关题目: 🟨 729. 我的日程安排表 I - 力扣(LeetCode)
优先队列 PriorityQueue
优先队列使用了堆数据结构,堆的形式为数组。
因为 PriorityQueue
中插入删除的数据是泛型类, 可以是基本类型对应的包装类, 也可以是自定义类, 在向上调整和向下调整的过程中,需要不断地比较,如果数据没有指定比较规则,或者为 null
时,程序就会抛出异常。
1 | Queue<Integer> priorityQueue1 = new PriorityQueue<>(); // 默认数组容量 11 |
Java 集合中的 PriorityQueue
底层的堆默认是小根堆,如果要设置大根堆,就必须传入比较器, 并重写 compare
方法。
调试(标准化输出)
1 | System.out.println(object); // 标准输出 |
Object
的 toString()
默认实现并不直观。
对数组进行「友好」的字符串转换:
1 | Arrays.toString(arr);// 将数组转换为可视字符串 |
ArrayList
、HashSet
继承了 AbstractCollection
,里面重写了 toString
。我们可以这样直观查看 ArrayList
中的内容:
1 | System.out.println(c); |
附 AbstractCollection
中 toString
源码:
1 | public String toString() { |
多线程编程
本章节主要就是背单词!
Semaphore
信号量
1 | Semaphore s = new Semaphore(0); |
语言无关的通用技巧
1 | // 循环增减 |
有时候有些对输入顺序有要求的,我们可以稍微调整一下顺序。
1 | // 函数参数交换 |
写题时常见的初始化最大最小值:
- 最大值:
0x3f3f3f3f
- 最小值:
0xc0c0c0c0
0x3f3f3f3f
作为整型的最大值?原因如下:
0x3f3f3f3f
的十进制是1,061,109,567
,是10^9
级别的,而一般场合下的数据都是小于10^9
的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。而平时用的Integer.MAX_VALUE=0x7fffffff
不能满足“无穷大加一个有穷的数依然是无穷大”这个条件,加上一个数后,根据补码规范它会变成了一个很小的负数。0x3f3f3f3f
的 2 倍还是一个「无穷大」的数。0x3f3f3f3f
的每 8 位(每个字节)都是相同的。我们在程序设计中经常需要使用memset(a, val, sizeof(a))
初始化一个数组a
,该语句把数值val
(0x00
~0xFF
)填充到数组a
的每个字节上,所以用memset
只能赋值出“每 8 位都相同”的int
。当需要把一个数组中的数值初始化成正无穷时,为了避免加法算术上溢出或者繁琐的判断,我们经常用memset(a, 0x3f, sizeof(a))
给数组赋0x3f3f3f3f
的值来代替。
模板写法
单调栈/队列
原型题: 🟩 496. 下一个更大元素 I - 力扣(LeetCode)
1 | // 假设数组A从左到右遍历 |
有时候,单调栈或单调队列中不一定存的是实际数字的值,有可能存的是指向实际数字的指针或下标。比如这一题:🟥 239. 滑动窗口最大值 - 力扣(LeetCode)
滑动窗口
样例题:🟨 3. 无重复字符的最长子串 - 力扣(LeetCode)
1 | public int lengthOfLongestSubstring(String s) { |
方向移动
如果需要物体在规定的棋盘下(比如 n*n
)进行方向移动,可以这样写:
1 | // 定义方向 |
相关题目:688. 骑士在棋盘上的概率 - 力扣(LeetCode)
本文参考
- 我久远积灰的笔记
- 0x3f3f3f3f是什么意思???-CSDN博客
- 牛客网在线编程_算法笔试_输入输出练习 (nowcoder.com)
- java中的next()方法,nextline()方法,hasnext()方法的用法系列(1)。_java 读取a b c用next-CSDN博客
- Java Scanner 类 | 菜鸟教程 (runoob.com)
- java中String和StringBuilder的replace方法_stringbuilder replace-CSDN博客
- Java-修改 String 指定位置的字符最全方法总结(StringBuilder 和 StringBuffer 的使用以及区别)_string替换指定位置字符-CSDN博客
- Java保留两位小数的几种写法总结[通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)
- 【Java】Java双端队列Deque使用详解_dequeuejava-CSDN博客
- Java【优先级队列】详细图解 / 模拟实现 + 【PriorityQueue】常用方法介绍_java优先队列-CSDN博客
- Java:获取子字符串 substring()_java substring(10)-CSDN博客
- 经典Hash函数的实现 - BarryW - 博客园
- java.util.TreeSet.floor()方法和java.util.TreeSet.ceiling()方法_treeset的ceil和floor-CSDN博客