【leetcode】双指针算法题

news2024/11/26 9:29:47

文章目录

  • 1.算法思想
  • 2.移动零
  • 3.复写零
    • 方法一
    • 方法二
  • 4.快乐数
  • 5.盛水最多的容器
    • 方法一(暴力求解)
    • 方法二(左右指针)
  • 6.有效三角形的个数
    • 方法一(暴力求解)
    • 方法二(左右指针)
  • 7.两数之和
  • 8.三数之和
  • 9.四数之和

在这里插入图片描述

1.算法思想

常见的双指针有两种形式,⼀种是左右指针,⼀种是快慢指针。

  1. 左右指针:⼀般用于有序的结构中,也称左右指针。
  • 左右指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
  • 左右指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
    left == right (两个指针指向同⼀个位置)
    left > right (两个指针错开)
  1. 快慢指针:⼜称为龟兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
    这种方法对于处理环形链表或数组非常有用。
    其实不单单是环形链表或者是数组,如果研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。
    快慢指针的实现⽅式有很多种,最常用的⼀种就是:
  • 在⼀次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现⼀快⼀慢。

废话不多说,我们来做题。

2.移动零

在这里插入图片描述
leetcode 283.移动零

题目要求我们将数组中的0全部移动到数组的末尾,并且其它元素的相对顺序不变而且不允许开辟额外的数组
那我们应该如何来解决这一题呢?

算法思路:
在本题中,我们可以⽤⼀个cur 指针来扫描整个数组,另⼀个dest 指针⽤来记录⾮零数序列的最后⼀个位置。
根据cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。
在cur遍历期间,使[0, dest] 的元素全部都是⾮零元素,[dest + 1, cur - 1] 的元素全是零

最初,我们不知道非零序列的位置,所以将dest置为-1,cur置为0。cur进行扫描,在扫描过程中:

  • 若cur对应元素不为0,cur后移
  • 若cur对应元素为0,dest先后移,然后再交换cur与dest,最后cur再后移。

在这里插入图片描述

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int dest = -1;
        int cur = 0;
        int n = nums.size();
        while(cur < n)
        {
            //cur不为0,交换
            if(nums[cur] != 0)
            {
                dest++;
                swap(nums[dest],nums[cur]);
            }
            //cur为0,继续后移
            cur++;
        }
    }
};

这样咱们就过啦。
在这里插入图片描述

3.复写零

在这里插入图片描述
leetcode 1089.复写零

方法一

先统计数组中0的个数,计算复写后数组的大小,使用reserve为数组重新开新的空间,在新空间上直接进行复写,不存在数组越界问题;在复写完成后,再使用对数组进行缩容,使其空间保持原状。

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int count = 0;
        int n = arr.size();
        int cur = 0;
        while(cur < n)
        {
            if(arr[cur] == 0)
                count++;
            cur++;
        }
        //开辟新空间
        arr.reserve(n+count);
        //此时cur == n,
        cur--;//重新指向最后一个元素
        int dest = n+count-1;
        
        while(cur >= 0)
        {
            if(arr[cur])
            {
                //不是0,无需复写
                arr[dest--] = arr[cur--];
            }
            else
            {
                //复写0
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
        arr.reserve(n);//恢复数组原状
    }
};

在这里插入图片描述
简单分析一下复杂度:只遍历了数组,时间复杂度为O(N);由于使用了reserve开辟了新空间,空间复杂度:O(N)

方法二

能不能在O(1)的空间复杂度下完成该题呢?

我们可以使用两个指针,cur指向最后一个要写的元素,dest指向数组的最后一个位置。

  • 若cur为0,复写0,dest移动两次
  • 若cur不为0,复写,dest移动一次

那现在的问题就是如何找最后一个要复写的位置。
通过模拟我们可以发现,cur = 0 ,dest = -1;让cur与dest同时走,若cur不为0,则都移动一次;若cur为0,cur移动一次,dest移动两次;直到dest走到数组的末尾,此时cur位置就是最后一个要写的位置

在这里插入图片描述

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

在这里插入图片描述
此时我们发现程序没过,情况又没想全

在这里插入图片描述

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int cur = 0;
        int dest = -1;
        int n = arr.size();
        while(cur < n)
        {
            if(arr[cur] == 0)
                dest += 2;
            else
                dest++;
            //防止越界
            if(dest >= n-1)
                break;
            cur++;
            
        }
        //已经越界,修正
        if(dest == n)
        {
            arr[n-1] = 0;
            dest-=2;
            cur--;
        }
        //从后向前复写
        for( ; cur >=0; cur--)
        {
            if(arr[cur])
                arr[dest--] = arr[cur];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
            }
        }
    }
};

在这里插入图片描述

此时,该代码地时间复杂度为O(N);空间复杂度为:O(1)

4.快乐数

在这里插入图片描述
leetcode 202.快乐数

根据题意,通过画图我们可以发现,这就是一种循环往复地题目。此时我们就可以考虑双指针算法。
在这里插入图片描述
看到这个环形,是不是会想起链表那里有一个判断环形链表的题目,这两题很相似。

我们可以知道,当重复执行x的时候,数据会陷⼊到⼀个「循环」之中。
而「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的(证明参考链表部分),也就是说他们总会相遇在⼀个位置上。如果相遇位置的值是1,那么这个数⼀定是快乐数;如果相遇位置不是1的话,那么就不是快乐数。

class Solution {
public:
    int Sum(int n)
    {
        int sum = 0;
        while(n)
        {
            sum += (n%10)*(n%10);
            n/=10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int slow = n;
        int fast = Sum(n);//让fast先走一步
        while(fast != slow)
        {
            slow = Sum(slow);
            fast = Sum(Sum(fast));
        }
        //当二者相等时,若为1,则是快乐数,否则则不是
        return slow == 1;
    }
};

5.盛水最多的容器

在这里插入图片描述
leetcode 11.盛水最多的容器
首先我们要明白,这个容器的容量是:两个柱子之间的距离×两柱中较矮的一个
所以此题我们可以利用双指针,寻找两柱中组成的容器中最大的一个即可。

方法一(暴力求解)

枚举出能构成的所有容器,找出其中容积最大的值。
容器容积的计算⽅式:
设两指针分别指向水槽板的最左端以及最右端,此时容器的宽度为j - i
由于容器的⾼度由两板中的短板决定,因此可得容积公式:v = (j - i) * min( height[i], height[j])

class Solution {
public:
    int maxArea(vector<int>& height) {
        int v = 0;
        int n = height.size();
        for(int i=0; i<n; i++)
        {
            for(int j = i+1; j<n; j++)
            {
                v = max(v,((j-i)*min(height[i],height[j])));
            }
        }
        return v;
    }
};

在这里插入图片描述

方法二(左右指针)

观察暴力解法以后,我们可以发现一个规律:
当使用最开始的左右区间算出来一个V1后,我们没必要使用这两个区间中较小的一个去和其它数枚举,因为枚举出来的结果一定是小于V1的
在这里插入图片描述
所以,可以按照以下步骤:

  • 先根据当前两柱计算V
  • 舍去两柱子中较小的一个
  • 根据新的柱子再计算体积tmp,V = max(V,tmp)
class Solution {
public:
    int maxArea(vector<int>& height) {
        int v = 0;
        int left = 0;
        int right = height.size()-1;
        while(left < right)
        {
            //先计算当前两柱组成的大小
            int tmp = (right-left) * min(height[left],height[right]);
            v = max(v,tmp);
            if(height[left] < height[right])
                left++;
            else
                right--;
        }
        return v;
    }
};

6.有效三角形的个数

在这里插入图片描述

leetcode 611.有效三角形的个数

我们都知道,组成三角形的条件是:任意两边之和大于第三边。

方法一(暴力求解)

但是使用这个条件只想到一个暴力解法,虽然说是暴⼒求解,但是还是想优化⼀下
判断三⻆形的优化:

  • 如果能构成三⻆形,需要满⾜任意两边之和要大于第三边。但是实际上只需让较小的两条边之和大于第三边即可
  • 因此我们可以先将原数组排序,然后从小到大枚举三元组,一方面省去枚举的数量,另一方面方便判断是否能构成三⻆形。
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(),nums.end());
        int count = 0;
        for(int i=0; i<n; i++)
        {
            for(int j=i+1; j<n; j++)
            {
                for(int k = j+1; k<n; k++)
                {
                    if(nums[i] + nums[j] > nums[k])
                        count++;
                }
            }
        }
        return count;
    }
};

方法二(左右指针)

将数组排序以后我们可以发现,如果我们固定最右侧数为第三条边,就只需使用左右指针找另外两条即可。
而且如果最左侧的数和右侧数加起来已经大于第三边了,那么左侧和右侧之间的数一定大于第三边,无需再枚举。

在这里插入图片描述

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        //先按照升序排序
        sort(nums.begin(),nums.end());
        int max = nums.size() - 1;
        int ret = 0;

        //至少要存在3条边
        while(max >= 2)
        {
            int left = 0;
            int right = max - 1;

            while(left < right)
            {
                if(nums[left] + nums[right] > nums[max])
                {
                    ret += right - left;
                    right--;
                }
                else
                    left++;
            }
            max--;
        }
        return ret;
    }
};

7.两数之和

在这里插入图片描述
leetcode 179.和为target的两个数

由于该数组是升序的,那么我们可以直接使用双指针,计算左右指针之和

  • 若和小于target,左指针右移
  • 若和大于target,右指针左移

在这里插入图片描述

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

8.三数之和

在这里插入图片描述
leetcode 15.三数之和

这一题和上一题两数之和的解法非常类似,唯一的不同点就是:该题不允许有重复的三元组

  • 先排序
  • 固定一个数aim
  • 使用两数之和法,找和为 - aim 的两个数即可
    • 不漏
      • 找到一个数以后,left++,right- -继续找
    • 去重
      • left 与right均需要去重,如果left++后任然和之前的相同,那就继续++;right同理,仍需要 - -
      • aim去重,如果aim移动后,仍然和上一个相等,那就继续移动

在这里插入图片描述

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;
        sort(nums.begin(),nums.end());
        int n = nums.size();
        int i = 0;
        while(i < n)
        {
            if(nums[i] > 0)//若num对应的都已经大于0,那么left 与right就更大,和不可能为0了
                break;
            int aim = -nums[i];//固定一个数,取其相反数
            int left = i+1;
            int right = n-1;
            while(left < right)
            {
                int sum = nums[left] + nums[right];
                if(sum > aim)
                    right--;
                else if(sum < aim)
                    left++;
                else
                {
                    ret.push_back({nums[i],nums[left],nums[right]});
                    //避免遗漏,继续找
                    left++;
                    right--;
                    //left,right去重
                    while(left < right && nums[left] == nums[left-1])
                        left++;
                    while(left < right && nums[right] == nums[right+1])
                        right--;
                }
            }
            i++;
            while(i < n && nums[i] == nums[i-1])
                i++;
        }
        return ret;
    }
};

9.四数之和

在这里插入图片描述
leetcode 18.四数之和
该题是在三数之和的基础之上的变形。

仍然还是采用上面的做法,

  • 先排序
  • 固定一个数a
  • 利用三数之和,固定一个数b,找和为target - nums[a] - nums[b]的数
    • 不漏
      • 找到一个数以后,left++,right- -继续找
    • 去重
      • left 与right均需要去重,如果left++后任然和之前的相同,那就继续++;right同理,仍需要 - -
      • b去重,如果b移动后,仍然和上一个相等,那就继续移动
      • a去重,如果a移动后,仍然和上一个相等,那就继续移动
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> ret;
        int n = nums.size();
        int a = 0;
        while(a < n)
        {
            int b = a+1;
            while(b < n)
            {
                long long aim = (long long)target -nums[a] - nums[b];
                int left = b+1;
                int right = n-1;
                while(left < right)
                {
                    if(nums[left] + nums[right] < aim)
                        left++;
                    else if(nums[left] + nums[right] > aim)
                        right--;
                    else
                    {
                        ret.push_back({nums[a],nums[b],nums[left],nums[right]});
                        left++;
                        right--;
                        while(left < right && nums[left] == nums[left-1])
                            left++;
                        while(left< right && nums[right] == nums[right+1])
                            right--;
                    }
                }
                b++;
                while(b < n && nums[b] == nums[b-1])
                    b++;
            }
            a++;
            while(a < n && nums[a] == nums[a-1])
                a++;
        }
        return ret;
    }
};

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

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

相关文章

k8s学习--基于k8s的ELK日志收集的详细过程

文章目录 FilebeatFilebeat主要特点Filebeat使用场景 ELK简介Elasticsearch简介Elasticsearch主要特点Elasticsearch使用场景 Logstash简介Logstash主要特点Logstash使用场景 Kibana简介Kibana主要特点Kibana使用场景 简单理解 环境一、ELK集群部署1.软件安装2.软件配置及启动(…

【python】PyQt5控件尺寸大小位置,内容边距等API调用方法实战解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Java毕业设计 基于SSM vue新生报到系统小程序 微信小程序

Java毕业设计 基于SSM vue新生报到系统小程序 微信小程序 SSM 新生报到系统小程序 功能介绍 学生 登录 注册 忘记密码 首页 学校公告 录取信息 录取详情 师资力量 教师详情 收藏 评论 用户信息修改 宿舍安排 签到信息 在线缴费 教室分配 我的收藏管理 我要发贴 我的发贴 管理…

STM32外扩SRAM及用法

一.概述 一般单片机有片内的RAM&#xff0c;但都不多&#xff0c;比如&#xff1a;STM32F407ZGT6 自带了 192K 字节的 RAM&#xff0c;对一般应用来说&#xff0c;已经足够了&#xff0c;不过在一些对内存要求高的场合&#xff0c;比如做华丽效果的 GUI&#xff0c;处理大量数据…

ionic7 使用Capacitor打包 apk 之后,设置网络权限

报错处理 在打包的时候遇到过几个问题&#xff0c;这里记录下来两个 Visual Studio Code运行ionic build出错显示ionic : 无法加载文件 ionic 项目通过 android studio 打开报错 capacitor.settings.gradle 文件不存在 ionic7 项目初始化以及打包 apk 这篇文章讲到了如果安装…

Java+前后端分离架构+ MySQL8.0.36产科信息管理系统 产科电子病历系统源码

Java前后端分离架构 MySQL8.0.36产科信息管理系统 产科电子病历系统源码 产科信息管理系统—住院管理 数字化产科住院管理是现代医院管理中的重要组成部分&#xff0c;它利用数字化技术优化住院流程&#xff0c;提升医疗服务质量和效率。以下是对数字化产科住院管理的详细阐述…

您的私人办公室!-----ONLYOFFICE8.1版本的桌面编辑器测评

随时随地创建并编辑文档&#xff0c;还可就其进行协作 ONLYOFFICE 文档是一款强大的在线编辑器&#xff0c;为您使用的平台提供文本文档、电子表格、演示文稿、表单和 PDF 编辑工具。 网页地址链接&#xff1a; https://www.onlyoffice.com/zh/office-suite.aspxhttps://www…

2个方法教你轻松移除pdf文件编辑限制

PDF是一种常见的办公文档格式&#xff0c;常用于文件共享和保护。然而&#xff0c;有时候我们需要编辑PDF文件中的内容&#xff0c;但受到了编辑限制。本文将介绍一些有效的方法&#xff0c;帮助您解除PDF的编辑限制&#xff0c;轻松进行编辑和修改。 一、通过密码取消PDF“限制…

运维锅总详解计算机缓存溢出

本文尝试从缓存溢出、如何平衡防止缓存溢出和OOM、conntrack缓存满载影响及优化措施、TCP/IP协议栈缓存满载影响及优化措施等方面对计算机缓存溢出进行详细分析&#xff0c;最后给出一些缓存满载的Prometheus告警规则。希望对您有所帮助&#xff01; 一、计算机缓存溢出简介 …

卫星轨道平面简单认识

目录 一、轨道平面 1.1 轨道根数 1.2 应用考虑 二、分类 2.1 根据运行高度 2.2 根据运行轨迹偏心率 2.3 根据倾角大小 三、卫星星座中的轨道平面 四、设计轨道平面的考虑因素 一、轨道平面 1.1 轨道根数 轨道平面是定义卫星或其他天体绕行另一天体运动的平面。这个平…

通过端口和进程pid查找启动文件/脚本

今天审计一个程序又让GPT给我上了一课&#xff0c;记一下笔记&#xff1a; 1、首先该程序开启了8080端口&#xff0c;使用如下命令得到pid为1817 netstat -tunlp|grep 80802、使用pid得到父进程 pstree -ps 1817输出结果如下&#xff1a; 3、看出程序是由systemd启动的&…

nginx安装演示(离线安装,直接安装在Linux中)

文章目录 1、创建文件夹 tool / nginx2、把安装文件放到 /opt/tool/nginx 目录下面3、yum install gcc4、yum install gcc-c5、tar -zxvf pcre-8.37.tar.gz6、./configure7、make8、make install9、tar -zxvf openssl-1.0.1t.tar.gz10、./config11、/config 1、创建文件夹 tool…

python绘制领域矩形

问题描述&#xff1a; 使用python书写代码实现以下功能&#xff1a;给定四个点的坐标&#xff0c;调用一个函数&#xff0c;可以使原来的四个点分别向四周上下左右移动15距离&#xff0c;分别记录下移动后的坐标&#xff0c;然后画出内侧矩形和外侧矩形 代码&#xff1a; im…

电脑为什么会提示丢失msvcp140.dll?怎么修复msvcp140.dll文件会靠谱点

电脑为什么会提示丢失msvcp140.dll&#xff1f;其实只要你的msvcp140.dll文件一损坏&#xff0c;然而你的电脑程序需要运用到这个msvcp140.dll文件的时候&#xff0c;就回提示你丢失了msvcp140.dll文件&#xff01;因为没有这个文件&#xff0c;你的很多程序都用不了的。今天我…

Purple Pi OH 更改SDK的编译选项

本文适用于在Purple Pi OH开发板更改SDK编译选项。触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;是华为Laval官方社区主荐的一款鸿蒙开发主板。 该主板主要针对学生党&#xff0c;极客&#xff0c;工程师&#xff0c;极大降低了开源鸿蒙开发者的入门门槛&#xff0c;具有以下…

数据为基 全面布局|美创再入《2024年中国网络安全市场全景图》

近日&#xff0c;网络安全行业研究机构数说安全正式发布《2024年中国网络安全市场全景图》&#xff08;以下简称全景图&#xff09;。 美创科技凭借以数据为中心的全面安全产品布局和领先能力&#xff0c;入榜数据库安全(数据库审计/数据库漏扫/数据库防火墙/数据库加密)、数据…

震撼发布!4M-21:苹果多模态AI巨擘,一键解锁21种模态

前沿科技速递&#x1f680; 来自洛桑联邦理工学院&#xff08;EPFL&#xff09;与苹果科研巨擘的强强联手&#xff0c;震撼发布全新跨时代成果——4M-21模型&#xff01;这一革命性单一模型&#xff0c;突破性地覆盖了数十种高度多样化的模态&#xff0c;通过大规模多模态数据集…

空状态页面设计的艺术与科学

空状态界面是用户在网站、APP中遇到的因无数据展示而中断体验的界面&#xff0c;这个界面设计对于解决用户疑惑有着很大的帮助。那么我们应该如何设计空状态界面呢&#xff1f;空状态是指在界面设计中&#xff0c;没有内容或数据时所显示的状态。它可能出现在各种情况下&#x…

Docker拉取失败,利用 Git将 Docker镜像重新打 Tag 推送到阿里云等其他公有云镜像仓库里

目录 一、开通阿里云容器镜像服务 二、Git配置 三、去DockerHub找镜像 四、编写images.txt文件 ​五、演示 六、其他注意事项 最近一段时间 Docker 镜像一直是 Pull 不下来的状态&#xff0c;想直连 DockerHub 是几乎不可能的。更糟糕的是&#xff0c;很多原本可靠的国内…

EasyExcel 单元格根据图片数量动态设置宽度

在使用 EasyExcel 导出 Excel 时&#xff0c;如果某个单元格是图片内容&#xff0c;且存在多张图片&#xff0c;此时就需要单元格根据图片数量动态设置宽度。 经过自己的研究和实验&#xff0c;导出效果如下&#xff1a; 具体代码如下&#xff1a; EasyExcel 版本 <depen…