基础算法--双指针【概念+图解+题解+解释】

news2024/10/1 21:27:07

 更多精彩内容.....

🎉❤️播主の主页✨😘

Stark、-CSDN博客

本文所在专栏:

数据结构与算法_Stark、的博客-CSDN博客

其它专栏:

学习专栏C语言_Stark、的博客-CSDN博客

项目实战C系列_Stark、的博客-CSDN博客​​​​​​

座右铭:梦想是一盏明灯,照亮我们前行的路,无论风雨多大,我们都要坚持不懈。


双指针概念

双指针技巧是 C++ 编程中的一个常用且强大的方法,特别是在处理数组或链表问题时。这种技巧通常有两种主要形态:滑动窗口和快慢指针。接下来,我将详细讲解这两种方法,包括基本原理、使用场景以及代码示例。

一、滑动窗口(Sliding Window)

滑动窗口可以用来解决一系列数组或字符串问题,尤其是当需要处理“连续”子数组/子字符串时特别有用。其基本思想是使用两个指针(或索引)来表示一个窗口的起始和结束位置,通过移动这些指针来逐步扩展或收缩窗口。

1. 基本思路
  • 使用两个指针(left 和 right),left 表示窗口的开始位置,right 表示窗口的结束位置。
  • 增加 right 拓展窗口,直到满足某个条件。
  • 一旦满足条件,就尝试移动 left 来收缩窗口,直到条件不再满足。
2. 使用场景
  • 查找最长或最短的连续子数组/子字符串。
  • 字符串的无重复字符子串问题。
3. 代码示例

以下是寻找给定字符串中,最长无重复字符子串的代码示例:

#include <iostream>  
#include <unordered_set>  
#include <string>  
using namespace std;

int lengthOfLongestSubstring(std::string s) {  
    unordered_set<char> charSet;  
    int left = 0, maxLength = 0;  

    for (int right = 0; right < s.length(); right++) {  
        while (charSet.find(s[right]) != charSet.end()) {  
            charSet.erase(s[left]);  
            left++;  
        }  
        charSet.insert(s[right]);  
        maxLength = max(maxLength, right - left + 1);  
    }  

    return maxLength;  
}  

int main() {  
    string s = "abcabcbb";  
    cout << "Longest substring without repeating characters: " ;
    cout << lengthOfLongestSubstring(s) << std::endl;  
    return 0;  
}  

二、快慢指针(Fast and Slow Pointers)

快慢指针技巧通常用于链表和数组中,其基本概念是使用两个指针以不同的速度遍历结构。

1. 基本思路
  • 一个指针(慢指针)每次向前移动一步,另一个指针(快指针)每次向前移动两步。
  • 这种方式使得快指针走得比慢指针快,从而可以检测到特定条件(如环的存在)。
2. 使用场景
  • 检测链表是否有环。
  • 寻找链表的中间节点。
3. 代码示例

以下是检测链表是否有环的代码示例:

#include <iostream>  
using namespace

struct ListNode {  
    int val;  
    ListNode *next;  
    ListNode(int x) : val(x), next(NULL) {}  
};  

bool hasCycle(ListNode *head) {  
    if (!head) return false;  

    ListNode *slow = head;  
    ListNode *fast = head;  

    while (fast && fast->next) {  
        slow = slow->next;         // 慢指针走一步  
        fast = fast->next->next;   // 快指针走两步  
        
        if (slow == fast) {  
            return true;           // 如果相遇,说明有环  
        }  
    }  
    return false;                  // 遍历完没有相遇,说明没有环  
}  

int main() {  
    ListNode *head = new ListNode(3);  
    head->next = new ListNode(2);  
    head->next->next = new ListNode(0);  
    head->next->next->next = new ListNode(-4);  
    head->next->next->next->next = head->next; // 创建环  

    if (hasCycle(head)) {  
        cout << "List has a cycle." << endl;  
    } 
    else {  
        cout << "List does not have a cycle." << endl;  
    }  

    return 0;  
}  

总结/其他场景

双指针技术是一种高效且灵活的算法策略,对于多种问题都可以应用。在使用双指针时,理解问题的结构及条件是至关重要的。熟练掌握滑动窗口和快慢指针后,可以解决很多典型算法问题。

运用双双指针的其它场景:

  1. 有序数组的两数之和:在有序数组中找到两个数,使它们的和等于目标值。
  2. 反转字符串:使用双指针可以有效地反转一个字符串。
  3. 寻找回文串:通过两个指针从两端向中间移动,判断字符串是否回文。
  4. 合并两个有序数组:使用两个指针分别指向两个数组的起始位置,进行合并。

算法真题实训

开胃菜:移动零

283. 移动零 - 力扣(LeetCode)

题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。

在这道题中,我们需要将所有的零全部移动到数组末尾不能改变原来非零元素的相对位置。一开始我们的思路可以暴力一些,直接就是两层循环,实现复杂度为O(n²)的代码。不过考虑到数据范围:

  • 1 <= nums.length <= 104
  • -2^31 <= nums[i] <= 2^31 - 1

这样做就很容易就超时了,不太保险。那么我们必须想办法优化一下。那么我们就可以使用双指针的思想,定义两个变量记录位置。一个表示零定位指针pre,一个表示非零定位指针cur。

通过循环将零定位指针pre移动到第一个零元素位置,非零定位指针cur移动到pre后面的第一个非零元素位置。交换两个指针的值。

class Solution {
public:
    void moveZeroes(vector<int>& v) {
        int n = v.size();
        int pre = 0, cur = 0;//两个指针同时出发
        while (cur < n) {
            swap(v[pre], v[cur]);
            while (pre < n && v[pre])
                pre++; // 如果前面不为0,前面往后走。
            while (cur < n && !v[cur])
                cur++; // 如果后面为0,后面往后走。
            if (pre > cur)
                swap(pre, cur);
        }
    }
};


class Solution {
public:
    void moveZeroes(vector<int>& v) {
        int n=v.size();
        int pre = 0, cur = 1;//两个指针一前一后
	    while (cur<n) {
		    if (!v[pre] && v[cur]) swap(v[pre], v[cur]);//如果前面为0,后面不为零,交换
		
		    while (pre<n&&v[pre])pre++;//如果前面不为0,前面往后走。
		    while (cur<n&&!v[cur])cur++;//如果后面为0,后面往后走。
		    if (pre > cur)swap(pre, cur);
	    }
    }
};

进阶篇:有效三角形的个数 

611. 有效三角形的个数 - 力扣(LeetCode)

题目:给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

同样的,我们可以使用三个指针来循环遍历所有的三元组看是否能构成三角形。进行简单的修改后我们才能通过,只是复杂度会很高。

class Solution {
public:
    int triangleNumber(vector<int>& v) {
		sort(v.begin(), v.end());//先进行排序
		int sum = 0;
		int fst = 0;
		for (auto e : v) {
			if (e == 0)fst++;
			else break;
		}//跳过所有的0元素
		for (int i = fst; i < v.size()-2; i++) {//第一条边,从第一个非零元素开始到最后。
			for (int j = i + 1; j < v.size()-1; j++) {//第二条边,从第一条边的下一个元素开始
				int max = v[j] + v[i];//两边之和
				int min = v[j] - v[i];//两边之差
				int p = 0, q = 0;
				for (int k = j + 1; k < v.size(); k++) {//第三条边从第二条边下一个元素开始
					if (!p && v[k] > min)p = k;
            //如果q还没找到合适的位置,那么这时候只要第三边大于两边之差即可确定范围上限
					if (!q && v[k] >= max)q = k;
            //如果p还没找到合适的位置,那么这时候只要第三边大于两边之和,就不再能确定三角形了
					if (p && q) break;
            //如果两个范围都找到了,提前结束循环。
				}
				if (p&&!q)q = v.size();
            //如果正常结束循环,没有进行break,q就没有被赋值,那么q=v.size();
				sum += (q - p);
            //q、p两个下标位置之间的都可以作为第一、二边的第三边。计入总数。
			}
		}
		return sum;//返回总数。
    }
};

能跑过,但这需要你考虑很多小的细节,还有那么一点可能跑不过测试。我们在算法比赛中不能冒险,所以我们需要其它的方法确保万无一失。什么方法呢?双指针! 双指针是两个指针,我们要找三个元素的关系,怎么办呢?我们可以选择将一个边用来循环,另外两条边进行双指针化解答。

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

感谢大家观看,持续关注博主,了解更多算法。 

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

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

相关文章

【算法竞赛】堆

堆是一种树形结构,树的根是堆顶,堆顶始终保持为所有元素的最优值。 有最大堆和最小堆,最大堆的根节点是最大值,最小堆的根节点是最小值。 本节都以最小堆为例进行讲解。 堆一般用二叉树实现,称为二叉堆。 二叉堆的典型应用有堆排序和优先队列。 二叉堆的概念 二叉堆是一棵…

Mybatis-Plus新花样(二)

多种插件 Mybatis-plus给我们提供了各种各样的插件&#xff0c;方便我们快捷开发。 一. 插件配置 Configuration public class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor new MybatisPlusInter…

CMIS5.2_光模块切应用(Application Selection and Instantiation)

目录 重要概念 DP配置、应用声明、应用码的区别 Control Set Provision 和 Commission ApplyDPInit 和 ApplyImmediate 判断应用是否切换成功 以800G光模块的3个应用对应的DP配置举例 1*800G应用&#xff1a; 2*400G应用&#xff1a; 8*100G应用&#xff1a; 应用声明…

ControlGAN:Controllable Text-to-Image Generation

1 研究目的 当前的生成网络通常是不可控的&#xff0c;这意味着如果用户更改句子的某些单词&#xff0c;合成图像将与原始文本生成的合成图像显着不同&#xff1b;当给定的文本描述&#xff08;例如颜色&#xff09;发生变化时&#xff0c;鸟类的相应视觉属性被修改&#xff0c…

我博客网站又遭受CC攻击了,记录一下

2024.9.29凌晨4点攻击开始&#xff0c;攻击目标是我的图床tc.zeruns.tech和博客blog.zeruns.tech&#xff0c;图床用的cdn是多吉云融合CDN&#xff0c;流量被刷了20GB左右就触发峰值关闭CDN了&#xff0c;HTTPS请求次数被刷了1.1亿次&#xff0c;因为设置了QPS&#xff0c;实际…

Oracle bbed编译安装及配置

1. 什么是bbed &#xff1f; Oracle Block Brower and EDitor Tool,是一个可以对oracle data block进行查看&#xff0c;编辑修改的内置工具。对于bbed&#xff0c;oracle本身是不提供支持的。 2. 如何编译bbed环境&#xff1f; 10g版本&#xff1a; 1) 编译bbed cd $ORACL…

【网络基础】网络常识快速入门知识清单,看这篇文章就够了

&#x1f490;个人主页&#xff1a;初晴~ 在现在这个高度智能化的时代&#xff0c;网络几乎已经成为了空气一般无处不在。移动支付、网上购物、网络游戏、视频网站都离不开网络。你能想象如果没有网络的生活将会变成什么样吗&#x1f914; 然而如此对于如此重要的网络&#xf…

深度学习500问——Chapter17:模型压缩及移动端部署(2)

文章目录 17.4.6 低秩分解 17.4.7 总体压缩效果评价指标有哪些 17.4.8 几种轻量化网络结构对比 17.4.9 网络压缩未来研究方向有哪些 17.5 目前有哪些深度学习模型优化加速方法 17.5.1 模型优化加速方法 17.5.2 TensorRT加速原理 17.5.3 TensorRT如何优化重构模型 17.5.4 Tensor…

Unity中Mesh多种网格绘制模式使用方法参考

Unity中MeshFilter中的Mesh默认情况下使用MeshTopology.Trigangles类型绘制网格&#xff0c;就是通常的绘制三角形网格&#xff0c;实际上Mesh有五种绘制模式&#xff0c;对应MeshTopology的枚举&#xff0c;分别是 Triangles网格由三角形构成。Quads网格由四边形构成。Lines网…

多线程——认识线程(Thread)

目录 前言 一、第一个多线程程序 1.程序编写 2.介绍jconsole 二、创建线程 1.继承Thread类 ①重写run方法 ②重写run方法&#xff0c;使用匿名内部类 2.实现Runnable接口 ①重写run方法 ②重写run方法&#xff0c;使用匿名内部类 ③使用 lambda 表达式 三、多线程…

【吊打面试官系列-MySQL面试题】为表中得字段选择合适得数据类型

大家好&#xff0c;我是锋哥。今天分享关于【为表中得字段选择合适得数据类型】面试题&#xff0c;希望对大家有帮助&#xff1b; 为表中得字段选择合适得数据类型 字段类型优先级: 整形>date,time>enum,char>varchar>blob,text 优先考虑数字类型&#xff0c;其次是…

c++类与对象二

文章目录 C类与对象二类的实例化类对象内存大小计算this指针特性 C类与对象二 类的实例化 用类创建对象的过程&#xff0c;称之为类的实例化 类是对对象进行描述的&#xff0c;限定了类有哪些成员&#xff0c;定义一个类并没有开辟内存空间。例如需要学生填写的个人表格&…

js 如何获取当日零点整的时间戳

最近遇到个问题需要取当日的零点整的时间戳去存取日程 上代码&#xff1a; const timestr new Date().setHours(0, 0, 0, 0) console.log(timestr) 效果展示&#xff1a; Tips&#xff1a;除了 Java 以外的语言需要除以1000 具体视情况而定 Java、js的时间戳都是毫秒级的…

每日一练:腐烂的橘子

994. 腐烂的橘子 - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#xf…

leetcode35--搜索插入位置--二分查找刷题

搜索插入位置 一共会出现下面四种情况&#xff1a; 目标值在数组所有元素之前 目标值等于数组中某一个元素 目标值插入数组中的位置 目标值在数组所有元素之后 首先在二分查找的代码之前处理掉目标值在数组所有元素之前和之后的情况如果目标值在数组中的某个位置&#xff0c…

51单片机系列-按键检测原理

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 独立按键是检测低电平的。 下面我们来看一张对应的电路原理图&#xff1a; 在这张图当中&#xff0c;P1&#xff0c;P2&#xff0c;P3内部都上拉了电阻&#xff0c;但是P0没有&am…

学习记录:js算法(四十九):二叉树的层序遍历

文章目录 二叉树的层序遍历网上思路队列循环 总结 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 图一&#xff1a; 示例 1&#xff1a;如图一 输入&#xff1a;roo…

【QT】亲测有效:“生成的目标文件包含了过多的段,超出了编译器或链接器允许的最大数量”错误的解决方案

在使用dlib开发人脸对齐功能时&#xff0c;出现了”生成的目标文件包含了过多的段&#xff0c;超出了编译器或链接器允许的最大数量的错误“。 主要功能代码如下&#xff1a; #include <QApplication> #include <QImage> #include <QDebug>#include <dlib…

叶绿素透射反射率与波长

本文在分析巢湖水体反射光谱特征的基础上,通过对光谱反射率与叶绿素a 的浓度之间的关系进行分析研究,结果表明,单波段光谱反射率与叶绿素a浓度的相关系数较小,不宜用于估算叶绿素a浓度&#xff0e;光谱反射率比值RFo5.m/Rss.nm.和 690nm反射率的一阶微分均与叶绿素a浓度有较好的…

leetcode每日一题day16(24.9.26)——数组元素和与数字和的绝对差

思路&#xff1a;遍历数组在对数组元素进行求和时并使用while循环求数字和&#xff0c;由于对于一个数&#xff0c;其必定大于其个数位数字的和,所以可以直接对sum进行减 代码 int differenceOfSum(vector<int>& nums) {int ans 0;for (int x : nums) {ans x; // …