|
@@ -328,6 +328,312 @@ name = 小咪, age = 1
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 第 11 题:综合大挑战——MyArray 可变数组(动态数组底层实现)⭐⭐⭐⭐⭐
|
|
|
|
|
+
|
|
|
|
|
+> 模仿 `ArrayList` 的底层原理,自己动手实现一个可变数组(动态数组)。本题综合运用 **构造方法、数组扩容、方法封装、排序算法** 等核心知识点。
|
|
|
|
|
+
|
|
|
|
|
+### 需求背景
|
|
|
|
|
+
|
|
|
|
|
+Java 中普通数组 `int[]` 一旦创建,长度就固定了,无法动态增减元素。请自行实现一个 **可变整型数组** `MyArray` 类,内部使用 `int[]` 存储数据,对外提供便捷的增删改查方法。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 实体类说明:MyArray
|
|
|
|
|
+
|
|
|
|
|
+**私有属性**:
|
|
|
|
|
+
|
|
|
|
|
+| 属性 | 类型 | 说明 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `data` | `int[]` | 内部真正存储数据的数组 |
|
|
|
|
|
+| `size` | `int` | 当前已存储的元素个数(不是数组长度) |
|
|
|
|
|
+
|
|
|
|
|
+**构造方法**:
|
|
|
|
|
+
|
|
|
|
|
+| 构造方法 | 说明 |
|
|
|
|
|
+|---------|------|
|
|
|
|
|
+| `MyArray()` | 无参构造,默认初始化容量为 **10** 的数组 |
|
|
|
|
|
+| `MyArray(int capacity)` | 指定初始容量,如果传入的容量 ≤ 0,则使用默认容量 10 |
|
|
|
|
|
+
|
|
|
|
|
+> **提示**:构造方法中只需 `data = new int[capacity]; size = 0;`,不要预填数据。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 核心方法列表
|
|
|
|
|
+
|
|
|
|
|
+要求实现以下 **11 个方法**:
|
|
|
|
|
+
|
|
|
|
|
+#### 1. 添加元素 — `add(int element)`
|
|
|
|
|
+
|
|
|
|
|
+将元素添加到数组的末尾。
|
|
|
|
|
+
|
|
|
|
|
+- 添加前先检查内部数组是否已满(`size == data.length`)
|
|
|
|
|
+- 如果已满,**自动扩容**为当前容量的 **1.5 倍**(即 `data.length * 3 / 2`)
|
|
|
|
|
+- 扩容步骤:创建新数组 → 将旧数组元素复制到新数组 → 将新数组赋值给 `data`
|
|
|
|
|
+- 最后将新元素存入 `data[size]`,`size` 自增 1
|
|
|
|
|
+
|
|
|
|
|
+#### 2. 获取元素 — `get(int index)`
|
|
|
|
|
+
|
|
|
|
|
+返回指定下标处的元素值。
|
|
|
|
|
+
|
|
|
|
|
+- 如果下标越界(`index < 0 || index >= size`),输出 `"下标越界,无法获取元素"`,返回 `-1`
|
|
|
|
|
+- 否则返回 `data[index]`
|
|
|
|
|
+
|
|
|
|
|
+#### 3. 修改元素 — `set(int index, int element)`
|
|
|
|
|
+
|
|
|
|
|
+将指定下标处的元素修改为新值。
|
|
|
|
|
+
|
|
|
|
|
+- 如果下标越界,输出 `"下标越界,无法修改元素"`,不执行修改
|
|
|
|
|
+- 否则将 `data[index]` 赋值为 `element`
|
|
|
|
|
+
|
|
|
|
|
+#### 4. 获取长度 — `size()`
|
|
|
|
|
+
|
|
|
|
|
+返回当前已存储的元素个数(`size` 属性值)。
|
|
|
|
|
+
|
|
|
|
|
+#### 5. 获取容量 — `capacity()`
|
|
|
|
|
+
|
|
|
|
|
+返回内部数组的总长度(`data.length`)。
|
|
|
|
|
+
|
|
|
|
|
+#### 6. 判断是否为空 — `isEmpty()`
|
|
|
|
|
+
|
|
|
|
|
+如果 `size == 0` 返回 `true`,否则返回 `false`。
|
|
|
|
|
+
|
|
|
|
|
+#### 7. 查找元素下标 — `indexOf(int element)`
|
|
|
|
|
+
|
|
|
|
|
+从前向后遍历数组,查找第一个值与 `element` 相等的元素。
|
|
|
|
|
+
|
|
|
|
|
+- 如果找到,返回该元素的下标
|
|
|
|
|
+- 如果没找到,返回 `-1`
|
|
|
|
|
+
|
|
|
|
|
+#### 8. 判断是否包含 — `contains(int element)`
|
|
|
|
|
+
|
|
|
|
|
+调用 `indexOf()` 方法,根据返回值是否为 `-1` 来判断。
|
|
|
|
|
+
|
|
|
|
|
+#### 9. 删除指定位置的元素 — `remove(int index)`
|
|
|
|
|
+
|
|
|
|
|
+删除指定下标处的元素。
|
|
|
|
|
+
|
|
|
|
|
+- 如果下标越界,输出 `"下标越界,无法删除元素"`,返回 `-1`
|
|
|
|
|
+- 否则:
|
|
|
|
|
+ - 用临时变量保存被删除的元素值
|
|
|
|
|
+ - 将 `index` 之后的所有元素向前移动一位(`data[j] = data[j+1]`)
|
|
|
|
|
+ - `size` 自减 1
|
|
|
|
|
+ - 返回被删除的元素值
|
|
|
|
|
+
|
|
|
|
|
+> **示意图**(删除下标 2 的元素):
|
|
|
|
|
+> ```
|
|
|
|
|
+> 删除前:[10, 20, 30, 40, 50] size=5
|
|
|
|
|
+> 删除后:[10, 20, 40, 50, 0] size=4
|
|
|
|
|
+> ```
|
|
|
|
|
+
|
|
|
|
|
+#### 10. 删除指定值的元素 — `removeByValue(int element)`
|
|
|
|
|
+
|
|
|
|
|
+删除数组中第一个值与 `element` 相等的元素。
|
|
|
|
|
+
|
|
|
|
|
+- 调用 `indexOf(element)` 获取下标
|
|
|
|
|
+- 如果下标为 `-1`,输出 `"未找到该元素,删除失败"`,返回 `false`
|
|
|
|
|
+- 否则调用 `remove(index)` 方法,返回 `true`
|
|
|
|
|
+
|
|
|
|
|
+#### 11. 数组排序 — `sort(boolean ascending)`
|
|
|
|
|
+
|
|
|
|
|
+对数组中的元素进行排序。
|
|
|
|
|
+
|
|
|
|
|
+- 参数 `ascending` 为 `true` 时**升序排列**(小→大),为 `false` 时**降序排列**(大→小)
|
|
|
|
|
+- 使用**冒泡排序**或**选择排序**实现
|
|
|
|
|
+- 注意:只对已存储的元素(`data[0] ~ data[size-1]`)排序,不要动后面的空闲位置
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 辅助方法
|
|
|
|
|
+
|
|
|
|
|
+#### `print()`
|
|
|
|
|
+
|
|
|
|
|
+遍历输出数组中的所有元素,格式为 `[元素1, 元素2, 元素3, ...]`。
|
|
|
|
|
+
|
|
|
|
|
+- 如果数组为空,输出 `[]`
|
|
|
|
|
+- 最后一个元素后面不要有逗号
|
|
|
|
|
+
|
|
|
|
|
+#### `clear()`
|
|
|
|
|
+
|
|
|
|
|
+清空数组中的所有元素,将 `size` 置为 `0`(内部数组对象保留,不清除)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 参考代码骨架
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class MyArray {
|
|
|
|
|
+ private int[] data; // 内部存储数组
|
|
|
|
|
+ private int size; // 当前元素个数
|
|
|
|
|
+
|
|
|
|
|
+ // 无参构造:默认容量 10
|
|
|
|
|
+ public MyArray() {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 有参构造:指定初始容量
|
|
|
|
|
+ public MyArray(int capacity) {
|
|
|
|
|
+ // TODO(capacity <= 0 时使用默认容量 10)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加元素(自动扩容)
|
|
|
|
|
+ public void add(int element) {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取指定位置的元素
|
|
|
|
|
+ public int get(int index) {
|
|
|
|
|
+ // TODO(下标越界检查)
|
|
|
|
|
+ return 0; // placeholder
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 修改指定位置的元素
|
|
|
|
|
+ public void set(int index, int element) {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取元素个数
|
|
|
|
|
+ public int size() {
|
|
|
|
|
+ return size;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取数组容量
|
|
|
|
|
+ public int capacity() {
|
|
|
|
|
+ return data.length;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 判断是否为空
|
|
|
|
|
+ public boolean isEmpty() {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 查找元素下标
|
|
|
|
|
+ public int indexOf(int element) {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 判断是否包含某元素
|
|
|
|
|
+ public boolean contains(int element) {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除指定位置的元素,返回被删除的值
|
|
|
|
|
+ public int remove(int index) {
|
|
|
|
|
+ // TODO(下标越界检查)
|
|
|
|
|
+ return 0; // placeholder
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除指定值的元素(第一个匹配项)
|
|
|
|
|
+ public boolean removeByValue(int element) {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 排序(true 升序,false 降序)
|
|
|
|
|
+ public void sort(boolean ascending) {
|
|
|
|
|
+ // TODO(冒泡排序)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 遍历输出
|
|
|
|
|
+ public void print() {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清空数组
|
|
|
|
|
+ public void clear() {
|
|
|
|
|
+ // TODO
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 测试类要求:MyArrayTest
|
|
|
|
|
+
|
|
|
|
|
+在 `main` 方法中按顺序完成以下操作,并观察输出是否符合预期:
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+public class MyArrayTest {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ // 1. 创建一个初始容量为 3 的 MyArray 对象
|
|
|
|
|
+ MyArray arr = new MyArray(3);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 输出初始状态
|
|
|
|
|
+ System.out.println("初始容量:" + arr.capacity()); // 3
|
|
|
|
|
+ System.out.println("是否为空:" + arr.isEmpty()); // true
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 添加 5 个元素(触发自动扩容)
|
|
|
|
|
+ for (int i = 1; i <= 5; i++) {
|
|
|
|
|
+ arr.add(i * 10); // 添加 10, 20, 30, 40, 50
|
|
|
|
|
+ }
|
|
|
|
|
+ arr.print(); // [10, 20, 30, 40, 50]
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 测试各个方法
|
|
|
|
|
+ System.out.println("元素个数:" + arr.size()); // 5
|
|
|
|
|
+ System.out.println("当前容量:" + arr.capacity()); // 4(3*1.5=4,再扩容 4*1.5=6)
|
|
|
|
|
+ System.out.println("下标2的元素:" + arr.get(2)); // 30
|
|
|
|
|
+ System.out.println("是否包含40:" + arr.contains(40)); // true
|
|
|
|
|
+ System.out.println("50的下标:" + arr.indexOf(50)); // 4
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 测试修改
|
|
|
|
|
+ arr.set(1, 99);
|
|
|
|
|
+ arr.print(); // [10, 99, 30, 40, 50]
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 测试删除
|
|
|
|
|
+ int deleted = arr.remove(2);
|
|
|
|
|
+ System.out.println("被删除的元素:" + deleted); // 30
|
|
|
|
|
+ arr.print(); // [10, 99, 40, 50]
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 测试按值删除
|
|
|
|
|
+ arr.removeByValue(10);
|
|
|
|
|
+ arr.print(); // [99, 40, 50]
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 测试升序排序
|
|
|
|
|
+ arr.sort(true);
|
|
|
|
|
+ arr.print(); // [40, 50, 99]
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 测试降序排序
|
|
|
|
|
+ arr.sort(false);
|
|
|
|
|
+ arr.print(); // [99, 50, 40]
|
|
|
|
|
+
|
|
|
|
|
+ // 10. 测试越界访问
|
|
|
|
|
+ System.out.println(arr.get(10)); // 下标越界,无法获取元素 → -1
|
|
|
|
|
+ arr.remove(10); // 下标越界,无法删除元素 → -1
|
|
|
|
|
+
|
|
|
|
|
+ // 11. 测试清空
|
|
|
|
|
+ arr.clear();
|
|
|
|
|
+ System.out.println("清空后是否为空:" + arr.isEmpty()); // true
|
|
|
|
|
+ arr.print(); // []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### 挑战加分项(可选)
|
|
|
|
|
+
|
|
|
|
|
+如果完成了基础版本,可以进一步挑战以下扩展功能:
|
|
|
|
|
+
|
|
|
|
|
+1. **泛型支持**:将 `MyArray` 改造为支持任意类型的 `MyArrayList<T>`(使用泛型,存储 `Object[]`)
|
|
|
|
|
+2. **缩容机制**:当元素个数少于容量的 1/4 时,自动缩容为当前容量的一半(节省内存)
|
|
|
|
|
+3. **`getMax()` / `getMin()`**:返回数组中的最大值和最小值
|
|
|
|
|
+4. **`addFirst(int element)`**:在数组头部插入元素(所有元素后移)
|
|
|
|
|
+5. **`toArray()`**:返回一个包含所有元素的新数组(长度 = size)
|
|
|
|
|
+6. **`subList(int fromIndex, int toIndex)`**:返回从 `fromIndex` 到 `toIndex` 的子数组视图
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+> **本题设计思路**:这个练习是模拟 JDK 中 `ArrayList` 的底层实现。通过手写动态数组,你能深刻理解:
|
|
|
|
|
+> - 构造方法如何初始化内部数据结构
|
|
|
|
|
+> - 数组长度固定的局限性以及「扩容」机制的实现原理
|
|
|
|
|
+> - 删除元素时「元素前移」的内存操作细节
|
|
|
|
|
+> - 方法封装如何对外提供简洁的 API,隐藏内部实现复杂度
|
|
|
|
|
+> - 这是通往「数据结构与算法」的第一级台阶 🪜
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 📋 题目总览
|
|
## 📋 题目总览
|
|
|
|
|
|
|
|
| 序号 | 所属部分 | 核心知识点 | 难度 |
|
|
| 序号 | 所属部分 | 核心知识点 | 难度 |
|
|
@@ -348,3 +654,4 @@ name = 小咪, age = 1
|
|
|
| 进阶 8 | 进阶 | 构造方法 + Random + Scanner + 循环 | ⭐⭐⭐⭐ |
|
|
| 进阶 8 | 进阶 | 构造方法 + Random + Scanner + 循环 | ⭐⭐⭐⭐ |
|
|
|
| 进阶 9 | 进阶 | 构造方法 + 封装 + 对象数组 + 排序 + 查找 | ⭐⭐⭐⭐ |
|
|
| 进阶 9 | 进阶 | 构造方法 + 封装 + 对象数组 + 排序 + 查找 | ⭐⭐⭐⭐ |
|
|
|
| 进阶 10 | 进阶 | 综合大挑战(构造方法 + 封装 + 包 + 对象数组 + 排序 + 平均分) | ⭐⭐⭐⭐⭐ |
|
|
| 进阶 10 | 进阶 | 综合大挑战(构造方法 + 封装 + 包 + 对象数组 + 排序 + 平均分) | ⭐⭐⭐⭐⭐ |
|
|
|
|
|
+| 进阶 11 | 进阶 | MyArray 可变数组(构造方法 + 数组扩容 + 增删改查 + 元素前移 + 冒泡排序 + 方法封装) | ⭐⭐⭐⭐⭐ |
|