【基础算法总结】双指针

news2024/11/13 14:58:43

目录

  • 一,双指针算法介绍
  • 二,算法原理和代码实现
    • 283.移动零
    • 1089.复写零
    • 202.快乐数
    • 11.盛最多水的容器
    • 611.有效三角形的个数
    • LRC179.和为s的两个数
    • 15.三数之和
    • 18.四数之和
  • 三,算法总结

一,双指针算法介绍

双指针算法是基础算法之一,一般用于涉及数组分块/数组划分这类问题 。这里的"指针"是利用数组下标或是一个数来充当的

在遍历过程中,两个指针的位置:
cur:从左往右扫描数组,遍历数组
dest:指向已处理的区间内,非0元素的最后一个位置。如下图
在这里插入图片描述
所以两个指针把数组分成了三个区间
在这里插入图片描述

二,算法原理和代码实现

283.移动零

在这里插入图片描述

在这里插入图片描述

算法原理:

我们也是定义两个变量 cur 和 dest,根据上面介绍的两个指针的位置初始化 cur = 0, dest = -1

在 cur 从前往后遍历的过程中,无非两种情况
1. 遇到0元素:cur++
2. 遇到非0元素:先swap(dest+1, cur), 再cur++, dest++

代码实现:

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

1089.复写零

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

算法原理:

这道题看起来简单,但是有很多坑,很多细节。我们使用双指针先在草稿纸上模拟,不难发现从前往后复写是不行的,会覆盖后面的数据。但是要如何从后往前复写呢,起始位置怎么确定

所以解决这个题有两个步骤:

1. 先找到最后一个复写的数
这一步骤也要用双指针算法:
在这里插入图片描述
当走完这个双指针,此时 cur 指向的数就是最后一个要复写的数,dest 指向的位置就是开始复写的第一个位置
2. 再从后往前进行复写操作
在 cur 从后往前遍历的过程中,无非两种情况:
(1) 遇到0元素:dest向前复写两个0,cur–,dest -= 2
(2) 遇到非0元素:先arr[dest] = arr[cur], 再cur++, dest++

细节/技巧问题:

(1) 在第一步的第三小步中一定要先判断 dest 是否已经结束
(2) 还要处理一种特殊情况:[1,0,2,3,0,4]。根据第一步的双指针,此时dest会越界,就要做特殊处理当 dest == n 时,直接把 arr[n-1] = 0, cur–, dest -= 2

代码实现:

class Solution
{
public:
    void duplicateZeros(vector<int>& arr)
    {
        // 找到最后一个要复写的位置
        int cur = 0, dest = -1, n = arr.size();
        while (cur < n)
        {
            if (arr[cur]) dest++;
            else dest += 2;

            if (dest >= n - 1) break;
            cur++; // 注意每次++之前都要先判断dest是否越界
        }

        // 处理特殊情况
        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 - 1] = 0;
                dest -= 2;
                cur--;
            }
        }
    }
};

下面是一开始我写的错误代码,以示警戒

class Solution
{
public:
    void duplicateZeros(vector<int>& arr)
    {
        // 先找到最后一个要复写的数
        int n = arr.size();
        int cur = 0, dest = -1;
        for (; dest < n - 1;)
        {
            if (arr[cur] != 0) dest++;
            else dest += 2;

            if (dest != n - 1)
                cur++;
        }

        // 处理边界情况
        if (dest == n) arr[n - 1] = 0, cur--, dest -= 2;

        // 从后往前完成复写操作
        for (; cur >= 0 && dest >= 0; )
        {
            if (arr[cur] != 0) arr[dest] = arr[cur], dest--;
            else arr[dest] = 0, arr[dest - 1] = 0, dest -= 2;

            cur--;
        }
    }
};

202.快乐数

在这里插入图片描述

在这里插入图片描述

算法原理:

首先来理解题目
在这里插入图片描述
所以这道题可以抽象成另类的 “链表是否带环问题”只不过这道题是一定带环的,只要根据环内的数进行判断即可

使用经典的快慢双指针算法
(1) 定义快慢指针
(2) 慢指针每次向后走一步,快指针向后走两步
(3) 判断相遇时候的值即可

拓展:为什么这个题一定成环?

使用鸽巢原理证明
在这里插入图片描述

细节/技巧问题:

(1) 这题的双指针不再是数组下标,而是一个数
(2) 在进入第一次循环时,先让 fast 指向第二个数,不然进不了循环

代码实现:

class Solution 
{
public:
    bool isHappy(int n) 
    {
        int slow = n, fast = mypow(n);
        while(slow != fast)
        {
            slow = mypow(slow);
            fast = mypow(mypow(fast));
        }
        if(slow == 1)
            return true;
        else
            return false;
    }

    int mypow(int n)
    {
        int sum = 0;
        while(n)
        {
            int a = n % 10;
            n /= 10;
            sum += a*a;
        }
        return sum;
    }
};

11.盛最多水的容器

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

算法原理:

解法1:暴力枚举,O(N*N)。这应该是大家心中第一个闪过的想法,就是使用两层for循环计算出全部体积,再求出最大值。但是这个解法超时。

解法2:利用单调性,使用双指针,O(N)
首先观察一个规律:
我们固定一个数,再向外枚举,会出现下面的情况,结果都是缩小,就可以把这个数直接抹去,避免无用枚举
在这里插入图片描述
所以可以把这个规律推广到全部数据:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a1dee81bedce4fab936c08ca8ba7f279.png
定义两个指针指向开头和结尾,此时可以计算出一个体积,根据上面的规律把较小的那个高度直接舍去,指针向里缩,继续计算体积…,直到两个指针相遇,统计出最大体积

细节/技巧问题:

体积= 高度 * 宽度。根据木桶原理,高度是小的那个,而宽度是两个下标相减

代码实现:

class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
        int cur1 = 0, cur2 = height.size() -1;
        int maxV = 0;
        while(cur1 <= cur2)
        {
            int H = min(height[cur1], height[cur2]);
            maxV = max(maxV, (cur2 - cur1) * H);
            // 利用单调性
            if(height[cur1] < height[cur2]) cur1++;
            else cur2--;
        }
        return maxV;
    }
};

611.有效三角形的个数

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

算法原理:

首先补充一个数学知识,只要一次判断,得出是否能构成三角形

若a <= b <= c 且 a +b > c,则可以构成三角形
所以可以得出一个优化:先把数组排序,O(N*logN)

数组排完序后

解法1:暴力枚举,O(N*logN + N^3),三层for循环,绝对超时。

解法2:利用单调性,使用双指针算法,O(N*logN + N^2)
(1) 先用c固定最大的元素,定义left 和right分别指向第一个元素,c的下一个元素。
(2) 若nums[left] + nums[right] > nums[c],由于left和 right之间的数都比 nums[left] 大,与 nums[right] 相加后一定大于c,构成三角形了,就不要一个个枚举了,直接right- -
(3) 此时三角形个数 = right - left
(4) 若nums[left] + nums[right] <= nums[c],由于left和 right之间的数都比 nums[right] 小,与 nums[left] 相加后一定小于c,也不要一个个枚举了,直接left++
(5) 以上是走完一趟的个数,再c–,固定下一个数
在这里插入图片描述

代码实现:

class Solution 
{
public:
    int triangleNumber(vector<int>& nums) 
    {
    	// 排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        
        int ret = 0; // 记录个数
        for(int c = n-1; c >= 2; c--)
        {
            int left = 0, right = c - 1;
            while(left < right)
            {
                if(nums[left] + nums[right] > nums[c])
                {
                    ret += right - left;
                    right--;
                } 
                else left++;
            }
        }
        return ret;
    }
};

LRC179.和为s的两个数

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

算法原理:

解法1:暴力枚举,两层for循环,O(N^2),绝对超时。没有好好利用单调递增这个特性!!

解法2:和上一题的解法2类似,也是利用单调性和双指针算法,O(N)
在这里插入图片描述

细节/技巧问题:

找到数对后就直接 break 结束循环

代码实现:

class Solution 
{
public:
    vector<int> twoSum(vector<int>& price, int target) 
    {
        vector<int> ret;
        int left = 0, right = price.size() - 1;
        while(left < right)
        {
            int sum = price[left] + price[right];
            if(sum > target) right--;
            else if(sum < target) left++;
            else 
            {
                ret.push_back(price[left]); 
                ret.push_back(price[right]);
                break;
            }
        }
        return ret;
    }
};

15.三数之和

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

算法原理:

解法1:排序+暴力枚举+利用set去重,O(N^3).

解法2:排序+双指针,O(N^2).
这道题其实是上一题的进阶版,首先排序,再固定一个数 a,在该数后面的区间内使用双指针算法快速的找到两个数的和是 -a
在这里插入图片描述

难点/细节/技巧:

这道题的算法不难想,难的是保证不重不漏

(1) 去重操作有两个方面:一是找到一种结果后,left 和 right 指针要跳过重复元素,二是当使用完一次双指针后,i 也需要跳过重复元素,此时要注意越界
(2) 还有一个小优化就是当固定的那个数是正数时,后面再也找不到两数和为负数了,直接结束

代码实现:

class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums)
    {
        // 排序
        sort(nums.begin(), nums.end());
        vector<vector<int>> vv;
        int n = nums.size();

        for(int i = 0; i <= n-3; i++) // 固定数 a
        {
            if(nums[i] > 0) break; // 小优化
            int left = i +1, right = n-1, a = nums[i];
            // 双指针
            while(left < right)
            {
                int sum = nums[left] + nums[right];
                if(sum > -a) right--;
                else if(sum < -a) left++;
                else 
                {
                    vv.push_back({nums[i], nums[left], nums[right]});
                    // 去重,注意越界
                    while(left < right && nums[left] == nums[left+1]) left++;
                    while(left < right && nums[right] == nums[right-1]) right--;

                    left++;
                    right--;
                }
            }
            while(i < n-1 && nums[i] == nums[i+1]) i++;
        }
        return vv;
    }
};

18.四数之和

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

算法原理:

本道题又是上一题的进阶版

在这里插入图片描述

难点/细节/技巧:

这道题会出现数据溢出的风险。所以当计算两个固定是数之和时,类型定义为 long long
其余细节参考上一题

代码实现:

class Solution 
{
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        // 排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> vv;
        for(int i = 0; i < n; i++) // 固定数a
        {
            // 三数和
            for(int j = i+1; j < n; j++)// 固定数b
            {
                // 双指针算法
                int left = j+1, right = n-1;
                long long t = (long long)target - (nums[i] + nums[j]);
                while(left < right)
                {
                    long long sum = nums[left] + nums[right];
                    
                    if(sum > t) right--;
                    else if(sum < t) left++;
                    else
                    {
                        vv.push_back({nums[i], nums[j], nums[left], nums[right]});
                        // 去重
                        while(left < right && nums[left] == nums[left+1]) left++;
                        while(left < right && nums[right] == nums[right-1]) right--;
                        left++;
                        right--;
                    }
                }
                while(j < n-1 && nums[j] == nums[j+1]) j++;
            }
            while(i < n-1 && nums[i] == nums[i+1]) i++;
        }
        return vv;
    }
};

三,算法总结

双指针算法是一种基础,但是十分经典的算法。通过上面的若干道题可知,"双指针"使用起来是十分灵活的,有时代指数组下标,有时也可以代指一个数使用双指针算法的关键之一就是要控制好边界,稍不留神就会出现数组越界的问题并且在使用这个算法时强烈建议各位一定要多画图,光靠想象容易出错并且会忽略很多细节问题

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

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

相关文章

【机器学习】朴素贝叶斯方法的概率图表示以及贝叶斯统计中的共轭先验方法

引言 朴素贝叶斯方法是一种基于贝叶斯定理的简单概率模型&#xff0c;它假设特征之间相互独立。 文章目录 引言一、朴素贝叶斯方法的概率图表示1.1 节点表示1.2 边表示1.3 无其他连接1.4 总结 二、朴素贝叶斯的应用场景2.1 文本分类2.2 推荐系统2.3 医疗诊断2.4 欺诈检测2.5 情…

菜鸟入门Docker

初始Docker Docker的概念 Docker的用途 DOcke的安装 Docker架构 配置Docker镜像加速器 Docker常用命令 Docker服务相关的命令。 Docker镜像相关的命令 Docker容器相关的命令 容器的数据卷 数据卷的概念和作用 配置数据卷 Docker应用部署 Docker部署mysql Docker…

RP2040 C SDK clocks时钟源配置使用

RP2040 C SDK clocks时钟源配置使用 &#x1f33f;RP2040时钟源API函数文档&#xff1a;https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_clocks &#x1f341;RP2040时钟树&#xff1a; 系统时钟源可以来自外部时钟输入&#xff08;exte…

<数据集>二维码识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1601张 标注数量(xml文件个数)&#xff1a;1601 标注数量(txt文件个数)&#xff1a;1601 标注类别数&#xff1a;1 标注类别名称&#xff1a;[QR] 序号类别名称图片数框数1QR16016286 使用标注工具&#xff1a;l…

外观模式facade

学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/facade 为程序库、 框架或其他复杂类提供一个简单的接口 把要做的事全放在一个类里给他做了&#xff0c;然后要用的时候直接创建这个类的实例

springboot项目引入Sentinel熔断

本文是springboot项目nacos进行引入&#xff0c;sentiel需自行按照部署 1.springboot包要是2.2.5或以上 <dubbo.version>2.7.7</dubbo.version><spring-boot.version>2.2.5.RELEASE</spring-boot.version><chainwork-boot.version>1.0.5-SNAPSH…

.Net C#检验科LIS系统成品源码 ,LIS 系统与 HIS 系统的连接方式详解

目录 系统定义与功能 应用特点 检验科 LIS 系统与 HIS 系统的连接方式 1、接口设计与数据交换 2、网络架构 3、数据格式与标准化 4、信息共享与协同工作 5、数据安全与隐私保护 6、技术支持与维护 LIS系统成品源码 总结 系统定义与功能 LIS系统&#xff0c;全称为实验…

python tkinter 简介

ttk模块是tkinter模块中非常重要的模块&#xff0c;相当于升级版的tkinter模块。 ttk模块包含18个组件&#xff0c;其中12个组件在tkinter模块中已经存在。这12个模块分别为Button&#xff08;按钮&#xff09;​、Checkbutton&#xff08;复选框&#xff09;​、Entry&#x…

OrangePi AIpro 香橙派 昇腾 Ascend C 算子开发 与 调用 - 通过aclnn调用的方式调用AddCustom算子

OrangePi AIpro 香橙派 昇腾 Ascend C 算子开发 与 调用 通过aclnn调用的方式调用 - AddCustom算子 - 单算子API执行(aclnn) 多种算子调用方式 *开发时间使用场景调用方式运行硬件基于Kernel直调工程&#xff08;快速&#xff09;少单算子调用&#xff0c;快速验证算法逻辑IC…

MySQL复习3

视图 视图&#xff08;view&#xff09;是一种虚拟存在的表&#xff0c;是一个逻辑表&#xff0c;本省没有数据&#xff0c;内容由查询定义。 基表&#xff1a;用来创建视图的表叫做基表 通过视图&#xff0c;我们可以查看基表的部分数据。视图数据来自定义视图的查询中使用…

[Go]-抢购类业务方案

文章目录 要点&#xff1a;1. 抢购/秒杀业务的关键挑战2. 技术方案3.关键实现点4.性能优化建议5.其他考虑因素 细节拆分&#xff1a;1. **高并发处理**2.**限流与防护**3.**库存控制**4. **异步处理**5. **数据一致性**6. **常用架构设计**7. **代码示例**8. 进一步优化9. 注意…

鸿蒙(API 12 Beta6版)图形加速【OpenGL ES平台内插模式】超帧功能开发

超帧内插模式是利用相邻两个真实渲染帧进行超帧计算生成中间的预测帧&#xff0c;即利用第N-1帧和第N帧真实渲染帧预测第N-0.5帧预测帧&#xff0c;如下图所示。由于中间预测帧的像素点通常能在前后两帧中找到对应位置&#xff0c;因此内插模式的预测帧效果较外插模式更优。由于…

android studio 模拟器 loadlibrary failed with 126:找不到指定的模块

loadlibrary failed with 126:找不到指定的模块 解决方法 解决方法&#xff1a;设备管理器-> 显示适配器-> 禁用 AMD Redeon 重启AndroidStudio

【学习笔记】 陈强-机器学习-Python-Ch14 支持向量机

系列文章目录 监督学习&#xff1a;参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归&#xff08;SAheart.csv&#xff09; 【学习笔记】 陈强-机器学习-Python-…

示波器在嵌入式中的作用和使用

你是否在开发嵌入式系统时&#xff0c;遇到过调试电路和信号分析的困难&#xff1f; 在嵌入式开发中&#xff0c;硬件调试和信号分析是必不可少的环节&#xff0c;而示波器作为一种强大的工具&#xff0c;能够帮助我们深入了解信号特性并解决难题。那么&#xff0c;如何正确使用…

Aigtek功率放大器可以驱动哪些传感器设备

功率放大器是一种电子设备&#xff0c;主要用于将输入信号增强到更高的功率级别并驱动各种负载。在传感器应用中&#xff0c;功率放大器可以用来驱动多种传感器设备&#xff0c;下面将介绍几个常见的应用场景。 光学传感器&#xff1a;光学传感器是一类基于光学原理工作的传感器…

Django发送邮件

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 Django 5框架Web应用开发_夏天又到了的博客-CSDN博客 本文学习怎么使用Django发送邮件。 尽管使用Python的smtplib模块发送电子邮件…

Flutter集成Firebase中的Remote Config

远程配置&#xff08;Remote Config&#xff09;的功能 动态配置参数&#xff1a;Remote Config 允许您在不更新应用程序的情况下&#xff0c;实时更改应用程序的参数&#xff0c;如文本、颜色、布尔值等条件化参数&#xff1a;您可以基于用户的特定条件&#xff08;例如用户地…

ADC——模数转换器

一、转换流程 在处理器中主要进行ADC 1、AD转换流程 &#xff1a;采样、保持、量化、编码 通过比较器获得的电信号转换数字信号&#xff0c;根据自己需求&#xff0c;如果要求速率就可以使用较多的比较器&#xff0c;不要求速率考虑成本就可以使用较少的比较器&#xff0c;将最…

【Oracle篇】全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)(第二篇,总共七篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…