C++算法中两夫妻的故事-双指针

千川汇海阔

风正好扬帆



🎥烟雨长虹,孤鹜齐飞的个人主页

🔥个人专栏

寒假带大家手撕算法

期待小伙伴们的支持与关注!!!


目录

双指针的简介 

双指针的介绍#

对撞指针的简介#

对撞指针常见的问题#

对撞指针的解题步骤#

快慢指针的简介#

快慢指针的解题步骤#

双指针的运用场景 

移除元素

思路#

代码的实现#

移动零 

思路#

代码的实现#

 复写零

思路#

代码实现#

 回文判定

题目描述#

输入描述#

输出描述#

输入输出样例

思路#

代码的实现#

 总结:

双指针的简介 

双指针的介绍#

双指针算法是一种常用的算法技巧,它通常用于在数组和字符串中进行快速查找、匹配、排序或移动操作。
双指针并非真的用指针实现,一般用两个变量来表示下标双指针算法使用两个指针在数据结构上进行迭代,并根据问题的要求移动这些指针。双指针往往也和单调性、排序联系在一起。

在数组的区间问题上,暴力法的时间复杂度往往是O(n^2)的,但双指针利用单调性可以优化到O(n)。

常见的双指针模型有:
<1>对撞指针
<2>快慢指针


对撞指针的简介#

对撞指针是指的是两个指针 left right 分别指向序列第一个元素和最后一个元素。然后left指针不断递增,right不断递减,直到两个指针的值相撞或错开 (即left>=right),或者满足其他要求的特殊条件为止。

对撞指针常见的问题#

<1>对撞指针一般用来解决有序数组或者字符串问题 

<2>查找有序数组中满足某些约束条件的一组元素问题:比如二分查找数字之和等问题

<3>字符串反转问题:反转字符串回文数颠倒二进制等问题

对撞指针的解题步骤#

<1>使用两个指针 left right。left 指向序列第一个元素,即:left = 1,right 指向序列最后一个元素,即: right=n。
<2>在循环体中将左右指针相向移动当满足一定条件时,将左指针右移,left ++。当满足另外一定条件时,将右指针左移,right--

<3>直到两指针相撞(即left == right)或者满足其他要求的特殊条件时,跳出循环体


快慢指针的简介#

快慢指针指的是两个指针从开始遍历序列,且移动速度一个快一个慢

移动快的指针被称为快指针,移动慢的指针被称为慢指针

两个指针以不同的速度不同的策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止

快慢指针的解题步骤#

<1>使用两个指针 left right。left一般指向序列的第一个元素,right一般指向序列的第零个元素

<2>在循环体将快慢指针向右移动。当满足一定条件时,将慢指针右移,当满足另一个条件时,将快指针右移

<3>到指针移动到数组的尾端,或者两指针相交,或者满足其他特殊情况时跳出循环

双指针的运用场景 

移除元素

题目链接:移除元素


给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5并且 nums 中的前五个元素为 0 1 3 0 4注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

思路#

题目类型:快慢指针

下面我们假设数组nums = {3,2,2,3},val = 2作为图片讲解案例

<1>首先我们将快慢双指针共同指向首元素,没遇到 val 时两个指针一起走,且left不断给right赋值

<2>当遇到 val 时,right停止脚步,left去寻找共同的目标(非val的数)

<3>找到非 val 的数时,left就将值赋给right,若left没有到数组末尾,right就前进一步

<4>最后返回right就是移除元素 val 后的大小


代码的实现#

class Solution 
{
public:
    int removeElement(vector<int>& nums, int val) 
    {
        int left = 0;
        int right = 0;
        int n = nums.size();  //计算数组大小
        while(left<n)
        {
            if(nums[left] !=  val)
            {
              nums[right++] = nums[left++];
            }
            else
            {
                left++;      //寻找共同的目标
            }
        }
        return right;
    }
};

移动零 


题目链接:移动零


给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]

输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]

输出: [0]

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

思路#

题目类型:快慢指针

下面我们以nums = [0,1,0,3,12]作为图片案例进行讲解

<1>我们可以把数组分为三个操作区间

<2>我们让left指向我们的首元素0,right指向数组-1的位置

<3>开始移动时,left往前走一步(left++),right暂时不动

<3>但left遇到0的时候,right往前走一步,right就到了0的位置,left往前寻找非0元素

<4>然后我们用库函数swap交换left和right的元素内容,交换完后left和right分别往前走一步

<5>以下是以上的循环操作,这样0就被我们移动到数组末尾啦


代码的实现#

class Solution {
public:
    void moveZeroes(vector<int>& nums)
    {
        int left = 0,right = -1;    //初始化双指针
        int n = nums.size();        //计算数组大小
        for(left = 0;left<n;left++) //移动双指针
        {
            if(nums[left])          //left指向的不是0元素就交换
            {
                swap(nums[++right],nums[left]);//交换完,right也要往前走
            }
        }
    }
};

 复写零

题目链接:复写零


给你一个长度固定的整数数组 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]

提示:

  • 1 <= arr.length <= 104
  • 0 <= arr[i] <= 9

思路#

题目类型:快慢指针

下面我们以arr = [1,0,2,3,0,4,5,0]作为图片案例进行讲解

<1>首先我们让left指向最后一个要复写的位置,让right指向数组末位置

<2>从后往前开始复写,将left的内容赋值给right,复写完后,两者一起向前走

<3>如果left的内容为0,right就复写两次

<4>以下是以上的操作循环

<5>注意:在复写操作的时候还需考虑边界问题

解释:当数组n-2的位置上的元素是0,则right在移动的时候会将数组外的一个元素修改成0,这样就发生了数组的越界访问,在力扣上的话会报错

办法:我们只需要将arr[n-1] = 0,right-=2即可


代码实现#

class Solution 
{
public:
    void duplicateZeros(vector<int>& arr) 
    {
        int left = 0,right = -1;
        int n = arr.size();
        while(left<n)//找到最后一个要复写的位置
        {
            if(arr[left])
            right++;
            else
            right += 2;
            if(right>=n-1)break;
            left++;
        }
        if(right == n)//判断边界
        {
            arr[n-1] = 0;
            right -= 2;
            left--;
        }
        while(left>=0)//开始复写
        {
            if(arr[left])
            {
                arr[right--] = arr[left--];
            }
            else
            {
                //到0时right向前复写两次
                arr[right--] = 0;
                arr[right--] = 0;
                left--;
            }
        }
    }
};

 回文判定

题目链接:回文判定


题目描述#

给定一个长度为n的字符串S。请你判断字符串S是否回文

输入描述#

输入仅1行包含一个字符串S。
1<= |S| <=10^6,保证 S 只包含大小写、字母。


输出描述#


若字符串 S 为回文串,则输出Y,否则输出N

输入输出样例

实例一#

输入#

abcba

输出#

Y

实例二#

输入#

abcbb

输出#

N

思路#

题目类型:对撞指针

下面我们以字符串abcba作为图片案例进行讲解

<1>我们让left指向字符串的首位置,right指向字符串的末位置

<2>然后让两个指针双向奔赴,即left++,right--。如果是回文就继续,如果不是回文就返回N

<3>直到left和right面基成功,即left == right,就返回Y


代码的实现#

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+9;
char a[N];
int main()
{
  cin>>a+1;                //我们用字符数组来接受字符串,+1是因为有'\0'
  int n = strlen(a+1);     //计算字符串的长度
  int left = 1,right = n;  //双指针的初始化
  bool flag = true;        
  while(left<right&&flag)
  {
    if(a[left] != a[right])//循环结束的标志
    {
      flag = false;
    }
      left++;
      right--;
  }
  cout<<(flag?"Y":"N")<<endl;
  return 0;
}

 总结:

<1>在遇到双指针问题的时候最好的办法是先画图模拟指针运动找出规律

<2>当遇到有序数组时,应该优先想到  双指针 来解决问题,因两个指针的同时遍历会减少空间复杂度和时间复杂度

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨长虹,孤鹜齐飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值