算法练习——双指针算法(更新中)

 

一、介绍双指针算法

        双指针(或称为双索引)算法是一种高效的算法技巧,常用于处理数组或链表等线性数据结构。它通过使用两个指针来遍历数据,从而减少时间复杂度,避免使用嵌套循环。双指针算法在解决诸如查找、排序、去重等问题时非常有效。

1.双指针算法的基本思想

        双指针算法的核心思想是通过两个指针(通常是索引)来遍历数组或链表,而不是使用嵌套循环。这两个指针可以是:

  • 快慢指针:一个指针移动速度比另一个快。

  • 左右指针:两个指针从数组的两端向中间移动。

  • 前后指针:两个指针从同一个方向移动,但速度或条件不同。

2.常见的双指针应用场景

1. 快慢指针

快慢指针常用于检测环、找到链表的中间节点等。

示例:检测链表中是否有环

#include <iostream>
using namespace std;struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}
};bool hasCycle(ListNode* head) {ListNode* slow = head;ListNode* fast = head;while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;if (slow == fast) {return true; // 发现环}}return false; // 没有环
}
2. 左右指针

左右指针常用于数组的两端向中间遍历,例如在排序数组中查找两个数的和。

示例:两数之和等于目标值

#include <iostream>
#include <vector>
using namespace std;vector<int> twoSum(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left < right) {int sum = nums[left] + nums[right];if (sum == target) {return {left, right};} else if (sum < target) {left++;} else {right--;}}return {};
}
3. 前后指针

前后指针常用于处理滑动窗口问题或数组中的去重问题。

示例:移除重复项

#include <iostream>
#include <vector>
using namespace std;int removeDuplicates(vector<int>& nums) {if (nums.empty()) return 0;int i = 0; // 前指针for (int j = 1; j < nums.size(); ++j) { // 后指针if (nums[j] != nums[i]) {i++;nums[i] = nums[j];}}return i + 1; // 返回新数组的长度
}

3.双指针算法的优点

  1. 时间复杂度低:通常可以将时间复杂度从 O(n²) 降低到 O(n)。

  2. 空间复杂度低:不需要额外的存储空间,通常为 O(1)。

  3. 逻辑清晰:代码简洁,易于理解和实现。

4.双指针算法的局限性

  1. 适用范围有限:主要用于线性数据结构,如数组和链表。

  2. 需要预排序:某些问题(如两数之和)需要先对数组进行排序。

5.总结

        双指针算法是一种非常实用的技巧,适用于多种问题场景。通过合理使用双指针,可以显著提高算法的效率,减少不必要的计算。在实际应用中,需要根据具体问题选择合适的指针策略(如快慢指针、左右指针等)。

二、实战——题目练习

例一(前后指针)61. 最长不含重复字符的子字符串 - AcWing题库​​​​​​

问题描述

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

假设字符串中只包含从 a 到 z 的字符。

数据范围

输入字符串长度 [0,1000]。

样例

输入:"abcabc"输出:3

 思路:

使用双指针算法,根据观察发现,当使用i,j两个快慢指针表示当前的指针移动到i的最长不重复序列时候,具有单调性,即i向后移动,j必然向右或者不动,不可能向左移动,这一单调性质导致可以使用双指针算法。

当快指针所指元素在两指针区间中重复,则慢指针向右移动,直到快指针所指元素在两指针区间不充复。 每移动一次计算一下不重复字串的长度,并与已有长度比较取最大值.

🌟 滑动窗口法:最长无重复子串的奇妙冒险 🌟

#include <iostream>
#include <string>
#include <algorithm> 
using namespace std;class Solution {
public:int longest(string str) {int st[26] = {0}; // 📖 字符账本:记录26个字母的出现次数(假设只有小写字母)int res = 0;      // 🏆 冠军记录:存储当前找到的最长子串长度int j = 0;        // 🚪 左门卫:维护窗口左边界// 🚀 右门卫i的探险之旅:逐个检查字符串中的字符for (int i = 0; i < str.size(); i++) {// 📌 步骤1:新字符进账本(比如读到'a',st[0]加1)st[str[i] - 'a']++;// 🚧 步骤2:发现重复!左门卫开始清理门户// 当新字符导致重复(账本中该字符数量>1)while (j < i && st[str[i] - 'a'] > 1) {st[str[j] - 'a']--; // 🧹 左门卫扫过的字符从账本擦除j++;                 // 🚶♂️ 左门卫右移一步}// 📏 步骤3:测量当前窗口长度,刷新冠军记录res = max(res, i - j + 1); // ✨ 比如i=2,j=0时长度是3}return res; // 🏁 返回最终找到的最长长度}
};int main() {Solution ans;string str;cin >> str; int result = ans.longest(str);cout << result << endl; return 0;
}

🎮 生动比喻:贪吃蛇版滑动窗口

想象你控制着一条贪吃蛇在字符串上爬行:

  • 蛇头(右指针i):不断向前吞噬字符

  • 蛇尾(左指针j):当吃到重复字符时,蛇尾会收缩直到吐出重复的字符

  • 蛇身:当前窗口内的字符就是蛇的身体

  • 限制规则:蛇的身体不能有重复的字符部件

示例过程:字符串 "abca"

  1. 🐍 蛇吃下 a → 长度1

  2. 🐍 蛇吃下 b → 长度2

  3. 🐍 蛇吃下 c → 长度3

  4. 🐍 蛇吃下 a → 检测到重复!

    • 蛇尾开始收缩:吐出第一个a → 长度变为3(bca)


🧠 核心原理详解

步骤操作作用时间复杂度
1️⃣右指针扩张探索新字符,扩大窗口右边界O(n)
2️⃣检测重复并收缩左边界确保窗口内字符唯一总计O(n)
3️⃣实时更新最大长度记录历史最大值O(1)

时空复杂度

  • ⏳ 时间:左右指针各遍历一次字符串 → O(2n) ≈ O(n)

  • 💾 空间:固定长度的字符计数器 → O(1)


🌰 举个栗子:处理 "abcabcbb"

右指针i当前字符窗口区间操作当前长度最大长度
0a[0,0]记录a11
1b[0,1]记录b22
2c[0,2]记录c33
3a[0,3]发现a重复,收缩窗口到[1,3]33
..................

最终找到最长无重复子串长度为3("abc"或"bca")


🚨 注意事项

  1. 字符范围:代码假设输入为小写字母,若需支持更多字符需扩大数组大小

  2. 边界情况:完美处理空字符串(返回0)、全相同字符等情况

  3. 移动逻辑:左指针的移动是跳跃式的,而非逐步移动,确保高效性

这个算法就像两个默契的探险队员,一个开拓疆土,一个巩固后方,共同找到最长的纯净领地! 🏔️

例二(前后指针)

问题描述

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 n。

第二行包含 n 个整数(均在 0∼1e5 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围

1≤n≤1e5

样例输入

5
1 2 2 3 5

样例输出

3

🎪 超市排队买水果模型 —— 滑动窗口原理详解

🌟 代码原理(生动比喻版)

想象你正在超市排队买水果,规则是:必须选择连续的一篮水果,且每种水果只能出现一次。你需要找到最长的合格水果篮长度。

角色分配

  1. 收银员小姐姐 (指针i):不断扫描新水果放入购物篮右端

  2. 理货员小哥 (指针j):当发现重复水果时,从购物篮左端拿出水果

  3. 智能标签 (mid数组):实时统计每种水果在购物篮中的数量

工作流程

  1. 收银员每拿到一个新水果(i右移),就在标签上+1

  2. 当发现某个水果数量>1(重复),理货员开始从左边拿出水果(j右移),直到重复水果被拿出

  3. 每次操作后测量购物篮长度,记录历史最大值


🧩 代码逐行解析(带详细注释)

#include<bits/stdc++.h>
using namespace std;
const int N =100010;  // 最大水果种类数(假设水果编号不超过10万)
int arr[N], mid[N], n; // arr-水果队列,mid-水果计数表,n-总水果数class Solution{
public:int longest(){int res = 0; // 记录最长无重复水果篮长度// 🛒 开始扫描水果队列(i是右边界,j是左边界)for(int i=0, j=0; i<n; i++) {mid[arr[i]]++; // 🏷️ 将新水果加入计数表(比如苹果+1)// 🚨 发现重复!开始调整左边界(类似超市警报响起)while(j<i && mid[arr[i]] > 1) {mid[arr[j]]--; // 🧹 把最左边的水果拿出篮子j++;           // 📏 左边界右移(购物篮缩短)}// 📐 计算当前购物篮长度,更新最大值res = max(res, i-j+1); }return res; // 🏆 返回最终找到的最大长度}
}; int main()
{Solution ans;cin >> n; // 输入总水果数// 🍎 输入水果队列(比如:苹果2 香蕉3 苹果2 橙子4)for(int i=0; i<n; i++) cin >> arr[i]; int end = ans.longest(); // 🧮 计算最长无重复区段cout << end << endl;     // 📢 输出结果return 0;
}

🌰 举个栗子:处理序列 [2,3,2,4]

步骤当前水果购物篮内容操作当前长度最大长度
12[2]正常放入11
23[2,3]正常放入22
32[2,3,2]发现重复!--
➡️ 拿出左边第一个2[3,2]2
44[3,2,4]正常放入33

最终结果:3(对应子序列 [3,2,4])


🚨 注意事项(常见疑问解答)

  1. 为什么用while不用if

    • 就像超市可能连续拿出多个水果才能消除重复,比如序列 [1,2,1,1] 需要移动j两次

  2. 数组越界问题

    • 代码假设水果编号在0-10万之间,实际使用应根据题目要求调整mid数组大小

  3. 时间复杂度

    • 左右指针各扫描一次,时间复杂度 O(n),比暴力法 O(n²) 高效得多

  4. 空数组处理

    • 当n=0时,返回0,符合逻辑


这个算法就像两个默契的超市员工,一个不断扩展货架,一个及时整理货物,共同找到最完美的商品陈列方案! 🛒✨

例三(左右指针)

1. 题目描述
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。

数组下标从 0 开始。请你求出满足 A[i]+B[j]=x 的数对 (i,j)。

数据保证有唯一解。

输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。

第二行包含 n 个整数,表示数组 A。

第三行包含 m 个整数,表示数组 B。

输出格式
共一行,包含两个整数 i 和 j。

数据范围
数组长度不超过 1e5。
同一数组内元素各不相同。
1≤数组元素≤1e9

输入样例

4 5 6

1 2 4 7
3 4 6 8 9

输出样例

1 1

代码展示 

#include<bits/stdc++.h>
using namespace std;const int N = 100010; // 定义常量N,用于数组的最大长度
int a[N], b[N]; // 定义两个数组a和b
int n, m, target; // 定义变量n表示数组a的长度,m表示数组b的长度,target表示目标值class Solution {
public:vector<int> targetsum() {for (int i = 0, j = m - 1; i < n; i++) { // 初始化i指向数组a的第一个元素,j指向数组b的最后一个元素while (j >= 0 && a[i] + b[j] > target) { // 如果a[i] + b[j]大于目标值,说明b[j]过大,需要减小jj--;}if (a[i] + b[j] == target) return {i, j}; // 如果找到符合条件的数对,返回索引对(i, j)}return {}; // 根据题意,这里不会执行,因为保证有唯一解}
};int main() {Solution ans; // 创建Solution类的对象anscin >> n >> m >> target; // 输入数组a的长度n,数组b的长度m,以及目标值targetfor (int i = 0; i < n; i++) cin >> a[i]; // 输入数组a的所有元素for (int i = 0; i < m; i++) cin >> b[i]; // 输入数组b的所有元素vector<int> end = ans.targetsum(); // 调用targetsum函数,得到结果cout << end[0] << " " << end[1] << endl; // 输出结果中的索引对return 0;
}

这段代码使用双指针技巧高效地寻找两个升序数组中满足和为给定目标值的元素对。以下是其原理的详细解释:

核心思路:

  1. 双指针策略:设定两个指针i和j,i从数组A的起始位置开始(i=0),j从数组B的末尾开始(j=m-1)。

  2. 利用有序性:由于数组是升序排列,当i增大时A[i]的值变大,此时为了保持总和接近目标值,必须减小B[j]的值(即j左移)。

  3. 指针移动逻辑

    • 对于每个A[i],不断左移j直到A[i] + B[j] ≤ 目标值。

    • 若此时和等于目标值,立即返回这对下标。

    • 若和仍小于目标值,则i右移以增大总和。

步骤详解:

  1. 初始化指针:i指向A的最小元素(头),j指向B的最大元素(尾)。

  2. 调整j的位置:对于当前A[i],若A[i]+B[j]过大,则不断左移j,直到找到合适的B[j]使和不超过目标。

  3. 检查匹配:若找到和正好等于目标值的组合,立即返回结果。

  4. 递增i:若当前组合不满足,i右移以尝试更大的A元素,同时j保持在上次调整后的位置(无需重置)。

算法优势:

  • 时间复杂度O(n+m):每个元素最多被访问一次,高效处理大规模数据。

  • 空间复杂度O(1):仅使用固定数量的变量,无需额外存储。

我们可以将这个问题类比成两人合作调整天平平衡的场景,帮助理解双指针的工作原理:


📖 生活场景类比

想象你和小伙伴在实验室调整一架天平。天平左侧的砝码盒(数组A)按重量从小到大排列,右侧的砝码盒(数组B)按重量从大到小排列。你们需要各选一个砝码,使得两侧砝码的总重量刚好等于目标值 x

操作规则:
  1. 你的策略(指针i):从左侧砝码盒最轻的开始(i=0),依次尝试更重的砝码。

  2. 小伙伴的策略(指针j):从右侧砝码盒最重的开始(j=m-1),依次尝试更轻的砝码。

动态调整过程:
  • 情况1:总和太重
    当你选中左侧的砝码 A[i] 时,小伙伴发现当前右侧的砝码 B[j] 加上你的选择后总重量超过了目标值 x。此时小伙伴会主动换一个更轻的右侧砝码(j--),直到总重量不再超标。

  • 情况2:总和合适
    如果调整后的 A[i] + B[j] 正好等于 x,两人立刻停止,成功找到解!

  • 情况3:总和太轻
    如果调整到最轻的右侧砝码后,总重量仍不足,你会换一个更重的左侧砝码(i++),而小伙伴保持当前右侧砝码的位置继续配合。


🌟 类比与代码的映射

生活场景代码逻辑意义
左侧砝码盒(从小到大排列)数组 A 升序排列指针 i 从左向右移动,尝试更大的值
右侧砝码盒(从大到小排列)数组 B 升序排列,但指针 j 从右向左移动指针 j 从右向左移动,尝试更小的值
两人实时沟通调整策略双指针 i 和 j 协同移动通过反向移动缩小搜索范围
找到平衡点后停止返回 {i, j}确保唯一解被高效定位

📝 代码注释与生活场景结合

vector<int> targetsum() {for (int i = 0, j = m - 1; i < n; i++) {  // 你从最轻的左侧砝码开始尝试while (j >= 0 && a[i] + b[j] > target) { j--;  // 小伙伴不断换更轻的右侧砝码,直到总重量不超标}if (a[i] + b[j] == target) {  // 天平平衡,找到解!return {i, j};}// 若总和太轻,你主动换下一个更重的砝码(i++),小伙伴保持当前位置}
}

💡 为什么这个策略高效?

  • 避免重复尝试:一旦小伙伴调整到某个右侧砝码 B[j],后续你更换更重的左侧砝码时,小伙伴只需从上一次的位置继续向左调整,无需从头开始(时间复杂度仅为 O(n + m))。

  • 利用有序性:砝码盒的排列顺序(升序/降序)天然引导指针移动方向,类似“夹逼”逐渐逼近目标。


这个类比将抽象的算法转化为具体的合作场景,生动展现了双指针如何通过“一增一减”的协同策略高效解决问题。

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

stm32week6

stm32学习 三.通信 5.硬件读取I2C 硬件读取I2C的代码(main.c与软件读取相同)&#xff1a; #include "stm32f10x.h" // Device header #include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址/*** 函 数&…

qt+opengl 播放yuv视频

一、实现效果 二、pro文件 Qt widgets opengl 三、主要代码 #include "glwidget.h"GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent) {connect(&m_timer, &QTimer::timeout, this,[&](){this->update();});m_timer.start(1000/33); }v…

文本对抗样本系列的论文阅读笔记(整理合订)

文本对抗样本系列的论文阅读笔记 以前调研文本对抗样本时的论文笔记梳理&#xff0c;论文都很经典&#xff0c;有现成的框架&#xff08;TextAttack&#xff09;可以直接用&#xff0c;论文中部分内容直接是截取自论文&#xff0c;所以存在中英混合笔记的情况。 BERT-Attack …

相对与绝对路径的关系

首先&#xff0c;我们一起来了解相对路径和绝对路径的概念&#xff1a; 相对路径&#xff1a;相对于当前工作目录的路径&#xff0c;不以 / 开头&#xff0c;以一个 ""、./、../、。例如&#xff1a;nginx、./nginx 或 ../nginx绝对路径&#xff1a;从根目录 / 开始…

java项目之基于ssm的在线学习系统(源码+文档)

项目简介 在线学习系统实现了以下功能&#xff1a; 该系统可以实现论坛管理&#xff0c;通知信息管理&#xff0c;学生管理&#xff0c;回答管理&#xff0c;教师管理&#xff0c;教案管理&#xff0c;公告信息管理&#xff0c;作业管理等功能。 &#x1f495;&#x1f495;作…

位运算刷题+总结

文章目录 判定字符是否唯一题解代码 丢失的数字题解代码 两整数之和题解代码 只出现一次的数字 II题解代码 消失的两个数字题解代码 总结 判定字符是否唯一 题目链接 题解 1. 哈希表&#xff0c;创建26个空间大小的哈希表 2. 位图&#xff0c;小写字符只有26个&#xff0c;…

Qt表格美化笔记

介绍 表格是一种常见的数据管理界面形式&#xff0c;在大批量的数据交互情形下使用的比较多 表格 可以通过样式表设置线条以及边框的颜色 QTableWidget { gridline-color : rgb(55, 60, 62); border: 1px solid rgb(62,112,181);}表头 如果表头和第一行的分割线显示&#…

【Godot4.2】Vector2向量插值的应用

求线段的等分点 extends Node2Dvar pos:Vector2 var split_num:int var p1 Vector2(200,200) var p2 Vector2(100,100)func _input(event: InputEvent) -> void:if event is InputEventMouseButton:if event.button_index MOUSE_BUTTON_WHEEL_DOWN:split_num clamp(spl…

Git使用(二)--如何配置 GitHub 远程仓库及本地 Git 环境

在日常的开发过程中&#xff0c;使用版本控制工具 Git 是一个非常重要的技能&#xff0c;特别是对于管理和协作开发。通过 GitHub&#xff0c;我们可以轻松地进行代码版本管理和共享。这篇博客将带您一步步学习如何配置 Git 环境并将本地仓库与 GitHub 远程仓库连接起来。 一、…

【算法工具】HDL: 基于摘要统计数据的高维连锁不平衡分析软件

## 前言 在基因组研究中&#xff0c;连锁不平衡(Linkage Disequilibrium, LD)分析是理解遗传变异之间关联的关键步骤。然而&#xff0c;当面对高维数据时&#xff0c;传统分析方法往往面临巨大计算挑战。今天为大家介绍一款强大的工具——HDL (High-Dimensional Linkage diseq…

MongoDB副本集部署完整教程

一般而言&#xff0c;副本集主要成员有三个&#xff1a;主节点&#xff0c;副本节点&#xff0c;仲裁节点 按照官方推荐方案&#xff0c;我们搭建一个三成员的副本集&#xff0c;这个副本集由一个主结点和两个副本结点组成。 这里采用三台虚拟机进行部署&#xff1a;node1(主节…

springcloud gateway通过数据库获取路由信息

在 Spring Cloud Gateway 中结合 MyBatis 动态从数据库加载路由配置&#xff0c;可以实现灵活的路由管理。以下是详细实现步骤&#xff1a; 1. 数据库表设计 创建路由配置表 gateway_route&#xff1a; CREATE TABLE gateway_route (id varchar(50) NOT NULL COMMENT 路由唯一…

蓝桥杯嵌入式组第十二届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 LED模块1.3.3 LCD模块1.3.4 TIM模块1.3.5 UART模块1.3.5.1 uart数据解析 2.源码3.第十二届题目 前言&#xff1a;STM32G431RBT6实现嵌入式组第十二届题目解析源码&#…

Git 的基本概念和使用方式(附有思维导图)

一、Git 简介 Git 是一个开源的分布式版本控制系统&#xff0c;由 Linus Torvalds 在 2005 年为帮助管理 Linux 内核开发版本而开发 。与集中式版本控制系统&#xff08;如 SVN&#xff09;不同&#xff0c;在分布式系统中&#xff0c;每个开发者的本地机器都拥有一个完整的 G…

【微服务】Nacos 配置动态刷新(简易版)(附配置)

文章目录 1、实现方法2、配置依赖 yaml3、验证效果 1、实现方法 环境&#xff1a;Nacos、Java、SpringBoot等 主要是在boostrap.yaml中的data-id属性下配置refresh:true来实现动态更新 2、配置依赖 yaml 具体的版本参考官方的说明&#xff1a;官方版本说明 <!--读取boo…

mac 被禁用docker ui后,如何使用lima虚拟机启动docker

本机macos 安装lima brew install lima创建配置 echo "\\ndynamic:\n big-sur:\n image: docker://docker:git\n linux:\n image: docker.io/limasoftware/ubuntu:20.04 \\n" > ~/.lima/default.yaml启动名叫default的虚拟机 limactl start default进…

WIFI无ip分配之解决方法(Solution to WiFi without IP allocation)

WIFI无ip分配之解决方法 在信息化无比发达的当下社会&#xff0c;电脑在日常生活中也发挥着巨大的作用&#xff0c;不管是电脑还是手机只有在网络环境中才能得到更好的运用。然而很多朋友在使用网络的时候都会遇到一些问题&#xff0c;最常见的就是无线网络连接上但是WiFi无IP…

bootloader相关部分

简单说明 程序烧录的方式主要有ICP,ISP,IAP 其中ICP就是常用的jlink等工具 ISP就是利用MCU自带的一些特殊引脚烧录&#xff0c;比如uart IAP就是利用用户写的bootloader代码烧录 bootloader主要分为三层&#xff0c;厂家出厂的bootrom ,用户自己写的bootloader&#xff0c;…

同盾v2 2025版 blackbox , wasm加解密,逆向协议算法生成,小盾安全

声明 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; # 欢迎交流 wjxch1004

云平台一键部署【SGLang】适用于大型语言模型和视觉语言模型的快速服务框架

SGLang 是一个适用于大型语言模型和视觉语言模型的快速服务框架。它通过共同设计后端运行时和前端语言&#xff0c;使您与模型的交互更快、更可控。 优点&#xff1a; 1.吞吐量碾压级优势 2.结构化输出快如闪电 3.多 GPU 优化 SGLang模型已经在趋动云『社区项目』上线&am…