日期:2026-07-16
项目:c260716
作者:WanJL
Java 中的多维数组本质上是数组的数组:
| 维度 | 本质 | 说明 |
|---|---|---|
| 一维数组 | 元素是数据类型的变量或常量 | int[] → 元素是 int |
| 二维数组 | 元素是一维数组 | int[][] → 元素是 int[] |
| 三维数组 | 元素是二维数组 | int[][][] → 元素是 int[][] |
核心概念:二维数组的每个元素其实是一维数组的引用地址值。
数组类型[][] 数组名 = new 数组类型[二维数组长度][一维数组长度];
// 示例:3行2列的二维数组
int[][] arr = new int[3][2]; // [行数][列数]
二维数组需要两层循环嵌套来遍历:
// 外层循环控制行数(二维数组的长度)
for (int i = 0; i < arr.length; i++) {
// 内层循环控制列数(每个一维数组的长度)
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
arr.length — 二维数组的长度(即行数)arr[i].length — 第 i 行一维数组的长度(即列数)int[][] arr1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
二维数组中的一维数组不必长度相同:
int[] arr01 = {1, 3};
int[] arr02 = {4, 5, 6};
int[] arr03 = {7, 8, 9, 15};
int[][] arr2 = {arr01, arr02, arr03}; // 存放的是一维数组的引用地址
遍历时同样使用两层循环,每个内层循环以对应一维数组的长度为界:
for (int i = 0; i < arr2.length; i++) {
for (int j = 0; j < arr2[i].length; j++) {
System.out.print(arr2[i][j] + " ");
}
System.out.println();
}
增强 for 循环(foreach) 是 Java 提供的一种简化数组遍历的语法,作用和普通 for 循环一样,主要用于遍历数组或集合中的所有元素。
for (数据类型 变量 : 数组名) {
代码块
}
int[] arr = {1, 2, 3, 4, 5, 6, 8};
for (int a : arr) {
System.out.print(a + " ");
}
// 输出:1 2 3 4 5 6 8
⚠️ 注意:foreach 遍历数组时无法获取索引值,所以只适合遍历全部元素。如果需要操作某个指定索引位置的元素,仍需使用普通
for循环。
int[] arr01 = {1, 3};
int[] arr02 = {4, 5, 6};
int[] arr03 = {7, 8, 9, 15};
int[][] arr2 = {arr01, arr02, arr03};
for (int[] a : arr2) { // 外层 foreach:遍历二维数组,得到每个一维数组
for (int a1 : a) { // 内层 foreach:遍历一维数组,得到每个元素
System.out.print(a1 + " ");
}
System.out.println();
}
外层
int[] a表示二维数组中的每个元素是一维数组,内层int a1表示一维数组中的每个元素是int。
| 特性 | 普通 for 循环 | 增强 for 循环(foreach) |
|---|---|---|
| 语法 | for(int i=0; i<len; i++) |
for(int a : arr) |
| 索引访问 | 支持(通过 i) |
不支持 |
| 适用场景 | 需要索引、修改元素、部分遍历 | 只需要遍历全部元素 |
| 代码简洁性 | 较繁琐 | 更简洁 |
| 可读性 | 一般 | 更好 |
java.util.Arraysjava.util.Arrays 是 Java 提供的数组工具类,包含大量操作数组的静态方法。
命名特点:如果一个类的类名是名词 +
s,大多数情况下这个类就是工具类。
使用前提:需要导包
import java.util.Arrays;
// import 是导包的关键字,后面跟着【类全路径名】—— 包名.类名
| 方法 | 作用 | 示例 |
|---|---|---|
Arrays.sort(arr) |
对数组进行升序排序 | Arrays.sort(arr); |
Arrays.toString(arr) |
将数组转换为字符串输出 | System.out.println(Arrays.toString(arr)); |
Arrays.copyOf(arr, newLength) |
拷贝数组(扩展或截断,多余补默认值) | int[] copy = Arrays.copyOf(arr, 10); |
Arrays.copyOfRange(arr, from, to) |
拷贝数组的指定范围 [from, to) |
int[] range = Arrays.copyOfRange(arr, 1, 4); |
Arrays.binarySearch(arr, key) |
二分查找(数组必须先排序),返回索引值(未找到返回负数) | int index = Arrays.binarySearch(arr, 689); |
int[] arr = {7, 6, 78, 1, 689, 87, 165, 7, 4, 5, 8};
// 排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// 输出:[1, 4, 5, 6, 7, 7, 8, 78, 87, 165, 689]
// 拷贝(扩展长度,多余补 0)
int[] copy = Arrays.copyOf(arr, 10);
System.out.println(Arrays.toString(copy));
// 拷贝指定范围 [1, 4) — 包含索引1,不包含索引4
int[] range = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(range)); // 输出:[4, 5, 6]
// 二分查找(需先排序),返回元素在数组中的索引
int index = Arrays.binarySearch(arr, 689);
System.out.println(index); // 输出:10(排序后 689 在索引 10)
⚠️
Arrays.binarySearch()要求数组必须先排序,否则结果不确定。
冒泡排序是所有排序算法中最直观、最基础的一种。它的名字来源于排序过程就像水里的泡泡一样,大的元素会逐渐"冒"到数组的末端。
重复遍历数组,依次比较相邻的两个元素,如果顺序错误(如从小到大排序时前一个比后一个大)就交换。每轮遍历把当前未排序部分的最大值"冒泡"到正确位置。
以数组 {5, 3, 8, 6, 4} 为例,从小到大排序:
第1轮(i=0):将最大值 8 冒泡到最后
[5, 3, 8, 6, 4] → 比较 5>3 → 交换 → [3, 5, 8, 6, 4]
[3, 5, 8, 6, 4] → 比较 5<8 → 不交换
[3, 5, 8, 6, 4] → 比较 8>6 → 交换 → [3, 5, 6, 8, 4]
[3, 5, 6, 8, 4] → 比较 8>4 → 交换 → [3, 5, 6, 4, 8] ← 8 到位
第2轮(i=1):将最大值 6 冒泡到倒数第二位
[3, 5, 6, 4, 8] → 比较 3<5 → 不交换
[3, 5, 6, 4, 8] → 比较 5<6 → 不交换
[3, 5, 6, 4, 8] → 比较 6>4 → 交换 → [3, 5, 4, 6, 8] ← 6 到位
第3轮(i=2):将最大值 5 冒泡到倒数第三位
[3, 5, 4, 6, 8] → 比较 3<5 → 不交换
[3, 5, 4, 6, 8] → 比较 5>4 → 交换 → [3, 4, 5, 6, 8] ← 5 到位
第4轮(i=3):将最大值 4 冒泡到倒数第四位
[3, 4, 5, 6, 8] → 比较 3<4 → 不交换 ← 4 已在正确位置
排序完成 ✅
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
// 冒泡排序需要两层循环
for (int i = 0; i < arr.length - 1; i++) {
// 外层循环:控制冒泡的轮数(共 arr.length-1 轮)
// 内层循环:在未排序区间 [0, arr.length-1-i] 中相邻比较
// 因为 i 后面的元素已经有序,不需要再比较了
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换位置(加减法交换)
arr[j] = arr[j] + arr[j + 1];
arr[j + 1] = arr[j] - arr[j + 1];
arr[j] = arr[j] - arr[j + 1];
}
}
}
System.out.println(Arrays.toString(arr));
代码要点:
| 部分 | 说明 |
|------|------|
| 外层循环 i | 控制冒泡的轮数,arr.length - 1 轮 |
| 内层循环 j | 在未排序区间 相邻比较,范围 [0, arr.length-1-i) |
| arr.length-1-i | 每轮结束后,末尾 i 个元素已有序,无需再比较 |
| 相邻比较 | arr[j] > arr[j+1] 表示前大后小,需要交换 |
为什么外层循环是
arr.length-1轮? 因为 n 个元素的数组,最多需要 n-1 轮冒泡就可以完成排序(最后一轮只剩一个元素,无需再排)。为什么内层循环是
arr.length-1-i? 第 i 轮结束后,数组末尾的 i 个元素已经是有序的(每轮冒泡出一个最大值),所以内层只需要比较前arr.length-1-i个元素。
如果某轮冒泡过程中没有发生任何交换,说明数组已经有序,可以提前结束排序。
int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
for (int i = 0; i < arr2.length - 1; i++) {
boolean b = false; // 标记本轮是否有交换
for (int j = 0; j < arr2.length - 1 - i; j++) {
if (arr2[j] > arr2[j + 1]) {
// 交换位置
arr2[j] = arr2[j] + arr2[j + 1];
arr2[j + 1] = arr2[j] - arr2[j + 1];
arr2[j] = arr2[j] - arr2[j + 1];
b = true; // 本轮有交换
}
}
// 如果本轮没有发生任何交换,说明数组已经有序,提前结束
if (b == false) {
break;
}
System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2));
}
优化点:引入 boolean 标记变量,如果某一轮没有发生交换,说明数组已有序,直接 break 退出循环。
| 特性 | 说明 |
|---|---|
| 算法类型 | 比较排序、交换排序 |
| 时间复杂度(平均) | O(n²) |
| 时间复杂度(最好) | O(n) — 数组已有序(优化版) |
| 时间复杂度(最坏) | O(n²) — 数组逆序 |
| 空间复杂度 | O(1) — 原地排序,不需要额外空间 |
| 稳定性 | ✅ 稳定(相等元素的相对位置不变) |
| 适用场景 | 小规模数据排序,教学演示 |
数据结构与算法初探:数组是最基础的线性结构之一,冒泡排序是最基础的排序算法之一。后续还会学习选择排序、插入排序、快速排序等更多算法。
选择排序的思路比冒泡排序更"聪明"——冒泡排序是不断交换相邻元素,而选择排序每轮只做一次交换。
每一轮从剩余未排序的元素中找出最小的元素,直接把它放到未排序部分的第 1 位(即已排序区间的末尾)。
以数组 {29, 10, 14, 37, 13} 为例,从小到大排序:
第1轮(i=0):在 [0, 4] 找最小值 10,与 arr[0]=29 交换
[29, 10, 14, 37, 13] → 找到最小值 10 (索引 1)
→ 交换 → [10, 29, 14, 37, 13] ← 10 到位
第2轮(i=1):在 [1, 4] 找最小值 13,与 arr[1]=29 交换
[10, 29, 14, 37, 13] → 找到最小值 13 (索引 4)
→ 交换 → [10, 13, 14, 37, 29] ← 13 到位
第3轮(i=2):在 [2, 4] 找最小值 14,已在正确位置,不交换
[10, 13, 14, 37, 29] → 找到最小值 14 (索引 2)
→ minIndex == i,不交换 ← 14 已在正确位置
第4轮(i=3):在 [3, 4] 找最小值 29,与 arr[3]=37 交换
[10, 13, 14, 37, 29] → 找到最小值 29 (索引 4)
→ 交换 → [10, 13, 14, 29, 37] ← 29、37 到位
排序完成 ✅
import java.util.Arrays;
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
// 外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置
for (int i = 0; i < arr.length - 1; i++) {
// 假设当前未排序区间的第 1 个元素就是最小的
int minIndex = i; // 假设 i 就是最小元素的索引值
// 内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; // 更新最小元素的索引值
}
}
// 如果最小元素索引值 minIndex 和当前 i 不相等,就交换位置
if (minIndex != i) {
arr[minIndex] = arr[minIndex] + arr[i];
arr[i] = arr[minIndex] - arr[i];
arr[minIndex] = arr[minIndex] - arr[i];
}
System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr));
}
代码要点:
| 部分 | 说明 |
|---|---|
外层循环 i |
控制选择的轮数,arr.length - 1 轮;i 也是当前要确定的位置 |
内层循环 j |
在未排序区间 [i+1, arr.length-1] 中寻找最小值 |
minIndex |
记录当前找到的最小元素的索引值,初始为 i |
| 交换条件 | 当 minIndex != i 时,说明最小值不在当前位置,需要交换 |
| 每轮只交换一次 | 找到最小值后,直接与当前位置交换,比冒泡排序更高效 |
| 特性 | 冒泡排序 | 选择排序 |
|---|---|---|
| 核心思路 | 相邻比较,大的往后冒 | 每轮选最小值,放到正确位置 |
| 每轮交换次数 | 多次(可能每轮交换 n 次) | 最多 1 次 |
| 比较次数 | 固定 O(n²/2) | 固定 O(n²/2) |
| 时间复杂度(平均) | O(n²) | O(n²) |
| 时间复杂度(最好) | O(n) — 优化版 | O(n²) — 仍要遍历全部 |
| 时间复杂度(最坏) | O(n²) | O(n²) |
| 空间复杂度 | O(1) | O(1) |
| 稳定性 | ✅ 稳定 | ❌ 不稳定 |
| 适用场景 | 小规模数据、教学演示 | 小规模数据、交换成本高的场景 |
稳定性说明:选择排序是不稳定的,因为当最小值与前面的元素交换时,可能改变相等元素的相对顺序。例如
[5, 5, 2]→ 第一轮将2与第一个5交换,两个5的相对顺序就变了。
以数组 {5, 3, 8, 6, 4} 为例:
冒泡排序(每轮可能多次交换):
第1轮:5>3→交换, 5<8不换, 8>6→交换, 8>4→交换 → [3,5,6,4,8]
第2轮:3<5不换, 5<6不换, 6>4→交换 → [3,5,4,6,8]
第3轮:3<5不换, 5>4→交换 → [3,4,5,6,8]
第4轮:3<4不换 → [3,4,5,6,8]
选择排序(每轮最多一次交换):
第1轮:找最小值 3 (索引1),与 arr[0]=5 交换 → [3,5,8,6,4]
第2轮:找最小值 4 (索引4),与 arr[1]=5 交换 → [3,4,8,6,5]
第3轮:找最小值 5 (索引4),与 arr[2]=8 交换 → [3,4,5,6,8]
第4轮:找最小值 6 (索引3),已在正确位置不交换 → [3,4,5,6,8]
插入排序是三种基础排序(冒泡、选择、插入)中最实用、使用最广泛的一种 O(n²) 排序算法。它的思想非常符合人类整理东西的习惯——就像打扑克牌时抓牌插入到手牌的正确位置。
把数组分为"已排序"和"未排序"两部分。每次从未排序部分取出第 1 个元素,在已排序部分从后往前找到合适的位置并插入。
以数组 {5, 3, 8, 6, 4} 为例,从小到大排序:
初始: [5 | 3, 8, 6, 4] 竖线左边为已排序,右边为未排序
第1轮(i=1):取未排序第1个元素 current=3,与已排序部分 [5] 比较
5 > 3 → 5 向后移 → [5, 5 | 8, 6, 4]
找到插入位置 j+1=0,插入 current=3
→ [3, 5 | 8, 6, 4] ← 3 和 5 有序
第2轮(i=2):取 current=8,与已排序部分 [3, 5] 比较
5 < 8 → 无需移动,直接插入在末尾
→ [3, 5, 8 | 6, 4] ← 8 已在正确位置
第3轮(i=3):取 current=6,与已排序部分 [3, 5, 8] 比较
8 > 6 → 8 向后移 → [3, 5, 8, 8 | 4]
5 < 6 → 停止,找到插入位置 j+1=2,插入 current=6
→ [3, 5, 6, 8 | 4] ← 6 插入到 5 和 8 之间
第4轮(i=4):取 current=4,与已排序部分 [3, 5, 6, 8] 比较
8 > 4 → 8 向后移 → [3, 5, 6, 8, 8]
6 > 4 → 6 向后移 → [3, 5, 6, 6, 8]
5 > 4 → 5 向后移 → [3, 5, 5, 6, 8]
3 < 4 → 停止,找到插入位置 j+1=1,插入 current=4
→ [3, 4, 5, 6, 8] ✅ 排序完成
import java.util.Arrays;
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
// 从第2个元素开始,假设 arr[0] 已经是已排序部分
for (int i = 1; i < arr.length; i++) {
// 获取当前要插入的元素(未排序的第1个)
int current = arr[i];
// 已排序部分的最后一个索引值
int j = i - 1;
// 从后往前扫描已排序部分,寻找插入位置
// 如果已排序的元素大于 current,就向后移动一位
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
// 此时 j+1 就是 current 应该插入的位置
arr[j + 1] = current;
System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr));
}
代码要点:
| 部分 | 说明 |
|---|---|
外层循环 i |
从索引 1 开始,i 是当前要处理的"未排序的第 1 个元素"位置 |
current |
暂存当前要插入的元素值,防止被覆盖 |
| 内层 while 循环 | 从后往前扫描已排序部分,比 current 大的元素逐一后移 |
arr[j+1] = current |
在找到的正确位置插入当前元素 |
| 循环条件 | j >= 0 && arr[j] > current — 未越界且当前比较值比 current 大 |
| 特性 | 冒泡排序 | 选择排序 | 插入排序 |
|---|---|---|---|
| 核心思路 | 相邻比较,大的往后冒 | 每轮选最小值交换 | 将元素插入已排序区的正确位置 |
| 每轮交换次数 | 多次(可能 n 次) | 最多 1 次 | 多次(元素后移) |
| 平均时间复杂度 | O(n²) | O(n²) | O(n²) |
| 最好时间复杂度 | O(n) — 优化版 | O(n²) | O(n) — 已有序 |
| 最坏时间复杂度 | O(n²) | O(n²) | O(n²) |
| 空间复杂度 | O(1) | O(1) | O(1) |
| 稳定性 | ✅ 稳定 | ❌ 不稳定 | ✅ 稳定 |
| 实用性 | 低(教学用) | 中(交换成本高时适用) | 高(小规模数据首选) |
三种排序的实用性排序:插入排序 > 选择排序 > 冒泡排序
为什么插入排序最实用?
- 当数据基本有序时,插入排序的时间复杂度可达到 O(n)
- 插入排序是稳定的,不会破坏相等元素的相对顺序
- 插入排序的代码简洁,实际运行效率在 O(n²) 级别中最高
java.util.Scanner 是 Java 提供的键盘输入工具类,可以读取用户在控制台中输入的数据。
| 步骤 | 说明 | 代码 |
|---|---|---|
| ① 导包 | 引入 Scanner 类(IDEA 可自动导包) | import java.util.Scanner; |
| ② 创建对象 | 创建 Scanner 对象,绑定到系统输入 | Scanner sc = new Scanner(System.in); |
| ③ 读取数据 | 调用对应的方法读取不同类型的数据 | sc.nextInt()、sc.next() 等 |
| ④ 关闭资源 | 关闭 Scanner 释放资源 | sc.close(); |
System.in属于系统标准输入流,Scanner 通过它来读取用户在控制台的输入。
| 方法 | 读取类型 | 示例 |
|---|---|---|
sc.nextByte() |
整数 byte | byte b = sc.nextByte(); |
sc.nextShort() |
整数 short | short s = sc.nextShort(); |
sc.nextInt() |
整数 int | int i = sc.nextInt(); |
sc.nextLong() |
整数 long | long l = sc.nextLong(); |
sc.nextFloat() |
小数 float | float f = sc.nextFloat(); |
sc.nextDouble() |
小数 double | double d = sc.nextDouble(); |
sc.next() |
单个字符串(以空格/换行符为结束符) | String s = sc.next(); |
sc.nextLine() |
整行字符串(以换行符为结束符) | String line = sc.nextLine(); |
sc.nextBoolean() |
布尔值 boolean | boolean b = sc.nextBoolean(); |
sc.next().charAt(0) |
字符 char | char c = sc.next().charAt(0); |
Scanner sc = new Scanner(System.in);
// 读取整数
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println("a + b = " + (a + b));
// 读取字符串
String s = sc.next();
System.out.println(s);
sc.close();
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的姓名:");
String name = sc.next();
System.out.println("请输入您的年龄:");
int age = sc.nextInt();
System.out.println("请输入您的身高(米):");
double height = sc.nextDouble();
System.out.println("您是否是在校学生(true/false):");
boolean isStudent = sc.nextBoolean();
System.out.println("\n========用户信息========");
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("身高:" + height);
System.out.println("在校学生:" + isStudent);
sc.close();
nextInt() 后面使用 nextLine() 的问题int a = sc.nextInt();
String s = sc.nextLine();
System.out.println(a); // 输出数字
System.out.println(s); // ❌ 直接输出空字符串,没有等待输入!
问题原因:
nextInt() 读取完数字后,换行符 \n 仍停留在输入缓冲区中nextLine() 以换行符为结束符,遇到缓冲区中的换行符就直接结束了s 读取到的是一个空字符串,程序不会等待用户输入解决方案:在 nextInt() 之后调用一次 nextLine() 吃掉多余的换行符。
int a = sc.nextInt();
sc.nextLine(); // ✅ 吃掉缓冲区中的换行符
String s = sc.nextLine(); // ✅ 正常读取整行字符串
System.out.println(a);
System.out.println(s);
⚠️ 注意事项:
- 在控制台中使用键盘输入的值,必须符合 Scanner 接收的类型,否则会抛出异常导致程序结束
- 例如用
nextInt()读取时输入了字母,会抛出InputMismatchException
next() vs nextLine() 区别| 特性 | next() |
nextLine() |
|---|---|---|
| 结束符 | 空格、Tab、换行符 | 仅换行符 |
| 能否读取空格 | ❌ 不能(遇到空格停止) | ✅ 能(读取整行) |
| 典型场景 | 读取单个单词 | 读取包含空格的句子 |
// 输入:Hello World
System.out.println(sc.next()); // 输出:Hello(遇到空格停止)
System.out.println(sc.nextLine()); // 输出:World(读取剩余部分)
题目:定义数组 {10, 20, 30, 40, 50},将第 1 个与第 5 个交换、第 2 个与第 4 个交换(首尾对称交换)。
核心思路:使用加减法交换(不借助第三个变量)实现两个位置的元素互换。
int[] arr = {10, 20, 30, 40, 50};
// 首尾交换:第1位和第5位
arr[0] = arr[0] + arr[4];
arr[4] = arr[0] - arr[4];
arr[0] = arr[0] - arr[4];
// 首尾交换:第2位和第4位
arr[1] = arr[1] + arr[3];
arr[3] = arr[1] - arr[3];
arr[1] = arr[1] - arr[3];
变量交换的三种方式:
| 方式 | 代码 | 优点 | 缺点 |
|---|---|---|---|
| 借助临时变量 | tmp = a; a = b; b = tmp; |
直观、安全 | 多用一个变量 |
| 加减法交换 | a = a + b; b = a - b; a = a - b; |
无需额外变量 | 可能溢出(大数) |
| 异或交换 | a = a ^ b; b = a ^ b; a = a ^ b; |
无需额外变量 | 可读性差 |
题目:定义数组 {1, 2, 3, 4, 5},将数组中的所有元素向右移动 2 个位置,超出数组范围的元素循环移到数组开头。
解题思路:
先理解全体向右移动 1 位的过程:
执行步骤图解(右移 1 位):
原始: [1, 2, 3, 4, 5]
第1步:保存最后一个元素 last = 5
第2步:从后往前移动 ←
[1, 2, 3, 4, 4] // arr[4]=arr[3]
[1, 2, 3, 3, 4] // arr[3]=arr[2]
[1, 2, 2, 3, 4] // arr[2]=arr[1]
[1, 1, 2, 3, 4] // arr[1]=arr[0]
第3步:arr[0] = last
结果: [5, 1, 2, 3, 4]
代码实现:
int[] arr = {1, 2, 3, 4, 5};
int k = 2; // 右移位数
// 如果k大于数组长度,取模(移动6次 = 移动1次)
k = k % arr.length;
// 整体右移k位(循环右移)
for (int i = 0; i < k; i++) {
// 保存最后一个元素
int last = arr[arr.length - 1];
// 从后往前,每个元素向后移动1位
for (int j = arr.length - 1; j > 0; j--) {
arr[j] = arr[j - 1];
}
// 把最后一个元素放到第0位
arr[0] = last;
}
关键点:
- 内层循环必须从后往前遍历(
j = length-1→j > 0),否则前面的值会被覆盖k = k % arr.length解决 k 大于数组长度时的重复移动问题(如移动 6 次 = 移动 1 次)
题目:定义数组 {5, 2, 8, 2, 5, 9, 1, 8, 3},去除重复元素,只保留第一次出现的元素,输出去重后的新数组。
核心思路:
判断元素是否重复的方式:检查当前元素 arr[i] 在它之前是否出现过(即 arr[0] ~ arr[i-1] 中是否有相同的值)。
int[] arr = {5, 2, 8, 2, 5, 9, 1, 8, 3};
int count = 0; // 不重复元素的个数
// 第一步:统计不重复元素的个数
for (int i = 0; i < arr.length; i++) {
boolean b = false; // 标记:先认为当前元素没有重复
for (int j = 0; j < i; j++) { // 检查 arr[i] 在它之前是否出现过
if (arr[i] == arr[j]) {
b = true; // 找到重复,标记改为 true
break; // 跳出内层循环
}
}
if (b == false) { // 没有重复,计数
count++;
}
}
// 第二步:创建新数组
int[] newArr = new int[count];
int index = 0; // 新数组的索引
// 第三步:将不重复元素放入新数组
for (int i = 0; i < arr.length; i++) {
boolean b = false;
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
b = true;
break;
}
}
if (b == false) { // 不重复的元素
newArr[index] = arr[i]; // 放入新数组
index++;
}
}
// 输出新数组
for (int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i] + " ");
}
// 输出:5 2 8 9 1 3
核心技巧:
- 使用布尔标记
boolean b来判断元素是否重复- 内层循环
j < i只检查当前元素之前的元素,确保只保留第一次出现的元素- 两步走:先统计个数 → 再填充数据(因为数组长度在创建后不可改变)
题目:定义数组 {11, 22, 33, 44, 55},用 for 循环遍历数组,计算所有元素的总和。
知识点:数组遍历、累加求和
int[] arr = {11, 22, 33, 44, 55};
int sum = 0; // 记录累加和
for (int i = 0; i < arr.length; i++) {
sum += arr[i]; // 通过 arr[i] 获取每个元素,累加到 sum
}
System.out.println(sum); // 输出:165
题目:定义数组 {3, 8, 1, 9, 4, 7, 2},找出数组中的最大值。
知识点:数组遍历、打擂法(假设法)
int[] arr = {3, 8, 1, 9, 4, 7, 2};
int max = arr[0]; // 先假设第一个元素是最大值
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) { // 如果有元素比当前最大值还大
max = arr[i]; // 更新最大值
}
}
System.out.println(max); // 输出:9
打擂法核心思路:先假设第一个元素是最大值,然后遍历数组,遇到更大的就更新。
题目:定义数组 {1, 2, 3, 4, 5},从后往前遍历,将元素倒序输出。
知识点:for 循环逆序遍历
int[] arr = {1, 2, 3, 4, 5};
// 从最大索引开始,递减到 0
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]);
}
// 输出:
// 5
// 4
// 3
// 2
// 1
从
arr.length - 1(最后一个元素的索引)开始,到0(第一个元素的索引)结束,每次递减 1。
题目:定义数组 {12, 7, 9, 24, 15, 8, 31, 46},遍历数组,统计奇数和偶数的个数。
知识点:数组遍历、取模判断、统计计数
int[] arr = {12, 7, 9, 24, 15, 8, 31, 46};
int count = 0; // 奇数个数
for (int i = 0; i < arr.length; i++) {
if (arr[i] % 2 != 0) { // 余2不等于0,说明是奇数
count++;
}
}
System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.length - count));
// 输出:奇数个数:4,偶数个数:4
偶数的判定:
arr[i] % 2 == 0
奇数的判定:arr[i] % 2 != 0
偶数个数 = 数组总长度 - 奇数个数
int[][] arr = new int[3][2];(3 行 2 列)arr.length,内层列数 arr[i].lengthfor(数据类型 变量 : 数组名) { },适合遍历全部元素java.util.Arrays,操作数组的静态方法集合Arrays.sort(arr):对数组进行升序排序Arrays.toString(arr):将数组转换为字符串输出Arrays.copyOf(arr, n):拷贝数组,指定新长度,多余补默认值Arrays.copyOfRange(arr, from, to):拷贝指定范围 [from, to)Arrays.binarySearch(arr, key):二分查找(必须先排序)arr.length - 1 轮[0, arr.length-1-i] 相邻比较boolean 标记,本轮无交换则提前结束i 是当前要确定的位置,范围 [0, arr.length-2][i+1, arr.length-1] 找最小值索引iminIndex != i 时才交换,每轮最多交换 1 次i 从 1 开始,标记当前要处理的未排序元素java.util.Scanner 类读取控制台输入nextInt()、nextDouble()、next()、nextLine()、nextBoolean() 等next() vs nextLine():next() 以空格/换行结束,nextLine() 仅以换行结束nextInt() 后使用 nextLine() 会读取到空字符串nextInt() 后调用一次 nextLine() 吃掉换行符InputMismatchExceptiontmp = a; a = b; b = tmp;a = a + b; b = a - b; a = a - b;k = k % arr.length 解决 k 大于数组长度的问题boolean 变量标记元素是否重复length-1 到 0 递减遍历n % 2 == 0 偶,n % 2 != 0 奇当直接打印数组变量时,输出的是数组在堆内存中的地址值:
int[] a = new int[10];
System.out.println(a); // 输出类似:[I@b4c966a
地址值含义:
[ — 一维数组I — 元素类型为 int@ — 分隔符b4c966a — 堆内存中的十六进制地址二维数组 arr2 = {arr01, arr02, arr03} 在内存中:
┌──────────┐
│ arr2[0] │────→ arr01: [1, 3]
├──────────┤
│ arr2[1] │────→ arr02: [4, 5, 6]
├──────────┤
│ arr2[2] │────→ arr03: [7, 8, 9, 15]
└──────────┘
二维数组的每个元素存储的是一维数组的引用地址,而不是元素本身。
Arrays.binarySearch() 基于二分查找算法(Binary Search):
时间复杂度:O(log n) — 效率远高于线性查找 O(n)
已排序数组:[1, 4, 5, 6, 7, 7, 8, 78, 87, 165, 689]
查找目标:689
第1次:比较中间元素 7 → 689 > 7,在右半部分查找
第2次:比较中间元素 87 → 689 > 87,继续在右半部分查找
第3次:比较中间元素 165 → 689 > 165,继续在右半部分查找
第4次:找到 689,返回索引 10
Arrays.copyOf() 和 Arrays.copyOfRange() 执行的是浅拷贝:
/**
* @author WanJl
* @version 1.0
* @title Demo01
* @description 多维数组
* @create 2026/7/16
*/
public class Demo01 {
public static void main(String[] args) {
/*
一维数组中所有的元素都是数据类型的变量或常量
二维数组中所有的元素都是一维数组
三维数组中所有的元素都是二维数组
......
Java多维数组的本质:其实就是数组的数组,二维数组的元素其实就是一维数组的引用地址值
二维数组的定义的格式:
数组类型[][] 数组名=new 数组类型[二维数组长度][一维数组长度];
*/
//数组类型[][] 数组名=new 数组类型[二维数组长度][一维数组长度];
int[][] arr = new int[3][2]; //[行数][列数]
//遍历二维数组需要两层循环嵌套
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
// 静态初始化二维数组----三行三列
int[][] arr1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < arr1.length; i++) {
for (int j = 0; j < arr1[i].length; j++) {
System.out.print(arr1[i][j] + " ");
}
System.out.println();
}
// 二维数组中的一维数组,不是必须要相同长度的,长度是不一样的。
int[] arr01 = {1, 3};
int[] arr02 = {4, 5, 6};
int[] arr03 = {7, 8, 9, 15};
int[][] arr2 = {arr01, arr02, arr03}; //二维数组存放的其实就是一维数组的引用地址
for (int i = 0; i < arr2.length; i++) {
for (int j = 0; j < arr2[i].length; j++) {
System.out.print(arr2[i][j] + " ");
}
System.out.println();
}
}
}
/**
* @author WanJl
* @version 1.0
* @title Demo02
* @description foreach迭代-增强for循环
* @create 2026/7/16
*/
public class Demo02 {
public static void main(String[] args) {
//我们之前使用的for循环,是for(int i=0;i<10;i++)
//可以使用增强for循环--foreach,作用和for循环一样
//主要是用来进行数组遍历
/*
格式:
for(数据类型 变量 : 数组名){
代码块
}
*/
int[] arr = {1, 2, 3, 4, 5, 6, 8};
for (int a : arr) {
System.out.print(a + " ");
}
System.out.println();
/*
foreach 遍历的数组,无法获取索引值,所以只适合遍历全部元素。想要获取某个索引位置的元素,需要for循环
*/
int[] arr01 = {1, 3};
int[] arr02 = {4, 5, 6};
int[] arr03 = {7, 8, 9, 15};
int[][] arr2 = {arr01, arr02, arr03};
for (int[] a : arr2) { //外层foreach
for (int a1 : a) { //内层foreach
System.out.print(a1 + " ");
}
System.out.println();
}
}
}
import java.util.Arrays;
/**
* @author WanJl
* @version 1.0
* @title Demo03
* @description 数组工具类 : java.util.Arrays;
* @create 2026/7/16
*/
public class Demo03 {
public static void main(String[] args) {
/*
数组工具类 : java.util.Arrays;
java中的命名特点,如果一个类的类名 是名词+s,那么大多数情况下,这个类就是一个工具类
*/
int[] arr = {7, 6, 78, 1, 689, 87, 165, 7, 4, 5, 8};
//使用工具类对数组进行排序
Arrays.sort(arr);
//输出arr数组--Arrays.toString方法 是直接把arr数组转换成字符串
System.out.println(Arrays.toString(arr));
// 拷贝(扩展长度,多余补 0)-- 把一个数组的元素复制到一个新的数组并指定新数组的长度,多余补 0
int[] copy = Arrays.copyOf(arr, 10);
System.out.println(Arrays.toString(copy));
// 拷贝指定范围 [1,4)
int[] range = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(range));
// 二分查找(需先排序),返回索引值
int index = Arrays.binarySearch(arr, 689);
System.out.println(index);
}
}
import java.util.Arrays;
/**
* @author WanJl
* @version 1.0
* @title Demo04
* @description 数组排序-冒泡排序
* @create 2026/7/16
*/
public class Demo04 {
/*
我们现在已经学了最基础的 数据结构与算法
数据结构:数组结构--属于线性结构的一种
算法:比如排序算法(冒泡、选择、插入...)
冒泡排序,是所有排序【算法】最直观的,最基础的一种。
冒泡排序就像水里的泡泡一样,大的元素会逐渐的冒到数组的末端。
冒泡排序的核心思想:
重复遍历数组,依次比较相邻的两个元素,如果顺序错误就交换。
每轮遍历把当前未排序的部分的最大值"冒泡"到正确位置。
*/
public static void main(String[] args) {
/*
冒泡排序的基础版
每一轮把未排序部分的最大值"冒泡"到数组末尾
*/
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
//冒泡排序需要两层循环
for (int i = 0; i < arr.length - 1; i++) { //外层循环,控制的一共冒泡多少轮
//内层循环 在未排序的区间[0,arr.length-1-i] 中相邻比较
//因为 i 后面的元素已经有序了,不需要再比较了
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
//交换位置(加减法交换)
arr[j] = arr[j] + arr[j + 1];
arr[j + 1] = arr[j] - arr[j + 1];
arr[j] = arr[j] - arr[j + 1];
}
}
}
System.out.println(Arrays.toString(arr));
// 优化版
int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
for (int i = 0; i < arr2.length - 1; i++) {
boolean b = false; //定义boolean类型的变量,用来标记本轮是否有交换
for (int j = 0; j < arr2.length - 1 - i; j++) {
if (arr2[j] > arr2[j + 1]) {
//交换位置
arr2[j] = arr2[j] + arr2[j + 1];
arr2[j + 1] = arr2[j] - arr2[j + 1];
arr2[j] = arr2[j] - arr2[j + 1];
b = true; //本轮有交换
}
}
//判断,如果本轮没有发生任何交换,说明数组已经有序,提前结束
if (b == false) {
break;
}
System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2));
}
}
}
import java.util.Arrays;
/**
* @author WanJl
* @version 1.0
* @title Demo05
* @description 数组排序-选择排序
* @create 2026/7/16
*/
public class Demo05 {
/*
选择排序:
选择排序的思路要比冒泡排序更聪明一点,冒泡排序是不断的交换相邻的元素,
而选择排序,是每轮只做一次交换。
选择的思路是每一轮从剩余未排序的元素中找出最小的元素,
直接把它放到未排序部分的第1位。
核心思路:每一轮从未排序区间选出最小值,与未排序区间的第1个元素进行交换。
优点:交换次数比冒泡更少
缺点:不稳定,可能会破坏相对顺序
*/
public static void main(String[] args) {
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
//外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置
for (int i = 0; i < arr.length - 1; i++) {
//假设当前未排序区间的第1个元素就是最小的
int minIndex = i;
//内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; //更新最小元素的索引值
}
}
//如果最小元素索引值和当前 i 不相等,就交换位置
if (minIndex != i) {
arr[minIndex] = arr[minIndex] + arr[i];
arr[i] = arr[minIndex] - arr[i];
arr[minIndex] = arr[minIndex] - arr[i];
}
System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr));
}
}
}
import java.util.Arrays;
/**
* @author WanJl
* @version 1.0
* @title Demo06
* @description 数组排序-插入排序
* @create 2026/7/16
*/
public class Demo06 {
/*
插入排序
是三种基础排序中最实用的一种,也是使用最广泛的一种O(n²)排序算法,
他的思想非常符合人整理东西的习惯。
把数组分为"已排序"和"未排序"两部分,每次从未排序部分取一个元素,
插入到已排序部分的正确位置。
就类似于打扑克时,抓牌然后插入到手中已排序好的牌中正确的位置。
核心思想:
把数组视为"已排序"(前部分)和未排序(后部分)。
每次从未排序部分中拿取第1个元素,
从后往前在已排序部分中找到合适的位置(比较)并插入
*/
public static void main(String[] args) {
int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74};
//从第2个元素开始,假设 arr[0] 已经是已排序部分
for (int i = 1; i < arr.length; i++) {
//获取当前要插入的元素,未排序的第1个
int current = arr[i];
//已排序部分的最后一个索引值
int j = i - 1;
//从后往前扫描已经排序部分,寻找插入位置
//如果已排序的元素大于current,就向后移动一位
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
//此时 j+1 就是current应该插入到的位置
arr[j + 1] = current;
System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr));
}
}
}
import java.util.Scanner;
/**
* @author WanJl
* @version 1.0
* @title Demo07
* @description Scanner键盘输入
* @create 2026/7/16
*/
public class Demo07 {
/*
Java中实现键盘输入的标准方式就是 Scanner 类
它是 java.util 包下的一个工具类,可以读取我们在控制台中输入的数据
操作步骤:
1、导包 import java.util.Scanner
2、创建 Scanner 对象
格式:Scanner sc = new Scanner(System.in);
3、调用方法读取数据
|- nextByte() / nextShort() / nextInt() / nextLong()
|- nextFloat() / nextDouble()
|- next() 读取单个字符串(空格/换行符结束)
|- nextLine() 读取整行字符串(换行符结束)
|- nextBoolean() 读取布尔值
|- next().charAt(0) 读取字符
4、关闭 Scanner sc.close();
*/
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
/*
Scanner有陷阱:
nextInt()后面使用nextLine()的问题
原因:nextInt()读取完数字后,换行符还停留在缓冲区中,
紧接着的nextLine()遇到了换行符就直接结束了
*/
// 解决方案:在nextInt()之后调用一次nextLine() 吃掉换行符
int a = sc.nextInt();
sc.nextLine(); // 吃掉换行符
String s = sc.nextLine();
System.out.println(a);
System.out.println(s);
sc.close();
}
}
import java.util.Scanner;
/**
* @author WanJl
* @version 1.0
* @title Demo08
* @description Scanner练习
* @create 2026/7/16
*/
public class Demo08 {
public static void main(String[] args) {
// 1、创建Scanner对象
Scanner sc = new Scanner(System.in);
// 2、读取不同类型的数据
System.out.println("请输入您的姓名:");
String name = sc.next();
System.out.println("请输入您的年龄:");
int age = sc.nextInt();
System.out.println("请输入您的身高(米):");
double height = sc.nextDouble();
System.out.println("您是否是在校学生(true/false):");
boolean isStudent = sc.nextBoolean();
// 3、输出用户信息
System.out.println("\n========用户信息========");
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("身高:" + height);
System.out.println("在校学生:" + isStudent);
// 4、关闭Scanner 释放资源
sc.close();
}
}
/**
* @author WanJl
* @version 1.0
* @title Exercises01
* @description 课堂练习01:首尾对称交换数组元素
* @create 2026/7/16
*/
public class Exercises01 {
public static void main(String[] args) {
int[] arr = {10, 20, 30, 40, 50};
System.out.print("交换前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
System.out.print("交换后:");
//首尾交换 第1位和第5位,第2位和第4位
arr[0] = arr[0] + arr[4];
arr[4] = arr[0] - arr[4];
arr[0] = arr[0] - arr[4];
arr[1] = arr[1] + arr[3];
arr[3] = arr[1] - arr[3];
arr[1] = arr[1] - arr[3];
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
/**
* @author WanJl
* @version 1.0
* @title Exercises02
* @description 课堂练习02:数组循环右移
* @create 2026/7/16
*/
public class Exercises02 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int k = 2; //右移位数
//如果k大于数组长度,要取模
k = k % arr.length;
System.out.print("移动前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
//整体右移k位(循环右移)
for (int i = 0; i < k; i++) {
//保存最后一个元素
int last = arr[arr.length - 1];
//从后往前,每个元素向后移动1位
for (int j = arr.length - 1; j > 0; j--) {
arr[j] = arr[j - 1];
}
//把最后1位给第0位
arr[0] = last;
}
System.out.print("移动后:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
/**
* @author WanJl
* @version 1.0
* @title Exercises03
* @description 课堂练习03:数组去重
* @create 2026/7/16
*/
public class Exercises03 {
public static void main(String[] args) {
/*
先统计不重复元素的个数
*/
int[] arr = {5, 2, 8, 2, 5, 9, 1, 8, 3};
int count = 0; //个数
for (int i = 0; i < arr.length; i++) {
boolean b = false; //设置false,表示先认为当前的数组元素是没有重复的
for (int j = 0; j < i; j++) { //找 arr[i]元素,在arr数组中,有没有重复的元素。
if (arr[i] == arr[j]) { //只要找到一个重复的
b = true; //就把b设置为 true
break; //跳出内层循环
}
}
if (b == false) { //那就说明这个元素是没有重复的,就计数
count++;
}
}
//创建的新的数组长度就是 count
int[] newArr = new int[count]; //动态初始化
int index = 0; //作为新数组的索引值
for (int i = 0; i < arr.length; i++) {
boolean b = false; //设置false,表示先认为当前的数组元素是没有重复的
//检查arr[i]在它之前是否有出现过
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
b = true;
break;
}
}
//判断如果我们设置的标记还是false,说明没有重复
if (b == false) {
newArr[index] = arr[i];
index++;
}
}
for (int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i] + " ");
}
}
}
/**
* @author WanJl
* @version 1.0
* @title HomeWork07
* @description 7月15日作业:数组元素求和
* @create 2026/7/16
*/
public class HomeWork07 {
public static void main(String[] args) {
int[] arr = {11, 22, 33, 44, 55};
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println(sum);
}
}
/**
* @author WanJl
* @version 1.0
* @title HomeWork08
* @description 7月15日作业:找数组最大值
* @create 2026/7/16
*/
public class HomeWork08 {
public static void main(String[] args) {
int[] arr = {3, 8, 1, 9, 4, 7, 2};
int max = arr[0]; //假设第一个元素是最大值
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
System.out.println(max);
}
}
/**
* @author WanJl
* @version 1.0
* @title HomeWork09
* @description 7月15日作业:数组倒序输出
* @create 2026/7/16
*/
public class HomeWork09 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]);
}
}
}
/**
* @author WanJl
* @version 1.0
* @title HomeWork10
* @description 7月15日作业:统计奇偶数个数
* @create 2026/7/16
*/
public class HomeWork10 {
public static void main(String[] args) {
int[] arr = {12, 7, 9, 24, 15, 8, 31, 46};
int count = 0; //奇数个数
for (int i = 0; i < arr.length; i++) {
if (arr[i] % 2 != 0) {
count++;
}
}
System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.length - count));
}
}
| 概念 | 说明 |
|---|---|
| 多维数组 | Java 中多维数组是数组的数组,二维数组的元素是一维数组的引用地址 |
| 二维数组遍历 | 外层循环遍历行(arr.length),内层循环遍历列(arr[i].length) |
| 不规则数组 | 二维数组中的每个一维数组长度可以不同 |
| 增强 for 循环 | for(类型 变量 : 数组) 语法,简化数组/集合遍历,无法获取索引 |
| Arrays 工具类 | java.util.Arrays 包含 sort、toString、copyOf、binarySearch 等静态方法 |
| Arrays.sort() | 对数组进行快速排序(Dual-Pivot Quicksort) |
| Arrays.toString() | 将数组转换为可读的字符串格式 [elem1, elem2, ...] |
| Arrays.copyOf() | 数组拷贝,可指定新长度,扩展部分补默认值,截断部分丢弃 |
| Arrays.copyOfRange() | 拷贝数组指定范围 [from, to),包含 from 不包含 to |
| Arrays.binarySearch() | 二分查找,数组必须先排序,返回索引值(未找到返回负数) |
| 冒泡排序 | 重复遍历数组,相邻元素两两比较,大的往后冒,每轮把最大值冒泡到末尾 |
| 冒泡排序-外层循环 | 控制冒泡轮数,n 个元素最多需要 n-1 轮 |
| 冒泡排序-内层循环 | 在未排序区间 [0, arr.length-1-i] 相邻比较,比较范围随轮数递减 |
| 冒泡排序-优化 | 引入 boolean 标记判断本轮是否有交换,无交换则提前结束排序 |
| 冒泡排序-时间复杂度 | 平均 O(n²),最好 O(n)(优化版已有序),最坏 O(n²) |
| 冒泡排序-空间复杂度 | O(1),原地排序,不需要额外空间 |
| 选择排序 | 每轮从未排序区间选出最小值,与未排序区间的第 1 个元素交换,每轮最多 1 次交换 |
| 选择排序-外层循环 | i 是当前要确定的位置,范围 [0, arr.length-2] |
| 选择排序-内层循环 | 在未排序区间 [i+1, arr.length-1] 找最小值索引 |
| 选择排序-minIndex | 记录最小元素的索引值,初始为当前位置 i,遇更小值则更新 |
| 选择排序-交换条件 | minIndex != i 时才执行交换,每轮最多交换 1 次 |
| 选择排序-时间复杂度 | 平均 O(n²),最好 O(n²),最坏 O(n²) — 始终要遍历全部 |
| 选择排序-空间复杂度 | O(1),原地排序 |
| 选择排序-稳定性 | ❌ 不稳定(交换可能改变相等元素的相对顺序) |
| 冒泡 vs 选择 | 冒泡多次交换,选择最多 1 次;冒泡稳定,选择不稳定 |
| 插入排序 | 将未排序的第 1 个元素插入到已排序部分的正确位置,类似打扑克理牌 |
| 插入排序-分治思想 | 把数组分为"已排序"和"未排序"两部分 |
| 插入排序-外层循环 | i 从 1 开始,标记当前要处理的未排序元素 |
| 插入排序-current | 暂存当前要插入的元素值,防止被覆盖 |
| 插入排序-内层 while | 从后往前扫描,比 current 大的元素逐一后移,找到插入位置 |
| 插入排序-时间复杂度 | 平均 O(n²),最好 O(n)(数据基本有序时),最坏 O(n²) |
| 插入排序-空间复杂度 | O(1),原地排序 |
| 插入排序-稳定性 | ✅ 稳定 |
| 插入排序-实用性 | 三种基础 O(n²) 排序中最实用,小规模数据首选 |
| 三种排序对比 | 实用性:插入 > 选择 > 冒泡;稳定性:冒泡 ✅、插入 ✅、选择 ❌ |
| Scanner 键盘输入 | java.util.Scanner 类,用于读取控制台输入 |
| Scanner 导包 | import java.util.Scanner; |
| Scanner 创建对象 | Scanner sc = new Scanner(System.in); |
| Scanner 关闭 | sc.close(); 释放资源 |
| Scanner-nextInt() | 读取 int 类型整数 |
| Scanner-nextDouble() | 读取 double 类型小数 |
| Scanner-next() | 读取单个字符串,以空格/换行结束 |
| Scanner-nextLine() | 读取整行字符串,以换行结束 |
| Scanner-nextBoolean() | 读取 boolean 类型值(true/false) |
| Scanner 陷阱 | nextInt() 后用 nextLine() 会读取到空字符串(缓冲区残留换行符) |
| Scanner 陷阱解决 | 在 nextInt() 后调用一次 nextLine() 吃掉换行符 |
| Scanner 类型不匹配 | 输入类型与接收类型不符时抛出 InputMismatchException |
| 变量交换-临时变量 | tmp = a; a = b; b = tmp; 最直观的交换方式 |
| 变量交换-加减法 | a = a + b; b = a - b; a = a - b; 不借助第三个变量 |
| 数组循环右移 | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 |
| 数组去重 | 两层循环 + 布尔标记,检查当前元素在它之前是否出现过 |
| 打擂法 | 先假设第一个是最大值/最小值,遍历数组遇到更值就更新 |
| 数组求和 | sum += arr[i] 遍历累加 |
| 倒序遍历 | for (int i = length-1; i >= 0; i--) |
| 奇偶判断 | n % 2 == 0 为偶数,n % 2 != 0 为奇数 |
| 取模优化 | 对循环移动的位数取模,避免无效重复移动 |