【算法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;
    }
};
相关推荐
通信小呆呆3 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
老歌老听老掉牙3 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
笑容掩饰我的失落3 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
Severus_black4 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl
2601_967979127 小时前
机器学习必修课:经典算法与Python实战
python·算法·机器学习·实战·课程
程序猿打工仔7 小时前
计算机基础(C语言)考编、专技岗
c语言·开发语言·算法
通信小呆呆7 小时前
电子战干扰技术:从“蛮力压制”到“精准欺骗”,再到“认知博弈”
算法·通信·isac
老师我太想进步了20267 小时前
【数据结构-6】算法美学之单链表(1)
数据结构·算法
xiaoye-duck8 小时前
《C++ 项目:手写大模型接入 SDK》DataManager数据管理模块:SQLite持久化存储
开发语言·数据库·c++·sqlite
希望我万事顺遂8 小时前
力扣hot100-滑动窗口最大值
数据结构·算法·leetcode