前情提示:Go语言学习者。本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正

关于golang算法文章,为了便于下载和整理,都已开源放在:

涉及题目

Leetcode 141.环形链表

Leetcode 142.环形链表II

Leetcode 704. 二分查找

Leetcode 167.两数之和 II - 输入有序数组

正文

我把双指针技巧再分为两类,一类是「快慢指针」,一类是「左右指针」。前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环;后者主要解决数组(或者字符串)中的问题,比如二分查找。

一、快慢指针的常见算法

快慢指针一般都初始化指向链表的头结点 head,前进时快指针 fast 在前,慢指针 slow 在后,巧妙解决一些链表中的问题。

1、判定链表中是否含有环

这应该属于链表最基本的操作了,如果读者已经知道这个技巧,可以跳过。

单链表的特点是每个节点只知道下一个节点,所以一个指针的话无法判断链表中是否含有环的。

如果链表中不含环,那么这个指针最终会遇到空指针 null 表示链表到头了,这还好说,可以判断该链表不含环。

/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func hasCycle(head *ListNode) bool{
for head != nil{
head = head.Next
}
return false
}

但是如果链表中含有环,那么这个指针就会陷入死循环,因为环形数组中没有 null 指针作为尾部节点。

经典解法就是用两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

/** 141
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func hasCycle(head *ListNode) bool {
var fast, slow *ListNode
fast = head
slow = head
for fast != nil && fast.Next != nil{
fast = fast.Next.Next
slow = slow.Next
if fast == slow{
return true
}
}
return false
}

2、已知链表中含有环,返回这个环的起始位置

这个问题一点都不困难,有点类似脑筋急转弯,先直接看代码:

// 142
func detectCycle(head *ListNode) *ListNode{
var fast, slow *ListNode
fast = head
slow = head
for fast != nil && fast.Next != nil{
fast = fast.Next.Next
slow = slow.Next
if fast == slow{
break
}
}
// 上面的代码类似 hasCycle函数
if fast == nil || fast.Next == nil{
// fast 遇到空指针说明没有环
return nil
}
slow = head
for slow != fast{
fast = fast.Next
slow = slow.Next
}
return slow
}

可以看到,当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。这是为什么呢?

第一次相遇时,假设慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步,也就是说比 slow 多走了 k 步(也就是环的长度)。

设相遇点距环的起点的距离为 m,那么环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。

巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点。

所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后就会相遇,相遇之处就是环的起点了。

3、寻找链表的中点

类似上面的思路,我们还可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。

for fast != nil && fast.next != nil{
fast = fast.next.next
slow = slow.next
}
// slow 就在中间位置
return slow

当链表的长度是奇数时,slow 恰巧停在中点位置;如果长度是偶数,slow 最终的位置是中间偏右:

寻找链表中点的一个重要作用是对链表进行归并排序。

回想数组的归并排序:求中点索引递归地把数组二分,最后合并两个有序数组。对于链表,合并两个有序链表是很简单的,难点就在于二分。

但是现在你学会了找到链表的中点,就能实现链表的二分了。关于归并排序的具体内容本文就不具体展开了。

4、寻找链表的倒数第 k 个元素

我们的思路还是使用快慢指针,让快指针先走 k 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾 null 时,慢指针所在的位置就是倒数第 k 个链表节点(为了简化,假设 k 不会超过链表长度):

var slow,fast *ListNode
slow = fast = head
for k-- > 0{
fast = fast.next
}
for fast != nil{
slow = slow.next
fast = fast.next
}
return slow

二、左右指针的常用算法

左右指针在数组中实际是指两个索引值,一般初始化为 left = 0, right = nums.length - 1 。

1、二分查找

前文「二分查找」有详细讲解,这里只写最简单的二分算法,旨在突出它的双指针特性:

// 704
func search(nums []int, target int) int{
left := 0
right := len(nums) - 1
for left <= right{
mid := (right + left) / 2
if nums[mid] == target{
return mid
}else if nums[mid] < target{
left = mid + 1
}else{
right = mid -1
}
}
return -1
}

2、两数之和

直接看一道 LeetCode 题目吧:

只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节 left 和 right 可以调整 sum 的大小:

// 167
func twoSum(numbers []int, target int) []int {
// 左右指针在数组的两端初始化
left := 0
right := len(numbers)-1
for left < right{
sum := numbers[left] + numbers[right]
if sum == target{
// 题目要求的索引是从1开始的
return []int{left+1, right+1}
}else if sum < target{
left++ // 让sum大一点
}else{
right-- // 让sum小一点
}
}
return []int{-1, -1}
}

3、反转数组

func reverse(nums []int){
left := 0
right := len(nums) - 1
for left < right{
// 交换nums[left]和nums[right]
temp := nums[left]
nums[left] = nums[right]
nums[right] = temp
left++
right--
}
}

4、滑动窗口算法

这也许是双指针技巧的最高境界了,如果掌握了此算法,可以解决一大类子字符串匹配的问题,不过「滑动窗口」稍微比上述的这些算法复杂些。

幸运的是,这类算法是有框架模板的,将在后面讲解了「滑动窗口」算法模板,帮大家秒杀几道 LeetCode 子串匹配的问题。

5、双指针技巧套路框架——Go语言版的更多相关文章

  1. 2、动态规划接替套路框架——Go语言版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  2. 4、BFS算法套路框架——Go语言版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  3. 3、回溯算法解题套路框架——Go语言版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  4. 7、滑动窗口套路算法框架——Go语言版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  5. 1、学习算法和刷题的框架思维——Go版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  6. Windows 8.1 with Update 镜像下载(增OEM单语言版)

    该系统已有更新的版本,请转至<Windows 8.1 with update 官方最新镜像汇总>下载. 2014年4月9日凌晨,微软向MSDN订阅用户开放了Windows 8.1 with ...

  7. 寒假答辩作品——掘地求升C语言版

    寒假答辩—掘地求升(C语言版) 前言 这个是作为寒假答辩作品写的. 之前考虑过用Unity写个游戏,但毕竟不熟悉C#,感觉几乎都是在套模板,而且写着不顺手,有想法却只能 看着C#发呆,很是无奈,所以决 ...

  8. 教孩子学编程 python语言版PDF高清完整版免费下载|百度云盘|Python入门

    百度云盘:教孩子学编程 python语言版PDF高清完整版免费下载 提取码:mnma 内容简介 本书属于no starch的经典系列之一,英文版在美国受到读者欢迎.本书全彩印刷,寓教于乐,易于学习:读 ...

  9. 手把手教你搭建SSH框架(Eclipse版)

    原文来自公众号[C you again],若需下载完整源码,请在公众号后台回复"ssh". 本期文章详细讲解了SSH(Spring+SpringMVC+Hibernate)框架的搭 ...

随机推荐

  1. Docker 急速入门

    1. 概述 之前聊了很多 SpringCloud 相关的话题,今天我们来聊聊服务容器 Docker. 2. 在 CentOS7 安装 Docker 2.1 卸载旧版本的Docker  # yum re ...

  2. linux 同时执行多个命令及几个基础命令

    先后不同的命令用分号:隔开即可 基础命令: 1.cd 进入目录 /代表根目录,.代表当前目录,..代表上一级目录 2.ls 显示当前目录下的所有文件和文件夹 -F区分目录和文件,文件后边是*代表可执行 ...

  3. uniapp增加自定义埋点功能

    起因 首先来说,uniapp其实是自带系统埋点统计功能的.基本也算是面面俱到. 但是一些未知原因,貌似数据有所丢失,再加上没有一些重要的定制化功能,以及最重要的数据安全方面的考虑,还是决定接入公司的埋 ...

  4. 洛谷 P7451 - [THUSCH2017] 杜老师(线性基+根分+结论题)

    题面传送门 看到乘积为平方数我们可以很自然地想到这道题,具体来说,我们对 \(1\sim 10^7\) 中所有质因子标号 \(1,2,\cdots,\pi(10^7)\),对于 \(x\in[l,r] ...

  5. 【代谢组学】Metabolomics资源推送

    入门课程 伯明翰大学: Metabolomics: Understanding Metabolism in the 21st Century 数据处理 阿拉巴马大学伯明翰分校5年(2013-2018) ...

  6. Linux之vi和vim编辑器

    目录 1. vi和vim简介 2. vi 和 vim 的三种常见模式 2.1 正常模式 2.2 插入模式 2.3 命令行模式 3. 三种模式间的切换 4. 常用快捷键案例 5. 常用命令 1. vi和 ...

  7. 使用C语言来扩展PHP,写PHP扩展dll

    转自http://www.cnblogs.com/myths/archive/2011/11/28/2266593.html 以前写过一次PHP扩展DLL,那个是利用调用系统的COM口实现的扩展,与P ...

  8. Spark中的分区方法详解

    转自:https://blog.csdn.net/dmy1115143060/article/details/82620715 一.Spark数据分区方式简要 在Spark中,RDD(Resilien ...

  9. Hive(四)【DML 数据导入导出】

    目录 一.数据导入 1.1 [load]--向数据中装载数据 案例 1.2 [insert]--查询语句向表中插入数据 案例 1.3 [as select]--查询语句中创建表且加载数据 案例 1.4 ...

  10. 零基础学习java------day15--------collections用法,比较器,Set(TreeSet,TreeMap),异常

    1. Collections用法 Collections: 集合的工具类public static <T> void sort(List<T> list) 排序,升序publi ...