🎯 数组(Array):程序员的百宝箱,一排小格子的魔法 📦

69 阅读6分钟

"如果说编程是一场冒险,那么数组就是你的第一个背包!" 🎒


😄 什么是数组?一个生活化的比喻

想象一下,你去超市买了一盒鸡蛋🥚,这盒鸡蛋是不是整整齐齐地排成一排?每个鸡蛋都有自己的位置(第1个、第2个、第3个...),而且这些格子的大小都是一样的。

这就是数组! 一组连续的"格子",每个格子大小相同,能存放同一类型的数据。

超市的鸡蛋盒:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│
└───┴───┴───┴───┴───┴───┴───┴───┘
  0   1   2   3   4   5   6   7    ← 索引(从0开始)

🔍 数组的本质原理

1️⃣ 连续内存空间

数组在内存中是一块连续的空间,就像是一排紧挨着的房间 🏠🏠🏠。

内存地址:  1000  1004  1008  1012  1016
          ┌────┬────┬────┬────┬────┐
          │ 1020304050 │
          └────┴────┴────┴────┴────┘
数组索引:   [0]  [1]  [2]  [3]  [4]

假设每个整数占4个字节:

  • arr[0] 在地址 1000
  • arr[1] 在地址 1004
  • arr[2] 在地址 1008
  • ...以此类推

2️⃣ 超快的随机访问(O(1)时间复杂度)

想要访问第i个元素?计算机只需要一个简单的数学公式:

元素地址 = 起始地址 + i × 元素大小

就像你知道你的座位号是"第8排第5座",你能直接走过去,不需要从第1排开始数!🎬

时间复杂度:O(1) - 超级快!⚡


💪 数组的优点 vs 😰 数组的缺点

✅ 优点:

优点说明生活比喻
🚀 访问快通过索引直接访问,O(1)时间知道门牌号直接找到房间
💾 缓存友好连续内存,CPU缓存命中率高书架上的书一本挨一本,拿起来方便
🎯 简单直观最基础、最易理解的数据结构小学生都能懂的"排队"

❌ 缺点:

缺点说明生活比喻
📏 固定大小一旦创建,大小不可变(Java)鸡蛋盒就这么大,多了放不下
🐌 插入删除慢中间插入/删除需要移动元素,O(n)时间队伍中间插队,后面的人都要往后挪
💸 空间浪费预分配大小,可能用不完买了12个格子的鸡蛋盒,只装了5个鸡蛋

🎬 数组的操作详解

1. 创建数组

// 方式1:声明并指定大小
int[] arr = new int[5];  // [0, 0, 0, 0, 0]

// 方式2:声明并初始化
int[] arr = {10, 20, 30, 40, 50};

// 方式3:先声明后赋值
int[] arr = new int[3];
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;

2. 访问元素 - 超快!⚡

int[] scores = {85, 92, 78, 95, 88};
System.out.println(scores[0]);  // 输出: 85 (第1个元素)
System.out.println(scores[3]);  // 输出: 95 (第4个元素)

// ⚠️ 注意:索引从0开始!

时间复杂度:O(1)

3. 修改元素 - 也超快!⚡

scores[2] = 99;  // 把第3个元素改成99

时间复杂度:O(1)

4. 插入元素 - 有点慢... 🐌

假设你要在位置2插入一个新元素:

原数组:
┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │    │
└────┴────┴────┴────┴────┴────┘
  0    1    2    3    4    5

在索引2插入25:
步骤1:把30、40、50往后挪 ➡️
步骤2:在位置2放入25

结果:
┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 25 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┴────┘
  0    1    2    3    4    5
public static int[] insert(int[] arr, int index, int value) {
    int[] newArr = new int[arr.length + 1];
    
    // 复制index之前的元素
    for (int i = 0; i < index; i++) {
        newArr[i] = arr[i];
    }
    
    // 插入新元素
    newArr[index] = value;
    
    // 复制index之后的元素
    for (int i = index; i < arr.length; i++) {
        newArr[i + 1] = arr[i];
    }
    
    return newArr;
}

时间复杂度:O(n) - n是数组长度

5. 删除元素 - 也有点慢... 🐌

原数组:
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
  0    1    2    3    4

删除索引2的元素:
步骤1:把40、50往前挪 ⬅️

结果:
┌────┬────┬────┬────┐
│ 10 │ 20 │ 40 │ 50 │
└────┴────┴────┴────┘
  0    1    2    3

时间复杂度:O(n)


🎯 数组的使用场景

适合用数组的场景:

  1. 数据量固定 - 比如一周7天的气温 🌡️

    double[] weekTemperature = new double[7];
    
  2. 需要频繁随机访问 - 比如学生成绩查询 📊

    int[] studentScores = {85, 90, 78, 92, 88};
    int score = studentScores[3];  // 快速查询第4个学生的成绩
    
  3. 顺序遍历 - 比如打印所有元素 🖨️

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

不适合用数组的场景:

  1. 频繁插入删除 - 用链表(LinkedList)更好 🔗
  2. 数据量不确定 - 用动态数组(ArrayList)更好 📈
  3. 需要快速查找 - 用哈希表(HashMap)更好 🔍

🌟 进阶:动态数组(ArrayList)

Java的数组大小固定很不方便?别担心,Java提供了ArrayList(动态数组)!

import java.util.ArrayList;

ArrayList<Integer> list = new ArrayList<>();
list.add(10);      // [10]
list.add(20);      // [10, 20]
list.add(30);      // [10, 20, 30]
list.remove(1);    // [10, 30]  删除索引1的元素

System.out.println(list.get(0));  // 输出: 10

🔍 ArrayList的扩容机制(面试重点!⭐)

ArrayList底层还是数组,但它会自动扩容

初始容量:10
扩容策略:当数组满了,创建一个新数组,大小是原来的1.5倍
         然后把旧数据复制过去

原数组(容量10):
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│10│20│30│40│50│60│70│80│90│满│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

扩容后(容量15):
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│10│20│30│40│50│60│70│80│90│满│  │  │  │  │  │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

💡 经典面试题

1. 两数之和(LeetCode 1)

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。

public int[] twoSum(int[] nums, int target) {
    HashMap<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) {
            return new int[] { map.get(complement), i };
        }
        map.put(nums[i], i);
    }
    return new int[0];
}

2. 最大子数组和(Kadane算法)

public int maxSubArray(int[] nums) {
    int maxSum = nums[0];
    int currentSum = nums[0];
    
    for (int i = 1; i < nums.length; i++) {
        currentSum = Math.max(nums[i], currentSum + nums[i]);
        maxSum = Math.max(maxSum, currentSum);
    }
    
    return maxSum;
}

📝 总结

特性数组说明
访问速度⚡⚡⚡⚡⚡ O(1)超快!
插入删除🐌🐌 O(n)慢...
空间利用😐 固定大小可能浪费
适用场景🎯 固定大小、频繁访问最基础的结构

🎓 记忆口诀

数组就像鸡蛋盒,
格子连续排成行。
访问超快用索引,
插删缓慢要移动。
大小固定不灵活,
ArrayList来帮忙!

🚀 下一步学习

  • 掌握了数组,接下来可以学习:
    1. 链表(LinkedList) - 解决数组插入删除慢的问题 🔗
    2. 栈(Stack) - 后进先出的魔法 📚
    3. 队列(Queue) - 先进先出的排队艺术 🎫

恭喜你!🎉 你已经掌握了程序员的第一个数据结构朋友——数组!

记住:数组虽然简单,但它是几乎所有高级数据结构的基础。就像学武功,基本功扎实了,才能练成绝世武功!💪


📌 小贴士:建议手写一遍数组的插入和删除操作,加深理解!

😊 有问题?记住:程序员最好的朋友是Google和Stack Overflow!