【LeetCode 算法专题突破】双指针(⭐)

news2025/1/22 9:14:37

文章目录

  • 前言
  • 1. 移动零
    • 题目描述
    • 代码
  • 2. 复写零
    • 题目描述
    • 代码
  • 3. 快乐数
    • 题目描述
    • 代码
  • 4. 盛最多水的容器
    • 题目描述
    • 代码
  • 5. 有效三角形的个数
    • 题目描述
    • 代码
  • 6. 三数之和
    • 题目描述
    • 代码
  • 7. 四数之和
    • 题目描述
    • 代码
  • 总结

前言

学算法入门必学的一个章节,双指针算法,不说废话,直接开始。

1. 移动零

我们先来一道经典的双指针题目试试水

题目链接:283. 移动零

题目描述


怎么样才能在不创建新数组的情况下把 0 移动到数组的末尾呢?(如果不是有这个要求我肯定也无脑创建一个数组遍历解决)来看代码:

代码

func moveZeroes(nums []int)  {
    slow, fast := 0, 0
    for fast < len(nums) {
        if nums[fast] != 0 {
            tmp := nums[fast]
            nums[fast] = nums[slow]
            nums[slow] = tmp
            slow++
        }
        fast++
    }
}

我们设置 slow,fast 两个指针,都从 0 开始遍历,fast 不断向后遍历查找不等于 0 的数交给 slow 位置,这样就会出现一种情况:

以 slow 为分界线,左边的区间都是排好的数字,右边就是没排好的数字,最后左边就排好了,而右边就都剩下 0 了

形象点说就是 fast 把数字都按序丢到了左边的区间

2. 复写零

像这样需要在数组中移动、删除、增加元素这类的操作,都是离不开双指针的算法思想

题目链接:1089. 复写零

题目描述


如果没有要求原地解决这道题目,也会非常的简单,但是如果需要我们原地求解,又好像有点无从下手了(如果使用 insert 方法来做这道题,那复杂度会非常的高)来看代码如何解决:

代码

func duplicateZeros(arr []int)  {
    left, right := 0, 0
    // 找到一共经过了几个 0, 调整好位置
    for right < len(arr) {
        if arr[left] == 0 { // 注意这里用的是 left
            right++
        }
        left++
        right++
    }
    left--
    right--
    // 反向遍历
    for left >= 0 {
        if right < len(arr) {
            arr[right] = arr[left]
        }
        if arr[left] == 0 { // 复写 0 的操作
            right--
            arr[right] = 0
        }
        left--
        right--
    }
}

这道题虽然是简单题,但是想要按照题目的要求来做出这道题并不容易,我们如果是第一次做,是很难想到使用反向迭代的双指针来做,所以还是重复我的一个观点:多刷算法,多积累,见多识广才能思路开阔。至少下一次遇到类似的题目我们就能匹配一下这个思路了。

回到正题,这道题如果使用双指针的话,需要我们从后往前迭代使用,直接用嘴说可能不太好理解,我给出的建议是,用题目给出的示例,然后对着代码把流程跑一遍,这样你能了解到这个算法的基本思路,可以看看图解:https://blog.csdn.net/Locky136/article/details/131537158

这里有两个需要注意的点:

  1. 一开始根据 left 经过多少个零来调整两个指针的位置(这里我用 left 和 right 命名是为了分辨两个指针的相对位置)
  2. 反向遍历时复写零的操作(为什么只复写了一次?因为还有一次和上一个操作合并了)

3. 快乐数

双指针还有一个非常重要的思想,就是快慢指针的思想,其实听名字大家差不多都能猜到具体的用法,但我们还是得来一道经典的题目试试水

题目链接:202. 快乐数

题目描述


这道题题意很好理解,所以我们直接根据示例,不说废话,直接上图:(题目的示例二,我们来模拟他的流程)

我们可以看到就像一个环,用快慢指针遍历,如果快指针追上了慢指针,那不就代表这段代码无限循环了吗?来看代码:

代码

func isHappy(n int) bool {
    Sum := func(n int) int { // 进行一次快乐数的计算
        sum := 0
        for n > 0 {
            tmp := n%10
            sum += tmp*tmp
            n /= 10
        }
        return sum
    }
    fast, slow := n, n
    for {
        slow = Sum(slow)
        fast = Sum(Sum(fast))
        if fast == slow {
            break;
        }
    }
    return fast == 1
}

其实这段算法的核心思想前面已经解释的很清楚了,这里就不赘述了,如果还有疑问跟着上面的图片走一遍代码就行~

4. 盛最多水的容器

再来一道经典的双指针题目练练手,如果没刷过这道题,你怎么敢说你刷过 LeetCode 呢~

题目链接:11. 盛最多水的容器

题目描述


这道题的思路说难不难,说简单其实也不一定想的出来(首先把暴力排除,用暴力匹配就没意思了,更何况 10^5 的数据量用 O(N^2) 的算法不一定能过完这些样例,可能会超时(不然也不是中等难度的题目了))那我们就来看看怎么用双指针来做出这道题吧~

代码

func maxArea(height []int) int {
    left, right, max := 0, len(height)-1, 0
    for left < right {
        tmp := Min(height[left], height[right])*(right-left) // 计算当前容量
        max = Max(max, tmp) // 迭代出最大容量
        if height[left] > height[right] {
            right--
        } else {
            left++
        }
    }
    return max
}

func Min(a, b int) int {
    if a > b {
        return b
    }
    return a
}

func Max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

在解释思路之前必须吐槽一下 LeetCode 的 Golang 编译器,最新版本的 Go 其实已经实装了 max 和 min 让我们更方便的求最大值和最小值,但是我试了一下,LeetCode 的编译器并没有支持这个版本,搞得我只好自己写找最大最小值的方法,可恶

这道题其实知道想清楚核心的思路就很好做,left 和 right 指针分别在两边,我们只需要让比较矮的那一边的柱子移动即可,因为如果让较高的柱子移动,容量只可能更小,而让较矮的柱子移动:如果遇到更高的柱子,容量就可能更大。(这感觉也有一点贪心的思想在里面),根据分析,我们只要不断移动较矮的柱子,就能求出最大的容量了。

5. 有效三角形的个数

咱们再来做一道题目,练习双指针~

题目链接:611. 有效三角形的个数

题目描述


听完题意,有点算法经验的我们都能看出这道题目,如果使用暴力来做,那复杂度会非常的高,而且也并不好写,那我们该怎么设计算法来解决这道题目?来看代码:

代码

func triangleNumber(nums []int) int {
    sort.Ints(nums)
    ans := 0
    for i := len(nums)-1; i >= 0 ; i-- {
        left, right := 0, i-1
        for left < right {
            if (nums[left]+nums[right]) > nums[i] {
                ans += (right-left)
                right--    
            } else {
                left++
            }
        }
    }
    return ans
}

这段代码的核心流程就是,将数组中最大的数作为三角形最长的那条边,也就是 i,利用三角形两边之长大于第三边的性质,快速找出符合要求的三角形,通过使用双指针的形式遍历余下的两条三角形的边

如果听完,代码也模拟完之后还有疑问的话,可以去看这篇文章,我曾经写过的详细文字解答,还配有清晰的图解:https://blog.csdn.net/Locky136/article/details/131590581

6. 三数之和

刷了这么多双支指针的题目了,怎么能错过 LeetCode 中大名鼎鼎的几数之和系列呢,两数之和,LeetCode 的第一道题,那是多少人刷题的起点,和梦想的开始呀,但是我们肯定不能还去做这么简单的题目,那必须是从三数之和开始刷起

题目链接:15. 三数之和

题目描述


这道题目其实并没有什么高深的技巧,抑或是复杂的算法,更多的是考察我们对代码的掌控能力,看看我们能不能写出一个像样的双指针算法,来看代码:

代码

func threeSum(nums []int) [][]int {
    sort.Ints(nums)
    var ans [][]int
    n := len(nums)-1
    for i, v := range nums[:n-1] {
        if i != 0 && v == nums[i-1] { // 跳过重复数字
            continue
        }
        if v+nums[i+1]+nums[i+2] > 0 { // 三数相加无论如何都会 > 0, 不需要再遍历 
            break
        }
        if v+nums[n]+nums[n-1] < 0 { // 三数最大值 < 0, 让 i 继续遍历
            continue
        }
        // 双指针
        left, right := i+1, n
        for left < right {
            s := v+nums[left]+nums[right]
            if s > 0 {
                right--
            } else if s < 0 {
                left++
            } else {
                ans = append(ans, []int{v, nums[left], nums[right]})
                // 跳过重复数字
                for left < right && nums[left] == nums[left+1] {
                    left++
                }
                for left < right && nums[right] == nums[right-1] {
                    right--
                }
                left++
                right--
            }
        }
    }
    return ans
}

思路其实很简单,依旧是遍历一个 i,作为起点,用双指针匹配出符合题目要求的值,最后拼接在一起返回即可,这里需要注意的是我们需要根据题目的要求跳过重复的数字,不然最后还得去重,那反而就更麻烦了。

7. 四数之和

接下来就是我们练习双指针的最后一道题目啦,四数之和~

题目链接:https://leetcode.cn/problems/4sum/

题目描述


四数之和相较于三数之和,需要枚举的数字多了一个,我们只需要在三数之和的条件下,多枚举一个数即可,不过对代码掌控能力的要求也随之升高了不少,来看代码:

代码

func fourSum(nums []int, target int) [][]int {
    sort.Ints(nums)
    var ans [][]int
    n := len(nums)-1
    for a := 0; a < n-2; a++ {
        value1 := nums[a]
        if a != 0 && value1 == nums[a-1] { // 跳过重复数字
            continue
        }
        if value1+nums[a+1]+nums[a+2]+nums[a+3] > target { // 四数之和 > target 
            break
        }
        if value1+nums[n-2]+nums[n-1]+nums[n] < target { // 四数最大和 < target
            continue
        }
        for b := a+1; b < n-1; b++ {
            value2 := nums[b]
            if b != a+1 && value2 == nums[b-1] { // 跳过重复数字
                continue
            }
            if value1+value2+nums[b+1]+nums[b+2] > target { // 四数之和 > target 
                break
            }
            if value1+value2+nums[n-1]+nums[n] < target { // 四数最大和 < target
                continue
            }
            left, right := b+1, n
            for left < right {
                sum := value1+value2+nums[left]+nums[right]
                if sum > target {
                    right--
                } else if sum < target {
                    left++
                } else {
                    ans = append(ans, []int{value1, value2, nums[left], nums[right]})
                    // 跳过重复数字
                    for left < right && nums[left] == nums[left+1] {
                        left++
                    }
                    for left < right && nums[right] == nums[right-1] {
                        right--
                    }
                    left++
                    right--
                }
            }
        }
    }
    return ans
}

省流:其实跟三数之和换汤不换药,就是多加上了一层循环,仅此而已,代码一下就能看的出来。不过为了更好的掌控代码,我就没有再用 Golang 提供的语法糖,而是老老实实的用 for 循环了。

总结

我刷过的,个人喜欢的,比较经典的,数组相关的双指针问题大概就是这些了,其实双指针并不能算是一个标准的算法,我个人更倾向于这是一个思考的方向,一个算法的基本素养。为什么这么说呢?因为之后我们去做更多其他算法题,多多少少都会用到这样一个思想,在做链表专题的时候,也会用到双指针的思想
所以想要学好算法,打好每一步的基础都是非常重要滴~

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

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

相关文章

Maven 快速入门

文章目录 一、Maven 间接和快速入门1.1 Maven 介绍1.2 Maven 主要作用理解1.3 Maven 安装和配置 二、基于 IDEA 的 Maven 工程创建2.1 梳理 Maven 工程 GAVP 属性2.2 IDEA 构建 Maven JavaSE 工程2.3 IDEA 构建 Maven JavaEE 工程2.4 Maven 工程项目结构说明 三、Maven 核心功能…

巧用正则表达式

文章目录 题目巧用正则表达式&#xff0c;题目将十进制转为16进制&#xff0c;可以采用Java的语法来表示 题目 巧用正则表达式&#xff0c;题目将十进制转为16进制&#xff0c;可以采用Java的语法来表示 String nInteger.toString(num,16); 那如何确定是否都是字母呢a-f呢&…

使用Python创建faker实例生成csv大数据测试文件并导入Hive数仓

文章目录 一、Python生成数据1.1 代码说明1.2 代码参考 二、数据迁移2.1 从本机上传至服务器2.2 检查源数据格式2.3 检查大小并上传至HDFS 三、beeline建表3.1 创建测试表并导入测试数据3.2 建表显示内容 四、csv文件首行列名的处理4.1 创建新的表4.2 将旧表过滤首行插入新表 一…

qml基础语法

文章目录 基础语法例子 属性例子 核心元素元素item RectangleText例子 Image例子 MouseArea例子Component&#xff08;组件&#xff09;例子简单变换例子 定位器ColumnRowGridFlowRepeater 布局InputKeys 基础语法 QML是一种用于描述对象如何相互关联的声明式语言。  QtQuick是…

qt-C++笔记之按行读取文件并切换复选框打印复选框拼接出的字符串

qt-C笔记之按行读取文件并切换复选框打印复选框拼接出的字符串 code review! 文章目录 qt-C笔记之按行读取文件并切换复选框打印复选框拼接出的字符串1.运行2.文件结构3.main.cc4.main.pro5.a.txt6.b.txt 1.运行 2.文件结构 3.main.cc 代码 #include <QApplication> #…

简易通讯录Promax

前言&#xff1a;哈喽小伙伴们&#xff0c;我们在前边的文章中已经介绍过了如何用C语言实现一个简易的通讯录&#xff0c;但是我们这个通讯录存在两个问题&#xff1a; 一是通讯录的大小不能自由变化&#xff1b;二是通讯录的信息在程序退出之后就没有了&#xff0c;不能保存。…

【C语言】进阶——文件操作

目录 前言 1.什么是文件 程序文件&#xff1a; 数据文件&#xff1a; 文件名&#xff1a; 2.文件的打开和关闭 2.1文件指针 2.2文件指针使用 2.3文件的打开和关闭 2.4 文件的使用方式 2.5文件的顺序读写 ✌字符输出函数fputc ✌字符输入函数fgetc ✌文本行输…

【重拾C语言】十三、动态数据组织(一)动态变量(malloc、calloc、realloc、free)

目录 前言 十三、动态数据组织 13.1 动态数据组织 13.2 动态变量 malloc函数 calloc函数 realloc函数 free函数 程序实例 前言 C语言中的动态数据组织是指在程序运行时根据需要动态地分配内存空间来存储数据。这允许程序在运行时根据实际需求来创建、修改和释放数据结…

【数字IC设计】VCS门级网表仿真

本文参考自文章。 除了RTL仿真以外&#xff0c;在IC设计过程中还会进行门级网表的仿真。当设计代码功能仿真通过之后&#xff0c;我们便使用Design Compiler工具对其进行逻辑综合&#xff0c;将RTL代码转化为由与、或、非等门电路和触发器组成的电路&#xff0c;称为门级网表(n…

25栈和队列-理解栈和队列

目录 LeetCode之路——232. 用栈实现队列 分析&#xff1a; LeetCode之路——225. 用队列实现栈 分析&#xff1a; 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;是两种基本的数据结构&#xff0c;它们在计算机科学中用于不同的目的。以下是它们的定…

【传输层协议】UDP/TCP结构特点与原理(详解)

文章目录 1. UDP1.1 UDP结构1.2 UDP特点1. 无连接2. 不可靠3. 面向数据报4. 缓冲区5. 大小受限6. 无序性 2. TCP2.1 TCP结构2.2 TCP特点1. 有连接2. 可靠性3. 面向字节流4. 拥塞控制5. 头部开销 2.3 TCP原理1. 确认应答&#xff08;安全机制&#xff09;2. 超时重传&#xff08…

Ceph分布式存储的简单介绍与Ceph集群的部署搭建

文章目录 1. 存储的概述1.1 单机存储设备1.1.1 DAS&#xff08;直接附加存储&#xff09;1.1.2 NAS&#xff08;网络附加存储&#xff09;1.1.3 SAN&#xff08;存储区域网络&#xff09; 1.2 单机存储的缺陷1.3 分布式存储&#xff08;软件定义的存储 SDS&#xff09;1.4 分布…

【计算机网络笔记】数据交换之报文交换和分组交换

系列文章目录报文交换分组交换存储-转发报文交换 vs 分组交换总结 系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 报文交换 报文&#xff1a;源&#xff08;应用&#xff09;发送的信息整体。比如一个文件、一…

Transformer 中 Positional Encoding 实现

参考博文&#xff1a; https://www.cnblogs.com/nickchen121/p/16470736.html 解决问题 位置编码的主要目的是确保模型能够理解序列中的元素之间的相对位置和顺序&#xff0c;从而更好地捕捉到语义信息。在Transformer模型中&#xff0c;位置编码通常与词嵌入&#xff08;w…

前端小知识之【浏览器内核】

目录 &#x1f31f;前言&#x1f31f;PC端浏览器内核&#x1f31f;Trident内核&#x1f31f;Gecko内核&#x1f31f;WebKit内核(Chromium)&#x1f31f;Blink内核 &#x1f31f;移动端浏览器内核&#x1f31f;应用&#x1f31f;写在最后 &#x1f31f;前言 通常所谓的浏览器内…

docker安装nessus

注册地址:https://zh-tw.tenable.com/products/nessus/nessus-essentials 临时邮箱:http://24mail.chacuo.net/ 帮助文档:https://docs.tenable.com/nessus/Content/DeployNessusDocker.htmdocker pull tenableofficial/nessusdocker run --name "my-nessus" -d -p 8…

【Go入门】编程语言比较:Golang VS Python

Golang&#xff1a;最佳人工智能语言&#xff0c;性能优于 Python 本节是学习go的引入&#xff0c;为了了解Python与go编程语言间比较。后续会完成相关课程&#xff0c;并分享笔记。 如今&#xff0c;世界各地有数百万用户使用 Golang 作为机器学习和人工智能的编程语言。 最好…

算法通过村第十四关-堆|白银笔记|经典问题

文章目录 前言在数组中寻找第K大的元素堆排序原理合并K个排序链表总结 前言 提示&#xff1a;想要从讨厌的地方飞出来&#xff0c;就得有藏起来的翅膀。 --三岛由纪夫《萨德侯爵夫人》 这里我们主要看一下经典的题目&#xff0c;这三个题目来说都是堆的热点问题。重点再理解处理…

Qt不能安装自己想要的版本,如Qt 5.15.2

使用在线安装工具安装Qt5.15.2时&#xff0c;发现没有Qt 5的相关版本&#xff0c;只有Qt 6的版本&#xff0c;这时选择右边的Archive&#xff0c;再点击筛选&#xff0c;这时就会出现之前的Qt版本。

vscode插件路径转移C盘之外盘

改变vscode系统路径 最近C盘路径不够了&#xff0c;网上的工具使用没那么精细&#xff0c;还不如自己手动看每个文件夹大小。在整理过长遇到vscode插件路径转移&#xff0c;方法如下&#xff1a; 桌面图标右键点击属性 改变–extensions-dir后面参数就可以了。