双指针(C++)

news2024/11/24 1:06:10

文章目录

  • 1、移动零
  • 2、复写零
  • 3、快乐数
  • 4、盛最多水的容器
  • 5、有效三角形的个数
  • 6、和为s的两个数
  • 7、三数之和
  • 8、四数之和


需要理解的是,双指针并非只有指针,双指针的意思是两个位置。比如对于数组来说,两个下标也是双指针。当然,也可以说,双指针是一种思想。

关于详细的考量,会在题目中展现。常用指针名字:dest和src,left和right,prev和cur,slow和fast

1、移动零

链接

在这里插入图片描述

题目要求很清晰,要求0全部在最后,0前面全部是非0数,那么这时候双指针的作用区分就很明确。左指针的左边都是非0数,左指针的右边则是0。这是一个初步的整体划分,先定义 左右指针为a和b

接下来想想逐步的操作,两个下标位置,也就是两个int数,指向两个数组里的数,然后判断是否为0;一方为0,就要交换一下,让0来到a的右边,也就是让b的位置的数字是0,然后继续遍历。这样一想,随着逐渐的往后走,b指针的右边就是待处理的部分,而ab之间是0,a左边则是非0数;这样3个区间就划分好了。

a指针指向非0数,左边都是非0数,所以a指针指向最后一个非0数,b指针左边都是0,右边是待处理的,所以b指针指向最后一个0;如果走完整个空间,就没有待处理的部分了。

数组最初的位置可以不是0,我们可以让a指针初始化为-1。因为最一开始我们并不知道是否有非0数,所以不如让a为-1,b位置是0,b开始判断是否为0;如果num[b]是0,那么b就++,a不动,这样ab之间就有0了;如果是非0,那就得交换一下,但是需要a指针往前走一步再交换,不能这样访问num[-1],而且如果一直是非0数,a++后再去交换,就相当于是原地交换数值,不发生变化。

class Solution {
public:
    void moveZeroes(vector<int>& nums)
    {
        int n = nums.size();
        int prev = -1, cur = 0;
        while(cur < n)
        {
            if(nums[cur] != 0)
            {
                swap(nums[++prev], nums[cur]);
            }
            ++cur;
        }
    }
};

2、复写零

链接

在这里插入图片描述

这道题一个简单的思路是再开一个数组,一个指针指向原数组,另一个指向新数组,原数组只要出现0,新数组内就写两个0,不是就写一个,直到新数组全部填完。假设是数组[1, 0, 2, 3],a指针指向原数组,b指针指向新开的初始化好的数组。a为1,b就填1;a走到0,b填两个0,b来到第二个0,a来到2;a为2,b填2。

两个数组的情况转成一个的。需要注意的是,0后的数字不能直接覆盖掉,而只是写两遍0,然后接着下一个数字。所以我们得保证其它数字不会被覆盖掉。像这样不能覆盖的问题,有一个常用想法就是从后向前走。为了不被覆盖,先把要复写的数确定下来,这样b指针指向最后一个位置,a指针指向前面某个位置,无论怎样,a都每次往前走一步,而如果有0,b就走两步,否则走一步。

在找最后一个要复写的数的过程中,a指针指向0位置,而b指针指向-1。我们定义b指针是指向结果的,也就是原数组的某个数在结果中应当待的位置,a为非0,b就一步;a为0,b就走两步;当b走到末尾时,也就是结果数组中最后一个数的位置,而此时a应当指向填写这个位置的数。综合考虑,b在-1位置是合适的。可以代入例子来观察。在每一次b指针走动后都去判断是否到了最后一个位置,到了就退出循环,没到就让a++。

但是按照这个思路,还是有问题,这个问题就越界问题。b指针不会乖乖地到数组末尾停止,而是可能会越界。如果在后面,a位置的数都是非0数,那么b不会越界,因为它会乖乖地走到末尾,然后一判断,等于n - 1,那就退出;一旦越界,就说明a指向了一个0,b在n - 2的位置,然后走两步来到了n,所以b只会越界到n位置,因为如果在n - 1时就会退出,也不会走到n + 1位置。面对这个情况,就和归并排序处理边界情况一样,简单操作一下即可:将数组最后一个位置置为0,然后a往前走一步,b往前走两步,回到正确的位置,并继续往前判断并填写。

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

3、快乐数

链接

在这里插入图片描述

这道题是怎么和双指针有关的?

先看一下题。快乐数没有公式,所以我们只能从代码上找思路。一个暴力解法就是直接算,算到1为止。但如果它不是快乐数,要在什么时候停止计算?这就是一个问题,我们要找的就是一个暂停条件。

如果不是快乐数,就会一直循环。对此能想到的停止办法就是参考环形队列,环形链表:定义快慢指针,然后快指针每一步都比慢指针走得多,让快追慢,追上并且在整个过程也没有出现1,就说明这不是快乐数。为什么?fast是如何追上slow的?fast能追上slow说明,fast又走到了开头位置,也就是slow开始的位置,然后追上slow。也就是说,fast走完了一整个循环过程,而这之中都没有出现1,那肯定就不是快乐数。

这里的快慢指针就代表计算出来的数,比如slow计算一次而fast计算两次。由于停止的条件是slow等于fast,所以为了能够进入,fast和slow再初始化时就不能相等,可以让fast先计算一次,反正最后都是会追上slow。

class Solution {
public:
    int bitSum(int n) 
    {
        int sum = 0, ret = 0;
        while (n) 
        {
            ret = n % 10;
            sum += ret * ret;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        if(n == 1) return true;
        int s = n, f = bitSum(n);
        while(s != f)
        {
            s = bitSum(s);
            if(s == 1) return true;
            f = bitSum(bitSum(f));
            if(f == 1) return true;
        }
        return false;
    }
};

也可以不在while里写判断,出来判断return s == 1或者判断f。

4、盛最多水的容器

链接

在这里插入图片描述

这道题是一道练习,只放代码,不写思路。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int l = 0, r = n - 1;
        int res = 0;
        while(left < right)
        {
            res = max(res, (r - l) * min(height[l], height[r]));
            if(height[l] > height[r]) --r;
            else ++l;
        }
        return res;
    }
};

5、有效三角形的个数

链接

在这里插入图片描述

由于三角形三条边需要满足a + b > c,所以O(N)的时间复杂度难以实现,即使固定住一个数,另外两个数也得挨个查看是否满足条件,不过这之中可以减少一些重复的遍历。

首先为了方便后序的遍历,先从小到大排序一下;然后从后往前走,固定最大的那个数,设定k为n - 1,剩下两个数就从前面这几个数中挑,所以k必然要至少为2,留出来两个数,设定前面两个数为i和j,如果nums[i] + nums[j] > nums[k],那就满足条件,当然也可以改成减法判断,这样就避免数值过大。

k固定后,i和j在运动。为了更有效地判断所有可能,让i = 0,j = k - 1,所以此时就能看出来,j肯定是k前面的最大数字,接着判断,如果不满足条件,j不需要更改,因为已经最大了,那么就增加i,直到满足条件就停下。此时nums[i] nums[j] nums[k]就是能组合成三角形的三条边,并且i继续增加也肯定满足条件,所以此时就可以直接加上 j - i,这就是从在j和k固定的情况下,符合条件的所有三元组个数。

这次结束后,j就往前走一步,继续按照上面的步骤去判断。k当然还是固定的,j变成第二个大的数,那么i?i其实也不用改变位置,因为之前更大的j和i前面的数字相加都不满足条件,那么现在更小的j也更不可能满足,所以只移动j就行了,然后从i现在的位置开始继续往后挨个判断。

这样的做法也是嵌套循环,只是减少了一些循环次数。对于k固定后,i和j的变化,以及三角形判断,还有更快的办法,这需要用到一些别的公式,可自行看力扣解析。

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size(), count = 0;
        int i = 0, j = 0, k = 0;
        for(k = n - 1; k >= 2; --k)
        {
            i = 0, j = k - 1;
            while(i < j)
            {
                if(nums[i] + nums[j] > nums[k])
                {
                    count += j - i;
                    --j;
                }
                else ++i;
            }
        }
        return count;
    }
};

6、和为s的两个数

链接

在这里插入图片描述
在这里插入图片描述

这道题可以用双指针来完成,但很多人想到的是哈希吧?这道题只写双指针的思路,哈希方法只写代码。

双指针的思路脱胎于暴力枚举。枚举就是算所有的数字,看看有没有符合要求的,而双指针就是有所选择地计算。

数组本身是升序的。定义ab两个位置,分别指向最小数和最大数,如果将ab相加后依然小于目标值,那么最大值不需要变,因为它已经最大了,所以只需要改变让a++,a指向的值变大,再次判断;如果大于目标值,那就让b–。如果等于,就找到了,直接返回。这里仍然使用减法来判断。

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array, int sum) {
        if(array.empty()) return {};
        int l = 0, r = array.size() - 1;
        int res = 0;
        while(l < r)
        {
            res = sum - array[l];
            if(res < array[r]) --r;
            else if(res > array[r]) ++l;
            else return {array[l], array[r]};
        }
        return {};
    }
};
class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array, int sum) {
        if(array.empty()) return {};
        unordered_map<int, int> um;
        int n = array.size(), ret = 0;
        for(int i = 0; i < n; i++)
        {
            ret = sum - array[i];
            if(um.find(ret) == um.end())
            {
                um[array[i]] = i;
            }
            else return {ret, array[i]};
        }
        return {};
    }
};

无论从时间还是空间上来看,双指针都明显优于哈希表。

7、三数之和

链接

在这里插入图片描述

两数之和为起点。

仔细看一下题,三个数不能相等,并且有相同的三元组只能有一个,比如-1 0 1和1 0 -1,只有有一个作为结果,另一个抛弃。

可以看出来,我们需要去重,在操作之前先对数组排序。用set去重是可行的,但这里不用多用别的容器,仅在这个数组上边判断边去重。

这道题与 和为s的两个数思路很像,我们先固定一个数,在剩下的区间内找符合条件的两个数即可,并且要找到所有的符合条件的组合;三个数都需要去重。不过如果我们固定的数是一个正数,那么它肯定不可能有对应的组合,因为后面的数都是正数,不可能相加为0。

另外一个重要的问题是越界,在代码中体现:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        int i = 0, j = 0, k = 0;
        int sum = 0;
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;

        while(i < n)
        {
            if(nums[i] > 0) break;//小优化
            j = i + 1, k = n - 1;
            while(j < k)
            {
                sum = nums[i] + nums[j] + nums[k];
                if(sum > 0) --k;//大于说明数值过大了, 就让最大的数k减小
                else if(sum < 0) ++j;//小于说明数值过小了, 就让较小的j增加, 因为i固定
                else
                {
                    res.push_back({nums[i], nums[j++], nums[k--]});
                    //去重 
                    while(j < k && nums[k] == nums[k + 1]) --k;
                    while(j < k && nums[j] == nums[j - 1]) ++j;
                }
            }
            //i去重, 如果是for循环就把i++给去掉, 要不会越界
            ++i;
            while(i < n && nums[i] == nums[i - 1]) i++;//i < n, 防止后面都是0, i直接出去了
        }
        return res;
    }
};

去重操作可以根据自己的思路去改变写代码的位置。

8、四数之和

链接

在这里插入图片描述
经历了和为s的两个数以及三数之和,四数之和也有所思路。三数之和以及四数之和都有一个基于暴力而升级的解法,就是排序 + set去重 + 暴力枚举。

四数之和也是利用双指针,或者说多指针。先固定一个数a,然后再固定一个数b,这样就剩下两个数了,然后运用找两数的方法去做。

当然,这个方法也有点暴力。它要处理的问题同样是去重,越界。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;

        long long ret = 0;
        int n = nums.size(), sum = 0;
        int i = 0, j = 0, l = 0, r = 0;
        while(i < n)
        {
            j = i + 1;
            while(j < n)
            {
                l = j + 1, r = n - 1;
                ret = (long long)target - nums[j] - nums[i];
                while(l < r)
                {
                    sum = nums[l] + nums[r];
                    if(sum > ret) --r;
                    else if(sum < ret) ++l;
                    else
                    {
                        res.push_back({nums[i], nums[j], nums[l++], nums[r--]});
                        //去重
                        while(l < r && nums[l] == nums[l - 1]) ++l;
                        while(l < r && nums[r] == nums[r + 1]) --r;
                    }
                }
                ++j;
                while(j < n && nums[j] == nums[j - 1]) ++j;
            }
            ++i;
            while(i < n && nums[i] == nums[i - 1]) ++i;
        }
        return res;
    }
}

long long是为了解决力扣例子的中的数字超大问题。

结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1640020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基础IO认识

回顾文件 我们之前认识文件只是在语言程度上理解&#xff0c;但是我们理解的不够彻底&#xff0c;要想真正理解文件要在os上理解。 简单代码认识 1 #include<stdio.h>2 int main(){3 FILE* fpfopen("log.txt","w");4 if(fpNULL){5 p…

【小浩算法 BST与其验证】

BST与其验证 前言我的思路思路一 中序遍历判断数组无重复递增思路二 递归边界最大值最小值的传递 我的代码测试用例1测试用例2 前言 BST是二叉树一个经典应用&#xff0c;我们常常将其用于数据的查找以及构建平衡二叉树等。今天我所做的题目是验证一颗二叉树是否为二叉搜索树&…

Web,Sip,Rtsp,Rtmp,WebRtc,专业MCU融屏视频混流会议直播方案分析

随着万物互联&#xff0c;视频会议直播互动深入业务各方面&#xff0c;主流SFU并不适合管理&#xff0c;很多业务需要各种监控终端&#xff0c;互动SIP硬件设备&#xff0c;Web在线业务平台能相互融合&#xff0c;互联互通&#xff0c; 视频混流直播&#xff0c;录存直播推广&a…

带环链表问题

带环链表就是字面意思带环的链表&#xff0c;例如以下这三种情况 练习题 1.给定一个链表&#xff0c;判断链表中是否带环. - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;快慢指针&#xff0c;慢指针走一步&#xff0c;快指针走两步&#xff0c;两个指针从链表的起…

硅片和SOI哪个研究方向更好?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;我研一将要结束&#xff0c;即将进入课题组。我们课题组方向有硅片和soi两种方向&#xff0c;这两种方向该如何选择呢&#xff1f; 硅片与…

python离线安装包的方法

python离线安装包的方法 访问对应安装包的镜像文件的网站找到适合自己的whl文件安装 访问对应安装包的镜像文件的网站 https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/<包名>/找到适合自己的whl文件 安装 下载完成后&#xff0c;进入opencv_python-3.4.11.45-c…

这是一个简单网站,后续还会更新

1、首页效果图 代码 <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <title>爱德照明网站首页</title> <style> /*外部样式*/ charset "utf-8"…

标准降水指数(SPI)、标准化降水蒸发蒸腾指数(SPEI)和帕尔默干旱指数(PDSI)的下载、读取和可视化

数据下载网址&#xff1a;WWDT Data (dri.edu)https://wrcc.dri.edu/wwdt/data/PRISM/ 以SPI为例说明&#xff0c; 标准化降水指数(Standardized Precipitation Index, SPI)是由Mckee et al(.1993)分析美国科罗拉多干旱时,发现降水服从偏态分布,基于此提出了标准化降水指数。…

【千帆平台】使用AppBuilder三步手搓应用创建精准多轮对话agent之K12互动式练习题

欢迎来到《小5讲堂》 这是《千帆平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言创建应用应用头像应用名称应用描述角色指令能力扩展开场白 …

gitlab设置保护分支

gitlab设置保护分支方法 进入代码仓库首页&#xff0c;找到settings下的repository并点击进入 找到Protected Branches 下的Exoand按钮&#xff0c;并点击展开 可以看到已经存在默认的保护分支&#xff0c;通常是master/main分支&#xff0c;也可以添加新的保护分支 新建保护分…

使用idm下载百度云被限速 idm下载大文件后要整合 idm下载百度网盘有限制最新解决办法教程 idm限速解除方法

Internet Download Manager简称IDM,是一款Windows系统专业下载加速工具,IDM下载器支持多种类型文件下载,并能完美恢复各种中断的下载任务,是一款Windows平台下的多线程下载器&#xff0c;支持浏览器自动嗅探功能下载资源文件&#xff0c;包括视频、音频以及图片等类型文件&…

BUUCTF:Web 解析(一)

一、[极客大挑战 2019] EasySQL 点击链接进入靶场 登录页面&#xff0c;查看页面源代码发现没留注释 先输入 admin or 11# 提交看看&#xff08;根据题意猜测考察的 SQL 注入&#xff09; 显示 Flag 二、[极客大挑战 2019] Havefun 打开靶场 页面只有一只猫&#xff0c;打开…

区块链 | IPFS:Merkle DAG(进阶版)

&#x1f98a;原文&#xff1a;Merkle DAGs: Structuring Data for the Distributed Web &#x1f98a;写在前面&#xff1a;本文属于搬运博客&#xff0c;自己留存学习。 1 Merkle DAG 当我们在计算机上表示图时&#xff0c;必须通过提供节点和边的具体表示来编码我们的数据…

jenkins 部署springboot 项目

文章目录 持续集成指定tag发布 基于Jenkins拉取GitLab的SpringBoot代码进行构建发布到测试环境实现持续集成 基于Jenkins拉取GitLab指定发行版本的SpringBoot代码进行构建发布到生产环境实现CD实现持续部署 持续集成 为了让程序代码可以自动推送到测试环境基于Docker服务运行…

服务网关GateWay原理

文章目录 自动装配核心类GatewayAutoConfigurationDispatcherHandler请求处理阶段apply方法httpHandler#handle方法WebHandler#handle方法DispatchHanlder#handle方法第一步 getHandler获取请求映射第二步 invokeHandler 请求适配第三步 handleResult请求处理总结 上一篇博文我…

C语言二叉树和堆

二叉树基础知识&#xff1a; 1.栈、队列和顺序表都是线性结构 但是二叉树不是&#xff0c;二叉树是多分支结构 2.任何一棵树都可以拆分为子树和根节点&#xff0c;许多二叉树的相关问题都是用分治的思想进行函数的递归进行解决。 例&#xff1a;前序&#xff0c;中序&#x…

社交媒体数据恢复:推特、Twitter

推特&#xff08;Twitter&#xff09;数据恢复&#xff1a;如何找回丢失的内容 随着社交媒体的普及&#xff0c;越来越多的人开始使用推特&#xff08;Twitter&#xff09;来分享生活点滴、发表观点和获取信息。然而&#xff0c;有时候我们会不小心删除了重要的推文&#xff0…

【牛客网】排列计算

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 如果直接涂色来计算单点权重&#xff0c;2e5*2e5必然超时。 所以用差分进行优化。 3. 代码实现 #include<bits/stdc.h> using name…

漏洞扫描神器:Nessus 保姆级教程(附破解步骤)

一、介绍 Nessus是一款广泛使用的网络漏洞扫描工具&#xff0c;用于发现和评估计算机系统和网络中的安全漏洞。它是一款功能强大的商业工具&#xff0c;由Tenable Network Security开发和维护。 以下是Nessus的一些主要特点和功能&#xff1a; 1. 漏洞扫描&#xff1a;Nessu…

来一篇错题集(虽然简单吧)

一.Assembly via Remainders #include<bits/stdc.h> using namespace std; typedef long long ll; int a[2000]; int b[2000]; int main(){int t;cin>>t;while(t--){int n;cin>>n;for(int i1;i<n-1;i){cin>>b[i];}int x1000000000;//使用1000000000…