双指针算法大总结!!看完这篇再也没有难的双指针题!!例题C++解法!

news2024/12/28 20:41:38

题源acwing、力扣

  • 讲解双指针
  • 题目一:盛最多水的容器
    • 思路
    • AC代码
  • 题目二:查找总价格为目标值的两个商品
    • 思路
    • AC代码
  • 题目三:链表的中间节点
    • 思路
    • AC代码
  • 题目四:环形链表
    • 思路
    • AC代码
  • 题目五:反转字符串中的元音字符
    • 思路
    • AC代码
  • 题目六:最长连续不重复子序列
    • 思路
    • AC代码
  • 题目七:判断子序列
    • 思路
    • AC代码

讲解双指针

使用双指针是降低算法复杂度的一个有效途径,有些问题的暴力解法时间O(n2),但是使用双指针可以大幅度降低算法复杂度。
和贪心算法一样,双指针难在想不到
常用的双指针法有以下几类:
左右指针:两个指针,相向而走,中间相遇
快慢指针:两个指针,有快有慢,同向而行
灵活运用:两个指针,灵活运用,伺机而动
下面从例题里感受这三类情况

题目一二是左右指针
题目三四是快慢指针

题目一:盛最多水的容器

题源力扣11.点击跳转力扣

给定一个长度为 n 的整数数组 height 。
有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
在这里插入图片描述
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

思路

最优解法为左右双指针算法,双指针法的难点在于难以想到也难以证明,需要大量的刷题去练习题感
暴力法:
找出每一种情况,求出盛水值,最大的就是答案

  1. i 指左挡板,从第一块到遍历倒数第二块
  2. j 指右挡板,从倒数第一块遍历到 i 后面一块
  3. res 保存最大值,不断更新
  4. 返回 res
class Solution{
publicint maxArea(vector<int>& height){
		if(height.size() <= 1) return 0;
		int res = 0;
		for(int i = 0; i < height.size() - 1; i ++)//以 i 为左挡板,从 0 开始
			for(int j = height.size() - 1; j > i; j --){
				int l = j - i;//底边
				int h = min(height[i], height[j]);
				res = max(res, l * h);
			}
		return res;
	}
}

答案正确,但是会超时,双指针算法还有可以优化的空间

优化:
假设S[l,r]为左挡板为l,右挡板为r的盛水值,那么我们有多少种情况?
在这里插入图片描述
再回顾一下,height数组[1,8,6,2,5,4,8,3,7]
暴力法里面,我们将所有情况都计算了一遍,实际上有很多情况不需要计算,因为一定不是答案
假设现在挡板是S[0,8],左高度是1,右高度是7,其中低一点高度的是1,现在移动右挡板还有意义吗?
没有意义
只要是向内移动右挡板,底边长一定变短,如果移动后高度比1高,就会变成底边长变短,高度不变,盛水量变小
只要是向内移动右挡板,底边长一定变短,如果移动后高度比1低,就会变成底边长变短,高度变低,盛水量变小

那么移动左挡板有意义吗?
有意义
只要是向内移动左挡板,底边长一定变短,移动后如果比1高,就会是底边长变短,高度变高,这种情况值得计算!

所以总结一下就是,每一种S[l,r]的情况下,计算后需要比较 l 和 r 的高度,如果height[l] < height[r]就执行 l ++
如果height[l] > height[r]就执行 r –

AC代码

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

题目二:查找总价格为目标值的两个商品

题源力扣179.点击跳转力扣

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

思路

题目很容易想到双指针,但是如果不留意冗余情况也容易超时
暴力法:
穷举每一种情况,如果两数之和等于target,就输出答案

  1. i 指向第一个数,从 0 到 price,size() - 2
  2. j 指向第二个数,从price.size() -1 到 i + 1
  3. 如果price[i] + price[j] == target,返回price[i], price[j]]
    不贴代码了,因为我感觉没人会真的暴力吧,不会吧不会吧(罒ω罒)

优化
暴力法显然没有考虑到数组有序的条件
小时候听说过1+2+3+…+99,问有多少个100,巧妙算法咋算的?是不是头加尾,1 + 99, 2 + 98…
这题也是一样的
j 应该从price.size() - 1开始
不多说了,这题很容易想

AC代码

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int j = price.size() - 1;
        for(int i = 0; i < price.size() - 2; i ++){
            while((price[i] + price[j]) > target) j --;
            if(price[i] + price[j] == target) return vector<int>{price[i], price[j]};
        }
        return {-1, -1};
    }
};

题目三:链表的中间节点

题源力扣876.点击跳转力扣

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
在这里插入图片描述
这题是快慢指针,快慢指针是指移动的步长,即每次向前移动速度的快慢。(例如,让快指针每次前移动2步,慢指针每次向前移动1步)。

思路

暴力法:

  1. 用一个指针遍历一遍链表,求出链表的最后一个节点编号n
  2. 中间节点为n/2,向上取整
  3. 用一个指针从表头往后走n/2向上取整步,指向的节点就是答案
class Solution{
public:
	ListNode* middleNode(ListNode* head) {
		if(!head) return NULL;
		int n = 0;
		ListNode *p = head;
		while(p->next){
			n ++;
			p = p->next;
		}
		int mid = (n + 1) / 2;
		p = head;
		while(mid --){
			p = p->next;
		}
		return p;
	}
}

暴力法可以AC,但是还有优化的空间

优化:
快慢指针,想象两个人跑步,一个人速度是另一个人的两倍,当速度快的到达终点的时候,另一个一定在中间,公式S=tv,t相同,v1 = 2*v2,那么路程也是两倍

AC代码

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
       if(!head) return NULL;
       ListNode *fast, *slow;
       fast = head;
       slow = head;
       while(fast->next)
       {
           fast = fast->next;
           if(fast->next) fast = fast->next;//fast走两步
           slow = slow->next;//slow走一步
       }
       return slow;
    }
};

题目四:环形链表

题源力扣141.点击跳转力扣

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。
在这里插入图片描述

思路

暴力法
遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,使用哈希表来存储所有已经访问过的节点。每次到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到遍历完整个链表即可。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> h;//保存访问过的节点
        while (head != nullptr) {
            if (h.count(head)) {//如果当前访问节点在h中,则有换
                return true;
            }
            h.insert(head);//将当前节点放入h
            head = head->next;
        }
        return false;
    }
};

暴力法也能AC

优化
快慢指针做,一个人快一个人慢,如果有圈,两个人必然相遇,一个人跑两圈,第二个人跑一圈,会在起始位置相遇

AC代码

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL || head->next == NULL) return false;
        ListNode *fast, *slow;
        slow = head;
        fast = head->next;
        while(slow != fast){
            if(fast == NULL || fast->next == NULL) return false;
            fast = fast->next->next;
            slow = slow->next;
        }
        return true;
    }
};

题目五:反转字符串中的元音字符

题源力扣345.点击跳转力扣

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。
元音字母包括 ‘a’、‘e’、‘i’、‘o’、‘u’,且可能以大小写两种形式出现不止一次。
在这里插入图片描述

思路

双指针法:
定义指针i, j, 初始化时指针i = 0, j = s.length() - 1;
从指针i开始从左到右遍历以找到第一个元音字符,从指针j开始从右到左遍历以找到第一个元音字符;
交换指针i与指针j锁指向的元音字符即可;
指针i和指针j在遍历过程中要注意数组越界情况。

AC代码

class Solution {
public:
    bool isVowel(char c){
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c =='A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
    }
    string reverseVowels(string s) {
        int i = 0;
        int j = s.size() - 1;
        while(i < j){
            if(!isVowel(s[i])){
                i ++;
                continue;
            }
            if(!isVowel(s[j])){
                j --;
                continue;
            }
            swap(s[i ++], s[j --]);

        }
        return s;
    }
};

题目六:最长连续不重复子序列

题源acwing799点击跳转acwing

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
在这里插入图片描述

思路

  1. 遍历数组a中的每一个元素 a[i],对于每一个 i ,找到j使得双指针 [j,i] 维护的是以a[i]结尾的最长不重复子序列,长度为 i - j + 1,将这一长度与r的较大者更新给r。
  2. 对于每一个 i ,如何确定 j 的位置:由于[j,i - 1]是前一步得到的最长连续不重复子序列,所以如果[j,i]中有重复元素,一定是a[i],因此右移 j 一直到 a[i] 不重复为止,(由于[j, i - 1]已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j具有“单调性”、本题可用双指针降低复杂度)。
  3. 用数组 s 记录子序列 a[j ~ i] 中各元素出现次数,遍历过程中对于每一个 i 有四步操作:cin元素a[i] -> 将a[i]出现次数s[a[i]]加1 -> 若a[i]重复则右移j(s[a[j]]要减1) -> 确定j及更新当前长度i - j + 1给r。

细节:当a[i]重复时,先把a[j]次数减1,再右移j。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int s[N];
int main(){
    int n;
    int r = 0;
    cin >> n;
    for(int i = 0, j = 0; i < n; i ++){
        cin >> a[i];
        s[a[i]] ++;//记录个数
        while(s[a[i]] > 1){
            -- s[a[j ++]];
        }
        r = max(r, i - j + 1);
    }
    cout << r;
}

题目七:判断子序列

在这里插入图片描述

思路

双指针算法
这题能扩展一种动态规划算法

  1. j 指针用来扫描整个b数组,i 指针用来扫描a数组。若发现a[i] == b[j],则让 i 指针后移一位。
  2. 整个过程中,j 指针不断后移,而i指针只有当匹配成功时才后移一位,若最后若 i == n,则说明匹配成功。

动态规划
bool dp[i][j] = true 表示a数组0 ~ i的序列是b数组0 ~ j序列的子序列
递推公式:
if(a[i - 1] == b[j - 1]) dp[i][j] = dp[i - 1][j - 1]
else dp[i][j] = dp[i][j - 1],i是不能减的,无论无何都要确保a数组的所有元素都在b数组中,所以只能b退一步去比较,

AC代码

//双指针
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n; i++) scanf("%d",&a[i]);
    for(int j = 0;j < m; j++) scanf("%d",&b[j]);

    int i = 0;
    for(int j = 0;j < m; j++)
    {
        if(i < n&&a[i] == b[j])  i++;
    }
    if(i == n) puts("true");
    else puts("false");
    return 0;
}
//动态规划
class Solution {
public:
    bool isSubsequence(string s, string t) {
        int lens = s.size();
        int lent = t.size();
        vector<vector<bool>> dp(lens + 1, vector<bool>(lent + 1, false));
        for(int i = 0; i <= lent; i ++) dp[0][i] = true;
        for(int i = 1; i <= lens; i ++){
            for(int j = 1; j <= lent; j ++){
                if(s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = dp[i][j - 1];
            }
        }
        return dp[lens][lent];
    }
};

后续学习中遇到新题还会更新,需要的小伙伴收藏

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

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

相关文章

【Vulnhub靶场AI-WEB-1.0打靶教程】

第一步&#xff1a;查看虚拟机的ip 第二步&#xff1a;扫描ip下开放的80端口 第三步&#xff1a;扫描查到的ip地址下的目录 第四步&#xff1a;访问查到的目录 访问robot.txt 第五步:访问robot.txt显示出的目录 第六步&#xff1a;打开kali终端&#xff0c;使用sqlmap功能 sq…

【Vue3】标签的 ref 属性

【Vue3】标签的 ref 属性 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。…

win10游戏出现错误代码0xc0000142是什么情况?几种方法修复错误代码0xc0000142

错误代码 "0xc0000142" 通常表示应用程序无法正确启动&#xff0c;这经常与系统文件损坏、兼容性问题或环境设置不当有关。下面列出了一些步骤&#xff0c;可能帮助你解决在Windows 10上运行游戏时遇到的0xc0000142错误。 快速修复错误代码0xc0000142问题 1. 以管理…

更新Win11后出现无法检测到相机的情况

使用腾讯会议的时候发现摄像头用不了&#xff0c;显示“未检测到摄像头”&#xff0c;打开设置显示“未找到任何相机” 试了很多方法都解决不了&#xff0c;后用了下面这种方法成功解决&#xff0c;相机就能用了 1.下载360驱动大师&#xff0c;检测下有没有要更新或者添加的驱…

希尔排序【C语言】

希尔排序 前言 在上一篇文章中我们了解了直接插入排序算法&#xff08;建议先阅读&#xff09;&#xff0c;但其实这个算法还是有一定优化空间的。而它优化之后&#xff0c;就变成了另一个大名鼎鼎的排序算法&#xff1a;希尔排序。 希尔排序(Shell’s Sort)是插入排序的一种…

PDF怎么转换成Word?这些工具一键搞定!

在日常生活中&#xff0c;我们经常遇到需要将PDF文件转换成Word文档的情况。PDF怎么转换成Word&#xff1f;一些工具的使用十分重要&#xff01;下文中就为大家推荐几个亲测好用的PDF转换工具。 一、Foxit PDF转换大师&#xff08;365客户端&#xff09; 链接&#xff1a;www…

【Story】编程迷航:从 “ 我怎么才学会 ? ” 到 “ 我怎么这么厉害 ! ”

目录 大学生编程入门指南&#xff1a;选择语言、制定计划与避坑技巧1. 选择适合的编程语言1.1 Python1.2 Java1.3 C/C1.4 JavaScript1.5 SQL 2. 制定有效的学习计划2.1 设定明确的目标2.2 制定学习时间表2.3 选择学习资源2.4 实践和项目 3. 避免常见学习陷阱3.1 避免过度焦虑3.…

7月31日学习笔记 基于域名,Ip,端口多虚拟主机配置以及上线商城系统

一&#xff0c;基于域名的虚拟主机 效果 劫持域名访问指定的文件夹的目录 步骤 1. vim /usr/local/nginx/conf/nginx.conf 2. 创建新的目录 mkdir /html/devopt/ 3. 在 devops 目录下添加新的 index.html 文件 4. 在 http 模块下添加新的 server 模块 1 . 设置监听的端…

数字的位操作——7、9、479、564、231、342

7. 整数反转(中等) 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例 1&…

钢铁无组织排放的超低改造(朗观视觉)

朗观视觉小编观察发现&#xff1a;随着环保政策的日益严格和公众对环境质量要求的不断提高&#xff0c;钢铁行业作为重工业的代表&#xff0c;面临着巨大的环保压力。无组织排放作为钢铁企业环保治理的难点之一&#xff0c;如何实现超低排放成为行业关注的焦点。本文将从技术路…

MAC安装mysql以及配置环境变量

安装mysql 下载mysql,网址&#xff1a;MySQL :: Download MySQL Community Server 我下载的版本是mysql-9.0.1-macos14-arm64.dmg 打开&#xff0c;双击 一路点击继续安装即可&#xff1b; 最后需要给root设置密码后就安装完成了 但是打开终端输入mysql&#xff0c;依然显…

河南萌新联赛2024第(三)场:河南大学

传送门&#xff1a;河南萌新联赛2024第&#xff08;三&#xff09;场&#xff1a;河南大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ B 正则表达式 思路&#xff1a;模拟 代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long lo…

狗狗换毛期怎么办?家里狗毛遍地,狗毛空气净化器帮你解决

对于养狗家庭来说&#xff0c;换毛季节的到来无疑是一场家中的“毛发风暴”。特别是养如萨摩耶、金毛等大型长毛犬的朋友们&#xff0c;更是深有体会。每日即便精心梳理&#xff0c;家中仍难以避免地被层层狗毛所覆。狗狗时不时晃一下身体&#xff0c;抖动身上的毛发&#xff0…

【list的模拟实现】

list的模拟实现 小杨 list相关类要实现的接口 namespace yang {// List的节点类template<class T>struct ListNode{ListNode(const T& val T());ListNode<T>* _prev;ListNode<T>* _next;T _val;};//List的迭代器类template<class T, class Ref, cla…

土壤分析仪:解锁土壤奥秘,赋能现代农业的绿色引擎

在广袤无垠的大地上&#xff0c;土壤是生命之源&#xff0c;滋养着万物生长。然而&#xff0c;随着现代农业的快速发展和环境的不断变化&#xff0c;土壤的健康状况日益受到关注。如何科学、精准地了解土壤的性质与养分状况&#xff0c;成为现代农业可持续发展的关键。这时&…

ST-LINK未能串口keil识别的一个可能解决方案(前提驱动安装无问题)

打开这个软件&#xff0c;在点击清除之前&#xff0c;按住单片机复位按钮不放&#xff0c;点击清除按钮&#xff0c;等待3-5秒放开复位按钮&#xff0c;即可清除重置&#xff0c;若提示没识别到&#xff0c;多重复几次&#xff0c;即可重置&#xff0c;重置完成之后再回到烧写软…

兴业严选|朝阳优质好房合集 低至6.3折起~

7月25日&#xff0c;存款挂牌利率迎来今年首次下调。中国工商银行、中国农业银行、中国银行、中国建设银行四家大型商业银行从7月25日起&#xff0c;均下调了人民币存款挂牌利率。这是今年以来大型商业银行首次下调人民币存款利率&#xff0c;也是自2022年9月以来的第五次下调。…

不是ChatGPT模型,第一个GAI是ELIZA,你听说过吗?

人工智能&#xff08;Artificial Intelligence, AI&#xff09;的概念可以追溯到20世纪50年代&#xff0c;当时数学家和计算机科学家开始探讨如何让机器模拟人类智能。1956年&#xff0c;达特茅斯会议被认为是人工智能研究的正式起点。然而&#xff0c;生成式人工智能&#xff…

day7 Excel教程——如何用单元格格式给表格化个妆?(超多干货)

day7 如何用单元格格式给表格化个妆&#xff1f; 目录 1. 单元格内容 Excel中单元格内容分为文本、数值、逻辑值。在没有任何格式下&#xff1a; 文本&#xff1a;左对齐&#xff0c;不能计算 数值&#xff1a;右对齐&#xff0c;可以计算 逻辑值&#xff1a;对/错&#xff0…

XR-Frame 计算相机与场景物体的距离

如下哦 const cameraTransform this.scene.getElementById(camera).getComponent(transform)const modelTransform this.scene.getElementById(yourNodeId).getComponent("transform");if (cameraTransform.worldPosition.distanceTo(modelTransform.worldPosition…