双指针算法(一)

目录

[🔹 移动零(283)](#🔹 移动零(283))

题目要求

核心思路

代码实现

探索思考

[🔹 复写零(1089)](#🔹 复写零(1089))

题目要求

核心思路

代码实现

探索思考

[🔹 快乐数(202)](#🔹 快乐数(202))

题目要求

核心思路

代码实现

探索思考

[💡 刷题总结](#💡 刷题总结)


最近刷了三道经典的算法题,分别是「移动零」「复写零」和「快乐数」,每一道都有巧妙的解题思路。我想把这些思路和对应的代码实现记录下来,也算是一次探索性学习的总结。


🔹 移动零(283)

题目要求

把数组里所有的 0 移动到末尾,同时保持非零元素的相对顺序,必须原地操作

核心思路

这是一个典型的双指针问题:

  • left 指针:指向当前可以放置非零元素的位置(初始为 -1
  • right 指针:遍历整个数组
  • right 遇到非零元素时,就和 left+1 位置的元素交换,这样保证了非零元素的顺序,也把 0 挤到了后面

代码实现

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int left = -1, right = 0;
        while (right < nums.size()) {
            if (nums[right] == 0) {
                right++;
            } else {
                swap(nums[++left], nums[right++]);
            }
        }
    }
};

探索思考

这道题的关键是理解 "原地操作" 的含义。如果允许使用额外数组,直接把非零元素存进去再补零就可以了,但双指针的方法让空间复杂度降到了 O(1),是最优解。


🔹 复写零(1089)

题目要求

遍历数组,遇到 0 就把它复写一遍,后面的元素右移,且不能超过数组长度

核心思路

这道题的难点在于如果直接从前往后复写,会覆盖掉还没处理的元素。所以我们用两次遍历的思路:

  1. 第一次遍历 :用 curdest 两个指针,计算出最终每个元素的目标位置,找到最后一个需要保留的元素。
  2. 第二次遍历 :从后往前复写,这样就不会覆盖未处理的元素。如果最后一个元素是 0 且刚好超出数组长度,需要单独处理。

代码实现

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int cur = 0, dest = -1;
        int n = arr.size();
        // 第一次遍历:找到最终的位置
        while (cur < n) {
            if (arr[cur]) dest++;
            else dest += 2;
            if (dest >= n - 1) break;
            cur++;
        }
        // 处理边界情况:最后一个0复写后超出数组
        if (dest == n) {
            arr[n - 1] = 0;
            cur--;
            dest -= 2;
        }
        // 第二次遍历:从后往前复写
        while (cur >= 0) {
            if (arr[cur]) {
                arr[dest--] = arr[cur--];
            } else {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
};

探索思考

从后往前的逆向思维是这道题的精髓。如果正向操作,每次遇到 0 都要移动后面的元素,时间复杂度会达到 O(n²),而两次遍历的方法把时间复杂度降到了 O(n),非常高效。


🔹 快乐数(202)

题目要求

判断一个数是不是快乐数:反复计算各位数字的平方和,最终如果能得到 1 就是快乐数,否则会进入无限循环。

核心思路

这道题的关键是如何判断 "无限循环"。我们可以用 ** 快慢指针(Floyd 判圈算法)** 来检测循环:

  • slow 指针:每次计算一次平方和
  • fast 指针:每次计算两次平方和
  • 如果存在循环,fast 最终会追上 slow;如果是快乐数,slow 会先到达 1

代码实现

cpp 复制代码
class Solution {
public:
    int bitsum(int n) {
        int sum = 0;
        while (n) {
            sum += pow(n % 10, 2);
            n = n / 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int slow = n, fast = bitsum(n);
        while (slow != fast) {
            slow = bitsum(slow);
            fast = bitsum(bitsum(fast));
        }
        return slow == 1;
    }
};

探索思考

这道题也可以用哈希表来记录已经出现过的数字,但快慢指针的方法不需要额外空间,空间复杂度是 O(1)。而且判圈算法在很多链表问题中也会用到,是一个非常通用的技巧。


💡 刷题总结

这三道题虽然看起来不同,但都用到了双指针的核心思想:

  • 移动零:同向双指针,一个负责找非零元素,一个负责放置
  • 复写零:两次遍历,正向找位置,逆向写元素
  • 快乐数:快慢指针,检测循环

探索性学习的乐趣就在于,你会发现很多看似无关的问题,背后都有相通的解题思路。多总结、多思考,才能真正把算法内化成自己的能力。

相关推荐
放下华子我只抽RuiKe55 小时前
算法的试金石:模型训练、评估与调优的艺术
人工智能·深度学习·算法·机器学习·自然语言处理·数据挖掘·线性回归
oem1105 小时前
C++中的享元模式实战
开发语言·c++·算法
流云鹤5 小时前
每日一题0316
算法
leonkay6 小时前
Golang语言闭包完全指南
开发语言·数据结构·后端·算法·架构·golang
颜酱6 小时前
BFS 与并查集实战总结:从基础框架到刷题落地
javascript·后端·算法
casual~6 小时前
第?个质数(埃氏筛算法)
数据结构·c++·算法
仰泳的熊猫7 小时前
题目2308:蓝桥杯2019年第十届省赛真题-旋转
数据结构·c++·算法·蓝桥杯
hssfscv7 小时前
力扣练习训练2(java)——二叉树的中序遍历、对称二叉树、二叉树的最大深度、买卖股票的最佳时机
java·数据结构·算法
y = xⁿ8 小时前
【LeetCodehot100】二叉树大合集 T94:二叉树的中序遍历 T104:二叉树的最大深度 T226:翻转二叉树 T101:对称二叉树
后端·算法·深度优先
不想看见4048 小时前
Search a 2D Matrix II数组--力扣101算法题解笔记
数据结构·算法