双指针方法是解决数组或链表问题中非常高效的技巧之一,尤其适用于原地修改数组或减少时间复杂度的场景。以下是对双指针方法的详细讲解。
1. 双指针方法的核心思想
双指针方法通常使用两个指针(或索引)在数组或链表中协同工作,通过一次遍历完成任务。常见的双指针模式包括:
- 快慢指针:一个指针用于遍历(快指针),另一个指针用于标记目标位置(慢指针)。
- 左右指针:一个指针从头部开始,另一个从尾部开始,向中间移动。
在 RemoveElement
问题中,使用的是快慢指针。
2. 问题描述
给定一个数组 nums
和一个值 val
,需要原地移除所有等于 val
的元素,并返回新数组的长度。要求:
- 空间复杂度为
O(1)
(不能使用额外数组)。 - 不需要考虑新数组长度之后的元素。
3. 双指针实现步骤
(1) 初始化指针
- 慢指针
index
:指向下一个有效元素的存储位置(初始为0
)。 - 快指针
i
:用于遍历数组(初始为0
)。
(2) 遍历数组
- 快指针
i
从0
开始,逐个检查数组元素。 - 如果
nums[i] != val
,说明这是一个需要保留的元素:- 将其赋值给
nums[index]
。 - 慢指针
index
右移一位。
- 将其赋值给
- 如果
nums[i] == val
,直接跳过(快指针继续右移)。
(3) 终止条件
- 快指针
i
遍历完整个数组后,慢指针index
的值即为新数组的长度。
4. 代码实现
public int RemoveElement(int[] nums, int val)
{int index = 0; // 慢指针for (int i = 0; i < nums.Length; i++) // 快指针 i{if (nums[i] != val){nums[index] = nums[i]; // 保留该元素index++; // 慢指针右移}}return index; // 新数组长度
}
5. 示例分析
以 nums = [3, 2, 2, 3]
, val = 3
为例:
步骤 | 快指针 i | nums[i] | 操作 | 数组状态 | 慢指针 index |
---|---|---|---|---|---|
1 | 0 | 3 | 跳过 | [3, 2, 2, 3] | 0 |
2 | 1 | 2 | nums[0] = 2 | [2, 2, 2, 3] | 1 |
3 | 2 | 2 | nums[1] = 2 | [2, 2, 2, 3] | 2 |
4 | 3 | 3 | 跳过 | [2, 2, 2, 3] | 2 |
最终返回 index = 2
,新数组为 [2, 2, ...]
(后续元素无需关心)。
6. 复杂度分析
- 时间复杂度:
O(n)
,只需遍历一次数组。 - 空间复杂度:
O(1)
,仅使用常数级别的额外空间。
7. 双指针的适用场景
- 原地修改数组:如删除重复项、移除特定元素。
- 链表问题:如判断环形链表、找到中间节点。
- 有序数组:如两数之和、合并有序数组。
8. 总结
双指针方法是解决数组/链表问题的利器,核心在于:
- 明确指针的分工(快指针遍历,慢指针标记)。
- 注意边界条件(如空数组、全部元素需删除)。
- 灵活选择变体(如交换法优化)。