[Go版]算法通关村第三关白银——双指针的妙用

目录

  • 双指针算法思想
    • 常见的两种类型:快慢指针和左右指针。
      • 快慢指针
      • 左右指针
  • 双指针应用实例
    • 题目:移除元素
      • 方法一:左右指针+交换值(图解如下)
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码
      • 方法二:快慢指针+覆盖值(图解如下)
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码
    • 题目:删除有序数组中的重复项
      • 方法:快慢指针+覆盖值
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码
    • 题目:删除有序数组中的重复项 II
      • 方法:快慢指针+覆盖值+统计重复次数
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码
    • 题目:轮转数组
      • 方法:反转数组
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码
    • 题目:汇总区间
      • 方法:快慢指针
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( n ) O(n) O(n)
        • Go代码
    • 题目:替换空格
      • 方法:快慢指针+切片扩容
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( s p a c e N u m ) O(spaceNum) O(spaceNum)
        • Go代码
    • 题目:荷兰国旗问题(颜色分类)
      • 方法:左右指针+交换值
        • 复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
        • Go代码

双指针算法思想

双指针是一种常用的算法思想,通常用于在数组或链表等数据结构中解决一些问题。双指针技巧通过使用两个指针来遍历数据结构,从而在一次遍历中解决问题,避免使用额外的数据结构,从而降低时间复杂度和空间复杂度。

常见的两种类型:快慢指针和左右指针。

快慢指针

快慢指针一般用于解决链表相关的问题。快指针每次移动两步,慢指针每次移动一步。通过快慢指针的遍历,可以找到链表的中间节点、判断链表是否有环、寻找链表倒数第 k 个节点等问题。

左右指针

左右指针一般用于解决数组或字符串相关的问题。左指针从数组的最左边开始,右指针从数组的最右边开始,然后向中间移动。通过左右指针的遍历,可以找到满足某种条件的子数组或子字符串,比如两数之和、反转数组、滑动窗口等问题。

双指针应用实例

题目:移除元素

题目链接:LeetCode-27. 移除元素
[Go版]算法通关村第三关白银——双指针的妙用_第1张图片

方法一:左右指针+交换值(图解如下)

[Go版]算法通关村第三关白银——双指针的妙用_第2张图片

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
  • 时间复杂度: O ( n ) O(n) O(n)

该函数使用了双指针技巧来移除数组中等于给定值 val 的元素。双指针方法可以将数组中所有等于 val 的元素移动到数组的末尾,并返回新数组的长度。
在最坏情况下,如果数组中所有元素都等于 val,那么需要遍历整个数组,时间复杂度是 O ( n ) O(n) O(n),其中 n 是数组的长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

该函数只使用了常量级别的额外空间来存储变量和常数。没有使用与输入规模成比例的额外数据结构。因此,空间复杂度是 O ( 1 ) O(1) O(1)

Go代码
func removeElement(nums []int, val int) int {
    length := len(nums)
    left, right := 0, length-1
    for left<=right {
        if nums[left]==val && nums[right]!= val {
            nums[left], nums[right] = nums[right], nums[left]
            left++
            right--
        }
        if nums[left] != val {
            left++
        }
        if nums[right] == val {
            right--
        }
    }
    return left
}

方法二:快慢指针+覆盖值(图解如下)

fast指针用于遍历数组,slow指针用于标记新数组的边界。当fast指针找到一个不等于val的元素时,将其复制到slow指针所在的位置,并递增slow指针。这样,slow指针之前的元素就构成了新数组。
[Go版]算法通关村第三关白银——双指针的妙用_第3张图片

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
Go代码
func removeElement(nums []int, val int) int {
    length := len(nums)
    slow, fast := 0, 0
    for ; fast<length; fast++ {
        if nums[fast] != val {
            nums[slow] = nums[fast]
            slow++
        }
    }
    return slow
}

题目:删除有序数组中的重复项

题目链接:LeetCode-26. 删除有序数组中的重复项
[Go版]算法通关村第三关白银——双指针的妙用_第4张图片

方法:快慢指针+覆盖值

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
Go代码
func removeDuplicates(nums []int) int {
    arrlen := len(nums)
    slow, fast := 0, 1
    for ; fast<arrlen; fast++ {
        if nums[fast] != nums[slow] {
            slow++
            nums[slow] = nums[fast]
        }
    }
    return slow+1
}

题目:删除有序数组中的重复项 II

题目链接:LeetCode-80. 删除有序数组中的重复项 II
[Go版]算法通关村第三关白银——双指针的妙用_第5张图片

方法:快慢指针+覆盖值+统计重复次数

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
Go代码
func removeDuplicates(nums []int) int {
    length := len(nums)
    if length == 0 {
        return 0
    }
    slow, fast := 0, 0
    count := 0
    pre := nums[fast]-1
    for ; fast<length; fast++ {
        if nums[fast] == pre {
            count++
        } else {
            count=0
        }
        if count <= 1 {
            nums[slow] = nums[fast]
            pre = nums[slow]
            slow++
        }
    }
    return slow
}

题目:轮转数组

题目链接:LeetCode-189. 轮转数组
[Go版]算法通关村第三关白银——双指针的妙用_第6张图片

方法:反转数组

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
Go代码
func reverse(arr []int, left int, right int) {
    length := len(arr)
    if length == 0 || length == 1 || left>=right || left<0 {
        return
    }
    for ; left<=right; left,right=left+1,right-1 {
        arr[left], arr[right] = arr[right], arr[left]
    }
}
func rotate(nums []int, k int)  {
    length := len(nums)
    k = k%length
    reverse(nums, 0, length-1)
    reverse(nums, 0, k-1)
    reverse(nums, k, length-1)
}

题目:汇总区间

题目链接:LeetCode-228. 汇总区间
[Go版]算法通关村第三关白银——双指针的妙用_第7张图片

方法:快慢指针

复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( n ) O(n) O(n)
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

    该函数使用了一个字符串切片 strArr 来存储结果,其长度不会超过输入数组 nums 的长度。在最坏情况下,整个数组都是独立的连续整数序列,结果中每个整数都需要一个独立的字符串表示。所以空间复杂度是 O ( n ) O(n) O(n)

Go代码
import (
    "fmt"
)
func summaryRanges(nums []int) []string {
    length := len(nums)
    strArr := make([]string, 0)
    slow, fast := 0, 0
    str := ""
    for ; fast < length; fast++ {
        if fast==length-1 || nums[fast]+1 != nums[fast+1] {
            if nums[fast] == nums[slow] {
                str = fmt.Sprintf("%d", nums[fast])
            } else {
                str = fmt.Sprintf("%d->%d", nums[slow], nums[fast])
            }
            strArr = append(strArr, str)
            slow = fast+1
        }
    }
    return strArr
}

题目:替换空格

题目链接:LeetCode-剑指 Offer 05. 替换空格
[Go版]算法通关村第三关白银——双指针的妙用_第8张图片

方法:快慢指针+切片扩容

  1. 因为字符串长度是固定的,而这里要把’ ‘换成’20%’,需要扩容才能装下转换后的字符,所以可以先转成byte切片,替换字符后,再转成字符串返回
  2. 遍历bype切片,查询有几个空格,算出所需的额外空间,然后是申请该空间大小的byte切片
  3. 将申请的切片append到原切片中
  4. 使用双指针,从尾部开始遍历并赋值,赋值的时候判断是赋原值还是换成20%即可。
复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( s p a c e N u m ) O(spaceNum) O(spaceNum)
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( s p a c e N u m ) O(spaceNum) O(spaceNum)

    该函数使用了常量级别的额外空间来存储变量和常数,以及一个大小为 2 * spaceNum 的临时字节切片 tmp 来存储替换空格后多出来的字符。空间复杂度随着输入字符串 s 中空格的数量线性增长。因此,空间复杂度是 O ( s p a c e N u m ) O(spaceNum) O(spaceNum),其中 spaceNum 是输入字符串 s 中空格的数量。

Go代码
func replaceSpace(s string) string {
    slice := []byte(s)
    length := len(slice)
    spaceNum := 0
    for _, v := range slice {
        if v == ' ' {
            spaceNum++
        }
    }
    tmp := make([]byte, spaceNum*2)
    slice = append(slice, tmp ...)
    slow, fast := len(slice)-1, length-1
    for fast >= 0 && slow >= 0 {
        if slice[fast] == ' ' {
            slice[slow] = '0'
            slice[slow-1] = '2'
            slice[slow-2] = '%'
            slow = slow-3
            fast--
        } else {
            slice[slow] = slice[fast]
            slow--
            fast--
        }
    }
    return string(slice)
}

题目:荷兰国旗问题(颜色分类)

题目链接:LeetCode-75. 颜色分类
[Go版]算法通关村第三关白银——双指针的妙用_第9张图片

方法:左右指针+交换值

  1. 因为是区分3个数,所以可以采用左右双指针法。使用指针 left 和 right 来分别追踪0和2应该放置的位置。
  2. 遍历该数组,如果是0则和left交换,如果是2则和right交换,注意和right交换之后的还需要再次被判断。
  3. 结束条件:i>right(因为,在i移动的过程中是逐渐远离left,靠近right的,中间=1不换的时候就直接往right方向走)
复杂度:时间复杂度: O ( n ) O(n) O(n)、空间复杂度: O ( 1 ) O(1) O(1)
Go代码
func sortColors(nums []int)  {
    length := len(nums)
    if length == 0 {
        return
    }
    left, right, i := 0, length-1, 0
    for i<=right {
        if nums[i] == 0 {
            nums[left], nums[i] = nums[i], nums[left]
            left++
            i++
        } else if nums[i] == 2 {
            nums[right], nums[i] = nums[i], nums[right]
            right--
        } else {
            i++
        }
    }
}

你可能感兴趣的:(算法与数据结构,golang,算法)