数组、二维数组与基础算法思维 | JavaSE

数组、二维数组与基础算法思维

一、学习目标

完成本章后,你应该能够:

  • 能够解释数组(Array)解决什么问题以及数组的基本特点。
  • 能够使用静态初始化和动态初始化创建数组。
  • 能够使用索引访问和修改数组元素。
  • 能够正确使用 length 并识别数组越界问题。
  • 能够解释动态初始化后各种类型数组元素的默认值。
  • 能够使用循环完成数组遍历。
  • 能够独立实现数组求和、平均值、最大值、最小值、查找、交换、逆序和统计。
  • 能够理解数组变量保存的是数组对象的引用,并分析两个变量指向同一数组时的行为。
  • 能够创建、访问和遍历二维数组。
  • 能够理解 Java 二维数组本质上是“数组的数组”,因此可以形成不规则二维数组。
  • 能够开始建立遍历、枚举、状态记录、最值更新、双指针和随机交换等基础算法思维。

二、核心知识

2.1 为什么需要数组

变量一次通常保存一个值:

double score1 = 90.5;
double score2 = 88.0;
double score3 = 96.5;
double score4 = 77.0;

如果一个班有:

8 名学生
50 名学生
500 名学生

显然不应该创建:

score1
score2
score3
...
score500

数组就是解决这一问题的基本数据结构。

数组是一个用于存储一批相同类型数据的数据容器。

例如:

double[] scores = {
        90.5,
        88.0,
        96.5,
        77.0
};

现在所有成绩被组织在一个数组中。


2.2 数组的核心特点

基础阶段需要首先建立:

一个数组
↓
存储一批元素
↓
元素具有统一类型
↓
每个元素具有索引
↓
数组创建后长度固定

例如:

int[] numbers = {10, 20, 30};

可以理解:

索引      0    1    2
        ┌────┬────┬────┐
元素    │ 10 │ 20 │ 30 │
        └────┴────┴────┘

2.3 数组是一种引用类型

Java 数据类型前面已经区分:

基本数据类型
引用数据类型

数组属于:

引用类型。

例如:

int[] arr = {10, 20, 30};

变量:

arr

不是三个整数本身。

它保存的是:

对这个数组对象的引用。

可以先建立简单模型:

arr
 │
 │ 引用
 ↓
┌────┬────┬────┐
│ 10 │ 20 │ 30 │
└────┴────┴────┘

这个思想将在下一组面向对象中变得非常重要。


2.4 数组静态初始化

如果创建数组时已经知道元素:

int[] numbers = {10, 20, 30};

这是最常见的静态初始化简化格式。

完整格式:

int[] numbers = new int[]{10, 20, 30};

通用格式:

数据类型[] 数组名 = {元素1, 元素2, ...};

或者:

数据类型[] 数组名 =
        new 数据类型[]{元素1, 元素2, ...};

2.5 int[] arrint arr[]

Java 都允许:

int[] arr;

和:

int arr[];

但是现代 Java 代码一般更推荐:

int[] arr;

因为:

int[]

更加清晰地表达:

这是一个 int 数组类型。


2.6 动态初始化

如果一开始只知道:

需要存多少个数据。

但还不知道具体内容,可以:

int[] arr = new int[5];

这里:

5

表示数组长度。

数组创建后:

索引:

0 1 2 3 4

一共:

5 个元素

2.7 数组长度一旦创建就固定

例如:

int[] arr = new int[5];

这个数组对象的长度就是:

5

不能把同一个数组对象“扩容”成:

10

如果需要另一个长度:

arr = new int[10];

实际上是:

创建了一个新的长度为 10 的数组,并让 arr 改为引用这个新数组。

不是原来的数组自己变长。


2.8 数组索引

访问数组元素:

数组名[索引]

例如:

int[] arr = {10, 20, 30};

System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);

得到:

10
20
30

Java 数组:

从索引 0 开始。

长度为 n 的数组合法索引范围:

0 ~ n - 1

2.9 为什么最后一个索引是 length - 1

长度:

3

数组:

索引:

0
1
2

所以:

最后索引 = 3 - 1 = 2

一般化:

长度 n
↓
合法索引:
0 ~ n-1

因此后面遍历通常写:

i < arr.length

而不是:

i <= arr.length

2.10 获取数组长度

数组提供:

arr.length

例如:

int[] arr = {10, 20, 30};

System.out.println(arr.length);

得到:

3

注意:

arr.length

不是:

arr.length()

数组使用的是:

length

字段。


2.11 修改数组元素

数组元素可以重新赋值:

int[] arr = {10, 20, 30};

arr[1] = 100;

System.out.println(arr[1]);

结果:

100

数组变成:

10 100 30

所以数组是一个:

可以修改元素内容的数据容器。


2.12 数组越界

假设:

int[] arr = {10, 20, 30};

合法:

arr[0]
arr[1]
arr[2]

非法:

arr[3]

因为:

3 >= arr.length

Java 会在运行时检查数组访问。

非法访问会产生:

ArrayIndexOutOfBoundsException

因此:

for (int i = 0; i <= arr.length; i++)

是一个典型错误。

正确通常是:

for (int i = 0; i < arr.length; i++)

2.13 动态初始化数组的默认值

例如:

int[] arr = new int[3];

虽然没有手动赋值,但数组元素创建时具有默认值。

常见规则:

| 元素类型 | 默认值 | | --------------------------- | ---------- | | byte / short / int / long | 0 | | float / double | 0.0 | | char | '\u0000' | | boolean | false | | 引用类型 | null |

例如:

int[] numbers = new int[3];

System.out.println(numbers[0]);

得到:

0

2.14 数组默认值与局部变量不是一回事

数组组件:

int[] arr = new int[3];

自动得到默认值。

但是普通局部变量:

int number;

不能在未初始化时直接:

System.out.println(number);

所以不要形成:

Java 中所有变量都会自动初始化。

更准确地说:

数组元素有默认初始化

而普通未初始化局部变量需要满足 Java 的明确赋值规则后才能读取。


2.15 什么是数组遍历

遍历(Traversal):

将数组中的元素按照某种顺序一个一个访问。

例如:

int[] arr = {20, 30, 40, 50};

for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

可以观察:

i = 0 → arr[0]
i = 1 → arr[1]
i = 2 → arr[2]
i = 3 → arr[3]

循环变量:

i

天然可以作为数组索引。

这就是上一章循环与数组产生连接的地方。


2.16 数组求和

例如:

int[] arr = {10, 20, 30, 40};

int sum = 0;

for (int i = 0; i < arr.length; i++) {
    sum += arr[i];
}

System.out.println(sum);

算法模型:

sum = 0
↓
遍历每一个元素
↓
把元素累计到 sum

2.17 数组平均值

先求总和:

double[] scores = {90, 80, 70, 100};

double sum = 0;

for (int i = 0; i < scores.length; i++) {
    sum += scores[i];
}

再:

double average = sum / scores.length;

这里体现:

遍历
+
累加
+
统一处理结果

2.18 数组最大值

假设:

int[] arr = {100, 50, 90, 60, 80};

不应该写:

int max = 0;

因为如果数组全部是负数:

-10 -20 -30

那么 0 根本不是数组中的数据。

更稳妥的初始化:

int max = arr[0];

然后从后续元素开始比较:

for (int i = 1; i < arr.length; i++) {

    if (arr[i] > max) {
        max = arr[i];
    }
}

核心算法:

假设第一个是最大值
↓
逐个拿后面的数据比较
↓
发现更大的
↓
更新 max

2.19 最值算法的本质:维护当前最优解

循环过程中:

max

始终表示:

截止目前已经看过的数据中的最大值。

例如:

数据:

50 90 60 100

过程:

max = 50

看到 90
→ max = 90

看到 60
→ max 仍然 90

看到 100
→ max = 100

这是非常经典的:

状态维护思想。


2.20 数组查找

需求:

找出指定元素第一次出现的索引。

例如:

int[] arr = {10, 20, 30, 20};

int target = 20;
int index = -1;

for (int i = 0; i < arr.length; i++) {

    if (arr[i] == target) {
        index = i;
        break;
    }
}

System.out.println(index);

这里:

-1

通常表示:

尚未找到 / 不存在。

发现目标:

记录索引
+
break

这就是最基础的:

线性查找(Linear Search)思想。


2.21 数组元素交换

交换:

arr[i]
arr[j]

不能直接:

arr[i] = arr[j];
arr[j] = arr[i];

因为第一句执行后,原来的 arr[i] 已经丢失。

需要临时变量:

int temp = arr[i];

arr[i] = arr[j];

arr[j] = temp;

模型:

A → temp
B → A
temp → B

这是算法中非常基础的操作。


2.22 数组逆序

例如:

1 2 3 4 5

变成:

5 4 3 2 1

可以使用:

左指针
右指针

向中间移动:

1 2 3 4 5
↑       ↑
i       j

交换:

arr[i]
arr[j]

然后:

i++
j--

直到:

i >= j

这就是双指针思想的第一次接触。


2.23 两个数组变量指向同一个数组

观察:

int[] a = {10, 20, 30};

int[] b = a;

b[0] = 100;

System.out.println(a[0]);

这里:

int[] b = a;

并没有复制一个完整新数组。

而是把:

a 中的数组引用

赋给:

b

所以:

a ──┐
    ├──→ [10, 20, 30]
b ──┘

执行:

b[0] = 100;

修改的其实是:

两个变量共同指向的那个数组对象。

所以通过:

a[0]

看到的也会是:

100

2.24 数组变量与数组对象要分开理解

例如:

int[] arr = new int[3];

可以区分:

arr
↓
数组变量

new int[3]
↓
数组对象

变量:

arr

保存引用。

真正的元素:

0
0
0

存在数组对象中。

这会直接为后面的:

类
对象
引用
null

做准备。


2.25 什么是二维数组

一维数组:

int[] row = {10, 20, 30};

二维数组:

int[][] matrix = {
        {10, 20, 30},
        {40, 50, 60},
        {70, 80, 90}
};

可以先看成表格:

10 20 30
40 50 60
70 80 90

但 Java 更本质的理解是:

二维数组是“数组中的元素仍然是数组”。

即:

int[][]
↓
数组
↓
每个元素的类型是 int[]

2.26 二维数组静态初始化

例如:

int[][] arr = {
        {10, 20, 30},
        {40, 50},
        {60, 70, 80, 90}
};

注意三行长度:

3
2
4

完全不同。

这说明:

Java 二维数组并不要求每一行长度相同。

所以所谓“二维数组”不一定是数学意义上的规则矩阵。


2.27 二维数组的引用结构

上面的二维数组可以理解为:

arr
 │
 ↓
┌──────┬──────┬──────┐
│ ref  │ ref  │ ref  │
└─┬────┴─┬────┴─┬────┘
  │      │      │
  ↓      ↓      ↓

[10,20,30]

[40,50]

[60,70,80,90]

因此:

arr[0]

得到的是:

第一行那个 int[] 数组。

而:

arr[0][1]

得到:

第一行数组中的索引 1 元素。

即:

20

2.28 二维数组动态初始化

例如:

int[][] arr = new int[3][5];

可以理解为:

创建 3 行

每一行创建长度为 5 的 int[]

形成:

3 × 5

结构。


2.29 arr.lengtharr[i].length

二维数组:

int[][] arr = {
        {1, 2, 3},
        {4, 5},
        {6, 7, 8, 9}
};
arr.length

表示:

外层数组元素个数

也可以理解:

行数

这里是:

3

而:

arr[0].length

是:

3
arr[1].length

是:

2

所以遍历二维数组时不能机械认为每一行长度完全相同。


2.30 二维数组遍历

标准写法:

for (int i = 0; i < arr.length; i++) {

    for (int j = 0; j < arr[i].length; j++) {

        System.out.print(arr[i][j] + "\t");
    }

    System.out.println();
}

外层:

遍历每一行数组

内层:

遍历当前这一行的全部元素

所以:

arr[i].length

而不是统一写死:

arr[0].length

更加通用。


三、使用方法

3.1 学生成绩录入

import java.util.Scanner;

public class ScoreDemo {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        double[] scores = new double[8];

        for (int i = 0; i < scores.length; i++) {

            System.out.print(
                    "请输入第 " + (i + 1) + " 名学生成绩:"
            );

            scores[i] = scanner.nextDouble();
        }
    }
}

数组解决:

存储 8 个成绩

循环解决:

重复录入 8 次

3.2 计算平均分、最高分和最低分

double sum = 0;

double max = scores[0];
double min = scores[0];

for (int i = 0; i < scores.length; i++) {

    double score = scores[i];

    sum += score;

    if (score > max) {
        max = score;
    }

    if (score < min) {
        min = score;
    }
}

double average = sum / scores.length;

一次遍历同时完成:

求和
求最大
求最小

3.3 查找元素

public static int findIndex(
        int[] arr,
        int target
) {

    for (int i = 0; i < arr.length; i++) {

        if (arr[i] == target) {
            return i;
        }
    }

    return -1;
}

这已经把:

数组
循环
if
方法
return

真正组合了起来。


3.4 数组原地逆序

public static void reverse(int[] arr) {

    int left = 0;
    int right = arr.length - 1;

    while (left < right) {

        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;

        left++;
        right--;
    }
}

这里没有创建:

第二个同长度数组

而是在原数组上交换。

这种方式叫:

原地(In-place)处理。


3.5 随机交换实现基础洗牌

假设:

int[] arr = {1, 2, 3, 4, 5};

可以选择随机位置并交换。

例如基础练习:

import java.util.Random;

Random random = new Random();

for (int i = 0; i < arr.length; i++) {

    int randomIndex =
            random.nextInt(arr.length);

    int temp = arr[i];
    arr[i] = arr[randomIndex];
    arr[randomIndex] = temp;
}

这可以让元素顺序发生随机变化。

作为基础数组练习足够。

如果未来需要研究严格的均匀洗牌算法,应进一步学习标准的 Fisher–Yates Shuffle;当前章节先掌握随机交换与数组操作。


3.6 斗地主做牌思路

源课程中的简化斗地主案例要求:

制作 54 张牌
+
洗牌

可以设计:

String[] cards = new String[54];

然后:

循环生成牌面
↓
依次放入数组
↓
Random 随机交换
↓
完成洗牌

这个案例的重要价值不是斗地主本身,而是综合:

数组存储
循环生成
索引访问
元素交换
随机数

3.7 二维数组统计班级成绩

例如:

double[][] scores = {
        {90, 80, 70},
        {88, 92},
        {60, 70, 80, 90}
};

计算每个班级平均分:

for (int i = 0; i < scores.length; i++) {

    double sum = 0;

    for (int j = 0; j < scores[i].length; j++) {
        sum += scores[i][j];
    }

    double average =
            sum / scores[i].length;

    System.out.println(
            "第 " + (i + 1)
                    + " 个班平均分:"
                    + average
    );
}

二维数组可以自然表达:

多个班级
↓
每个班级多个学生

四、原理与进阶

4.1 Java 数组是对象

从 Java 语言模型看:

数组是对象,并且数组对象在运行时创建。

因此:

int[] arr = new int[5];

不是简单“声明 5 个变量”。

而是:

创建数组对象
↓
产生数组引用
↓
arr 保存这个引用

这也是:

int[] b = arr;

不会自动深复制数组的原因。


4.2 数组长度为什么不能修改

数组对象创建时:

new int[5]

长度已经确定。

它的:

length

表示组件数量。

如果数据数量以后需要:

不断增长
不断缩小

固定长度数组就不够方便。

后面学习:

ArrayList
集合框架

正是为动态数据管理提供更方便的抽象。


4.3 二维数组不是连续数学矩阵模型

Java:

int[][]

本质是:

数组的元素类型仍然是 int[]

因此允许:

int[][] arr = {
        {1},
        {2, 3},
        {4, 5, 6}
};

甚至可以先创建外层:

int[][] arr = new int[3][];

再分别创建:

arr[0] = new int[2];
arr[1] = new int[5];
arr[2] = new int[1];

这再次说明:

Java 多维数组本质上是嵌套数组,而不是强制规则矩阵。


4.4 数组算法的通用模式

很多数组题看起来不同,其实可以归纳为几个基本模板。

模式一:遍历

一个一个访问

模式二:累加

sum += current

模式三:计数

满足条件
→ count++

模式四:最值维护

发现更优
→ 更新 best

模式五:查找

满足目标
→ 记录位置
→ 必要时提前结束

模式六:交换

temp

模式七:双指针

左端 + 右端
→ 向中间移动

模式八:二维枚举

外层行
内层列

真正掌握这些模式后,大量基础数组题都会变成:

已知模板的组合。


4.5 从“写代码”升级到“分析算法”

例如:

找数组最大值。

不要第一反应直接敲代码。

应该先说:

输入:
一个数组

目标:
找到最大元素

状态:
当前最大值 max

初始状态:
max = 第一个元素

处理:
遍历剩余元素

更新规则:
current > max
→ max = current

输出:
max

这已经是在编写:

算法设计。

Java 代码只是把这个算法翻译成具体语法。


4.6 空数组是一个重要边界

Java 可以创建:

int[] arr = new int[0];

它的:

length = 0

这是合法数组。

但是:

arr[0]

不合法。

同样:

int max = arr[0];

这种最大值算法如果允许传入空数组,就会产生问题。

这说明算法设计必须考虑:

正常数据
+
边界数据
+
非法输入

这种意识会贯穿整个软件开发过程。


五、实践应用

数组是计算机科学最基础的数据结构之一。

典型应用:

成绩列表
传感器数据
游戏地图
图片像素
矩阵
查找算法
排序算法
缓冲区
固定大小数据集合

二维数组常见:

棋盘
座位表
地图
成绩表
矩阵
游戏关卡
图像像素网格

同时,数组也是以后学习:

ArrayList
HashMap
栈
队列
树
图
排序
搜索
动态规划

时非常重要的基础。


六、常见问题

6.1 数组索引为什么从 0 开始?

Java 数组规定:

长度为 n
↓
合法索引 0 ~ n-1

基础阶段首先按语言规则正确使用。

从数据结构和地址偏移角度理解 0-based indexing 可以在后续学习计算机组成和数据结构时进一步深入。


6.2 arr.length 为什么没有括号?

因为数组的:

length

是数组提供的长度字段,而不是一个普通方法调用。

所以:

arr.length

而不是:

arr.length()

6.3 为什么 i <= arr.length 会出错?

数组最后一个合法索引:

arr.length - 1

当:

i == arr.length

时已经越界。

因此通常写:

i < arr.length

6.4 数组长度可以是 0 吗?

可以。

new int[0]

是合法空数组。

但其中没有任何可访问元素。


6.5 动态数组为什么里面已经有数据?

例如:

new int[5]

数组组件创建时会按照类型获得默认值。

因此不是“什么都不存在”,而是:

0 0 0 0 0

6.6 数组变量本身可以是 null 吗?

可以:

int[] arr = null;

这表示:

arr 当前没有引用任何数组对象

如果此时:

arr.length

就会出现空引用相关问题。

null 和对象引用将在面向对象阶段继续深入。


6.7 int[] b = a 是复制数组吗?

不是。

它复制的是:

数组引用的值。

因此两个变量可以引用同一个数组对象。


6.8 二维数组每一行必须长度相同吗?

不必须。

例如:

int[][] arr = {
        {1, 2},
        {3},
        {4, 5, 6}
};

完全合法。


6.9 为什么二维数组遍历使用 arr[i].length

因为:

arr[i]

本身就是当前行数组。

每一行长度可能不同。

所以应该获取:

当前行自己的 length

6.10 最大值为什么不能总初始化成 0?

因为数组可能是:

-100 -50 -20

如果:

int max = 0;

最终可能错误得到:

0

0 根本不在数组中。

更稳妥的方式通常是:

int max = arr[0];

前提是数组非空。


6.11 数组和 ArrayList 是不是一样?

不是。

数组:

固定长度
Java 语言内建结构

ArrayList

集合框架中的类
更方便进行动态数量管理

后面会专门学习。


七、练习与验收

7.1 知识问答

  1. 什么是数组?
  2. 为什么大量同类型数据适合使用数组?
  3. 数组属于基本类型还是引用类型?
  4. 数组静态初始化有哪些常见格式?
  5. 什么是动态初始化?
  6. 数组创建后长度能否修改?
  7. 数组索引从多少开始?
  8. 长度为 n 的数组最大合法索引是多少?
  9. arr.length 表示什么?
  10. 数组越界会发生什么?
  11. 动态初始化后的元素默认值分别是什么?
  12. 什么叫遍历?
  13. 为什么数组遍历经常使用 i < arr.length
  14. 数组求最大值的基本思想是什么?
  15. 为什么最大值初始值不应该机械写成 0
  16. int[] b = a 后,ab 是两个数组对象还是两个引用变量?
  17. 什么是二维数组?
  18. 为什么二维数组可以理解为“数组中的元素仍然是数组”?
  19. arr[i]arr[i][j] 分别是什么?
  20. 二维数组每行长度必须相同吗?

7.2 代码阅读

禁止运行:

public class ArrayRead01 {

    public static void main(String[] args) {

        int[] a = {10, 20, 30};

        int[] b = a;

        b[0] = 100;

        System.out.println(a[0]);
        System.out.println(b[0]);
    }
}

要求:

  1. 预测两行输出。
  2. ab 是两个数组对象吗?
  3. 画出引用关系图。
  4. 如果希望得到真正独立的新数组,需要解决什么问题?

继续分析:

public class ArrayRead02 {

    public static void main(String[] args) {

        int[][] arr = {
                {1, 2},
                {3, 4, 5},
                {6}
        };

        System.out.println(arr.length);
        System.out.println(arr[1].length);
        System.out.println(arr[1][2]);
    }
}

要求:

  1. 预测三行输出。
  2. 分别解释三个表达式访问了什么。
  3. 画出二维数组结构。

7.3 手写代码

  1. 创建长度为 5 的整数数组并遍历。
  2. 计算数组全部元素之和。
  3. 计算平均值。
  4. 寻找最大值。
  5. 寻找最小值。
  6. 查找指定元素第一次出现的索引,不存在返回 -1
  7. 统计某元素出现次数。
  8. 交换两个指定位置元素。
  9. 原地逆序数组。
  10. 录入 8 名学生 Java 成绩并输出平均、最高、最低分。
  11. 创建 3 × 5 二维数组并遍历。
  12. 计算二维数组每一行的和。
  13. 找到二维数组最大元素及其行列索引。

7.4 Debug

public class ArrayDebug01 {

    public static void main(String[] args) {

        int[] arr = {10, 20, 30};

        for (int i = 0; i <= arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

要求:

  1. 找出错误。
  2. 判断是编译期错误还是运行期错误。
  3. 解释合法索引范围。
  4. 修复程序。

继续:

int[] arr = {-10, -20, -5};

int max = 0;

for (int value : arr) {

    if (value > max) {
        max = value;
    }
}

System.out.println(max);

要求:

  1. 判断算法结果是否正确。
  2. 找出问题不在语法,而在哪里。
  3. 设计更合理的最大值初始化方案。

7.5 综合训练

综合案例一:班级 Java 成绩

录入 8 名学生成绩。

最终输出:

全部成绩
总分
平均分
最高分
最低分

要求:

  1. 必须使用数组保存数据。
  2. 不允许定义 8 个独立成绩变量。
  3. 合理拆分方法。

综合案例二:随机洗牌

准备一个数组:

1 ~ 54

模拟 54 张牌。

要求:

创建数组
↓
初始化 54 个元素
↓
使用 Random
↓
随机交换数组元素
↓
输出打乱后的结果

完成后思考:

当前随机交换方法是否保证所有排列具有完全相同的概率?

把这个问题保留到后续算法学习阶段。

综合案例三:班级成绩二维模型

使用二维数组表示:

三个班级
每班人数可以不同

要求:

  1. 输出全部成绩。
  2. 计算每个班平均分。
  3. 找出全体学生最高分。
  4. 记录最高分属于第几个班、第几个学生。

7.6 本章验收

关闭资料后确认:

  • [ ] 能从零创建静态数组。
  • [ ] 能从零创建动态数组。
  • [ ] 能正确访问和修改元素。
  • [ ] 能解释 length 与最大合法索引。
  • [ ] 能识别数组越界。
  • [ ] 能说出数组默认值。
  • [ ] 能完成数组遍历。
  • [ ] 能完成求和、平均值、最大值和最小值。
  • [ ] 能完成简单线性查找。
  • [ ] 能交换数组元素。
  • [ ] 能原地逆序数组。
  • [ ] 能解释数组引用。
  • [ ] 能从零创建二维数组。
  • [ ] 能遍历不规则二维数组。
  • [ ] 能解释 arr[i]arr[i][j]
  • [ ] 能把数组问题先描述成算法步骤,再翻译成 Java。

如果目前只能:

“照着模板写 for (int i = 0; i < arr.length; i++)

却不能解释:

i 为什么从 0 开始
为什么必须 < length
arr[i] 到底是什么

那么数组仍然只是会写,还没有真正掌握。