菜单

数组问题

相关源文件

本文档解释了 LeetCode 仓库中常用的数组问题模式和技术。数组是最基本的数据结构之一,构成了许多算法挑战的基础。我们专注于处理数组数据结构的有效方法,包括操作、搜索和优化。

有关特定数据结构的更多信息,请参阅 基本数据结构。有关相关的滑动窗口和双指针技术,请参阅 滑动窗口和双指针

数组问题模式

数组问题通常会归纳为几种常见的模式,这些模式在不同的问题中会反复出现。识别这些模式是高效解决数组相关挑战的关键。

来源: problems/26.remove-duplicates-from-sorted-array.md60-75), problems/283.move-zeroes.md35-45), problems/167.two-sum-ii-input-array-is-sorted.md38-45), problems/220.contains-duplicate-iii.md82-116)

双指针技术

双指针技术是解决数组问题最通用的方法之一。它涉及使用两个指针,它们可以

  1. 从两端相互靠近
  2. 以不同的速度沿同一方向移动
  3. 扮演不同的角色(例如,读与写)

快慢指针

快慢指针以不同的速度遍历数组。此方法对于以下情况很有用:

  • 查找循环
  • 查找中间元素
  • 删除重复项

在“删除排序数组中的重复项”中,我们使用慢指针来跟踪下一个唯一元素的位置

if (nums[fast] !== nums[slow]) {
    slow++;
    nums[slow] = nums[fast];
}

来源: problems/26.remove-duplicates-from-sorted-array.md60-111), problems/142.Linked-List-Cycle-II.md66-110)

左右指针

左右指针从数组的两端开始,向中间移动。这对于以下情况尤其有效:

  • 在已排序数组中搜索
  • 两数之和问题
  • 分区问题

对于已排序输入的“两数之和II”

left = 0, right = len(numbers) - 1
while left < right:
    if numbers[left] + numbers[right] < target:
        left += 1
    if numbers[left] + numbers[right] > target:
        right -= 1
    if numbers[left] + numbers[right] == target:
        return [left+1, right+1]

来源: problems/167.two-sum-ii-input-array-is-sorted.md147-157), problems/75.sort-colors.md54-97)

读写指针

在这种变体中,一个指针遍历数组,而另一个指针将值写入正确的位置

对于“移动零”,读写方法可保持非零元素的顺序

let index = 0;  // write pointer
for (let i = 0; i < nums.length; i++) {  // read pointer
    const num = nums[i];
    if (num !== 0) {
        nums[index++] = num;
    }
}

来源: problems/283.move-zeroes.md35-72), problems/26.remove-duplicates-from-sorted-array.md99-111)

滑动窗口

滑动窗口技术维护一个窗口(子数组),该窗口在数组中滑动,根据需要进行扩展或收缩。

固定长度窗口

对于需要处理固定大小子数组的问题

可变长度窗口

对于窗口大小根据条件变化的窗口

来源: problems/219.contains-duplicate-ii.md38-44), problems/220.contains-duplicate-iii.md102-116)

前缀和

前缀和是一种技术,我们计算数组元素的累积和,从而能够进行 O(1) 的范围求和查询。

这项技术对于以下方面特别强大:

  • 范围求和查询
  • 查找给定和的子数组
  • 最大子数组问题

对于“两个不重叠子数组的最大和”问题

// Compute prefix sum
for (let i = 1; i < n + 1; i++) {
    asum[i] = asum[i - 1] + a[i - 1]
}

来源: problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md73-116), problems/152.maximum-product-subarray.md36-143)

基于哈希的方法

哈希表和集合对于数组问题非常有用,尤其是当查找速度至关重要时。

元素跟踪

用于跟踪特定距离内的元素

const visited = {};
for (let i = 0; i < nums.length; i++) {
    if (visited[nums[i]] !== undefined && i - visited[nums[i]] <= k) {
        return true;
    }
    visited[nums[i]] = i;
}

来源: problems/219.contains-duplicate-ii.md38-75), problems/349.intersection-of-two-arrays.md40-99)

桶排序方法

对于“Contains Duplicate III”,桶方法根据值范围对元素进行分组

来源: problems/220.contains-duplicate-iii.md84-144)

投票算法

Boyer-Moore 投票算法

这个巧妙的算法以 O(n) 的时间和 O(1) 的空间复杂度查找多数元素

多数元素的代码

let count = 1;
let majority = nums[0];
for (let i = 1; i < nums.length; i++) {
    if (count === 0) {
        majority = nums[i];
    }
    if (nums[i] === majority) {
        count++;
    } else {
        count--;
    }
}

用于查找多个多数元素

来源: problems/169.majority-element.md39-76), problems/229.majority-element-ii.md51-74)

分区技术

荷兰国旗问题

一种三向分区方法,用于“排序颜色”等问题

实现模式

p0 = 0, p1 = 0, p2 = len(nums) - 1
while p1 <= p2:
    if nums[p1] == 0:        # Red
        nums[p0], nums[p1] = nums[p1], nums[p0]
        p0++
        p1++
    elif nums[p1] == 2:      # Blue
        nums[p1], nums[p2] = nums[p2], nums[p1]
        p2--
    else:                    # White
        p1++

来源: problems/75.sort-colors.md40-151)

动态规划用于数组

许多数组问题涉及动态规划方法,特别是子数组问题。

最大乘积子数组

来源: problems/152.maximum-product-subarray.md36-142)

数组操作复杂度

此表总结了常见数组操作的时间复杂度

操作未排序数组已排序数组基于哈希
搜索O(n)O(log n)O(1)
插入O(1) (尾部)O(n)O(1)
删除O(n)O(n)O(1)
查找最小值/最大值O(n)O(1)O(n)
查找重复项O(n²) 或 O(n log n)O(n)O(n)
两数之和O(n²) 或 O(n log n)O(n)O(n)

来源:problems/167.two-sum-ii-input-array-is-sorted.md38-45), problems/26.remove-duplicates-from-sorted-array.md170-173), problems/219.contains-duplicate-ii.md131-134)

常见的数组问题实现模式

原地修改

许多数组问题要求原地修改,以达到 O(1) 的空间复杂度

原地删除重复项

let slowP = 0;
for (let fastP = 0; fastP < size; fastP++) {
    if (nums[fastP] !== nums[slowP]) {
        slowP++;
        nums[slowP] = nums[fastP];
    }
}
return slowP + 1;

来源:problems/26.remove-duplicates-from-sorted-array.md98-111), problems/283.move-zeroes.md98-136)

需要考虑的边缘情况

在解决数组问题时,请注意这些常见的边缘情况

  1. 空数组
  2. 只有一个元素的数组
  3. 所有元素都相同的数组
  4. 极端值数组(非常大或非常小)
  5. 乘法问题中处理零元素
  6. 负数影响最小/最大计算

来源:problems/152.maximum-product-subarray.md129-136), problems/26.remove-duplicates-from-sorted-array.md87-88), problems/169.majority-element.md58-61)

此表总结了存储库中的关键数组问题类型

问题类型示例关键技术
删除重复项26. 删除排序数组中的重复项双指针(快慢指针)
移动元素283. 移动零双指针(读写指针)
查找元素167. 两数之和 II - 输入数组已排序双指针(左右指针)
众数169. 多数元素Boyer-Moore 投票算法
排序75. 颜色分类荷兰国旗问题
最大子数组152. 乘积最大子数组动态规划
存在重复元素219. 存在重复元素 II哈希表
不重叠子数组1031. 两个非重叠子数组的最大和前缀和 + 动态规划

来源:problems/26.remove-duplicates-from-sorted-array.md), problems/283.move-zeroes.md), problems/167.two-sum-ii-input-array-is-sorted.md), problems/169.majority-element.md), problems/75.sort-colors.md), problems/152.maximum-product-subarray.md), problems/219.contains-duplicate-ii.md), problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md)