我要成为算法高手-双指针篇

news2024/11/15 21:57:19

目录

  • 什么是双指针?
  • 问题1:移动零
  • 问题2:复写零
  • 问题3:快乐数
  • 问题4:盛最多水的容器
  • 问题5:有效三角形个数
  • 问题6:查找总价格和为目标值的两个商品(两数之和)
  • 问题7:三数之和
  • 问题8:四数之和
  • 总结

什么是双指针?

双指针算法是一种常见的解决问题的技巧,通常使用两个变量在链表或者数组中进行迭代、遍历,从而达到解决问题的目的。光说理论太抽象,我们来几道题目吧~~

问题1:移动零

力扣链接:移动零
在这里插入图片描述
分析一下题目要求: 将给定数组中所有的0移动到数组的末尾,也就是将数组分成两个区域,前半部分是非零元素,后半部分都是零,并且保持非零元素的相对顺序,如示例1,非零元素的顺序:1,3,12,处理之后非零元素的顺序也要是1,3,12,在这里插入图片描述

解题思路:
既然是双指针算法,那我们就定义两个"指针"咯,这里的"指针",我们是指数组下标
定义两个"指针",dest,cur,
dest:已经处理好的区间内,非0元素的最后一个位置
cur:从左往右遍历数组
dest初始为-1,表示刚开始还没非0元素,cur初始为0
在扫描的过程中,一直让数组保持下面这个状态就好了
在这里插入图片描述
图解:
在这里插入图片描述
代码实现:

public void moveZeroes(int[] nums) {
    //双指针:定义dest和cur,dest初始值为-1
    //dest的作用:非0元素的最后一个位置,也就是[0,dest]的区间是非0元素
    //cur的作用:从左往右扫描数组,遍历数组
    int dest = -1;
    for (int cur = 0; cur < nums.length; cur++) {
        if (nums[cur] != 0) {
            //cur遇到非0元素,先交换dest+1位置和cur位置的元素,再dest++,cur++
            int tmp = nums[dest + 1];
            nums[dest + 1] = nums[cur];
            nums[cur] = tmp;         
            dest++;
        }
    }
}

问题2:复写零

力扣链接:复写零
在这里插入图片描述
分析一下题目要求: 所谓复写,也就是让我们抄一遍数组的内容,遇到非零的直接抄这个数,遇到零抄两遍零,题目要求我们在原数组上进行操作,也就是说不能重新开辟一个新的数组
在这里插入图片描述
解题思路:
先找到最后一个需要被复写的数,然后从后往前进行复写,如果从前往后,后面的数会被覆盖掉。
如何找到最后一个被复写的数?还是利用双指针算法,如图
在这里插入图片描述
根据cur位置的值,判断dest向前走一步还是两步,走完判断dest是否到达结束位置(dest是否>=arr.length-1),如果dest没有到达结束位置,则让cur++。重复上面的步骤,当dest到达最后位置时,cur指向的就是最后一个被复写的数,要注意的是,dest的位置可能是arr.length-1,也可能是arr.length,如果dest=arr.length(如下图),需要处理这个边界问题,
在这里插入图片描述
处理办法:arr[arr.length - 1] = 0;dest -= 2;cur- -;
在这里插入图片描述
接下来就可以从后往前开始复写了,拿下图举例
在这里插入图片描述

代码实现:

    public void duplicateZeros(int[] arr) {
        int cur = 0;//指向最后一个位置
        int dest = -1;//dest指结果中,最后需要复写的位置,开始时不知道dest在哪,所以-1
        //先找到最后一个被复写的数
        while (cur < arr.length) {
            if (arr[cur] == 0) {
                dest += 2;
            } else {
                dest++;
            }
            if (dest >= arr.length - 1) {
                break;
            }
            cur++;
        }
        //边界情况,可能出现:最后要复写两个0,第二个0在arr.length这个位置
        if (dest == arr.length) {
            arr[arr.length - 1] = 0;
            dest -= 2;
            cur--;
        }
        while (cur >= 0) {

            if (arr[cur] != 0) {
                arr[dest] = arr[cur];
                dest--;
            } else {
                arr[dest--] = 0;
                arr[dest--] = 0;
            }
            cur--;
        }
    }

问题3:快乐数

力扣链接:快乐数
在这里插入图片描述
题目要求:题目让我们判断给定的数是不是快乐数,快乐数:每次都对这个数进行一次操作(让它的值替换为它每一位的数的平方之和),重复这个操作,判断最终结果是不是变成1或者无限循环变不到1
解题思路:有点类似之前写过的判断链表是否成环,题目说明了结果是1或者无限循环,但是其实1也是无限循环,1重复上述操作结果永远都是1,所以我们可以使用快慢指针的思想,当两个指针相遇时,判断两个指针指向的数是不是1即可(如果不熟悉判断链表是否成环,可以看这篇哦链表OJ)
在这里插入图片描述
代码实现:

//返回n的每一位的平方之和
public int func(int n) {
    int sum = 0;
    while (n != 0) {
        int t = n % 10;
        sum += t * t;
        n /= 10;
    }
    return sum;
}

public boolean isHappy(int n) {
    int fast = func(n);
    int slow = n;
    while (fast != slow) {
        slow = func(slow);
        fast = func(func(fast));
    }
    return fast == 1;
}

问题4:盛最多水的容器

力扣链接:盛最多水的容器
在这里插入图片描述
题目要求: 给定了n条垂线,题目要找出两条线,与X轴构成的容器最多能盛水的容积
解题思路: 容积V = h*w,其中,h指高度,也就是两条线的最短的那条,w指宽度
在这里插入图片描述
如图:
在这里插入图片描述

代码实现

public int maxArea(int[] height) {
    //根据规律:向内枚举时,要么宽度肯定减小,但是高度只能是不变或减小(木桶效应)
    int left = 0;
    int right = height.length - 1;
    int maxV = 0;
    int V = 0;
    while (left < right) {
        V = (right - left) * Math.min(height[left], height[right]);
        if (V > maxV) {
            maxV = V;
        }
        //让高度小的移动,高度小也叫说明容积小,不符合要求
        if (height[left] <= height[right]) {
            left++;
        } else {
            right--;
        }
    }
    return maxV;
}

问题5:有效三角形个数

力扣链接:有效三角形个数
在这里插入图片描述
题目要求:
给定非负整数数组,在数组中挑3个能组成三角形数,求有几种挑法
解题思路:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现:

    public int triangleNumber(int[] nums) {
        // 利用单调性,使用双指针算法
        int count = 0;
        int[] ret = nums;
        Arrays.sort(ret);// 先排个序
        int flg = ret.length - 1;//固定好flg,表示最大的数
        while (flg >= 2) {
            //固定好flg之后,再利用双指针
            int left = 0;
            int right = flg - 1;
            while (left < right) {
                if (ret[left] + ret[right] > ret[flg]) {
                    count += (right - left);
                    right--;
                } else {
                    left++;
                }
            }
            flg--;
        }
        return count;
    }

问题6:查找总价格和为目标值的两个商品(两数之和)

力扣链接:查找总价格和为目标值的两个商品
在这里插入图片描述
题目要求: 给定数组和target(题目说明了已经排好序了),求数组中和为target的两个数,以数组的形式返回这两个数即可
解题思路: 定义双指针left,right分别指向第一个和最后一个元素,求两个指针指向的元素之和如果小于target,说明,要大一点!让left++,同理如果大于target,说明要小一点~~,让right–,当两个值等于target,此时可以返回了
代码实现:

    public int[] twoSum(int[] price, int target) {
        int[] ret = new int[2];//返回的数组
        //定义双指针left,right
        int left = 0;
        int right = price.length - 1;
        while (left < right) {
            int sum = price[left] + price[right];
            if (sum < target) {
                left++;
            } else if (sum > target) {
                right--;
            } else {
                ret[0] = price[left];
                ret[1] = price[right];
                break;
            }
        }
        return ret;
    }

问题7:三数之和

力扣链接:三数之和
在这里插入图片描述
题目要求:
给定整数数组,判断是否存在相加和为0的三个数,这三个数的下标不能重复,也就是说这三个数下标是不一样的,返回所有的三元组,答案不能是重复的三元组
解题思路:
在这里插入图片描述
找到之后left和right继续移动,解决了不漏的问题,能够把所有的三元组都找出来,但是并没有满足题目要求:不能重复,解决办法就是:当找到两个数后,记录left和right的值,left和right跳过重复的数,另外,固定的数a(下标是flg)也要跳过重复的数
代码实现:

    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);//先排序
        int flg = 0;//固定一个的数
        List<List<Integer>> ret = new ArrayList<>();// 返回的List
        while (flg <= nums.length-2) {
            //双指针算法,left,right
            int left = flg + 1;//左指针
            int right = nums.length - 1;//右指针
            int a = nums[flg];//双指针找目标值和为-a的两个数
            if(a>0) {
                //小优化,大于0了,后面的都是>0的数,肯定找不到-a
                break;
            }
            //双指针算法,思路和求两数之和一样
            while (left < right) {
                if (nums[left] + nums[right] < (-a)) {
                    left++;
                } else if (nums[left] + nums[right] > (-a)) {
                    right--;
                } else {
                    List<Integer> list = new ArrayList<>();
                    list.add(a);
                    list.add(nums[left]);
                    list.add(nums[right]);
                    ret.add(list);//
                    // 找到结果之后,left,right跳过重复元素,避免越界
                    int leftNumber = nums[left];
                    int rightNumber = nums[right];
                    while (nums[left] == leftNumber && left < nums.length - 1) {
                        left++;
                    }
                    while (nums[right] == rightNumber && right > 0) {
                        right--;
                    }
                }
            }
            // flg也要跳过重复的元素,flg不能越界
            while (nums[flg] == a&&(flg <= nums.length-2)) {
                flg++;
            }
        }
        return ret;
    }

问题8:四数之和

力扣链接:四数之和
在这里插入图片描述
题目要求:
在给定数组中,找出4个和为target的数,这四个数的下标不能重复
解题思路:
明白了两数之和跟三数之和后,四数之和的思路就很简单了,先排序,固定一个数a(最左边或者最右边的数,都是一样的),然后在后面的区间按照三数之和的思路:固定一个数b,按照两数之和找出和为target-a-b的两个数
在这里插入图片描述
同样的,left、right、flg1、flg2也要跳过重复的数
代码实现:

    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> ret = new ArrayList<>();// 返回的List
        for (int i = 0; i < nums.length; ) {//固定数a
            for (int j = i + 1; j < nums.length; ) {//固定数b
                int left = j + 1;
                int right = nums.length - 1;
                long aim = (long) target - (nums[i] + nums[j]);//a+b
                while (left < right) {
                    if (nums[left] + nums[right] < aim) {
                        left++;
                    } else if (nums[left] + nums[right] > aim) {
                        right--;
                    } else {
                        List<Integer> list = new ArrayList<>();
                        list.add(nums[left]);
                        list.add(nums[right]);
                        list.add(nums[i]);
                        list.add(nums[j]);
                        ret.add(list);
                        //处理细节,去重
                        int leftNumber = nums[left];
                        int rightNumber = nums[right];
                        while (nums[left] == leftNumber && left < nums.length - 1) {
                            left++;
                        }
                        while (nums[right] == rightNumber && right > 0) {
                            right--;
                        }
                    }
                }
                j++;
                while ((j < nums.length - 2) && nums[j - 1] == nums[j]) {
                    j++;
                }
            }
            i++;
            while ((i < nums.length - 3) && nums[i - 1] == nums[i]) {
                i++;
            }
        }
        return ret;

总结

遇到数组划分问题(按要求将数组分划分几个区域)或者如果数组存在单调性(有序的)时,我们可以考虑双指针算法~~

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

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

相关文章

Nextjs 集成TinyMCE实现富文本编辑器

目录 一、导入依赖 二、动态导入 三、完整案例 四、参考文档 一、导入依赖 yarn add tinymce/tinymce-react二、动态导入 import dynamic from next/dynamic;const Editor dynamic(() > import(tinymce/tinymce-react).then(mod > mod.Editor),{ssr: false} ); 三…

力扣hot100:394. 字符串解码(递归/括号匹配,字符串之间相对顺序)

LeetCode&#xff1a;394. 字符串解码 本题容易想到用递归处理&#xff0c;在写递归时主要是需要明确自己的递归函数的定义。 不过我们也可以利用括号匹配的方式使用栈进行处理。 1、递归 定义递归函数string GetString(string & s,int & i); 表示处理处理整个numbe…

Django CreateView视图

CreateView是Django提供的一个基于类的视图&#xff0c;用于处理创建新对象的操作。它可以帮助你轻松地创建一个表单页面&#xff0c;用户可以通过这个页面创建新的对象。 1&#xff0c;添加视图 Test/app3/views.py from django.shortcuts import render# Create your views…

汇聚荣科技有限公司实力如何?

汇聚荣科技有限公司实力如何?在科技日新月异的今天&#xff0c;一个公司的实力往往体现在其技术创新能力、市场占有率、团队专业度、客户满意度以及财务健康状况等多个维度。针对“汇聚荣科技有限公司”这一话题&#xff0c;我们将从这五个方面进行深入探讨。 一、技术创新能力…

易舟云财务软件:数字化时代的财务管家

在数字化浪潮的推动下&#xff0c;财务软件成为了企业提升财务管理效率、实现数字化转型的关键工具。易舟云财务软件&#xff0c;正是这样一款深受企业喜爱的财务管理系统。本文将带你详细了解易舟云财务软件的特点、版本区别以及如何使用它来优化财务工作。 易舟云财务软件的特…

2024050901-重学 Java 设计模式《实战访问者模式》

重学 Java 设计模式&#xff1a;实战访问者模式「模拟家长与校长&#xff0c;对学生和老师的不同视角信息的访问场景」 一、前言 能力&#xff0c;是你前行的最大保障 年龄会不断的增长&#xff0c;但是什么才能让你不慌张。一定是能力&#xff0c;即使是在一个看似还很安稳…

格式化输出相关

格式化输出 使用print() %格式化 语法 “%[零个或一个或多个辅助指令]<格式化符号>”%(一个或多个变量、表达式、值) 格式化符号 %c 格式化为字符&#xff0c;整数或字符 %r 使用repr()函数格式化字符串 %s 使用str()函数格式化为字符串 %d 格式化为整数 %u…

Mybatis05-一对多和多对一处理

多对一和一对多 多对一 多对一的理解&#xff1a; 多个学生对应一个老师 如果对于学生这边&#xff0c;就是一个多对一的现象&#xff0c;即从学生这边关联一个老师&#xff01; 结果映射&#xff08;resultMap&#xff09;&#xff1a; association 一个复杂类型的关联&…

扩展学习|风险管理的文献综述汇总(持续更新向)

一、风险管理发展历程和趋势综述&#xff08;2007年发表&#xff09; 文献来源&#xff1a;[1]严复海,党星,颜文虎.风险管理发展历程和趋势综述[J].管理现代化, 2007(2):4.DOI:CNKI:SUN:GLXX.0.2007-02-009. 简介&#xff1a;该文以风险管理发展历程中的大事件为线索, 对风险管…

设计模式-享元模式(结构型)

享元模式 享元模式是一种结构型模式&#xff0c;它主要用于减少创建对象的数量&#xff0c;减少内存占用。通过重用现有对象的方式&#xff0c;如果未找到匹配对象则新建对象。线程池、数据库连接池、常量池等池化的思想就是享元模式的一种应用。 图解 角色 享元工厂&#xf…

Nginx05-负载均衡详解、LNMP+NFS、会话保持、负载均衡状态检查upstream-check、平滑升级

目录 写在前面Nginx05Nginx 负载均衡&#xff08;upstream模块&#xff09;概述常见选择负载均衡和反向代理的区别Nginx负载均衡的方式Nginx运行状况检查备份服务器Nginx upstream模块选项说明 实验1 负载均衡两台frontfront配置lb01配置测试流程梳理 实验2 LNMPNFS小实验NFS配…

网络安全课程开发

我们为卡巴斯基实验室开发了一个交钥匙教育门户网站&#xff0c;并为其开设了网络安全课程。在资源上&#xff0c;你可以熟悉课程的理论部分-观看视频或阅读插图文本版本&#xff0c;然后通过回答问题来验证你的知识。通过最终测试后&#xff0c;用户将获得证书。 对于这个项目…

hive 安装 嵌入模式 笔记

$ hive $ HIVE_HOME/bin/schematool -dbType derby –initSchema $ schematool -verbose -validate -dbType derby $HIVE_HOME/bin/hiveserver2 这个启动了先不要关闭&#xff0c;再打开一个终端进行下面的步骤 Beeline -u show databases 总结 报错1 WARN jdbc.HiveConnecti…

【ARM Cache 及 MMU 系列文章 6.4 -- ARMv8/v9 如何读取 Cache Tag 及分析其数据?】

请阅读【ARM Cache 及 MMU/MPU 系列文章专栏导读】 及【嵌入式开发学习必备专栏】 文章目录 Cache Tag 数据读取测试代码Cache Tag 数据读取 在处理器中,缓存是一种快速存储资源,用于减少访问主内存时的延迟。缓存通过存储主内存中经常访问的数据来实现这一点。为了有效地管…

图钉AI导航

1、图钉AI导航 https://www.tudingai.com/

应急物资管理系统|DW-S300构筑现代化战备保障的利器

行业背景 智慧应急物资管理系统&#xff08;智物资DW-S300&#xff09;是一套成熟系统&#xff0c;依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 政府相关部门设立的应急物资库是…

Python pickle反序列化

基础知识 Pickle Pickle在Python中是一个用于序列化&#xff08;将对象转换为字节流&#xff09;和反序列化&#xff08;将字节流转换回对象&#xff09;的标准库模块。它主要用于将Python对象保存到文件或通过网络进行传输&#xff0c;使得数据可以跨会话和不同的Python程序共…

面试题:缓存穿透,缓存击穿,缓存雪崩

1 穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; ——简介&#xff1a;缓存穿透指的是恶意用户或攻击者通过请求不存在于缓存和后端存储中的数据来使得所有请求都落到后端存储上&#xff0c;导致系统瘫痪。 ——详述&#xff1a;(缓存穿透是指查询一个一定不存在的数…

【lesson7】服务端业务处理模块实现

文章目录 业务处理实现思路业务处理类设计成员变量成员函数RunModuleupLoadlistShowdownLoadgetETagInfo 业务处理实现思路 云备份项目中 &#xff0c;业务处理模块是针对客户端的业务请求进行处理&#xff0c;并最终给与响应。而整个过程中包含以下要实现的功能&#xff1a; …

通过微软提供的工具制作win10启动盘

1.到如下链接下载微软提供的工具&#xff1a;MediaCreationTool_22H2.exe https://www.microsoft.com/zh-cn/software-download/windows10 2.启动该工具&#xff0c;选择下面选项&#xff0c;根据提示一步步操作即可&#xff08;注意&#xff1a;要提前准备一个实际空间大于8…