【算法C++】双指针(一)

双指针的基本思想:

用两个指针在序列上移动,配合条件减少循环次数,把 O (n²) 暴力优化到 O (n),一般只用一趟或者两趟遍历。

两个指针通常叫左指针、右指针(慢指针、快指针),按移动方式分成两类:

  1. 快慢指针(同向双指针) 两个指针朝同一个方向走,一个走得快、一个慢。 常见场景:链表找环、链表中点、滑动窗口、移除数组元素。 核心:快指针负责探测,慢指针标记有效位置。
  2. 对撞指针(相向双指针) 左指针在开头,右指针在末尾,往中间靠拢。 前提:数组有序。 常见场景:两数之和、三数之和、反转数组、判断回文。 核心:根据当前和 / 大小关系,决定移左还是移右。

关键点

  • 双指针本质:利用单调性,避免重复枚举
  • 不一定同时移动,每次只动其中一个
  • 适用:数组、字符串、链表;大多要求有序或者有单调性质

练习题:

移动零

「数组分两块」是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部
分。这种类型的题,⼀般就是使⽤「双指针」来解决。
1. 题⽬链接:283. 移动零 - 力扣(LeetCode)
2. 题⽬描述:
给定⼀个数组 nums ,编写⼀个函数将所有 0 移动到数组的末尾,同时保持⾮零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进⾏操作。
⽰例 1:
输⼊: nums = 0,1,0,3,12
输出: 1,3,12,0,0
⽰例 2:
输⼊: nums = 0 输出: 0
3. 解法(快排的思想:数组划分区间 - 数组分两块):

底层逻辑:设置dest 和 cur 把,数组分为三个部分,dest 之前的数组为无0原顺序的数组,dest 和 cur 的都为储存0元素,cur 到 数组末的部分为还未检查部分,cur == nums.size(),检查完毕

dest 和 cur 运行逻辑:

cur 如果不为0的元素那么就与 dest 的位置进行 swap 调换,dest ++,如果是为0不做调换,cur每次进行++操作确保不会死循环。

代码展示:

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        size_t dest,cur;
        dest = cur = 0;

        while(cur!=nums.size())
        {
            if(nums[cur]!=0)
            {
                std::swap(nums[dest++],nums[cur]);
            }
            cur++;
        }
    }
};

复写零

1089. 复写零 - 力扣(LeetCode)

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地进行上述修改,不要从函数返回任何东西。

示例 1:

复制代码
输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

复制代码
输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]

我的想法:

通过消耗空间,建一个目标数组,在进行拷贝给arr数组,达到目的。

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        vector<int> Marr;
        for(size_t i = 0,j=0;j<arr.size(),i!=arr.size();i++)
        {
            if(arr[i]==0)
            {
                j+=2;
                Marr.push_back(0);
                Marr.push_back(0);
            }
            else
            {
                Marr.push_back(arr[i]);
                j++;
            }
        }
        for(size_t i = 0;i<arr.size();i++)
        {
            arr[i]=Marr[i];
        }
    }
};

最优解法:
如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆
盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两
步:
i. 先找到最后⼀个复写的数;
ii. 然后从后向前进⾏复写操作。

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int dest = -1,cur = 0,n = arr.size();
        while(cur < n )
        {
            if(arr[cur])
                dest++;
            else
                dest+=2;
            if(dest >= n - 1) break;
            cur++;
        }
        if(dest == n)
        {
            arr[n - 1] = 0;
            dest -= 2,cur--;
        }
        while(cur>=0 && dest != cur)
        {
            
            if(arr[cur])arr[dest--]=arr[cur--];
            else
            {
                arr[dest--]=0;
                arr[dest--]=0;
                cur--;
            }
        }
    }
};

快乐数:

202. 快乐数 - 力扣(LeetCode)

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

我的思路:

正常解题,一步一步解析分析出2为什么不是快乐数,进行计算。

发现:

2->4->16->37->58->89->145->42->20->4这个死循环里面,发现如果数据一旦到了4的时候就是死循环,就符合非快乐数,所以进行循环4的拦截。

cpp 复制代码
class Solution {
public:
    bool isHappy(int n) {
        int count,_n;
        
        while(n!=1 && n!=4)
        {
            count = 0;
            while(n!=0)
            {
                _n = n%10;
                count +=(_n*_n);
                n/=10;
            }
            n = count;
        }
        return n == 1;
    }
};

但缺点也很明显,需要计算,而却也不能真的确定真的就是4会导致死循环,有可能还存在其他死循环的可能性,不能真的解决问题。

快慢指针解题:

如果是非快乐数就会在一定的有序的数字之间跳动,类似循环,快乐数就会排到一定的数值都会变成1,1,1的循环,有快慢指针解决的话,在有循环的数值又快指针追到慢指针或者在1的循环快指针追到慢指针,也确实可以实现证明是否为快乐数。

cpp 复制代码
class Solution
{
public:
    int isfun(int n) // 返回 n 这个数每⼀位上的平⽅和
    {
        int sum = 0;
        while(n)
        {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        int slow = n, fast = isfun(n);
        while(slow != fast)    
        {
            slow = isfun(slow);
            fast = isfun(isfun(fast));
        }
        return slow == 1;
    }
};
相关推荐
seowmt1 小时前
GEO效果该什么时候看数据:从立项到复盘的时间线复盘
算法·ip·seo·geo·faq
jimy11 小时前
lvalue、xvalue、prvalue(二):引用是哪一类?
开发语言·c++·1024程序员节
小豆庄1 小时前
本地视频脱敏的追踪系统踩坑——车牌过滤、转场误判与ID冲突排查
人工智能·算法·目标检测·音视频·视频·敏捷流程·纹理贴图
忆挽篱笙歌1 小时前
进程替换函数与shenll命令行解释器
前端·javascript·算法
YukiRinLL1 小时前
字符串存储与 BF、KMP 匹配算法
数据结构·算法
小超人诞生日记1 小时前
Linux:环境变量 & 进程虚拟地址空间
linux·算法
杜 硕1 小时前
从零开始的C++生活——第三讲
开发语言·c++·生活
小白起 v1 小时前
【算法学习 · 01】深入理解大 O 表示法:从「能跑就行」到「专业评估」算法性能
算法
梦帮科技1 小时前
脑机接口(BCI)与神经系统安全:EEG 神经电信号欺骗注入、神经闭环解码算法与生物黑客反制
算法·安全