【算法】利用双指针法解决算法题(C++)

news2024/9/25 13:18:51

文章目录

  • 1. 前言
  • 2. 双指针法引入
    • 283.移动零
  • 3. 使用双指针法解决算法题
    • 1089.复写零
    • 202.快乐数
    • 11.盛最多水的容器
    • [611.有效三角 形的个数](https://leetcode.cn/problems/valid-triangle-number/description/)
    • LCR179.查找总价格为目标值的两个商品
    • 15.三数之和
    • 18.四数之和

1. 前言

  1. 双指针并非真正意义上的指针,实际上一般使用下标表示/代替。
  2. 通常用于处理数组或链表等数据结构。
  3. 主要思想是使用两个指针在数组或链表中进行迭代、比较或操作

2. 双指针法引入

我们直接通过下面一道例题进行双指针方法的引入

283.移动零

在这里插入图片描述

思路

  1. 如图所示,我们定义双指针,每次移动指针,数组都满足:
    • cur左侧元素为处理过的元素,右侧为未处理的元素
    • 处理后的元素分为非零元素和零
    • dest 左侧为非零元素,dest与cur之间为零
  2. 细节注意:
    • 由于cur从0开始遍历数组,将dest从-1位置开始
    • 如果cur当前元素不为0,则于dest位置元素交换(先进行++dest)

代码

void moveZeroes(vector<int>& nums) {
    // cur: 从左到右遍历数组,dest: 非零元素的最后一个位置
    // [0, dest] 非零 [dest+1, cur-1] 零 [cur+1, n-1] 未处理
    int cur = 0, dest = -1;
    for(cur = 0; cur < nums.size(); ++cur)
    {
        if(nums[cur] != 0)
            std::swap(nums[++dest], nums[cur]);
    }
}

3. 使用双指针法解决算法题

1089.复写零

在这里插入图片描述

思路

  1. 题目要求复写数组中的0,且数组长度不变(且应就地修改数组)
  2. 此时我们思考:
    • 当从前向后复写的时候,如1023 -> 1002,此时就会发生问题,当我们将0复写到2的位置后,2已经被覆盖,后续找不到该元素了
    • 此时我们可以尝试进行从后向前复写
  3. 当决定从后向前复写的操作,此时思路步骤如下图所示:
    在这里插入图片描述

代码

void duplicateZeros(vector<int>& arr) {
    int cur = 0, dest = -1;
    int n = arr.size();
    // 先找到最后一个复写的数
    while(dest < n - 1)
    {
        // num[cur] 不为0,cur,dest后移一步,为零cur后移一步dest后移两步
        if(arr[cur] != 0)  dest++;
        else    dest += 2;

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

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

    // 从后向前复写
    while(cur >= 0)
    {
        if(arr[cur] != 0)
        {
            arr[dest--] = arr[cur];
        }
        else
        {   // nums[cur] 为零,复写两次
            arr[dest--] = 0;
            arr[dest--] = 0;
        }
        cur--;
    }
}

202.快乐数

在这里插入图片描述

思路

在这里插入图片描述

  1. 我们首先通过上面的方式有了判断快乐数的方法,即一直算平方和看最后成环是否有1
  2. 对于此类成环问题,如判断链表是否有环,我们采用快慢指针来解决:
    • 如果快慢指针相遇,如果相遇位置不等于1,则不是快乐数
  3. 由此我们可以编写代码↓

代码

  • 将求 每位平方和 的操作写位函数
  • 快慢指针的移动,即求每位平方和的次数:
    • 慢指针每次求一次,快指针每次求两次。
    • 当两指针相遇,通过判断某个指针是否为1返回最终结果。
// 求每位平方和
int bitSquare(int n)
{
    int sum = 0; 
    while(n > 0)
    {
        int bit = n % 10;
        sum += bit * bit;
        n /= 10;
    }
    return sum;
}

bool isHappy(int n) {
    // 由已知得,数字一定成环,当slow与fast相遇
    // 如果相遇位置值为1,则是快乐数
    int slow = n, fast = bitSquare(n);;
    while(slow != fast)
    {
        // 慢指针每次一步,快指针每次两步
        slow = bitSquare(slow);
        fast = bitSquare(bitSquare(fast));
    }

    return fast == 1;
}

11.盛最多水的容器

在这里插入图片描述

思路

  • 解法一:暴力解法:

    在这里插入图片描述

  • 解法二:利用单调性,使用双指针法

    在这里插入图片描述

  • 有了双指针法的前提算法思想,我们可以总结出步骤:

    • 每次统计以left,right为边界的容量,并移动值更小的指针,重复步骤直到指针相遇

代码

int maxArea(vector<int>& height) {
   int left = 0, right = height.size() - 1, ret = 0;
    
    while(left < right)
    {
        // 算出本次体积
        int v = min(height[left], height[right]) * (right - left);
        ret = max(ret, v);
        
        // 调整指针
        if(height[left] > height[right]){
            right--;
        }
        else{
            left++;
        }
    }
    
    return ret;
}

611.有效三角 形的个数

在这里插入图片描述
思路

  • 解法一:暴力枚举
    • 用三个for循环计算更新结果,时间开销太大!
for(int i = 0; i < n-1; ++i)
	for(int j = i + 1; j < n - 1; ++j)
		for(int k = j + 1; k < n - 1; ++k)
			check(i, j, k); // 省略具体步骤
  • 解法二:根据单调性用双指针法

    1. 我们知道三条边构成三角形的条件是 任意两条边之和>第三条边
    • 而对于a <= b <= c 的三条边来说,由于a+c > b, b + c > a恒成立,我们只需判断a + b > c 是否成立即可 (注意下图思路中,我们用下标表示其在数组中的值)

    在这里插入图片描述
    2. 则思路如下:

    • 排序数组
    • 通过外层for循环固定最大边,内层while循环双指针找满足条件的边

代码

// 给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数
int triangleNumber(vector<int>& nums) {
    std::sort(nums.begin(), nums.end()); // 先排序数组
    
    int n = nums.size(), count = 0;
    
    // 外层for循环 控制最大数
    for(int i = n - 1; i >= 1; --i)
    {
        int left = 0, right = i - 1;
        
        while(left < right)
        { 
            if(nums[left] + nums[right] <= nums[i])   left++;
            else { // 如果 left + right > 最大数,则区间内的所有left与right组合均满足三角形
                count += right - left;
                right--;
            }
        }
    }
    
    return count;
}

LCR179.查找总价格为目标值的两个商品

在这里插入图片描述

思路

  • 解法一:暴力枚举
    • 通过两层for循环,判断是否满足条件
    • 时间开销大,O(n^2)
for(int i = 0; i < n; ++i)
	for(int j = i + 1; j < n - 1; ++j)
		check(nums[i] + nums[j] == target);
  • 解法二:利用单调性使用双指针法
    1. 题目要求找到数组中任意两个和为target的数: 在这里插入图片描述

代码

vector<int> twoSum(vector<int>& price, int target) {
    int n = price.size();
    int left = 0, right = n - 1;
    while(left < right)
    {
        int sum = price[left] + price[right]; // 记录两数和
        if(sum < target)
            left++;
        else if (sum > target)
            right--;
        else 
            return {price[left], price[right]};
    }
    return {};
}

15.三数之和

在这里插入图片描述

思路

  1. 题目要求找到数组中三个不同的位置,满足nums[i] + nums[j] + nums[k] == 0,并找到所有满足条件的三元组:
  • 解法一:排序+暴力枚举+使用set去重

  • 解法二:排序+双指针法
    在这里插入图片描述

代码

vector<vector<int>> threeSum(vector<int>& nums) {
    // 排序数组 便于后面去重等
    std::sort(nums.begin(), nums.end());
    
    int n = nums.size();
    vector<vector<int>> ret;

    // for循环固定第一个数,则固定的数后的序列进行“找和为target的两个数”
    // 如果 i = -5, 则target = 5
    for(int i = 0; i < n - 2; ++i)
    {
        // 如果固定的数重复,跳过重复的数
        if(i > 0 && nums[i] == nums[i-1])    continue;

        int left = i + 1, right = n - 1;
        while(left < right)
        {
            int sum = nums[i] + nums[left] + nums[right];
            if(sum < 0)
            {
                left++;
            }   
            else if(sum > 0)
            {
                right--;
            }
            else // 找到满足的数,插入到ret中,并更新left和right
            {
                ret.push_back({nums[i], nums[left], nums[right]});
                
                // 判断left和right的下一位是否重复,如果重复则跳过重复的数
                while(left < right && nums[left] == nums[left+1]) {
                    left++;
                }
                while(left < right && nums[right] == nums[right-1]) {
                    right--;
                }

                left++, right--;
            }
        }
    }
    return ret;
}

18.四数之和

在这里插入图片描述

思路

  1. 分析题目,题目要求得到 a + b + c + d = target,当我们固定了两个数后,只需判断 c + d = targer - a - b 即可。
  2. 该题与三数之和的解法如出一辙:
  • 排序+双指针法
    • 先通过两个for循环分别固定前两位数
    • 随后通过双指针法进行比较移动

代码

vector<vector<int>> fourSum(vector<int>& nums, int target) {
    // 1. 排序
    std::sort(nums.begin(), nums.end());
    
    vector<vector<int>> ret; // 结果数组
    int n = nums.size();

    // 固定第一个数
    for(int i = 0; i < n; ++i)
    {
        if(i > 0 && nums[i] == nums[i - 1]) continue; // i跳过重复数
        
        // for循环内即 “三数之和” 的代码
        // 固定第二个数
        for(int j = i + 1; j < n; ++j)
        {
            if(j > i + 1 && nums[j] == nums[j - 1]) continue; // j 跳过重复数
            
            int left = j + 1, right = n - 1;
            long long _target = target - (long long)nums[i] - (long long)nums[j]; // 内层循环的_target为减去i和j的值

            while(left < right)
            {
                long long sum = (long long)nums[left] + (long long)nums[right]; // sum值与_target比较
                
                if(sum > _target)   right--;
                else if(sum < _target) left++;
                else{
                    ret.push_back({nums[i], nums[j], nums[left], nums[right]});
                    
                    // left, right 去重
                    while(left < right && nums[left] == nums[left + 1])
                        left++;
                    
                    while(left < right && nums[right] == nums[right - 1])
                        right--;
                    
                    left++, right--;
                }
                    
            }
        }
    }
    return ret;
}

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

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

相关文章

Scala(一)基本类型

Scala语言快速入门&#xff08;基本类型&#xff09; 一、Linux和Windows环境安装 这部分跳过&#xff0c;直接使用IDEA进行搭建&#xff0c;和其他编程语言配置差不多 二、HelloWorld 1.object表示一个伴生对象&#xff08;相当于一个对象&#xff09;2.HelloWorld是对象的…

Java项目-瑞吉外卖项目优化Day2

读写分离Linux配置 如果只有一台数据库&#xff0c;会导致数据库压力过大&#xff0c;也可能出现磁盘损坏数据丢失的情况。所以考虑借助mysql数据库的主从复制解决以上两个问题。 mysql主从复制的原理 实现主从复制的操作 注意此处的master_log_file的值和master_log_pos的值要…

渗透测试——1.1初认识kali

一、kali的下载 官方地址&#xff1a;www.kali.org 下载后压缩就可用vmware打开即可&#xff08;初始账号和密码都是kali&#xff09; 二、三种网络配置 1、桥接模式&#xff1a;将虚拟机看成局域网中的独立主机 2、NAT模式&#xff1a;将物理机当做路由器&#xff08;rout…

php一句话木马变形技巧

一、什么是一句话木马&#xff1f; 一句话木马就是只需要一行代码的木马&#xff0c;短短一行代码&#xff0c;就能做到和大马相当的功能。为了绕过waf的检测&#xff0c;一句话木马出现了无数中变形&#xff0c;但本质是不变的&#xff1a;木马的函数执行了我们发送的命令。 …

深度学习 | 基本循环神经网络

1、序列建模 1.1、序列数据 序列数据 —— 时间 不同时间上收集到的数据&#xff0c;描述现象随时间变化的情况。 序列数据 —— 文本 由一串有序的文本组成的序列&#xff0c;需要进行分词。 序列数据 —— 图像 有序图像组成的序列&#xff0c;后一帧图像可能会受前一帧的影响…

开源持续测试平台Linux MeterSphere本地部署与远程访问

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

C# 读取Word表格到DataSet

目录 功能需求 Office 数据源的一些映射关系 范例运行环境 配置Office DCOM 关键代码 组件库引入 ​核心代码 杀掉进程 总结 功能需求 在应用项目里&#xff0c;多数情况下我们会遇到导入 Excel 文件数据到数据库的功能需求&#xff0c;但某些情况下&#xff0c;也存…

算法中的数学一:判定质数和求约数相关

1.试除法求质数 质数就是大于1的整数中除了1和自身没有其他因数的数 1.1暴力求解 暴力求解的思路就是从2遍历到自身判断是否有被整除的数&#xff0c;时间复杂度为O(n)的 bool is_prime(int x) {if(x<2)return false;for(int i2;i<x;i){if(x%i0){return false;}}return…

LSTM和GRU vs 循环神经网络RNN

1、考虑下列三种情况下&#xff0c;对比一下普通RNN的表现和LSTM和GRU表现&#xff1a; &#xff08;1&#xff09;早期观测值对预测未来观测者具有非常重要的意义。 考虑一个极端情况&#xff0c;其中第一个观测值包含一个校验和&#xff0c; 目标是在序列的末尾辨别校验和是…

Scala安装

Scala安装使用 windows安装,配置环境变量 以下载Scala2.11为例&#xff0c;操作在Windows中安装Scala。 官网下载scala2.11&#xff1a;All Available Versions | The Scala Programming Language下载好后安装。双击msi包安装,记住安装的路径。配置环境变量&#xff08;和配…

精品Nodejs实现的微信小程序的校园跑腿系统-快递收取件

《[含文档PPT源码等]精品Nodejs实现的微信小程序的校园跑腿系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 操作系统&#xff1a;Windows 10、Windows 7、Windows 8 …

C++:list增删查改模拟实现

C:list增删查改模拟实现 前言一、list底层双链表验证、节点构造1.1 list底层数据结构1. 2 节点构造 二、迭代器封装实现&#xff08;重点、难点&#xff09;2.1 前置说明2.2 迭代器实现 三、list实现3.1 基本框架3.2 迭代器和const迭代器3.2 构造函数、析构函数、拷贝构造、赋值…

解决FTP传输慢的问题(ftp传输慢为什么)

在企业运营中&#xff0c;使用FTP进行文件或数据传输是相当普遍的做法。尽管FTP是一种传统的文件传输工具&#xff0c;但在实际应用中&#xff0c;我们可能会面临传输速度缓慢的问题&#xff0c;这不仅影响工作效率&#xff0c;还浪费时间。为了解决这一问题&#xff0c;我们可…

Vue中Render函数、_ref属性、_props配置的使用

Render函数 由于导入的vue为vue.runtime.xxx.js是运行版的vue.只包含&#xff1a;核心功能&#xff1a;没有模板解析器 完整版的Vue为vue.js包含&#xff1a;核心功能模板解析器 vue.runtime.esm.js中的esm为ES6的模块化 //导入的vue并非完整的vue&#xff0c;这样做的好处是…

实战:朴素贝叶斯文本分类器搭建与性能评估

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

Druid源码阅读-DruidStatInterceptor实现

上次我们在druid-spring-boot-starter里面看到有一个DruidSpringAopConfiguration的配置类&#xff0c;然后引入了DruidStatInterceptor这样一个切面逻辑。今天我们就来看一下这个类的实现。 DruidStatInterceptor 这个类的包路径下入com.alibaba.druid.support.spring.stat。…

服务器代码上传到gitlab

服务器代码上传到gitlab 安装Git工具linux&#xff1a;安装Git&#xff0c;使用自带的源安装 yum install git生成密钥文件&#xff1a;使用ssh-keygen生成密钥文件.ssh/id_rsa.pub ssh-keygen 使用cat命令查看密钥&#xff0c;将下面的密钥复制一份 在gitlab上建立一个和…

2023年12月16日~12月22日(自适应反馈机制下基于卷积神经网络的高清晰反射波反演算法:CNN-RWI)

标题&#xff1a;Adaptive Feedback Convolutional-Neural-Network-Based High-Resolution Reflection-Waveform Inversion 全波形反演&#xff08;FWI&#xff09;是一种非线性拟合观测地震记录从而获得高清晰速度模型的最优化算法。FWI能够通过拟合浅层初至波和反射波获得较准…

浅学JWT跨域认证

Json Web令牌简称JWT 由HeaderPayloadSignature组成 Header JWT头是一个描述JWT元数据的JSON对象&#xff0c;alg属性表示签名使用的算法&#xff0c;默认为HMAC SHA256&#xff08;写为HS256&#xff09;&#xff1b;typ属性表示令牌的类型&#xff0c;JWT令牌统一写为JWT。…

湘沪数字产业(上海)协同创新中心正式启动!

前言 随着全球化的加速和市场竞争的日益激烈&#xff0c;产业协作已成为推动科技创新和经济发展的重要手段。在创新驱动战略的推动下&#xff0c;全国形成了在科创核心城市建设科创中心的浪潮&#xff0c;旨在充分利用不同区域的产业优势&#xff0c;加强产业协作&#xff0c;…