代码随想录:基础算法

排序

力扣题目:912. 排序数组

冒泡排序

1
2
3
4
5
6
7
8
9
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    bubbleSort(arr)
    fmt.Println(arr)
}

func bubbleSort(arr []int) {
	// ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func bubbleSort(arr []int) {
	length := len(arr)
	for i := 0; i < length-1; i++ { // i控制排序的轮数(每轮让1个元素有序)
		for j := 0; j < length-i-1; j++ { // j控制实际参与排序的元素(每轮减少1个元素)
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
}

选择排序

1
2
3
4
5
6
7
8
9
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    selectionSort(arr)
    fmt.Println(arr)
}

func selectionSort(arr []int) {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func selectionSort(arr []int) {
    for i := 0; i < len(arr)-1; i++ {
       minIndex := i
       for j := i + 1; j < len(arr); j++ {
          if arr[j] < arr[minIndex] {
             minIndex = j
          }
       }
       arr[i], arr[minIndex] = arr[minIndex], arr[i]
    }
}

插入排序

1
2
3
4
5
6
7
8
9
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    insertionSort(arr)
    fmt.Println(arr)
}

func insertionSort(arr []int) {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func insertionSort(arr []int) {
    for i := 0; i < len(arr)-1; i++ {
       currentVal := arr[i+1]
       insertIndex := i
       for insertIndex >= 0 && currentVal < arr[insertIndex] {
          arr[insertIndex+1] = arr[insertIndex]
          insertIndex--
       }
       arr[insertIndex+1] = currentVal
    }
}

快速排序

1
2
3
4
5
6
7
8
9
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    quickSort(arr, 0, len(arr)-1)
    fmt.Println(arr)
}

func quickSort(arr []int, left, right int) {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func partition(arr []int, left, right int) int {
    pivot := arr[right]
    i := left - 1 // i指向左边小于pivot元素集合中的最后一个元素
    for j := left; j < right; j++ {
       if arr[j] <= pivot {
          i++
          arr[i], arr[j] = arr[j], arr[i]
       }
    }
    arr[i+1], arr[right] = arr[right], arr[i+1] // 把pivot放在“中间”的位置
    return i + 1                                // i+1就是pivot的位置下标
}

func quickSort(arr []int, left, right int) {
    if left < right {
       pivotIndex := partition(arr, left, right)
       quickSort(arr, left, pivotIndex-1)
       quickSort(arr, pivotIndex+1, right)
    }
}

归并排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    help := make([]int, len(arr))
    mergeSort(arr, help, 0, len(arr)-1)
    fmt.Println(arr)
}

func mergeSort(arr, help []int, left, right int) {
    // ......
}
答案

也是一种分治的思想,将原数组分为左右两个数组,然后对左右两个数组递归执行mergeSort函数,直到最后数组元素个数小于等于1。在mergeSort函数中,调用merge函数,将左右两个数组合并。

具体如下:

merge函数签名:mergeSort(arr, help []int, left, right int)

在mergeSort函数中,先求得元素的中点位置下标,然后递归调用:

mergeSort(arr, help, left, middleIndex) // 对左边排序

mergeSort(arr, help, middleIndex+1, right) // 对右边排序

merge(arr, help, middleIndex, left, right) // 合并左右的数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func mergeSort(arr, help []int, left, right int) {
    // 递归的出口:left>=right
    if left < right {
       middleIndex := left + (right-left)>>1      // 求中点下标
       mergeSort(arr, help, left, middleIndex)    // 左边排序
       mergeSort(arr, help, middleIndex+1, right) // 右边排序
       merge(arr, help, middleIndex, left, right) // 合并左右的数组
    }
}

func merge(arr, help []int, middleIndex, left, right int) {
    p1 := left
    p2 := middleIndex + 1
    i := left
    for ; p1 <= middleIndex && p2 <= right; i++ {
       if arr[p1] <= arr[p2] {
          help[i] = arr[p1]
          p1++
       } else {
          help[i] = arr[p2]
          p2++
       }
    }
    for ; p1 <= middleIndex; i++ {
       help[i] = arr[p1]
       p1++
    }
    for ; p2 <= right; i++ {
       help[i] = arr[p2]
       p2++
    }
    for j := left; j <= right; j++ {
       arr[j] = help[j]
    }
}

堆排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    fmt.Println("原始数组:", arr)
    heapSort(arr)
    fmt.Println("排序后数组:", arr)
}

func heapSort(arr []int) {
    // ......
}
答案

堆排序之前需要先构建大(小)根堆。

大根堆:首先,堆是一个完全二叉树,其次,二叉树中所有子树的根节点都大于它的孩子节点,也就是堆的根节点是最大的元素。

我们现在需要将元素从小到大排序,那么我们需要构建大根堆(a[0] 为最大值),

然后取出 a[0] 的值和数组中的最后一个元素交换,这样最后一个元素就是最大的了。

然后再对 0 位置进行堆化,又得到 a[0] 为最大值,再和倒数第二个元素进行交换,依次循环下去。

构建大根堆:

我们首先要找到最后一个非叶子节点 i(下标为 n/2-1),这样我们才能判断其左右子孩子和该节点元素的大小,然后进行堆化。

现在我们要对 i 位置进行堆化,先假设 i 位置元素值最大(largest=i),比较左右两个孩子元素和 i 位置元素的大小关系。

拿到 i,i 左孩子,i 右孩子 三个中最大元素的下标,更新 largest 的值。

如果 i 和 largest 不相等,交换 arr[largest] 和 arr[i] 的值,然后对 largest 位置再次进行堆化(递归调用)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func main() {
    arr := []int{9, 2, 5, 1, 3, 7, 6, 4, 8}
    fmt.Println("原始数组:", arr)
    heapSort(arr)
    fmt.Println("排序后数组:", arr)
}

// 调整堆
func heapify(arr []int, n, i int) {
    largest := i
    left := 2*i + 1
    right := 2*i + 2

    // 如果左子节点大于根节点,则更新最大值索引
    if left < n && arr[left] > arr[largest] {
       largest = left
    }

    // 如果右子节点大于根节点,则更新最大值索引
    // 注意加上 right < n 的条件限制,因为堆最多n个元素
    if right < n && arr[right] > arr[largest] {
       largest = right
    }

    // 如果最大值不是根节点,则交换根节点和最大值,然后继续调整堆
    if largest != i {
       arr[i], arr[largest] = arr[largest], arr[i]
       heapify(arr, n, largest) // 对 largest 位置再次堆化
    }
}

// 堆排序
func heapSort(arr []int) {
    n := len(arr)

    // 构建最大堆
    for i := n/2 - 1; i >= 0; i-- { // 从最后一个非叶子节点(下标为n/2-1)开始,因为在heapify要比较i位置元素和它左右孩子元素的大小
       heapify(arr, n, i)
    }

    // 逐个将最大值(根节点)移到数组末尾,并调整堆
    for i := n - 1; i >= 0; i-- {
       arr[0], arr[i] = arr[i], arr[0] // 此时 arr[0]是最大的元素,将其和数组中的最后一个元素交换,然后再对0位置进行堆化
       heapify(arr, i, 0)              // 注意这里的第二个参数为i
    }
}

数组

二分搜索

704. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

1
2
3
4
5
6
7
8
9
func main() {
    nums := []int{0, 1, 2, 3, 4, 5, 6}
    r := search(nums, 4)
    fmt.Println(r)
}

func search(nums []int, target int) int {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func search(nums []int, target int) int {
    left := 0
    right := len(nums) - 1
    for left <= right { // 注意区间的开闭,这里采用左闭右闭区间
       middle := left + (right-left)>>1
       if target == nums[middle] {
          return middle
       }
       if target < nums[middle] {
          right = middle - 1
       } else {
          left = middle + 1
       }
    }
    return -1
}

移除元素

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    nums := []int{1, 2, 3, 3, 3, 4, 5, 3, 5}
    r := removeElement(nums, 3)
    fmt.Println(r)
    fmt.Println(nums)
}

func removeElement(nums []int, val int) int {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func removeElement(nums []int, val int) int {
    // 使用快慢指针的思想
    // 快指针fast遍历数组,慢指针slow指向符合要求的数组集合的最后一个元素。
    // 当fast指向的元素不等于val时,把该值赋给slow指向的位置,然后slow加1。
    fast := 0
    slow := -1
    for ; fast < len(nums); fast++ {
       if nums[fast] != val {
          slow++
          nums[slow] = nums[fast]
       }
    }
    return slow + 1
}

有序数组的平方

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

1
2
3
4
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

1
2
3
4
5
6
7
8
9
func main() {
    arr := []int{-4, -1, 0, 3, 10}
    r := sortedSquares(arr)
    fmt.Println(r)
}

func sortedSquares(nums []int) []int {
    // ......
}
答案

分析:观察平方之后的数组,我们会发现一个规律,那就是数组的两边大,中间小。

我们就可以考虑使用双指针,分别指向数组的左(left)和右(right),然后比较左右两个数哪个大,就放到数组的最后,然后一定该指针的位置,再次比较,直到left不小于right。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func sortedSquares(nums []int) []int {
    left := 0
    right := len(nums) - 1
    help := make([]int, len(nums))
    for i := len(nums) - 1; left <= right; i-- {
       if nums[right]*nums[right] >= nums[left]*nums[left] {
          help[i] = nums[right] * nums[right]
          right--
       } else {
          help[i] = nums[left] * nums[left]
          left++
       }
    }
    return help
}

长度最小的子数组

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 连续

子数组

[nums_l, nums_l+1, ..., nums_r-1, nums_r] ,并返回其长度。如果不存在符合条件的子数组,返回 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    target := 4
    nums := []int{1, 4, 4}
    l := minSubArrayLen(target, nums)
    fmt.Println(l)
}

func minSubArrayLen(target int, nums []int) int {
    // ......
}
答案

思路:

本题可以采用滑动窗口的思想。

定义一个left和right指针,先移动right指针,直到left到right之间的元素之和大于target。

然后移动left指针,缩短滑动窗口的长度。

定义一个minLen变量,保存符合条件的最小长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func minSubArrayLen(target int, nums []int) int {
    left := 0
    right := 0
    minLen := math.MaxInt
    sum := 0
    exist := false
    for right = 0; right < len(nums); right++ {
       sum += nums[right]
       for sum >= target { // 当满足条件时,移动left指针(这个过程是循环移动的,所以这里要用for)
          exist = true
          minLen = min(minLen, right-left+1)
          sum -= nums[left]
          left++
       }
    }
    if !exist {
       return 0
    }
    return minLen
}

螺旋矩阵 II

59. 螺旋矩阵 II

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例 1:

spiraln.jpg

1
2
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

1
2
3
4
5
6
7
8
9
func main() {
    n := 4
    r := generateMatrix(n)
    fmt.Println(r)
}

func generateMatrix(n int) [][]int {
    // ......
}
答案

分析:

要解决这道题,需要明确思路。先一圈一圈的遍历,需要遍历n/2圈。

然后确定每条边的遍历规则,最重要的是每条边的规则要统一,比如我们采用左闭右开的原则。

也就是每条边填充第一个元素,但不填充最后一个元素,留给下一条边来处理。

如何确定第一条边的最后一个填充位置呢?

  • 我们可以定义一个offset变量:第1圈时,offset=1;第2圈时,offset=2。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func main() {
    n := 4
    r := generateMatrix(n)
    fmt.Println(r)
}

func generateMatrix(n int) [][]int {
    // 创建n*n的切片,学会这种写法
    matrix := make([][]int, n)
    for i := range matrix {
       matrix[i] = make([]int, n)
    }
    count := 1
    var (
       i      = 0 // 行
       j      = 0 // 列
       c      = 0 // 圈数
       startX = 0 // 代表第几层循环
       offset = 1 // 第1圈,offset=1
    )
    for c = 0; c < n/2; c++ {
       for j = startX; j < n-offset; j++ { // 第1条边,y坐标变化
          matrix[c][j] = count
          count++
       }
       for i = startX; i < n-offset; i++ { // 第2条边,x坐标变化
          matrix[i][j] = count
          count++
       }
       for ; j > startX; j-- { // 第3条边,y坐标变化
          matrix[i][j] = count
          count++
       }
       for ; i > startX; i-- { // 第4条边,x坐标变化
          matrix[i][j] = count
          count++
       }
       startX++
       offset++
    }
    if n%2 != 0 { // 如果n为奇数,中心位置单独处理
       matrix[startX][startX] = count
    }
    return matrix
}

链表

反转链表

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

rev1ex1.jpg

1
2
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type ListNode struct {
    Val  int
    Next *ListNode
}
func main() {
    var (
       node1 ListNode
       node2 ListNode
       node3 ListNode
       node4 ListNode
    )
    node1.Val = 1
    node2.Val = 2
    node3.Val = 3
    node4.Val = 4
    node1.Next = &node2
    node2.Next = &node3
    node3.Next = &node4

    cur := &node1
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }

    fmt.Println("\n----reversed------")

    r := reverseList(&node1)
    cur = r
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }
}

func reverseList(head *ListNode) *ListNode {
    // ......
}
答案

分析:

可以采用双指针 + 辅助执行指针的方式。

pre指针指向头结点的前一个结点(nil),cur指针指向头结点,辅助指针temp指向cur的下一个结点(帮助反转后的链表找原来的下一个结点)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func reverseList(head *ListNode) *ListNode {
	var pre *ListNode // 定义一个pre,作为虚拟头结点
	cur := head
	var temp *ListNode
	for cur != nil {
		temp = cur.Next // 记录原来的下一个结点
		cur.Next = pre
		pre = cur
		cur = temp
	}
	return pre
}

两两交换链表中的节点

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

swap_ex1.jpg

1
2
输入:head = [1,2,3,4]
输出:[2,1,4,3]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
type ListNode struct {
    Val  int
    Next *ListNode
}
func main() {
    var (
       node1 ListNode
       node2 ListNode
       node3 ListNode
       node4 ListNode
    )
    node1.Val = 1
    node2.Val = 2
    node3.Val = 3
    node4.Val = 4
    node1.Next = &node2
    node2.Next = &node3
    node3.Next = &node4

    cur := &node1
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }

    fmt.Println("\n----After swapPairs------")

    r := swapPairs(&node1)
    cur = r
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }
}
func swapPairs(head *ListNode) *ListNode {
    // ......
}
答案

分析:

本题需要两两交换结点,而不是结点中的数字,所以我们需要把结点两两分组。

要完成这一功能,我们还是需要定义一个虚拟头结点pre,它来获取两两一组的结点。

然后定义一个cur,最开始指向虚拟头结点pre,需要用两个临时指针temp、temp1表示cur的下个结点和下下下个结点(防止找不到后面的结点)。

即:temp=cur.Next、temp1=cur.Next.Next.Next

然后才能进行交换,cur.Next = cur.Next.Next 、cur.Next.Next = temp、cur.Next.Next.Next = temp1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func swapPairs(head *ListNode) *ListNode {
    pre := &ListNode{0, head}
    pre.Next = head // 添加一个虚拟头结点,指向真正的头结点
    var temp, temp1 *ListNode
    cur := pre // cur最开始指向pre

    if head.Next == nil || head.Next.Next == nil {
       return head
    }

    for cur.Next != nil && cur.Next.Next != nil {
       temp = cur.Next
       temp1 = cur.Next.Next.Next
       cur.Next = cur.Next.Next
       cur.Next.Next = temp
       cur.Next.Next.Next = temp1
       cur = cur.Next.Next
    }
    return pre.Next
}

递归实现

参考:https://lyl0724.github.io/2020/01/25/1/

需要明确3点:递归的终止条件、本次递归需要做什么、本地递归的返回值。

终止条件:head为nil或者head.Next为nil

本次递归需要做什么:交换3个结点的前2个结点。

(看做只有head、head.Next、swapPairs(head.Next),3个结点)

返回值:返回3个结点中交换后的头结点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func swapPairs(head *ListNode) *ListNode {
	if head == nil || head.Next == nil { // 确定递归出口
		return head
	}
	next := head.Next           // 先记录第2个结点
	head.Next = swapPairs(next.Next) // 第1个结点指向第3个结点
	next.Next = head            // 第2个结点指向第1个结点

	return next
}

删除链表的倒数第 N 个结点

删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

img

1
2
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type ListNode struct {
    Val  int
    Next *ListNode
}
func main() {
    var (
       node1 ListNode
       node2 ListNode
       node3 ListNode
       node4 ListNode
    )
    node1.Val = 1
    node2.Val = 2
    node3.Val = 3
    node4.Val = 4
    node1.Next = &node2
    node2.Next = &node3
    node3.Next = &node4

    cur := &node1
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }

    fmt.Println("\n----After removeNthFromEnd------")

    r := removeNthFromEnd(&node1, 2)
    cur = r
    for cur != nil {
       fmt.Printf("%d ", cur.Val)
       cur = cur.Next
    }
}

func removeNthFromEnd(head *ListNode, n int) *ListNode {
    // ......
}
答案

思路:

定义两个快慢指针(fast和slow),先让fast移动n步,然后再一起移动两个指针,直到slow指向nil,这时slow刚好指向了要删除的元素。

但如何删除这个元素呢?

我们需要先引进一个虚拟头结点pre,让pre开始指向head,并在后面的移动中始终指向slow的前一个结点,它可以帮助我们删除slow结点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    pre := &ListNode{0, head}
    pre.Next = head
    fast := head
    slow := head
    if head == nil || head.Next == nil {
       return nil
    }
    for i := 0; i < n; i++ { // 快指针先移动n步
       fast = fast.Next
    }
    for fast != nil { // 三个指针同时移动
       pre = pre.Next
       slow = slow.Next
       fast = fast.Next
    }
    pre.Next = pre.Next.Next // 删除slow位置的结点
    return pre.Next
}

链表相交

面试题 02.07. 链表相交

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

图示两个链表在节点 c1 开始相交:

img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构


1
2
3
func getIntersectionNode(headA, headB *ListNode) *ListNode {
	// ......
}
答案

分析:先遍历计算出两个链表的长度,计算二者的差值n,然后将长的链表先移动n步,然后一起移动两个链表,判断每个遍历到的结点是否相同。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    listA := headA
    listB := headB
    var lenA, lenB int
    for listA != nil {
       listA = listA.Next
       lenA++
    }
    for listB != nil {
       listB = listB.Next
       lenB++
    }
    listA = headA
    listB = headB

    n := lenDiff(lenA, lenB)

    if lenA > lenB { // 将长的链表先移动n步
       for i := 0; i < n; i++ {
          listA = listA.Next
       }
    } else {
       for i := 0; i < n; i++ {
          listB = listB.Next
       }
    }
    for i := 0; i < min(lenA, lenB); i++ {
       if listA == listB {
          return listA
       }
       listA = listA.Next
       listB = listB.Next
    }
    return nil
}

func lenDiff(lenA, lenB int) int {
    n := lenA - lenB
    if n < 0 {
       return -n
    }
    return n
}

环形链表 II

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

img

1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

答案

思路1:map实现。

思路2:快慢指针,快指针一次走2步,慢指针一次走1步,如果快慢指针相遇了,说明链表有环。

如果有环,快慢指针肯定会相遇

如果没环,cur肯定会为nil

比较关键的问题是:如何找到环的入口?

通过数学公式推导可以知道,slow和fast相遇点 到 环入口的距离 和 head开始 到 环入口的距离 相等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 方法1:map实现
func detectCycle1(head *ListNode) *ListNode {
    cur := head
    nodeMap := make(map[*ListNode]struct{}) // 保存已经遍历过的结点到map中
    if head == nil || head.Next == nil {
       return nil
    }
    for cur != nil {
       if _, exist := nodeMap[cur]; exist {
          return cur
       }
       nodeMap[cur] = struct{}{}
       cur = cur.Next
    }
    return nil
}

// 方法2:快慢指针
func detectCycle(head *ListNode) *ListNode {
    slow := head
    fast := head
    var meet *ListNode // 默认为nil
    fmt.Println(meet)
    if head == nil || head.Next == nil || head.Next.Next == nil {
       return nil
    }
    for fast != nil && fast.Next != nil {
       slow = slow.Next // 这里一定要先移动,在判断slow是否等于fast,因为初始时slow是等于fast的。
       fast = fast.Next.Next
       if slow == fast { // 找到相遇结点
          meet = slow
          break
       }
    }
    slow = head // slow从head出发,meet从相遇点出发
    for meet != nil {
       if slow == meet { // slow和meet相遇点即为环入口
          return slow
       }
       meet = meet.Next
       slow = slow.Next
    }
    return nil
}

哈希表

有效的字母异位词

242. 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

示例 1:

1
2
输入: s = "anagram", t = "nagaram"
输出: true

答案

代码随想录提供的思路:可以共用同一个hashmap,一个做加法,一个做减法,如果最后map中全为0,则返回true

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 2024.04.03 自己写的版本,需要创建两个hashmap
func isAnagram00(s string, t string) bool {
    charCountS := make(map[rune]int)
    charCountT := make(map[rune]int)

    for _, v := range s {
       if _, exist := charCountS[v]; exist {
          charCountS[v]++
       } else {
          charCountS[v] = 1
       }
    }
    for _, v := range t {
       if _, exist := charCountT[v]; exist {
          charCountT[v]++
       } else {
          charCountT[v] = 1
       }
    }
    return maps.Equal(charCountS, charCountT)
}

// 方法2:同一个hashmap,一个做加法,一个做减
func isAnagram(s string, t string) bool {
    charCountS := make(map[rune]int) // 定义一个hashmap,key为字符,value为字符出现的次数

    for _, v := range s {
       if _, exist := charCountS[v]; exist {
          charCountS[v]++ // 做加法
       } else { // 不存在时,初始化为1
          charCountS[v] = 1
       }
    }

    for _, v := range t {
       if _, exist := charCountS[v]; !exist { // s的hashset中不存在该字符,直接退出
          return false
       } else {
          charCountS[v]-- // 做减法
       }
    }
    for _, v := range charCountS {
       if v != 0 {
          return false
       }
    }
    return true
}

两个数组的交集

349. 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

1
2
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    nums1 := []int{4, 9, 5}
    nums2 := []int{9, 4, 9, 8, 4}
    r := intersection(nums1, nums2)
    fmt.Println(r)
}

func intersection(nums1 []int, nums2 []int) []int {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func intersection(nums1 []int, nums2 []int) []int {
    result := make([]int, 0)
    nums1Map := make(map[int]struct{})
    commonMap := make(map[int]struct{})
    for _, v := range nums1 {
       if _, exist := nums1Map[v]; !exist { // 不存在,写入map
          nums1Map[v] = struct{}{}
       }
    }
    for _, v := range nums2 {
       if _, exist := nums1Map[v]; exist {
          commonMap[v] = struct{}{}
       }
    }
    for k := range commonMap {
       result = append(result, k)
    }
    return result
}

快乐数

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

1
2
3
4
5
6
7
输入:n = 19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

1
2
3
4
5
6
7
8
func main() {
    r := isHappy(20)
    fmt.Println(r)
}

func isHappy(n int) bool {
    // ......
}
答案

分析:本题需要先理清快乐数的循环终止条件。参照代码随想录:sum为各个位上数字的平方和,如果sum曾经出现过了,说明num不是快乐数,否则就要一直循环下去,直到sum等于1。(重要)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func main() {
    r := isHappy(20)
    fmt.Println(r)
}

func isHappy(n int) bool {
    sumsMap := make(map[int]struct{})
    bitNum := 0 // 每位的数字
    num := n
    sum := 0
    for {
       sum = 0
       for num != 0 {
          bitNum = num % 10
          sum += bitNum * bitNum
          num = num / 10
       }
       if _, exist := sumsMap[sum]; exist {
          fmt.Println(sum)
          return false
       }
       if sum == 1 {
          return true
       }
       sumsMap[sum] = struct{}{}
       fmt.Println(sum)
       num = sum
    }
}

两数之和

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 

1
2
3
4
5
6
7
8
9
func main() {
    nums := []int{2, 7, 11, 15}
    target := 9
    r := twoSum(nums, target)
    fmt.Println(r)
}
func twoSum(nums []int, target int) []int {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func twoSum(nums []int, target int) []int {
    numMap := make(map[int]int)
    for i, v := range nums {
       if mapValue, exist := numMap[target-v]; exist {
          return []int{i, mapValue}
       }
       numMap[v] = i
    }
    return nil
}

四数相加 II

454. 四数相加 II

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

1
2
3
4
5
6
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
    num1 := []int{-1, -1}
    num2 := []int{-1, 1}
    num3 := []int{-1, 1}
    num4 := []int{1, -1}
    r := fourSumCount(num1, num2, num3, num4)
    fmt.Println(r)
}
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
    // ......
}
答案

思路:

如果直接暴力求解,需要写 4 层 for 循环,时间复杂度太高。

我们可以考虑把两个数组看成 1 组,4 个数组就被分成了 2 组,这个问题也就转换成了之前的 2 数之和。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
    count := 0
    sum1 := 0
    sum1Map := make(map[int]int)
    for _, v1 := range nums1 {
       for _, v2 := range nums2 {
          sum1 = v1 + v2
          if _, exist := sum1Map[sum1]; !exist {
             sum1Map[sum1] = 1
          } else {
             sum1Map[sum1]++
          }
       }
    }
    for _, v3 := range nums3 {
       for _, v4 := range nums4 {
          if _, exist := sum1Map[-v3-v4]; exist {
             count += sum1Map[-v3-v4] // 这里的计数很关键,第二组每出现一次符合条件的,就应该加上前面map中存的次数
          }
       }
    }
    return count
}

赎金信

383. 赎金信

较简单,可以少练。

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

1
2
输入:ransomNote = "aa", magazine = "aab"
输出:true

1
2
3
4
5
6
7
8
9
func main() {
    ransomNote := "aabb"
    magazine := "aab"
    r := canConstruct(ransomNote, magazine)
    fmt.Println(r)
}
func canConstruct(ransomNote string, magazine string) bool {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func canConstruct(ransomNote string, magazine string) bool {
    runeMap := make(map[rune]int)
    for _, v := range magazine {
       if _, exist := runeMap[v]; !exist {
          runeMap[v] = 1
       } else {
          runeMap[v]++
       }
    }
    for _, v := range ransomNote {
       if _, exist := runeMap[v]; !exist {
          return false
       } else {
          runeMap[v]--
       }
    }
    for _, v := range runeMap {
       if v < 0 {
          return false
       }
    }
    return true
}

三数之和

15. 三数之和

本题较为复杂,建议多练习。

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例 1:

1
2
3
4
5
6
7
8
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
    //nums := []int{-1, 0, 1, 2, -1, -4}
    //nums := []int{1, 2, -2, -1}
    nums := []int{3, 0, -2, -1, 1, 2}
    r := threeSum(nums)
    fmt.Println(r)
}

func threeSum(nums []int) [][]int {
    // ......
}
答案

分析:

参考:https://leetcode.cn/problems/3sum/solutions/9729/three-sum-giftu-jie-by-githber/

(直接使用 3 重循环会导致运行超时,所以需要改进为两层循环)

先将数组进行排序(解决重复元组的问题)

从左侧开始,选定一个值为 定值 ,右侧进行求解,获取与其相加为 0 的两个值,类似于快排,

定义首和尾,首尾与 定值 相加等于 0,记录这三个值

小于 0,首部右移

大于 0,尾部左移

定值右移,重复该步骤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// map的key不能是slice,但可以是array
func threeSum(nums []int) [][]int {
    sort.Ints(nums)
    valuesMap := make(map[[3]int]struct{}) // key为值组合而成的数组
    result := make([][]int, 0)
    for i := 0; i < len(nums); i++ { // 固定一个数
       left := i + 1 // 双指针
       right := len(nums) - 1
       for left < right {
          if nums[i]+nums[left]+nums[right] == 0 {
             target := []int{nums[i], nums[left], nums[right]}
             if _, exist := valuesMap[[3]int(target)]; !exist { // 将map的key设置为数组类型,判断是否该组合是否出现过
                valuesMap[[3]int(target)] = struct{}{}
                result = append(result, target)
             }
             left++
          } else if nums[i]+nums[left]+nums[right] < 0 { // 小于0,右移left指针
             left++
          } else { // 大于0,左移right指针
             right--
          }
       }
    }
    return result
}

四数之和

18. 四数之和

1
2
3
4
5
6
7
8
func main() {
    nums := []int{1, 0, -1, 0, -2, 2}
    r := fourSum(nums, 0)
    fmt.Println(r) // [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
}
func fourSum(nums []int, target int) [][]int {
    // ......
}
答案

思路:在三数之和的基础上,外面再嵌套一层循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func fourSum(nums []int, target int) [][]int {
    sort.Ints(nums)
    numsMap := make(map[[4]int]struct{})
    result := make([][]int, 0)
    var targetArray []int
    for i := 0; i < len(nums); i++ {
       if nums[i] > 0 && nums[i] > target && target > 0 { // 添加剪枝策略
          break
       }
       for j := i + 1; j < len(nums); j++ {
          if nums[j] > 0 && nums[j] > target && target > 0 { // 添加剪枝策略
             break
          }
          left := j + 1
          right := len(nums) - 1
          for left < right {
             if nums[i]+nums[j]+nums[left]+nums[right] == target {
                targetArray = []int{nums[i], nums[j], nums[left], nums[right]}
                if _, exist := numsMap[[4]int(targetArray)]; !exist {
                   numsMap[[4]int(targetArray)] = struct{}{}
                   result = append(result, targetArray)
                }
                left++
             } else if nums[i]+nums[j]+nums[left]+nums[right] < target {
                left++
             } else {
                right--
             }
          }
       }
    }
    return result
}

字符串

反转字符串

344. 反转字符串

1
2
3
4
5
6
7
8
func main() {
    s := []byte{'h', 'e', 'l', 'l', 'o'}
    reverseString(s)
    fmt.Println(string(s))
}
func reverseString(s []byte) {
	// ......
}
答案
1
2
3
4
5
6
7
8
9
func reverseString(s []byte) {
	left := 0
	right := len(s) - 1
	for left < right {
		s[left], s[right] = s[right], s[left]
		left++
		right--
	}
}

反转字符串 II

注意控制边界条件

541. 反转字符串 II

1
2
3
4
5
6
7
8
9
func main() {
    s := "abcdefg"
    k := 2
    r := reverseStr(s, k)
    fmt.Println(r) // "bacdfeg"
}
func reverseStr(s string, k int) string {
    // ......
}
答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func reverseStr(s string, k int) string {
    // 定义一个left和right指针
    sChar := []byte(s)
    left := 0
    right := len(sChar) - 1
    for right-left >= 2*k {
       lStart := left
       lEnd := left + k - 1
       for lStart < lEnd {
          sChar[lStart], sChar[lEnd] = sChar[lEnd], sChar[lStart]
          lStart++
          lEnd--
       }
       left += 2 * k
    }
    if right-left >= k {
       lStart := left
       lEnd := left + k - 1
       for lStart < lEnd {
          sChar[lStart], sChar[lEnd] = sChar[lEnd], sChar[lStart]
          lStart++
          lEnd--
       }
    } else {
       lStart := left
       lEnd := right
       for lStart < lEnd {
          sChar[lStart], sChar[lEnd] = sChar[lEnd], sChar[lStart]
          lStart++
          lEnd--
       }
    }
    return string(sChar)
}

路径加密

较简单,不用练习。

LCR 122. 路径加密

1
2
3
4
5
6
7
8
func main() {
    path := "a.aef.qerf.bb"
    r := pathEncryption(path)
    fmt.Println(r)
}
func pathEncryption(path string) string {
    // ......
}
答案
1
2
3
4
5
6
7
8
9
func pathEncryption(path string) string {
    c := []byte(path)
    for i := 0; i < len(c); i++ {
       if c[i] == '.' {
          c[i] = ' '
       }
    }
    return string(c)
}

反转字符串中的单词

本题需要较强的逻辑推理,建议多加练习。

151. 反转字符串中的单词

1
2
3
4
5
6
7
8
func main() {
    s := "  the sky   is blue"
    r := reverseWords(s)
    fmt.Println(r) // blue is sky the
}
func reverseWords(s string) string {
	// ......
}
答案

代码随想录的思路:先将所有的字符串翻转,然后将每个单词翻转(单词由1个或多个空格隔开)

如何删除空格?和移除数组的元素的思想相同,使用快慢指针,当快指针的元素不是空格时,将该元素加入慢指针的数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 方法1:自己写的
func reverseWords(s string) string {
	result := ""
	// 这里调用了strings类的系统函数Split
	c := strings.Split(s, " ")
	for i := len(c) - 1; i >= 0; i-- {
		if c[i] != "" {
			result += c[i] + " "
		}
	}
	result = strings.TrimSpace(result)
	return result
}

// 方法2
func reverseWords(s string) string {
    result := ""
    s = strings.TrimSpace(s) // 去除单词首尾的空格
    sChar := []byte(s)
    reverseString(sChar)
    spaceCount := 0
    slow := -1 // slow代表的去除多余的空格后,数组元素的最后一个位置
    fast := 0
    for ; fast < len(s); fast++ { // 去除单词中间的空格
       if sChar[fast] == ' ' {
          spaceCount++
       }
       if sChar[fast] != ' ' {
          spaceCount = 0
       }
       if spaceCount <= 1 { // 当符合要求时,将快指针指向的元素存入慢指针的后面一个位置
          slow++
          sChar[slow] = sChar[fast]
       }
    }
    sChar = sChar[0 : slow+1] // 只取0到slow之间的元素作为新的有效数组
    slow = 0
    fast = 0
    for fast < len(sChar) {
       if fast != len(sChar)-1 && sChar[fast+1] != ' ' { // 最后1个元素时,fast不用加1
          fast++
          continue // 当fast后面遇到空格时,执行交换,否则fast后移
       }
       reverseString(sChar[slow : fast+1]) // 交换slow到fast之间的字符
       slow = fast + 2
       fast = slow
    }
    result = string(sChar)
    return result
}

func reverseString(s []byte) {
    left := 0
    right := len(s) - 1
    for left < right {
       s[left], s[right] = s[right], s[left]
       left++
       right--
    }
}

动态口令

较为简单,不用练习。

LCR 182. 动态口令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    password := "s3cur1tyC0d3"
    target := 4
    r := dynamicPassword(password, target)
    fmt.Println(r) // "r1tyC0d3s3cu"
}

func dynamicPassword(password string, target int) string {
    // ......
}
答案

自己的思路:先用一个切片 slice1 记录下前 target 个字符,然后将原切片中 target 之后的元素都往前移动 target 步骤,然后将 slice1 添加到原切片的末尾。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func dynamicPassword(password string, target int) string {
    slice1 := make([]byte, target)
    charSlice := []byte(password)
    for i := 0; i < target; i++ {
       slice1[i] = charSlice[i]
    }
    for i := 0; i < len(charSlice)-target; i++ {
       charSlice[i] = charSlice[i+target]
    }
    j := 0
    for i := len(charSlice) - target; i < len(charSlice); i++ {
       charSlice[i] = slice1[j]
       j++
    }
    return string(charSlice)
}

找出字符串中第一个匹配项的下标

需要仔细,注意一些边界条件。

28. 找出字符串中第一个匹配项的下标

1
2
3
4
5
6
7
8
9
func main() {
    haystack := "mississippi"
    needle := "issippi"
    r := strStr(haystack, needle)
    fmt.Println(r) // 4
}
func strStr(haystack string, needle string) int {
    // ......
}
答案

自己的思路:双重循环,一个遍历haystack,一个遍历needle,依次比较两个字符串每位的字符是否相等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func strStr(haystack string, needle string) int {
    if len(needle) > len(haystack) {
       return -1
    }
    for i := 0; i < len(haystack); i++ {
       j := 0
       tempI := i                    // i的临时变量,判断其后面的字符和j后面的字符是否相同,如果相同则同时移动tempI和j,如果不相同,则增加i
       if haystack[i] != needle[j] { // 如果第1个字符就不匹配,直接增加i的值
          continue
       }
       for j < len(needle) {
          if tempI < len(haystack) && haystack[tempI] == needle[j] { // 注意这里加上tempI<len(haystack)的条件限制,因为tempI可能超出数组haystack的范围
             tempI++
             j++
          } else {
             break
          }
       }
       if j == len(needle) { // 说明匹配上了
          return i
       }
    }
    return -1
}

重复的子字符串

459. 重复的子字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
	//s := "abcabcabcabc"
	s := "bb"
	r := repeatedSubstringPattern(s)
	fmt.Println(r)
}

func repeatedSubstringPattern(s string) bool {
	// ......
}
答案

如果 s 由重复子串构成,那么让 s1 = s + s,然后去掉 s1 的开始字符和结束字符,则 s1 中应该还是包含 s。否则,s 就不是由重复子串构成的。

参考: https://leetcode.cn/problems/repeated-substring-pattern/solutions/1405588/yi-xing-dai-ma-by-mizar-c-k2kd/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func repeatedSubstringPattern(s string) bool {
	s1 := (s + s)[1 : 2*len(s)-1]
	return strStr(s1, s) >= 0
}

func strStr1(haystack string, needle string) int {
	if len(needle) > len(haystack) {
		return -1
	}
	for i := 0; i < len(haystack); i++ {
		j := 0
		tempI := i                    // i的临时变量,判断其后面的字符和j后面的字符是否相同,如果相同则同时移动tempI和j,如果不相同,则增加i
		if haystack[i] != needle[j] { // 如果第1个字符就不匹配,直接增加i的值
			continue
		}
		for j < len(needle) {
			if tempI < len(haystack) && haystack[tempI] == needle[j] { // 注意这里加上tempI<len(haystack)的条件限制,因为tempI可能超出数组haystack的范围
				tempI++
				j++
			} else {
				break
			}
		}
		if j == len(needle) { // 说明匹配上了
			return i
		}
	}
	return -1
}

栈与队列

有效的括号

20. 有效的括号

1
2
3
4
5
6
7
8
9
func main() {
    s := "[[(])[]{}]"
    r := isValid(s)
    fmt.Println(r) // false 
}

func isValid(s string) bool {
    // ......
}
答案

分析:本题比较适合用栈来实现,需要对左右括号进行匹配。具体如下:

遍历字符串,如果遇到的是左括号,则将其对应的右括号入栈(关键),如果遇到的是右括号,则将其与栈顶元素比较,如果不相等,直接返回false;如果相等,则弹出栈顶元素(注意判断栈为空的情况),继续比较。

遍历完字符串之后,再判断栈是否为空,如果不为空,则返回false。

(本题可以使用map来模拟一个简单的栈)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func main() {
    s := "[[(])[]{}]"
    r := isValid(s)
    fmt.Println(r)
}

func isValid(s string) bool {
    if len(s)%2 != 0 { // 剪枝
       return false
    }
    // 遍历s,如果是左括号,则将其对于的右括号加入栈中
    signMap := map[byte]byte{
       '(': ')',
       '[': ']',
       '{': '}',
    }
    stack := make(map[int]byte) // map模拟栈,key为元素下标,value为元素值

    for i := 0; i < len(s); i++ {
       if rSign, exist := signMap[s[i]]; exist { // 是左括号
          stack[len(stack)] = rSign // key为len(stack)-1表示栈顶
       } else {
          if len(stack) == 0 || s[i] != stack[len(stack)-1] { // 栈为空,或栈顶元素和s[i]不相等,返回false
             return false
          } else { // 相等,弹出栈顶元素(删除map中index为len(stack)-1的元素)
             delete(stack, len(stack)-1)
          }
       }
    }
    if len(stack) != 0 {
       return false
    }
    return true
}

删除字符串中的所有相邻重复项

1047. 删除字符串中的所有相邻重复项

1
2
3
4
5
6
7
8
9
func main() {
    s := "abbaca"
    r := removeDuplicates(s)
    fmt.Println(r) // ca
}

func removeDuplicates(s string) string {
    // ......
}
答案

分析:本题和括号匹配是差不多的,也是匹配之前是否出现过相同的元素。

遍历字符串 s,判断该元素是否和栈顶元素相同,如果不相同直接遍历 s 的下一个元素,如果相同,将栈顶的元素弹出。

我们需要明确栈中存放的元素是什么?那就是消除之后剩下的元素,不过我们需要把字符串按照出栈的顺序拼接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func removeDuplicates(s string) string {
    // 遍历字符串
    stack := make(map[int]byte)
    for i := 0; i < len(s); i++ {
       if s[i] != stack[len(stack)-1] { // 不相等,加入栈中
          stack[len(stack)] = s[i]
       } else {
          delete(stack, len(stack)-1) // 相等,从栈顶弹出
       }
    }
    sByte := make([]byte, len(stack))
    j := 0
    for i := 0; i < len(stack); i++ { // 逆序栈中元素
       sByte[j] = stack[i]
       j++
    }
    return string(sByte)
}

逆波兰表达式求值

150. 逆波兰表达式求值

1
2
3
4
5
6
7
8
9
func main() {
    tokens := []string{"2", "1", "+", "3", "*"}
    r := evalRPN(tokens)
    fmt.Println(r)  // 9
}

func evalRPN(tokens []string) int {
    // ......
}
答案

思路:遍历数组,当遇到的字符串不是运算符时,将该字符串入栈,当遇到运算符时,将栈中弹出两个元素,与该运算符运算,将结果再写入到栈中。

继续上面的步骤,直到遍历完所有的元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func evalRPN(tokens []string) int {
    tokenMap := map[string]struct{}{
       "+": {},
       "-": {},
       "*": {},
       "/": {},
    }
    map01 := make(map[int]string)
    for i := 0; i < len(tokens); i++ {
       if _, exist := tokenMap[tokens[i]]; !exist {
          map01[len(map01)] = tokens[i]
       } else {
          num1, _ := strconv.Atoi(map01[len(map01)-2]) // 注意这里数字的顺序
          num2, _ := strconv.Atoi(map01[len(map01)-1])
          delete(map01, len(map01)-1)
          delete(map01, len(map01)-1)
          map01[len(map01)] = calc(num1, num2, tokens[i])
       }
    }
    result, _ := strconv.Atoi(map01[0])
    return result
}
func calc(num1, num2 int, token string) string {
    result := 0
    if token == "+" {
       result = num1 + num2
    } else if token == "-" {
       result = num1 - num2
    } else if token == "*" {
       result = num1 * num2
    } else {
       result = num1 / num2
    }
    return strconv.Itoa(result)
}

滑动窗口最大值

239. 滑动窗口最大值

本题较为复杂,可以多加练习。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    nums := []int{-7, -8, 7, 5, 7, 1, 6, 0}
    k := 4
    r := maxSlidingWindow(nums, k)
    fmt.Println(r)  // [7 7 7 7 7]
}

func maxSlidingWindow(nums []int, k int) []int {
    // ......
}
答案

思路:本题我们可以用单调队列(双端队列)来实现,具体如下:

我们让队列中的元素始终是依次递减的,也就是队列出口处的元素最大,往后依次减小。

为了实现这一功能,我们需要自定义出队和入队规则。(使用切片来模拟队列)

入队规则:让比当前元素更大的元素都出队,然后让当前元素入队;

出队规则:如果现在的最大值和队列出口位置的元素相等,则将该元素出队,否则什么也不做。

得到滑动窗口的最大值:队列出口处的元素就是目标最大值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func maxSlidingWindow(nums []int, k int) []int {
    var queue []int // 切片模拟双端队列
    push := func(curNum int) {
       // 注意:这里是for而不是if,因为需要循环遍历队列入口的元素
       for len(queue) > 0 && curNum > queue[len(queue)-1] { // 当前元素大于队列入口处元素值,将入口元素弹出
          queue = queue[:len(queue)-1]
       }
       queue = append(queue, curNum) // 将当前元素加入队列
    }
    pop := func(value int) {
       // 注意:这里是if而不是for,因为只pop一个元素
       if len(queue) > 0 && value == queue[0] { // 当要pop的元素为当前最大元素时,才真正的pop
          queue = queue[1:]
       }
    }
    for i := 0; i < k; i++ { // 将前k个元素加入队列
       push(nums[i])
    }
    n := len(nums)
    ans := make([]int, n-k+1) // ans中共有n-k+1个元素
    ans[0] = queue[0]
    for i := k; i < n; i++ {
       pop(nums[i-k])
       push(nums[i])
       ans[i-k+1] = queue[0]
    }
    return ans
}

前K个高频元素

347. 前K个高频元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    nums := []int{1, 1, 1, 2, 2, 3}
    k := 2
    r := topKFrequent(nums, k)
    fmt.Println(r) // [1,2]
}

func topKFrequent(nums []int, k int) []int {
    // ......
}
答案

思路:先使用 map 统计每个元素出现的次数(key 为元素值,value 为频率)。

然后使用小顶堆来存放元素出现的次数,小顶堆中只存放 k 个数据。

为什么使用小顶堆而不是大顶堆?因为每次 push 进元素的时候,就要把现在堆中值最小的元素(频次最小)弹出,所以要选择最小元素位于堆顶的小顶堆。

如何构建堆呢?

  1. 可以使用 container/heap 包来构建,我们需要实现 Len()、Less()、Swap()、Push()、Pop() 这 5 个方法。
  2. 自己先最小堆堆化的过程,和堆排序一样。

方法1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func topKFrequent(nums []int, k int) []int {
    countMap := make(map[int]int)
    for i := 0; i < len(nums); i++ {
       countMap[nums[i]]++
    }
    // 初始化小顶堆
    h := &MinHeap{}
    heap.Init(h)

    // 将 map 的值插入小顶堆
    for num, count := range countMap {
       heap.Push(h, &Item{num, count})

       if h.Len() > k {
          heap.Pop(h) // 如果堆的大小超过k,则弹出堆顶元素
       }
    }
    // 将小顶堆的元素依次弹出,逆序保存到result中
    result := make([]int, k)
    for i := 0; i < k; i++ {
       result[k-i-1] = heap.Pop(h).(*Item).num
    }
    return result
}
type Item struct { // 定义小顶堆中存放的结点类型
    num   int
    count int
}
type MinHeap []*Item
// Len 要使用堆,必须实现下面的5个方法:Len、Less、Swap、Push、Pop。
func (h MinHeap) Len() int            { return len(h) }
func (h MinHeap) Less(i, j int) bool  { return h[i].count < h[j].count } // 比较的是count字段,而且是i<j(小顶堆)
func (h MinHeap) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(*Item)) }
func (h *MinHeap) Pop() interface{} {
    old := *h // 记录原来的小根堆
    n := len(old)
    item := old[n-1]  // 取出堆顶元素,用作返回值
    *h = old[0 : n-1] // 去掉堆顶元素
    return item
}

上面的方法使用到了 container/heap 包来构建堆,需要记忆的东西比较多。

所以我们可以自己手动构建一个堆,并完成堆化的过程(和堆排序一样)

方法2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
func topKFrequent(nums []int, k int) []int {
	// 先使用map统计每个元素出现的次数
	// 然后构建小顶堆(堆顶元素最小),当堆中元素个数超过k时,弹出堆顶元素
	// 最后弹出堆中剩余的k个元素得到目标数组
	numsCount := make(map[int]int)
	result := make([]int, k)
	for _, num := range nums {
		numsCount[num]++
	}
	// 构建小顶堆
	elemCount := 0 // map中元素计数
	var heap minHeap
	for num, count := range numsCount {
		elemCount++
		if elemCount <= k { // 前k个元素加入到heap中
			heap = append(heap, heapItem{num, count})
			if elemCount == k { // 加完之后,再堆化
				for i := k / 2; i >= 0; i-- { // 堆化
					heapify(heap, k, i)
				}
			}
		} else if count > heap[0].count { // 新增元素的count大于堆顶元素,用新增元素替换堆顶元素
			heap[0] = heapItem{num, count}
			heapify(heap, k, 0)
		}
	}
	for i := k - 1; i >= 0; i-- {
		result[i] = heap[0].num // 将堆顶元素放到result数组的末尾
		heap = heap[1:]         // 弹出堆顶元素,继续堆化
		heapify(heap, i, 0)
	}
	return result
}
type heapItem struct {
	num   int
	count int
}
type minHeap []heapItem
// 共n个元素,对i位置构建小根堆
func heapify(arr minHeap, n, i int) {
	minimum := i
	l := i*2 + 1
	r := i*2 + 2
	if l < n && arr[l].count < arr[minimum].count {
		minimum = l
	}
	if r < n && arr[r].count < arr[minimum].count {
		minimum = r
	}
	if i != minimum {
		arr[i], arr[minimum] = arr[minimum], arr[i]
		heapify(arr, n, minimum)
	}
}

推荐使用方法2,不需要记忆太多的内容,只需要自己构建堆并完成堆化的过程(和堆排序一样)。

0%