前情提示: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. Codeforces 1464F - My Beautiful Madness(树的直径)

    Codeforces 题面传送门 & 洛谷题面传送门 树上数据结构大杂烩(?) 首先考虑什么样的点能够在所有路径的 \(d\) 邻居的交集内.显然如果一个点在一条路径的 \(d\) 邻居内则必 ...

  2. Codeforces 1422F - Boring Queries(树套树)

    upd on 2021.9.5:昨天的那个版本被 2-tower 卡爆了,故今天重发一个. Codeforces 题面传送门 & 洛谷题面传送门 没往"每个数最多只有一个 \(> ...

  3. Codeforces 1175F - The Number of Subpermutations(线段树+单调栈+双针/分治+启发式优化)

    Codeforces 题面传送门 & 洛谷题面传送门 由于这场的 G 是道毒瘤题,蒟蒻切不动就只好来把这场的 F 水掉了 看到这样的设问没人想到这道题吗?那我就来发篇线段树+单调栈的做法. 首 ...

  4. Comet OJ Contest #13 D

    Comet OJ Contest #13 D \(\displaystyle \sum_{i=0}^{\left\lfloor\frac{n}{2}\right\rfloor} a^{i} b^{n- ...

  5. DirectX12 3D 游戏开发与实战第六章内容

    利用Direct3D绘制几何体 学习目标 探索用于定义.存储和绘制几何体数据的Direct接口和方法 学习编写简单的顶点着色器和像素着色器 了解如何用渲染流水线状态对象来配置渲染流水线 理解怎样创建常 ...

  6. 【R方差分析】蛋白质表达量多组比较

    初始数据类似: 蛋白质组数据虽不是严格的正态分布,但目前最常用的检验方法还是T检验(两组比较)和方差分析(多组比较).这个话题值得深究,这里不展开. 主要是求多个蛋白的Pvalue值或FDR,用于差异 ...

  7. 30-Container With Most Water-Leetcode

    Given n non-negative integers a1, a2, -, an, where each represents a point at coordinate (i, ai). n ...

  8. 我的分布式微服务框架:YC-Framework

    YC-Framework官方文档:http://framework.youcongtech.com/ YC-Framework源代码:https://github.com/developers-you ...

  9. mysql-centos8下安装

    参考文章 1.下载安装包 客服端与服务端 依赖包 2.linux下检查是否安装 rpm -qa | grep -i mysql 安装过会显示软件名称,没安装过就是空的 3.安装包传到虚拟机 先需要把安 ...

  10. KMP算法思路

    题目 给定一个字符串\(S\),求\(M\)字符串是否是\(S\)字符串中的子串.如果是,返回\(M\)对应\(S\)的第一个下标,否则返回-1. 例如:S串为a b c d a b c d a b ...