算法:双指针解决数组划分和数组分块问题

文章目录

  • 实现原理
  • 实现思路
  • 典型例题
    • 移动0
    • 复写0
    • 快乐数
    • 盛最多水的容器
    • 有效三角形的个数
    • 三数之和
    • 四数之和
  • 总结

在快速排序或者是其他和数组有关的题目中,有很经典的一类题目是关于数组划分的,数组划分就是把数组按照一定的规则划分为不同的区间,使得达到某种目的

首先先看实现的原理是什么

实现原理

两个指针的作用?

cur:从左向右扫描数组,遍历数组

dest:已处理的区间内,非零元素的最后一个位置

数组划分就是把数组划分成三个区间:

[0,dest][dest+1,cur-1][cur,n-1]

而这三个区间就对应到了题目要求的区间,假设现在有这样的题目

在这里插入图片描述
那经过区间划分,就可以把[0,dest]划分为非0的区域,[dest+1,cur-1]划分为只有0的区间,而剩下的就是待处理的区间

实现思路

有了上面的理论基础,实现思路就简单多了:

我们让cur从前向后遍历,如果cur遇到0元素,就让cur++,因为cur相当于是一个用来探路的指针,而dest的作用就是用来进行区间的划分,于是根据这个原理,当cur遇到了非0的元素时,就让dest++再让curdest这两个位置的元素进行一次交换,交换后的结果就达到了,把非0元素交换到前面,0元素换到后面的结果

技巧

永远让dest初始值为-1,cur的初始值为0,并且让cur最后更变值,可以有效处理掉很多越界问题

典型例题

先看几个简单的题目熟悉这个算法的思路:

移动0

在这里插入图片描述

void moveZeroes(vector<int>& nums) 
{int cur=0;int dest=-1;while(cur<nums.size()){if(nums[cur]==0){cur++;}else{dest++;swap(nums[dest],nums[cur]);cur++;}}
}

看上述代码,就严格执行了刚才代码的思路

  1. 如果cur遇到0元素,就让cur++

  2. cur遇到了非0的元素时,就让dest++再让curdest这两个位置的元素进行一次交换


复写0

在这里插入图片描述

既然通篇介绍的主要是双指针算法,那这个题也要使用双指针的基本原理

如果此题可以创建多个数组,那么创建一个数组,上面一个指针控制原数组,下面控制新数组,如果是0就填入两个0,如果不是0就填入该数字,到达对应数量就不再进行写入数据

但这里要求不能够使用额外的空间,因此就要把异地的双指针变成原地双指针

那原地双指针如何实现?

首先,原地的双指针问题在于,如果从前向后遍历,当遍历到的数字是0,写入两个0后,原来的数据就被覆盖掉了,这样就会一直进行0的循环,因此解决方案就是从后向前遍历

那么接下来思考如何遍历?一个从最后开始,那另外一个?显然,这是下一个需要解决的问题,另外一个部分应该从哪里开始

在这里插入图片描述
通过这里画图也能看出,cur的位置其实就是复制结束后的位置,那么这个位置的寻找过程就是下一步要进行的问题

cur应该如何寻找?其实又可以演化为双指针的问题,从开始找,当遇到0就向后走两次,遇到非0就走一次,那么这样就可以找到cur

这样的思路是没有问题的,但是也有特殊情况,如果最后元素是0,那dest向后走两步不就越界了吗?因此这里也需要对边界做特殊处理,如果边界为0,那么就让最后输出的destcur向前一步走即可避免越界的情况出现

class Solution 
{
public:void duplicateZeros(vector<int>& arr) {int cur=0,dest=-1,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;cur--;dest-=2;}while(cur>=0){if(arr[cur]==0){arr[dest--]=arr[cur];arr[dest--]=arr[cur--];}else{arr[dest--]=arr[cur--];}}}
};

快乐数

在这里插入图片描述

这个题思路也很奇特,先模拟一下实现的流程:

情景1:

在这里插入图片描述

情景2:

在这里插入图片描述
此时对题意就有了基本了解,那么这个图其实和链表中的环形链表很相似,我们其实就可以把他抽象成一个环形链表的相遇问题,当相遇的时候,如果对应的值不是1,那么就证明这里并不是快乐数,相反就是快乐数

因此这个题就很好解决了,本质上这个原理和环形链表的快慢指针的过程是一样的

class Solution 
{
public:int CalRes(int num){int res = 0;while (num){res += (num % 10) * (num % 10);num = num / 10;}return res;}bool isHappy(int n) {int num = n;int slow = CalRes(num);int fast = CalRes(CalRes(num));while (slow != fast){slow = CalRes(slow);fast = CalRes(CalRes(fast));}if (fast == 1){return true;}else{return false;}}
};

盛最多水的容器

在这里插入图片描述

本题设计也很巧妙,但依旧是利用双指针来解决,知道了双指针的解决原理后解决并非难事

一个从左走 一个从右走 根据木桶效应,计算出结果后要舍弃小的部分,继续向内遍历,使得最终时间复杂度控制在O(N)内

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

有效三角形的个数

在这里插入图片描述

看到这个题,第一思路是直接暴力枚举三次for循环,直接找,但最后是通过不了的,时间复杂度过高了,因此这里还是使用双指针的解法,但是要利用单调性进行解决

首先,对于三个数字我们要进行判断的时候,如果这个数字是单调排序的,比如这里是升序排序,那么只需要判断前两个数相加的和大于第三个数即可,因此根据这个原理,我们就可以采取下面的思维方式

依据单调性采用双指针解决问题

这个算法的思路就是,先固定右边最大的数字作为最大的数,倒数第二大的数字为right,左边的数为left

如果此时left+right>固定,那么此时left右边的数同样符合条件,那么只需要right--即可

如果此时left+right<固定,那么此时left后面的数也不符合要求,就让left++

循环结束后,再通过挪动右边的数进行循环,这样时间复杂度在O(N^2)的基础上就解决了这个问题

class Solution 
{
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(),nums.end());int cut=0;for(int max=nums.size()-1;max>=2;max--){int left=0,right=max-1;while(left<right){if(nums[left]+nums[right]>nums[max]){cut+=right-left;right--;}else{left++;}}}return cut;}
};

三数之和

在这里插入图片描述

此题难度在于代码实现的细节处理和去重的问题上

代码思路有两数之和的思路铺垫,整体难度不大,控制住其中一个进行另外两个数据的相加即可,但在边界的处理上需要进行一些操作

最后是去重的操作,去重的操作是很重要的一步,细节很多,要处理区间内的去重和区间外单独的去重

vector<vector<int>> threeSum(vector<int>& nums) 
{vector<vector<int>> v;// 排序sort(nums.begin(),nums.end());for(int i=0;i<nums.size();){// 优化if(nums[i]>0){break;}int left=i+1,right=nums.size()-1;while(left<right){if(nums[left]+nums[right]+nums[i]>0){right--;}else if(nums[left]+nums[right]+nums[i]<0){left++;}else{v.push_back({nums[i],nums[left],nums[right]});left++;right--;// 去重1while(left<right && nums[left]==nums[left-1]){left++;}while(left<right && nums[right]==nums[right+1]){right--;}}}       // 去重2i++;while(i<nums.size() && nums[i]==nums[i-1]){i++;}}return v;
}

四数之和

在这里插入图片描述

代码思路和三数之和基本相同,注意long long问题即可

class Solution 
{
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> v;// 排序sort(nums.begin(),nums.end());// 控制第一个数for(int i=0;i<nums.size();){// 控制第二个数for(int j=i+1;j<nums.size();){// 在区间内找int left=j+1,right=nums.size()-1;while(left<right){long long tmp=(long long)nums[i]+nums[j]+nums[left]+nums[right];if(tmp>target){right--;}else if(tmp<target){left++;}else{v.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--;}}}// 对固定数去重j++;while(j<nums.size() && nums[j]==nums[j-1]){j++;}}i++;while(i<nums.size() && nums[i]==nums[i-1]){i++;}}return v;}
};

总结

双指针问题是入门算法题,但却有很大的使用场景,具体来说,当要进行数组划分和数组分块的时候,可以选择先进行排序,进行有序数组

对于有序数组来说,利用单调性解决问题是常见的手段,在实际应用题目中有很大的利用价值

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

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

相关文章

JLSX 模版指令导出Excel

1. 官方相关链接 官网&#xff1a;https://jxls.sourceforge.net/reference/if_command.html JxlsAPI&#xff1a; https://jxls.sourceforge.net/javadoc/jxls/index.html Jxls POI&#xff1a; https://jxls.sourceforge.net/javadoc/jxls/index.html Jxls JExcel&#xff1…

SpringCloud实用篇4——MQ RabbitMQ SpringAMQP

目录 1 初识MQ1.1 同步和异步通讯1.1.1 同步通讯1.1.2 异步通讯 1.2 技术对比 2.快速入门2.1 安装RabbitMQ2.1.1 单机部署2.1.2集群部署 2.2 RabbitMQ消息模型2.3.导入Demo工程2.4 入门案例2.4.1 publisher实现2.4.2 consumer实现 3 SpringAMQP3.1 Basic Queue 简单队列模型3.1…

513. 找树左下角的值

513. 找树左下角的值 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;__513找树左下角的值__BFS__513找树左下角的值__从右往左出队 错误经验吸取 原题链接&#xff1a; 513. 找树左下角的值 https://leetcode.cn/problems/find-bottom-…

打开vim的语法高亮

在一个Ubuntu中自带的vim版本是8.2.4919&#xff0c;默认就是开始了语法高亮的&#xff0c;打开一个Java文件效果如下&#xff1a; 它不仅仅对Java文件有语法高亮&#xff0c;对很多的文件都有&#xff0c;比如vim的配置文件也有语法高亮&#xff0c;有语法高亮时读起来会容易…

Curson 编辑器

Curson 汉化与vacode一样 Curson 自带chat功能 1、快捷键ctrlk(代码中编辑) 2、快捷键ctrll 右侧打开窗口

API 自动化测试难点总结与分享

API自动化测试的难点包括&#xff1a; 接口的参数组合较多&#xff0c;需要覆盖各种可能的情况。接口的状态和数据关联较多&#xff0c;需要验证返回结果是否符合预期。接口的并发访问和性能测试较为复杂&#xff0c;需要合理规划和调度测试策略。接口的安全性和权限控制较为重…

性能比较 - Spring Boot 应用程序中的线程池与虚拟线程 (Project Loom)

本文比较了 Spring Boot 应用程序中的不同请求处理方法&#xff1a;ThreadPool、WebFlux、协程和虚拟线程 (Project Loom)。 在本文中&#xff0c;我们将简要描述并粗略比较可在 Spring Boot 应用程序中使用的各种请求处理方法的性能。 高效的请求处理在开发高性能后端…

React 高阶组件(HOC)

React 高阶组件(HOC) 高阶组件不是 React API 的一部分&#xff0c;而是一种用来复用组件逻辑而衍生出来的一种技术。 什么是高阶组件 高阶组件就是一个函数&#xff0c;且该函数接受一个组件作为参数&#xff0c;并返回一个新的组件。基本上&#xff0c;这是从 React 的组成…

Flink内核源码解析

Flink内核源码 Flink RPC 网络通信框架Akka 以往&#xff0c;我们接触过非常多的大数据技术栈相关的框架&#xff0c;用的比较多的大数据相关组件&#xff0c;常用的RPC实现技术如下&#xff1a; 技术组件RPC实现HadoopNIO Protobuf (Protobuf即Protocol Buffers&#xff0…

YB2416是支持高电压输入的同步降压电源管理芯片

简介&#xff1a; YB2416是支持高电压输入的同步降压电源管理芯片&#xff0c;在 4~30V 的宽输入电压范围内可实现3A的连续电流输出。通过调节 FB 端口的分压电阻&#xff0c;可以输出1.8V到28V的稳定电压。YB2416具有优秀的恒压/恒流(CC/C)特性。YB2416 采用电流模式的环路控制…

(二)结构型模式:8、代理模式(Proxy Pattern)(C++示例)

目录 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 2、代理模式的UML图学习 3、代理模式的应用场景 4、代理模式的优缺点 5、C实现代理模式的实例 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 代理模式&#xff08;Proxy&#xff09;&#xff0c;为…

​怎么将物理机硬盘克隆到虚拟机?

​用户案例&#xff1a;克隆后的硬盘是否能用于虚拟机&#xff1f; “我有一台安装了Windows10的计算机&#xff0c;现在正在尝试克隆电脑上的硬盘。然后我想把这个硬盘放在自己的虚拟机中使用&#xff0c;这样我就可以从克隆的硬盘中启动相同的Windows10系统。” …