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

2023-11-18

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

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

实现原理

两个指针的作用?

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--;
                // 去重1
                while(left<right && nums[left]==nums[left-1])
                {
                    left++;
                }
                while(left<right && nums[right]==nums[right+1])
                {
                    right--;
                }
            }
        }       
        // 去重2
        i++;
        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;
    }
};

总结

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法:双指针解决数组划分和数组分块问题 的相关文章

  • Dapper 强类型查询返回默认对象值

    刚刚开始使用 Dapper 并喜欢它 我遇到了问题 它返回正确数量的对象 但它们的属性都有默认值 using var dbConnection Connection await dbConnection OpenAsync const st
  • 以编程方式 Godaddy 发送的电子邮件不在“已发送邮件”文件夹中 C#.net

    我正在通过以下方式发送电子邮件ASP NET代码使用godaddy邮件服务器 邮件发送成功 但未存储在已发送邮件文件夹中 我正在使用下面的代码 SmtpClient client new SmtpClient client Host smt
  • 在两个 .cpp 文件之间定义全局变量 [重复]

    这个问题在这里已经有答案了 如何在 A cpp 和 B cpp 之间共享 全球化 bool 变量 其中它们都不包含其他 h 文件 他们有其他联合头文件 但彼此没有 我可以在这些共享标头中定义全局变量吗 Thanks 我可以在这些共享标头中定
  • C++ 有像 Pascal 一样的“with”关键字吗?

    withPascal 中的关键字可用于快速访问记录的字段 有人知道 C 是否有类似的东西吗 前任 我有一个包含许多字段的指针 但我不想这样输入 if pointer gt field1 pointer gt field2 pointer g
  • C/C++ 中随机数生成器的实现[重复]

    这个问题在这里已经有答案了 我对 C 中随机数生成器的实现有点困惑 它也与 C 中的明显不同 如果我理解正确 对 srand seed 的调用会以某种方式初始化可通过 rand 访问的隐藏变量 种子 该变量又将函数指向预先生成的序列 例如例
  • C++ - 模板专业化和部分专业化

    我一直在互联网和 stackoverflow 上寻找具体的答案 但我似乎找不到 我必须创建一个通用类 然后实现特定的功能 我的具体说明是 您需要使用模板表达式参数以及模板类专业化和部分专业化 我有一个模板类 template
  • TestMethod:异步任务 TestSth() 不适用于 .NET 4.0

    我正在尝试使用 NET 4 0 BCL Async 和 MsTest 运行异步测试方法 看来这个设置不能处理 测试方法 异步Task测试Sth 由于测试用例资源管理器中缺少条目 将签名更改为异步后void 我可以运行测试用例 但结果错误 根
  • initializer_list 和默认构造函数重载决策

    include
  • 首先EntityFramework数据库 - 类型映射 - 将binary(8)从SQL映射到C#中的int

    在 SQL 内部 我有一个主键为二进制 8 的表 当我使用该表添加到我的模型中时Update Model from Database我可以看到该列有 type Binary 在 C 中 我将该列设为byte 我可以将该列映射到 int 吗
  • 我们应该使用 Eval 还是 Databind 事件?

    当使用 Asp Net 并使用 ListView 等控件创建网站时 使用 Eval 命令是一个好习惯吗 还是应该在 databind 事件中填充文字和数据 取决于您是否想在更新事件上写回数据 在这种情况下数据绑定 如果您只想读取该数据 可以
  • 使用 Microsoft Graph 创建用户

    如何使用 Microsoft graph 创建用户 因为我在保存过程中遇到了权限失败的问题 我确实有几个问题 在图中调用创建用户 API 将在哪里创建用户 是在 Azure AD 还是其他地方 我尝试通过传递 json 和必需的标头来调用创
  • IClaimsTransformation 未触发

    我尝试过实施一个IClaimsTransformation我在 ASP NET CORE 3 1 Web 应用程序中找到的类 public class ClaimsTransformer IClaimsTransformation publ
  • IBM Watson 对话服务错误:无法从“方法组”转换为“conversation.onMessage”

    我正在尝试运行 IBM Watson会话服务团结和下面是代码片段 https github com watson developer cloud unity sdk conversation private Conversation m C
  • 在 C# 中生成随机值

    如何使用以下命令生成随机 Int64 和 UInt64 值RandomC 中的类 这应该可以解决问题 这是一个扩展方法 因此您可以像调用普通方法一样调用它Next or NextDouble上的方法Random目的 public stati
  • AspNetCore.SignalR:无法启动未处于初始状态的连接

    我无法让 ASP NET Core SignalR 应用程序正常运行 我有这个服务器端代码 public class PopcornHub Hub private int Users public async Task BroadcastN
  • .NET 的 HttpWebResponse 是否会自动解压缩 GZiped 和 Deflated 响应?

    我正在尝试执行一个接受压缩响应的请求 var request HttpWebRequest HttpWebRequest Create requestUri request Headers Add HttpRequestHeader Acc
  • 停止 TcpListener 的正确方法

    我目前正在使用 TcpListener 来处理传入连接 每个连接都有一个线程用于处理通信 然后关闭该单个连接 代码如下 TcpListener listener new TcpListener IPAddress Any Port Syst
  • 如何在Linux上构建GLFW3项目?

    我已经使用 cmake 和 make 编译了 glfw3 和包含的示例 没有出现任何问题 开始编写我的第一个项目 作为 opengl 和 glfw 的新手 并且对 C 和 CMake 没有经验 我正在努力理解示例构建文件 甚至要链接哪些库和
  • 为什么从绑定返回的对象会忽略额外的参数?

    假设我有一个带有两个参数的函数 void f int x int y 我想绑定其中之一 我可以用std bind如下 auto partiallyBoundF std bind f 10 1 partiallyBoundF仅需要一个参数 但
  • 编译器什么时候内联函数?

    在 C 中 函数仅在显式声明时才内联inline 或在头文件中定义 或者编译器是否允许内联函数 因为他们认为合适 The inline关键字实际上只是告诉链接器 或告诉编译器告诉链接器 同一函数的多个相同定义不是错误 如果您想在标头中定义函

随机推荐

  • element-plus按需引入后ElMessage与ElLoading在页面中如何使用

    一 按照官网按需引用element plus pnpm install element plus pnpm add D unplugin vue components unplugin auto import vite config ts
  • 如何恢复华为手机中丢失的通讯录(使用时光机)

    1 登录 https cloud huawei com 2 如果忘记密码 可使用手机登录 1 手机中 华为帐号 2 扫描网页二维码 3 进入系统 3 点击 设置 4 点击 联系人时光机 5 选择需要还原的记录按 还原 6 打开通讯禄将还原的
  • RocketMQ概论

    目录 前言 1 概述 2 下载安装 集群搭建 3 消息模型 4 如何保证吞吐量 4 1 消息存储 4 1 1顺序读写 4 1 2 异步刷盘 4 1 3 零拷贝 4 2 网络传输 前言 RocketMQ的代码示例在安装目录下有全套详细demo
  • Linux常用命令-压缩解压命令

    一 gz gzip 文件 压缩文件 只能压缩文件 gunzip 压缩文件 解压文件 二 tar 打包目录 tar gz tar命令压缩语法 tar 选项 zcf 压缩后文件名 目录 c 打包 v 显示详细信息 f 指定文件名 z 打包同时压
  • 49天精通Java,第10天,Java继承和多态

    目录 一 继承 二 多层次继承 三 多态 1 多态的优点 2 多态存在的三个必要条件
  • Visual Studio 2015 debug 显示 utf-8 汉字

    这两天调试程序 内容是utf8编码的 visual studio 默认显示ansi的 所以中文全乱码了 上网上只找到vs2013及之前版本的解决办法 于是 自己对比vs2013的解决办法 让vs2015也显示了utf 8字符 具体在 C P
  • 12333新农合网上查询_社保卡余额如何查询?这五个方法轻松查询

    阅读本文前 请您先点击上面的 蓝色字体 再点击 关注 这样您就可以继续免费收到文章了 每天都有分享 完全是免费订阅 请放心关注 注 本文转载自网络 不代表本平台立场 仅供读者参考 著作权属归原创者所有 现在大多数人都有社保卡 就算是农民朋友
  • UPLOAD labs 第四关

    第四关考点是 htaccess 作为一个铁废物 来百度一下 大意就是htaccess是apache服务中的一个配置文件 负责相关目录下的网页配置 它负责相关目录下的网页配置 通过htaccess文件 可以帮我们实现 网页301重定向 自定义
  • Ubuntu 17.04系统创建Android Studio桌面快捷方式的方法

    下面以 Android Studio 为例 阐述Ubuntu系统中创建桌面快捷方式的方法 假设已将 Android Studio 下载到 home
  • 在 Win11安装 Ubuntu20.04子系统 WSL2 到其他盘(此处为D盘,因为C盘空间实在不能放应用)

    该篇文章记录了在 win11 中安装 Ubuntu20 04 子系统 先安装到 C盘 再通过打包的方式 安装到 D盘 上 因为是安装后写的文章 可能会有所疏漏 所以有任何问题可以在评论区留言 0 设置 windows 功能 打开这三个 配置
  • everything 和quicklook联动

    everything 和quicklook联动 老凶残的解决方案了 https www logcg com archives 1584 html
  • 数据库原理之关系数据库关系运算

    关系数据库关系运算 选择 投影 链接 除运算 选择运算 选择运算是从关系R中选取使逻辑表达式F为 真的元组 是从行的角度进行的运算 投影运算 投影操作主要是从列的角度进行运算 但投影之后不仅取消可原关系中的某些列 而且还可能取消某些元组 避
  • Qt自定义代理与实例

    代理的定义 代理 Delegate 就是在视图组件上为编辑数据提供编辑器 如在表格组件中编辑一个单元格的数据时 缺省是使用一个QLineEdit编辑框 代理负责从数据模型获取相应的数据 然后显示在编辑器里 修改数据后 又将其保存到数据模型中
  • android自定义属性详解,android开发教程之自定义属性用法详解

    android开发中要对代码进行生成 然而生成后的代码也可以进行更改的 下面是爱站技术频道小编带给大家的android开发教程之自定义属性用法详解 希望能给你学习这方面知识带来帮助 最近项目中经常需要用到自定义控件 因此自定义属性也是经常要
  • 基于EasyCode定制Mybatisplus全自动单表实现:新增/批量新增/修改/批量删除/分页查询/ID查询

    基于EasyCode定制Mybatisplus全自动单表实现CRUD接口 分页查询 ID查询 新增 批量新增 修改 批量删除 注意使用了MybatisPlus的自动填充功能 和insertBatchSomeColumn扩展批量插入功能 分页
  • 浅析MySQL JDBC连接配置上的两个误区

    相信使用MySQL的同学都配置过它的JDBC驱动 多数人会直接从哪里贴一段URL过来 然后稍作修改就上去了 对应的连接池配置也是一样的 很少有人会去细想这每一个参数都是什么含义 今天我们就来聊两个比较常见的配置 是否要开启autoRecon
  • 推荐一款cpp解析json工具--rapidjson

    项目地址 http code google com p rapidjson 上面有很详细的介绍 http code google com p rapidjson wiki UserGuide 作者介绍说 Rapidjsonis an att
  • k8备份与恢复-Velero

    简介 Velero 是一款可以安全的备份 恢复和迁移 Kubernetes 集群资源和持久卷等资源的备份恢复软件 Velero 实现的 kubernetes 资源备份能力 可以轻松实现 Kubernetes 集群的数据备份和恢复 复制 ku
  • 华为OD机试真题-优秀学员统计 【2023.Q1】

    题目描述 公司某部门软件教导团正在组织新员工每日打卡学习活动 他们开展这项学习活动已经一个月了 所以想统计下这个月优秀的打卡员工 每个员工会对应一个id 每天的打卡记录记录当天打卡员工的id集合 一共30天 请你实现代码帮助统计出打卡次数t
  • 算法:双指针解决数组划分和数组分块问题

    文章目录 实现原理 实现思路 典型例题 移动0 复写0 快乐数 盛最多水的容器 有效三角形的个数 三数之和 四数之和 总结 在快速排序或者是其他和数组有关的题目中 有很经典的一类题目是关于数组划分的 数组划分就是把数组按照一定的规则划分为不