1. 数组基础概念与核心特性
数组作为编程语言中最基础的数据结构之一,其重要性不言而喻。在内存中,数组是一块连续的内存空间,存储相同类型的数据元素。这种连续存储的特性带来了两个关键优势:一是可以通过索引快速访问任意位置的元素(时间复杂度O(1));二是由于内存连续性,遍历数组时具有很好的缓存局部性(cache locality),这在性能敏感的场景中尤为重要。
以C语言为例,数组声明方式为:
c复制int arr[10]; // 声明一个包含10个整数的数组
而在Java中,数组是对象,声明方式略有不同:
java复制int[] arr = new int[10];
数组的维度也不仅限于一维。二维数组(矩阵)在图像处理、游戏开发等领域应用广泛。例如表示一个3x3的矩阵:
python复制matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
注意:不同语言对数组的实现有差异。C/C++中的数组是静态的,大小固定;而Java/Python等高级语言中的数组实际上是动态数组,可以自动扩容。
2. 数组基本操作与算法
2.1 遍历与搜索
数组遍历是最基础的操作,但不同遍历方式对性能有显著影响。以查找特定元素为例,线性搜索是最直接的方式:
javascript复制function linearSearch(arr, target) {
for(let i = 0; i < arr.length; i++) {
if(arr[i] === target) return i;
}
return -1;
}
对于已排序的数组,二分搜索可以将时间复杂度从O(n)降到O(log n):
java复制int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while(left <= right) {
int mid = left + (right - left)/2;
if(arr[mid] == target) return mid;
if(arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
2.2 排序算法
数组排序是算法中的经典问题。快速排序利用分治思想,平均时间复杂度O(n log n):
c++复制void quickSort(vector<int>& arr, int low, int high) {
if(low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int partition(vector<int>& arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for(int j = low; j < high; j++) {
if(arr[j] < pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i+1], arr[high]);
return i+1;
}
对于小规模数据,插入排序可能更高效:
python复制def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i-1
while j >=0 and key < arr[j] :
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
3. 数组高级应用与技巧
3.1 双指针技术
双指针是处理数组问题的强大技术,常见于滑动窗口、两数之和等问题。例如移除有序数组中的重复元素:
java复制public int removeDuplicates(int[] nums) {
if(nums.length == 0) return 0;
int slow = 0;
for(int fast = 1; fast < nums.length; fast++) {
if(nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
另一个经典例子是盛水最多的容器问题:
python复制def maxArea(height):
left, right = 0, len(height)-1
max_area = 0
while left < right:
area = min(height[left], height[right]) * (right - left)
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
3.2 前缀和与差分数组
前缀和技术可以高效解决子数组求和问题。例如计算数组区间和:
c++复制class NumArray {
private:
vector<int> prefix;
public:
NumArray(vector<int>& nums) {
prefix.resize(nums.size()+1);
for(int i=0; i<nums.size(); i++)
prefix[i+1] = prefix[i] + nums[i];
}
int sumRange(int left, int right) {
return prefix[right+1] - prefix[left];
}
};
差分数组则适用于频繁的区间更新操作:
java复制// 假设有一个初始全0的数组,进行多次区间增减操作
void rangeUpdate(int[] diff, int l, int r, int val) {
diff[l] += val;
if(r+1 < diff.length) diff[r+1] -= val;
}
// 最后通过差分数组恢复原始数组
void restoreArray(int[] arr, int[] diff) {
arr[0] = diff[0];
for(int i=1; i<arr.length; i++)
arr[i] = arr[i-1] + diff[i];
}
4. 特殊数组结构与应用
4.1 树状数组(Fenwick Tree)
树状数组支持高效的前缀查询和单点更新(O(log n)时间复杂度),常用于动态统计问题:
python复制class FenwickTree:
def __init__(self, size):
self.size = size
self.tree = [0]*(self.size + 1)
def update(self, index, delta):
while index <= self.size:
self.tree[index] += delta
index += index & -index
def query(self, index):
res = 0
while index > 0:
res += self.tree[index]
index -= index & -index
return res
4.2 后缀数组
后缀数组是字符串处理中的强大工具,可用于高效解决子串搜索、最长重复子串等问题。构建后缀数组的DC3算法较为复杂,以下是简化版实现:
c++复制vector<int> buildSuffixArray(const string &s) {
int n = s.size();
vector<int> sa(n), rank(n);
for(int i=0; i<n; i++) {
sa[i] = i;
rank[i] = s[i];
}
for(int k=1; k<n; k*=2) {
auto cmp = [&](int i, int j) {
if(rank[i] != rank[j]) return rank[i] < rank[j];
int ri = i+k <n ? rank[i+k] : -1;
int rj = j+k <n ? rank[j+k] : -1;
return ri < rj;
};
sort(sa.begin(), sa.end(), cmp);
vector<int> tmp(n);
tmp[sa[0]] = 0;
for(int i=1; i<n; i++)
tmp[sa[i]] = tmp[sa[i-1]] + (cmp(sa[i-1], sa[i]) ? 1 : 0);
rank = tmp;
}
return sa;
}
5. 数组问题实战解析
5.1 子数组问题
给定一个整数数组nums和一个整数k,找出和为k的连续子数组的个数。使用前缀和+哈希表的优化解法:
java复制public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int sum = 0, count = 0;
for(int num : nums) {
sum += num;
count += map.getOrDefault(sum - k, 0);
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}
5.2 数组去重问题
对于已排序数组,原地删除重复元素(保留至多两个):
python复制def removeDuplicates(nums):
if len(nums) <= 2: return len(nums)
slow = 2
for fast in range(2, len(nums)):
if nums[fast] != nums[slow-2]:
nums[slow] = nums[fast]
slow += 1
return slow
对于未排序数组,找出所有重复元素:
c++复制vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for(int i=0; i<nums.size(); i++) {
int index = abs(nums[i])-1;
if(nums[index] < 0)
res.push_back(index+1);
nums[index] = -nums[index];
}
return res;
}
5.3 数组旋转与搜索
在旋转排序数组中搜索目标值(无重复元素):
java复制public int search(int[] nums, int target) {
int left = 0, right = nums.length-1;
while(left <= right) {
int mid = left + (right-left)/2;
if(nums[mid] == target) return mid;
if(nums[left] <= nums[mid]) { // 左半部分有序
if(nums[left] <= target && target < nums[mid])
right = mid-1;
else
left = mid+1;
} else { // 右半部分有序
if(nums[mid] < target && target <= nums[right])
left = mid+1;
else
right = mid-1;
}
}
return -1;
}
6. 多维数组处理技巧
6.1 二维数组遍历
螺旋遍历二维数组是常见的面试题:
python复制def spiralOrder(matrix):
if not matrix: return []
m, n = len(matrix), len(matrix[0])
res = []
left, right, top, bottom = 0, n-1, 0, m-1
while left <= right and top <= bottom:
# 从左到右
for j in range(left, right+1):
res.append(matrix[top][j])
top += 1
# 从上到下
for i in range(top, bottom+1):
res.append(matrix[i][right])
right -= 1
if top <= bottom: # 防止单行情况
# 从右到左
for j in range(right, left-1, -1):
res.append(matrix[bottom][j])
bottom -= 1
if left <= right: # 防止单列情况
# 从下到上
for i in range(bottom, top-1, -1):
res.append(matrix[i][left])
left += 1
return res
6.2 矩阵运算
矩阵快速幂可以高效解决递推问题,如斐波那契数列:
c++复制vector<vector<long long>> matrixMultiply(vector<vector<long long>>& a,
vector<vector<long long>>& b) {
int n = a.size();
vector<vector<long long>> res(n, vector<long long>(n));
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
for(int k=0; k<n; k++)
res[i][j] += a[i][k] * b[k][j];
return res;
}
vector<vector<long long>> matrixPower(vector<vector<long long>> mat, int power) {
int n = mat.size();
vector<vector<long long>> res(n, vector<long long>(n));
for(int i=0; i<n; i++) res[i][i] = 1; // 单位矩阵
while(power > 0) {
if(power & 1) res = matrixMultiply(res, mat);
mat = matrixMultiply(mat, mat);
power >>= 1;
}
return res;
}
long long fibonacci(int n) {
if(n <= 1) return n;
vector<vector<long long>> mat = {{1,1}, {1,0}};
auto res = matrixPower(mat, n-1);
return res[0][0];
}
7. 数组与其他数据结构的转换
7.1 数组模拟链表
在某些场景下,可以用数组高效模拟链表结构。例如LRU缓存实现:
java复制class LRUCache {
class Node {
int key, value;
Node prev, next;
Node(int k, int v) { key=k; value=v; }
}
private Map<Integer, Node> map;
private Node head, tail;
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
head = new Node(0,0);
tail = new Node(0,0);
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if(!map.containsKey(key)) return -1;
Node node = map.get(key);
remove(node);
addToHead(node);
return node.value;
}
public void put(int key, int value) {
if(map.containsKey(key)) {
Node node = map.get(key);
node.value = value;
remove(node);
addToHead(node);
} else {
if(map.size() == capacity) {
map.remove(tail.prev.key);
remove(tail.prev);
}
Node newNode = new Node(key, value);
map.put(key, newNode);
addToHead(newNode);
}
}
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void addToHead(Node node) {
node.next = head.next;
node.next.prev = node;
head.next = node;
node.prev = head;
}
}
7.2 数组与树的转换
将有序数组转换为平衡二叉搜索树:
python复制class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def sortedArrayToBST(nums):
def helper(left, right):
if left > right: return None
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = helper(left, mid-1)
root.right = helper(mid+1, right)
return root
return helper(0, len(nums)-1)
8. 性能优化与边界处理
8.1 缓存友好访问模式
现代CPU的缓存行通常为64字节,合理利用可以显著提升性能。例如矩阵乘法优化:
c++复制// 低效的访问方式
void matMultSlow(const vector<vector<int>>& a,
const vector<vector<int>>& b,
vector<vector<int>>& c) {
int n = a.size();
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
for(int k=0; k<n; k++)
c[i][j] += a[i][k] * b[k][j];
}
// 优化后的访问方式(利用缓存局部性)
void matMultFast(const vector<vector<int>>& a,
const vector<vector<int>>& b,
vector<vector<int>>& c) {
int n = a.size();
for(int i=0; i<n; i++)
for(int k=0; k<n; k++) {
int r = a[i][k];
for(int j=0; j<n; j++)
c[i][j] += r * b[k][j];
}
}
8.2 边界条件处理
数组问题中常见的边界陷阱包括:
- 空数组输入
- 单个元素数组
- 全相同元素数组
- 极大/极小值测试用例
- 整数溢出问题(特别是在求和场景)
例如,计算数组最大子序和时需要考虑负数情况:
java复制public int maxSubArray(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int maxSum = nums[0], 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;
}
9. 语言特定数组特性
9.1 Java数组特性
Java中的数组是对象,具有length属性。ArrayList是基于数组的动态实现:
java复制// 数组与ArrayList转换
int[] arr = {1,2,3};
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());
// 多维数组
int[][] matrix = new int[3][4];
// 不规则数组
int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[3];
9.2 C++数组与vector
C++提供了多种数组容器,各有适用场景:
cpp复制// 原始数组
int arr1[10];
// std::array (固定大小,栈分配)
array<int, 5> arr2 = {1,2,3,4,5};
// std::vector (动态大小,堆分配)
vector<int> vec = {1,2,3};
vec.push_back(4); // 自动扩容
// 多维vector
vector<vector<int>> matrix(3, vector<int>(4));
9.3 JavaScript数组方法
JS数组提供了丰富的高阶函数:
javascript复制const arr = [1,2,3,4,5];
// 函数式操作
const doubled = arr.map(x => x*2);
const evens = arr.filter(x => x%2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);
// 现代ES6+特性
const [first, ...rest] = arr; // 解构
const newArr = [...arr, 6]; // 扩展运算符
10. 实战经验与性能调优
在实际项目中处理大型数组时,我有几点重要经验:
- 预分配空间:对于已知大小的数组,预先分配足够空间可以避免频繁扩容带来的性能损耗。例如在C++中:
cpp复制vector<int> data;
data.reserve(1000000); // 预分配空间
- 批量操作优于单元素操作:尽可能使用批量赋值/复制函数。例如在Java中:
java复制// 低效
for(int i=0; i<1000; i++) list.add(i);
// 高效
list.addAll(IntStream.range(0, 1000).boxed().collect(Collectors.toList()));
- 选择合适的数据视图:对于大型多维数据,考虑使用一维数组+索引计算来替代多维数组,有时能获得更好的缓存性能:
python复制# 二维数组的一维表示
class Matrix:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.data = [0]*(rows*cols)
def __getitem__(self, pos):
i, j = pos
return self.data[i*self.cols + j]
def __setitem__(self, pos, value):
i, j = pos
self.data[i*self.cols + j] = value
- 并行化处理:对于计算密集型数组操作,考虑使用并行流或GPU加速。例如Java并行流:
java复制int[] largeArray = ...;
Arrays.parallelSort(largeArray); // 并行排序
int sum = Arrays.stream(largeArray).parallel().sum(); // 并行求和
- 内存映射文件:处理超大型数组时,可以使用内存映射文件技术避免一次性加载全部数据:
java复制RandomAccessFile file = new RandomAccessFile("large.data", "r");
FileChannel channel = file.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 然后可以像访问数组一样访问buffer
int firstInt = buffer.getInt();
