leetcode第283题移动零

283. 移动零 - 力扣(LeetCode)

题目描述:

解题思路:

1. 题意核心约束

- 不能打乱非零元素先后顺序;

- 只能原地修改,空间尽量 O(1);

- 零全部后置,非零全部前置。

2. 最优思想:快慢双指针

1. 快指针 fast:全程从头遍历到尾,作用:逐个扫描元素,专门寻找非零数。

2. 慢指针 slow:指向数组左侧,作用:标记下一个非零元素应该存放的位置。

3. 整体流程

- fast 遇到非零元素:把该元素放到 slow 位置,slow 向后移动;

- fast 遇到 0:直接跳过,不做处理;

- 遍历结束后,slow 左边全是有序非零数,slow 及后面全部补 0 即可。

3. 为什么不用左右对撞双指针?

左右双指针(左找0、右找非零交换)会打乱非零元素相对顺序,不符合题目要求,只能用来做“元素分割”,不适合本题。

三、我的两次踩坑与报错分析

坑1:左右双指针越界 + 顺序破坏

坑 1:左右双指针越界
第一次尝试用左右双指针思路写代码,直接报 heap-buffer-overflow 堆内存越界错误。

错误代码:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            if (nums[right] == 0) {
                right++; // 致命错误:指针越界
            }
            if (nums[left] == 0) {
                swap(nums[left], nums[right]);
                left++;
                right++;
            }
        }
    }
};

坑2:快慢指针循环条件错误

第二次改用快慢指针,循环条件写错,依旧越界崩溃。

while(slow <= fast) 无数组长度限制,fast 无限自增,最终越界+死循环。

错误代码:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0, fast = 0;
        while (slow <= fast) { // 无边界限制,必死循环+越界
            if (nums[fast] != 0) {
                swap(nums[slow], nums[fast]);
                fast++;
                slow++;
            } else {
                fast++;
            }
        }
    }
};

循环条件错误:没有限制 fast 不超过数组长度,fast 会一直自增到越界。
死循环:slow 和 fast 只会变大,循环永远不停止。

四、解法一:快慢指针交换法(C++ + 思路)

解题思路

一遍遍历,fast 找非零,和 slow 位置交换;交换后 slow 右移,保证下一个非零依次往前排;零会自动被挤到后面,天然维持相对顺序。

#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        // slow:待存放非零元素的位置
        int slow = 0;
        // fast:遍历寻找非零元素
        for (int fast = 0; fast < nums.size(); ++fast) {
            // 找到非零,交换到前面
            if (nums[fast] != 0) {
                swap(nums[slow], nums[fast]);
                slow++;
            }
        }
    }
};

五、解法二:赋值补零法(C++ + 思路)

解题思路

分两步,逻辑更直白,新手好理解:

1. 第一次遍历:把所有非零元素,依次覆盖写入数组前段;

2. 记录非零元素总个数 slow,第二次遍历:从 slow 下标开始,后面全部赋值为 0。

#include <vector>
using namespace std;

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0;
        // 1. 非零元素前置
        for (int fast = 0; fast < nums.size(); ++fast) {
            if (nums[fast] != 0) {
                nums[slow++] = nums[fast];
            }
        }
        // 2. 末尾统一补 0
        for (int i = slow; i < nums.size(); ++i) {
            nums[i] = 0;
        }
    }
};

六、复杂度分析

- 时间复杂度:O(n)

仅遍历数组常数次,无嵌套循环;

- 空间复杂度:O(1)

仅使用两个临时指针变量,完全原地操作。

七、两种解法对比

解法

思路特点

难度

适用场景

快慢交换法

一趟遍历、原地交换、代码精简

中等

面试首选、代码简洁

赋值补零法

两步拆分、逻辑直白、极易理解

简单

算法入门、新手学习

八、新手避坑总结

1. 数组遍历必须限制下标范围,防止越界报错;

2. 双指针分工要明确:fast 找元素,slow 定位置;

3. 审题注意隐藏条件:本题强制要求「非零相对顺序不变」;

4. 双指针是数组、链表高频考点,本题是入门经典模板,建议默写熟记。

九、写在最后

LeetCode 283 是数组双指针的第一道模板题。

核心解题思路就一句话:

快指针扫描、慢指针占位,非零前置、零值后置。

吃透这道题,后续重复元素删除、数组元素移动类题目都可以用同套思想秒杀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值