📂 题型核心:同向快慢指针

这类题目属于双指针中的同向分类。其核心在于通过两个指针同向移动,将数组分为“已处理区域”和“待探索区域”,从而在 O(1) 空间内完成原地修改。

🛠️ 统一通用模板 (Java)

绝大多数原地修改题目(去重、移除、过滤)都可以套用以下逻辑:

1
2
3
4
5
6
7
8
9
10
11
public int process(int[] nums) {
int slow = 0; // 慢指针:维护有效区域的边界
for (int fast = 0; fast < nums.length; fast++) { // 快指针:探索整个数组
if (满足特定条件(nums[fast])) {
// 如果快指针指向的是我们需要保留或处理的元素
nums[slow] = nums[fast]; // 搬运到慢指针处
slow++; // 慢指针前进一步,扩大有效区
}
}
return slow; // 返回有效区域的长度
}

1. [LC 26] 删除有序数组中的重复项

  • 题目链接26. Remove Duplicates from Sorted Array
  • 标签:数组、双指针、原地修改
  • 核心思路:使用快慢指针。slow 维护不重复区域边界,fast 寻找新元素。当 nums[fast] != nums[slow] 时,说明找到了新元素。

💻 我的题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int removeDuplicates(int[] nums) {
// 1. 边界检查 (防御性编程:先判空,再判长度)
if (nums == null || nums.length == 0) {
return 0;
}

// 2. 双指针初始化
int slow = 0;

// 3. 循环遍历 (推荐用 for 循环,让 fast 自动递增,代码更简洁)
for (int fast = 1; fast < nums.length; fast++) {
// 只有当快指针发现不同元素时,慢指针才动
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}

// 4. 返回长度 (索引 + 1)
return slow + 1;
}
}

📈 复杂度分析

  • 时间复杂度:O(N)。fast 指针遍历一遍数组。
  • 空间复杂度:O(1)。原地修改,只使用了两个变量。

2. [LC 27] 移除元素

  • 题目链接27. Remove Element
  • 标签:数组、双指针、原地修改、字节高频
  • 核心思路fast 负责观察每一个元素是否等于 valslow 只有在发现合法元素时才接纳并移动。

💻 我的题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int removeElement(int[] nums, int val) {
// 1. 边界防御:先判空,再判长度
if(nums == null || nums.length == 0 ){
return 0;
}

int slow = 0, fast = 0;
// 2. 核心双指针逻辑
while (fast < nums.length) {
if(nums[fast] == val){
// 如果遇到要删除的,快指针直接跳过
fast++;
}
else{
// 如果遇到要保留的,搬运给 slow,两人各进一步
nums[slow] = nums[fast];
slow++;
fast++;
}
}
// 3. 最终 slow 指向的位置恰好就是有效元素的个数
return slow;
}
}

🚀 优化建议 (Standard Template)

1
2
3
4
5
6
// 高手推荐:更简洁的 for 循环写法
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast]; // 合并操作,显得更老道
}
}

📈 复杂度分析

  • 时间复杂度:O(N)。
  • 空间复杂度:O(1)。

3. [LC 283] 移动零

  • 题目链接283. Move Zeroes
  • 标签:数组、双指针、逻辑复用、字节跳动高频
  • 核心思路:将“移动零”看作“移除所有值为 0 的元素”。先利用快慢指针挤压非零元素,再后期手动补零。

💻 我的题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// @lc code=start
class Solution {
public void moveZeroes(int[] nums) {
// 1. 边界防御
if(nums == null || nums.length == 0){
return;
}

// 2. 复用移除逻辑:获取非零元素的边界
int point = removeZero(nums);

// 3. 将剩余位置补零
for(int i = point; i < nums.length; i++){
nums[i] = 0;
}
}

/**
* 助手函数:移除数组中的零,返回非零区域的长度
* 本质是 LC 27 的逻辑复用
*/
int removeZero(int[] nums){
int slow = 0, fast = 0;
while (fast < nums.length) {
if(nums[fast] == 0){
fast++;
} else {
nums[slow] = nums[fast];
slow++;
fast++;
}
}
return slow;
}
}
// @lc code=end

📈 复杂度分析

  • 时间复杂度:O(N)。
  • 空间复杂度:O(1)。