排序
力扣题目: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
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:

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:

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:

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:

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. 链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:

题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
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:

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. 有效的字母异位词
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意: 若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
示例 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. 两个数组的交集
给定两个数组 nums1
和 nums2
,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 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
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 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. 赎金信
较简单,可以少练。
给你两个字符串:ransomNote
和 magazine
,判断 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 != j
、i != k
且 j != 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 进元素的时候,就要把现在堆中值最小的元素(频次最小)弹出,所以要选择最小元素位于堆顶的小顶堆。
如何构建堆呢?
- 可以使用 container/heap 包来构建,我们需要实现 Len()、Less()、Swap()、Push()、Pop() 这 5 个方法。
- 自己先最小堆堆化的过程,和堆排序一样。
方法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,不需要记忆太多的内容,只需要自己构建堆并完成堆化的过程(和堆排序一样)。