目录
基础知识
关键字
控制访问类
关键字 | 说明 |
---|---|
private | 私有的 |
public | 公共的 |
protected | 受保护的 |
default | 默认的 |
类、方法和变量修饰符
关键字 | 说明 |
---|---|
new | 创建( 对象 ) |
class | 类 |
interface | 接口 |
extends | 继承于 |
abstract | 抽象的 |
final | 最终的( 不可继承 / 重写 / 重新赋值 ) |
implements | 实现( 接口 ) |
static | 静态的 |
void | 无返回值的 |
synchronized | 线程同步的( 同一时间只能被一个线程访问 ) |
native | 本地 / 原生方法( 由c / c++实现 ) |
strictfp(strict float point) | 严格的 ( 遵守 IEEE 浮点数算术标准,保证不同硬件平台所执行的结果一致 ) |
transient | 临时的( 只能修饰成员变量,使其不参与序列化过程 ) |
volatile | 易变的( 保证任何时刻,两个不同线程访到的某个成员变量总是同一个值 ) |
程序控制语句
关键字 | 说明 |
---|---|
break | 跳出当前循环 |
continue | 跳转到下一次循环的迭代 |
switch | 根据值选择执行 |
case | 定义一个供 switch 选择的值 |
default | 定义一个供 switch 返回的默认值 |
do | 执行 |
if | 如果 |
else | 否则 |
for | for 循环 |
while | while循环 |
instanceof | 测试左边的对象是否是右边的类的实例 ,返回 Boolean |
return | 返回 |
错误处理
关键字 | 说明 |
---|---|
assert | 断言 |
try | 尝试 |
catch | 捕捉异常 |
finally | 在 try 和 catch 块之后、方法完成之前运行,不管是否抛出或捕获异常 |
throw | 抛出一个异常对象 |
throws | 用在声明方法时,表示该方法可能要抛出异常 |
assertion( 断言 )是一种调试程序的方式
- 默认是不运行的,除非增加 jvm 参数 -ea / -enableassertions
基本语法:
assert expression1 ; assert expression1 : expression2 ;
- expression1 表示一个 boolean 表达式,expression2 表示一个基本类型 / 表达式 / 一个对象,用于在失败的时候的输出信息
- 当 expression1 为 false 的时候,程序将会停止并抛出 java.lang.AssertionError 对象,如果含有 expression2 则附带 expression2 内容
包相关
关键字 | 说明 |
---|---|
package | 包 |
import | 导入 |
基本类型
关键字 | 说明 |
---|---|
boolean | 布尔型 |
byte | 字节型 |
short | 短整型 |
int | 整型 |
long | 长整型 |
float | 单精度浮点 |
double | 双精度浮点 |
char | 字符型 |
变量引用
关键字 | 说明 |
---|---|
super | 父类的 |
this | 当前类的 |
保留关键字
关键字 | 说明 |
---|---|
goto | 是关键字,但无法使用 |
const | 是关键字,但无法使用 |
基本数据类型
类型 | 最大值 | 最小值 | 大约 |
---|---|---|---|
byte | 127( 27-1 ) | -128( -27 ) | 100 |
short | 32767( 215 - 1 ) | -32768( -215 ) | 3 * 104 |
int | 2,147,483,647( 231 - 1 ) | -2,147,483,648( -231 ) | 2 * 109 |
long | 9,223,372,036,854,775,807( 263 -1 ) | -9,223,372,036,854,775,808( -263 ) | 9 * 1018 |
类型 | 大小 | 说明 |
---|---|---|
char | 16 位 | Unicode 字符( \u0000 - \uFFFF )( 0 - 65535 ) |
float | 32位 | 符合 IEEE 754 标准的单精度浮点数 |
double | 64 位 | 符合 IEEE 754 标准的双精度浮点数 |
boolean | / | 只有两个取值( true / false ) |
ASCII 码表
ASCII ( American Standard Code for Information Interchange ): 美国信息交换标准代码。
标准 ASCII 码使用 1字节( 第一位为0 )来表示字符。
- ASCII 控制字符( 字符编码 : 0 - 31)
- ASCII 打印字符( 字符编码 : 32 - 127)
十进制 | 符号 | 十进制 | 符号 | 十进制 | 符号 | 十进制 | 符号 |
---|---|---|---|---|---|---|---|
32 | 空格 | 56 | 8 | 80 | P | 104 | h |
33 | ! | 57 | 9 | 81 | Q | 105 | i |
34 | " | 58 | : | 82 | R | 106 | j |
35 | # | 59 | ; | 83 | S | 107 | k |
36 | $ | 60 | < | 84 | T | 108 | l |
37 | % | 61 | = | 85 | U | 109 | m |
38 | & | 62 | > | 86 | V | 110 | n |
39 | ' | 63 | ? | 87 | W | 111 | o |
40 | ( | 64 | @ | 88 | X | 112 | p |
41 | ) | 65 | A | 89 | Y | 113 | q |
42 | * | 66 | B | 90 | Z | 114 | r |
43 | + | 67 | C | 91 | [ | 115 | s |
44 | , | 68 | D | 92 | \ | 116 | t |
45 | - | 69 | E | 93 | ] | 117 | u |
46 | . | 70 | F | 94 | ^ | 118 | v |
47 | / | 71 | G | 95 | _ | 119 | w |
48 | 0 | 72 | H | 96 | ` | 120 | x |
49 | 1 | 73 | I | 97 | a | 121 | y |
50 | 2 | 74 | J | 98 | b | 122 | z |
51 | 3 | 75 | K | 99 | c | 123 | { |
52 | 4 | 76 | L | 100 | d | 124 | | |
53 | 5 | 77 | M | 101 | e | 125 | } |
54 | 6 | 78 | N | 102 | f | 126 | ~ |
55 | 7 | 79 | O | 103 | g | 127 | 删除 |
Unicode 和 UTF-8
Unicode( 统一码 )仅仅是一个字符集,码点空间为 U+000000 - U+10FFFF,用数字来映射不同的字符,仅仅规定了特定字符对应的二进制代码,至于这个二进制代码如何存储则没有任何规定。
- Unicode 使用17个平面,每个平面的码点范围为 U+xx0000 - U+xxFFFF,其中xx表示16进制的 0x00 到 0x10,每个平面有 216 = 65536个码位,所以 Unicode 总共有 17 * 65536 = 1114112个码位。
- 所有最常见的字符都放在基本平面 BMP( Basic Multilingual Plane ),这是 Unicode 最先定义和公布的一个平面,编码从 U+000000 到 U+00FFFF。
- 剩下的字符都放在辅助平面,码点范围从 U+010000 到 U+10FFFF。
UTF-8 以字节为单位对Unicode进行编码,使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。
Unicode 字符集范围(十六进制) | UTF-8 编码(⼆进制) |
---|---|
0000 0000 - 0000 007F | 0xxxxxxx |
0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-16 : 基本平面的字符占用2个字节,辅助平面的字符占用4个字节。
UTF-32 : 每个字符均占用四个字节。
类
Character 类
- 常见方法
方法 | 解释 |
---|---|
boolean isLetter(char ch) | 判断字符是否为字母 |
boolean isDigit(char ch) | 判断字符是否为数字 |
boolean isLetterOrDigit(char ch) | 判断字符是否为字母或数字 |
boolean isSpaceChar(char ch) | 判断字符是否为空格 |
boolean isLowerCase(char ch) | 判断字符是否为小写字母 |
boolean isUpperCase(char ch) | 判断字符是否为大写字母 |
char toLowerCase(char ch) | 返回对应字符的小写 |
char toUpperCase(char ch) | 返回对应字符的大写 |
字母大小写判断 / 转换
-
判断
-
转换
String类
StringJoiner类
Map 类
value 值为 List 的初始化
例 : 统计数组 [ n u m s ] [ nums ] [nums] 中数字的出现位置。
public class MapTest
{
Map<Integer, List<Integer>> map;
public void setMap(int[] nums)
{
map = new HashMap<>();
for (int i = 0; i < nums.length; i++)//方法1
{
map.computeIfAbsent(nums[i], Key -> new ArrayList<>()).add(i);
}
for (int i = 0; i < nums.length; ++i)//方法2
{
map.putIfAbsent(nums[i], new ArrayList<>());
map.get(nums[i]).add(i);
}
for (int i = 0; i < nums.length; i++)//方法3
{
List<Integer> list = map.getOrDefault(nums[i], new ArrayList<>());
list.add(i);
map.put(nums[i], list);
}
}
}
算法
前缀和
Prefix sum( 前缀和 )
用于解决原数组不变,多次求区间和的问题。
输入整数数组
[
n
u
m
s
]
[ nums ]
[nums] 和查询数组
[
f
i
n
d
S
u
m
]
[ findSum ]
[findSum],每个查询包含两个整数
l
l
l 和
r
r
r ,表示查询第
l
l
l 个数到第
r
r
r 个数的和
(注意 : 是第
l
l
l 个数到第
r
r
r 个数,也就是
n
u
m
s
[
0
]
nums [ 0 ]
nums[0] 为第一个数)
- 对于 n n n 个数,先创建一个长度为 n + 1 n+1 n+1 的前缀和数组 [ p r e f i x S u m ] [prefixSum] [prefixSum]
- p r e f i x S u m [ 0 ] = 0 prefixSum[0]=0 prefixSum[0]=0
- p r e f i x S u m [ i ] = p r e f i x S u m [ i − 1 ] + n u m s [ i − 1 ] prefixSum[i]=prefixSum[i-1]+nums[i-1] prefixSum[i]=prefixSum[i−1]+nums[i−1]
- s u m ( l , r ) = p r e f i x S u m [ r ] − p r e f i x S u m [ l − 1 ] sum(l,r)=prefixSum[r]-prefixSum[l-1] sum(l,r)=prefixSum[r]−prefixSum[l−1]
证明 |
---|
p
r
e
f
i
x
S
u
m
简
写
为
p
r
e
prefixSum简写为pre
prefixSum简写为pre
p
r
e
[
l
−
1
]
=
p
r
e
[
0
]
+
p
r
e
[
1
]
+
…
+
p
r
e
[
l
−
2
]
+
p
r
e
[
l
−
1
]
pre[l-1]=pre[0]+pre[1]+ \ldots+pre[l-2]+pre[l-1]
pre[l−1]=pre[0]+pre[1]+…+pre[l−2]+pre[l−1]
p
r
e
[
r
]
=
p
r
e
[
0
]
+
p
r
e
[
1
]
+
…
+
p
r
e
[
l
−
1
]
+
p
r
e
[
l
]
+
p
r
e
[
l
+
1
]
…
+
p
r
e
[
r
−
1
]
+
p
r
e
[
r
]
pre[r]=pre[0]+pre[1]+ \ldots+pre[l-1]+pre[l]+pre[l+1] \ldots+pre[r-1]+pre[r]
pre[r]=pre[0]+pre[1]+…+pre[l−1]+pre[l]+pre[l+1]…+pre[r−1]+pre[r]
p
r
e
[
r
]
−
p
r
e
[
l
−
1
]
=
p
r
e
[
l
]
+
p
r
e
[
l
+
1
]
+
…
+
p
r
e
[
r
−
1
]
+
p
r
e
[
r
]
pre[r]-pre[l-1]=pre[l]+pre[l+1]+ \ldots+pre[r-1]+pre[r]
pre[r]−pre[l−1]=pre[l]+pre[l+1]+…+pre[r−1]+pre[r]
输入 :
[ n u m s ] [ nums ] [nums] = [ 1,2,3,4,5,6,7,8,9,10,11 ]
[ f i n d S u m ] [ findSum ] [findSum] = [ [ 1,5 ],[ 6,11 ],[ 1,11 ] ]
输出 :
[ 15,51,66 ]
i n d e x index index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
[ n u m s ] [ nums ] [nums] | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | / |
[ p r e f i x S u m ] [ prefixSum ] [prefixSum] | 0 | 1 | 3 | 6 | 10 | 15 | 21 | 28 | 36 | 45 | 55 | 66 |
public void getSum(int[] nums, int[][] findSum)
{
int[] prefixSum = new int[nums.length + 1];
for (int i = 1; i < prefixSum.length; i++)
{
prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
}
for (int[] find : findSum)
{
int l = find[0];
int r = find[1];
System.out.print(prefixSum[r] - prefixSum[l - 1]+" ");
//如果 findSum 中的 l 和 r 为原数组下标而不是第 l / r 个数,可以 :
//int l = find[0] + 1; 例 : 原数组下标 0 对应第 1 个数
//int r = find[1] + 1;
//或者 :
//prefixSum[r +1] - prefixSum[l]
}
}
补充 |
---|
让 [ p r e f i x S u m ] [prefixSum] [prefixSum] 长的度比 [ n u m s ] [nums] [nums] 多 1 是为了设置 p r e f i x S u m [ 0 ] = 0 prefixSum[0]=0 prefixSum[0]=0,然后让 p r e f i x S u m [ 1 ] prefixSum[1] prefixSum[1] 保存 [ n u m s ] [nums] [nums] 的第一个数的前缀和。
因为如果 p r e f i x S u m [ 0 ] prefixSum[0] prefixSum[0] 保存的是 [ n u m s ] [nums] [nums] 第一个数的前缀和,在计算 s u m ( l , r ) sum(l,r) sum(l,r) 的时候需要减去第 l l l 个数的上一个数的前缀和,当 l = 1 l=1 l=1 时,因为 [ p r e f i x S u m ] [prefixSum] [prefixSum] 的第一个数就是 [ n u m s ] [nums] [nums] 第一个数的前缀和,所以在寻找 [ n u m s ] [nums] [nums] 第一个数上一个数的前缀和时就会导致数组越界。
另一种解决方法是在计算 p r e f i x S u m [ r ] − p r e f i x S u m [ l − 1 ] prefixSum[r] - prefixSum[l - 1] prefixSum[r]−prefixSum[l−1] 前进行判断,如果 l = 1 l=1 l=1 则直接返回 p r e f i x S u m [ r ] prefixSum[r] prefixSum[r],由于每次查询都要多进行一次判断,所以不建议采用这种写法。
水塘抽样算法
Reservoir Sampling( 水塘抽样 / 蓄水池抽样 )
用于解决内存无法加载全部数据时,从包含未知大小的数据流中随机选取 k 个数据,并且要保证每个数据被抽取到的概率相等的问题。
- k = 1 k = 1 k=1
对 于 第 i 个 数 据 , 以 1 i 的 几 率 保 留 , 作 为 最 终 的 结 果 。 对于第 i 个数据,以 \frac{1}{i} 的几率保留,作为最终的结果。 对于第i个数据,以i1的几率保留,作为最终的结果。
证明 |
---|
总共有 1 1 1 个数时 :
p ( n 1 ) = 1 1 = 1 p(n_1) = \frac{1}{1} = 1 p(n1)=11=1
总共有 2 2 2 个数时 :
结 果 为 n 1 的 概 率 = 选 择 n 1 的 概 率 × 不 保 留 n 2 的 概 率 结果为n_1的概率 = 选择n_1的概率 \times 不保留n_2的概率 结果为n1的概率=选择n1的概率×不保留n2的概率
p ( n 1 ) = p ( n 1 ) × ( 1 − p ( n 2 ) ) = 1 × ( 1 − 1 2 ) = 1 2 p(n_1) = p(n_1) \times (1-p(n_2)) = 1 \times ( 1 - \frac{1}{2}) = \frac{1}{2} p(n1)=p(n1)×(1−p(n2))=1×(1−21)=21
p ( n 2 ) = 1 2 p(n_2) = \frac{1}{2} p(n2)=21
总共有 3 3 3 个数时 :
结 果 为 n 1 的 概 率 = 选 择 n 1 的 概 率 × 不 保 留 n 2 的 概 率 × 不 保 留 n 3 的 概 率 结果为n_1的概率 = 选择n_1的概率 \times 不保留n_2的概率 \times 不保留n_3的概率 结果为n1的概率=选择n1的概率×不保留n2的概率×不保留n3的概率
p ( n 1 ) = p ( n 1 ) × ( 1 − p ( n 2 ) ) × ( 1 − p ( n 3 ) ) = 1 × ( 1 − 1 2 ) × ( 1 − 1 3 ) = 1 3 p(n_1) = p(n_1) \times (1-p(n_2)) \times (1-p(n_3)) = 1 \times ( 1 - \frac{1}{2}) \times ( 1 - \frac{1}{3}) = \frac{1}{3} p(n1)=p(n1)×(1−p(n2))×(1−p(n3))=1×(1−21)×(1−31)=31
结 果 为 n 2 的 概 率 = 选 择 n 2 的 概 率 × 不 保 留 n 3 的 概 率 结果为n_2的概率 = 选择n_2的概率 \times 不保留n_3的概率 结果为n2的概率=选择n2的概率×不保留n3的概率
p ( n 2 ) = p ( n 2 ) × ( 1 − p ( n 3 ) ) = 1 2 × ( 1 − 1 3 ) = 1 3 p(n_2) = p(n_2) \times (1-p(n_3)) = \frac{1}{2} \times ( 1 - \frac{1}{3}) = \frac{1}{3} p(n2)=p(n2)×(1−p(n3))=21×(1−31)=31
p ( n 3 ) = 1 3 p(n_3) = \frac{1}{3} p(n3)=31
总共有 n n n 个数,对于第 m m m 个和第 n n n 数 :
结 果 为 n m 的 概 率 = 选 择 n m × 不 保 留 n m + 1 × 不 保 留 n m + 2 × … × 不 保 留 n n − 1 × 不 保 留 n n 结果为n_m的概率 = 选择n_m \times 不保留n_{m+1} \times 不保留n_{m+2} \times\ldots \times 不保留n_{n-1} \times 不保留n_{n} 结果为nm的概率=选择nm×不保留nm+1×不保留nm+2×…×不保留nn−1×不保留nn
p ( n m ) = 1 m × m m + 1 × m + 1 m + 2 × … × n − 2 n − 1 × n − 1 n = 1 n p(n_m) = \frac{1}{m} \times \frac{m}{m+1} \times \frac{m+1}{m+2} \times \ldots \times \frac{n-2}{n-1} \times \frac{n-1}{n} = \frac{1}{n} p(nm)=m1×m+1m×m+2m+1×…×n−1n−2×nn−1=n1
p ( n n ) = 1 n p(n_n) = \frac{1}{n} p(nn)=n1
public int getRandom(Sample sample)
{
int index = 1;
int choice = 0;
while (sample != null)
{
if (random.nextInt(index) == 0)
{
choice = sample.val;
}
index++;
sample = sample.nextSample;
}
return choice;
}
- k > 1 k > 1 k>1
对 于 前 k 个 数 据 , 全 部 保 留 。 对于前 k 个数据,全部保留。 对于前k个数据,全部保留。
对 于 第 k + i 个 数 据 , 以 k k + i 的 几 率 保 留 , 以 1 k 的 几 率 替 换 当 前 保 留 的 k 个 数 中 的 一 个 , 作 为 最 终 的 结 果 。 对于第 k+i 个数据,以 \frac{k}{k+i} 的几率保留,以 \frac{1}{k}的几率替换当前保留的k个数中的一个,作为最终的结果。 对于第k+i个数据,以k+ik的几率保留,以k1的几率替换当前保留的k个数中的一个,作为最终的结果。
证明 |
---|
总共有 k k k 个数时 :
p ( n r [ r ∈ 1 : k ] ) = 1 p(n_r[r\in1:k])=1 p(nr[r∈1:k])=1
总共有 k + 1 k+1 k+1 个数时 :
结 果 为 n r [ r ∈ 1 : k ] 的 概 率 = 选 择 n r × n k + 1 不 替 换 n r 结果为n_r[r\in1:k]的概率 = 选择n_r \times n_{k+1}不替换n_r 结果为nr[r∈1:k]的概率=选择nr×nk+1不替换nr
n k + 1 不 替 换 n r 的 概 率 = 不 保 留 n k + 1 + 保 留 n k + 1 × 不 替 换 n r n_{k+1}不替换n_r的概率 = 不保留n_{k+1} +保留n_{k+1} \times 不替换n_r nk+1不替换nr的概率=不保留nk+1+保留nk+1×不替换nr
p ( n r ) = 1 × ( 1 k + 1 + k k + 1 × k − 1 k ) = k k + 1 p(n_r) =1\times (\frac{1}{k+1} + \frac{k}{k+1} \times \frac{k-1}{k}) = \frac{k}{k+1} p(nr)=1×(k+11+k+1k×kk−1)=k+1k
p ( n k + 1 ) = k k + 1 p(n_{k+1}) =\frac{k}{k+1} p(nk+1)=k+1k
总共有 n n n 个数,对于第 m m m 个和第 n n n 个数 :
结 果 为 n m 的 概 率 = 选 择 n m × n m + 1 不 替 换 n m × n m + 2 不 替 换 n m × … × n n − 1 不 替 换 n m × n n 不 替 换 n m 结果为n_m的概率 = 选择n_m \times n_{m+1}不替换n_m \times n_{m+2}不替换n_m \times\ldots \times n_{n-1}不替换n_m \times n_n不替换n_m 结果为nm的概率=选择nm×nm+1不替换nm×nm+2不替换nm×…×nn−1不替换nm×nn不替换nm
p ( n m ) = k m × m m + 1 × m + 1 m + 2 × … × n − 2 n − 1 × n − 1 n = k n p(n_m) = \frac{k}{m} \times \frac{m}{m+1} \times \frac{m+1}{m+2} \times \ldots \times \frac{n-2}{n-1} \times \frac{n-1}{n} = \frac{k}{n} p(nm)=mk×m+1m×m+2m+1×…×n−1n−2×nn−1=nk
p ( n n ) = k n p(n_n) = \frac{k}{n} p(nn)=nk
public int[] getRandom(Sample sample, int k)
{
int index = 1;
int[] choice = new int[k];
for (int i = 0; i < k; i++)
{
choice[i] = sample.val;
sample = sample.nextSample;
}
while (sample != null)
{
int randomNum = random.nextInt(index);
if (randomNum < k)
{
choice[randomNum] = sample.val;
}
index++;
sample = sample.nextSample;
}
return choice;
}
经典题目
数字的字典序
题目 : 输入一个整数 n n n ,按字典序返回范围 [ 1 , n n n ] 内所有整数。
递归思路 :
- 从 1 到 9 开始,在每个数的末位后添加 0 到 9(不超过 n n n)。
迭代思路 :
-
字典序中一个数的下一个数一定是在这个数的末位后加上 0(不超过 n n n)。
-
如果因为 n n n 的限制不能在末尾后加 0,那么这个数的下一个数就是这个数加上1(不超过 n n n 且末位不是9)。
-
末位是 9 不能加 1 的原因是例如在 [ 1 , 1000 ] 的字典序中,119 的下一个数应该是 12 而不是 120,所以在遇到末位是 9 的时候,需要将这个数末尾的所有 9 都去掉后加 1 以此得到下一个数。
输入 :
n n n = 13
输出 :
[ 1,10,11,12,13,2,3,4,5,6,7,8,9 ]
- 解法1 : 递归
public List<Integer> lexicalOrder(int n)
{
List<Integer> ans = new ArrayList<>();
if (n < 10)
{
for (int i = 1; i <= n; i++)
{
ans.add(i);
}
return ans;
}
for (int i = 1; i <= 9; i++)
{
dfs(i, n, ans);
}
return ans;
}
void dfs(int cur, int max, List<Integer> ans)
{
ans.add(cur);
for (int i = 0; i <= 9; i++)
{
int newNum = cur * 10 + i;
if (newNum > max)//如果已经大于max则不需要获得之后的num了
{
return;
}
dfs(newNum, max, ans);
}
}
- 解法2 : 迭代
public List<Integer> lexicalOrder(int n)
{
List<Integer> ans = new ArrayList<Integer>();
int number = 1;
for (int i = 0; i < n; i++)
{
ans.add(number);
if (number * 10 <= n)
{
number *= 10;//进入下一层
}
else
{
while (number % 10 == 9 || number + 1 > n)//如果末位已经到9或者已经到达最大值
{
number /= 10;//返回上一层
}
number++;//获得下一个合法的数字
}
}
return ans;
}