这些题目是力扣150中的内容,题目的代码是我想出或者根据题解思路复现的。里面的一些方法和技巧,是我根据自己做题中的一些不懂或者不熟练的内容,让AI生成的总结技巧。这个教程适合对于go语言有一定使用或者纯新手的快速了解go语言算法题的技巧和特性,如果你可以看完,我相信你对数组和切片这部分的大多数算法一定有了不错的了解(没有针对解法做一个总结,因为我觉得如果刷或者浏览一部分题目之后,看到某些特征就有一定的基础思路,看到复杂度就会有一定的优化想法)。如果有不足或者需要改进的地方,欢迎大家在评论区批评指正。
后续的150内容我立一个flag在这周之内出完,希望大家可以多多支持。
合并两个有序数组(9.15.1)
方法1 追加之后排序
这个方法针对无序数组也可以使用
func merge(nums1 []int, m int, nums2 []int, n int) {
//使用数组处理方法直接处理
copy(nums1[m:],nums2)//追加切片
sort.Ints(nums1)//对整形数组的排序方法
}
方法2 双指针法
func merge(nums1 []int, m int, nums2 []int, n int) {
//创建结果数组并且进行赋值
result := make([]int,0,m+n)
//创建左右指针
p1 := 0
p2 := 0
//赋值
for {
//防止nums中有空切片 并且顺带可以简化for循环
if p1==m{
result=append(result,nums2[p2:]...)
break
}
if p2==n{
result=append(result,nums1[p1:]...)
break
}
if nums1[p1]>=nums2[p2]{
result = append(result,nums2[p2])
p2++
continue
}else{
result = append(result,nums1[p1])
p1++
continue
}
}
copy(nums1,result)
}
注意
result := make([]int,m+n)//这样写再加上append方法,会造成前面m+n个元素全为0
知识点
make方法
make 是 Go 语言中用于初始化内建引用类型(slice、map、channel) 的函数,它分配内存并初始化相应的数据结构,返回的是类型本身(而不是指针)。
用于切片 (Slice) - 力扣最常用
切片是动态数组,是解决数组类题目的核心数据结构。
语法:
// 语法1: 指定长度和容量
slice := make([]T, length, capacity)
// 语法2: 省略容量,则长度 == 容量
slice := make([]T, length)
T: 切片元素的类型 (如int,string,byte)length: 切片的当前长度(已有元素个数)capacity: 切片的总容量(底层数组能容纳的元素个数)
力扣实战技巧:
-
预分配内存以避免频繁扩容:这是最重要的性能优化点。如果你能预估结果切片的大致大小,使用
make预先分配足够的capacity可以避免append操作时底层数组的多次重新分配和拷贝,极大提升性能。- 例子 (力扣 509. 斐波那契数):你知道要计算前 N 个斐波那契数。
func fib(n int) int { if n <= 1 { return n } // 预分配一个长度为 n+1 的切片,避免后续 append 的扩容开销 dp := make([]int, n+1) dp[0], dp[1] = 0, 1 for i := 2; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] } return dp[n] }
- 例子 (力扣 509. 斐波那契数):你知道要计算前 N 个斐波那契数。
-
创建空切片但有容量:当你需要一个初始为空的切片,但又知道即将添加大量元素时。
- 例子:收集二叉树遍历的结果。
// 假设你知道树节点大约有1000个 result := make([]int, 0, 1000) // 然后使用 result = append(result, value) 添加元素,效率极高
- 例子:收集二叉树遍历的结果。
-
创建具有初始长度的切片:当你需要直接通过索引
dp[i]赋值,而不是使用append时,必须指定足够的length。- 动态规划 (DP) 问题:几乎所有的 DP 题都需要先创建一个
dp数组(切片)。// 力扣 70. 爬楼梯 func climbStairs(n int) int { if n <= 2 { return n } dp := make([]int, n+1) // 创建长度为 n+1 的切片 dp[1] = 1 dp[2] = 2 for i := 3; i <= n; i++ { dp[i] = dp[i-1] + dp[i-2] // 直接按索引赋值 } return dp[n] }
- 动态规划 (DP) 问题:几乎所有的 DP 题都需要先创建一个
用于映射 (Map) - 也很常用
Map 用于存储键值对,解决需要快速查找、计数的题目。
语法:
m := make(map[KeyType]ValueType)
m := make(map[KeyType]ValueType, initialCapacity) // 推荐指定初始容量
initialCapacity: 提示 Go 运行时预先分配大约能存储initialCapacity个元素的空间。虽然不是严格的限制,但指定一个合理的值可以避免初期的内存重整,提升性能。
力扣实战技巧:
-
用于计数:统计元素出现的频率。
- 例子 (力扣 1. 两数之和):虽然最优解是一遍哈希,但也能体现 map 的创建。
func twoSum(nums []int, target int) []int { // 创建一个map,key是数字,value是索引 numMap := make(map[int]int, len(nums)) // 建议指定容量为nums的长度 for i, num := range nums { complement := target - num if idx, ok := numMap[complement]; ok { return []int{ idx, i} } numMap[num] = i } return nil }
- 例子 (力扣 1. 两数之和):虽然最优解是一遍哈希,但也能体现 map 的创建。
-
用于记录状态:记录访问过的节点或状态,避免重复处理(如图的遍历、回溯算法)。
- 例子:克隆图(力扣 133)、避免回溯中的重复选择。
用于通道 (Channel) - 并发题目中使用
Channel 用于 goroutine 之间的通信,在并发类的题目中可能会用到。
语法:
ch := make(chan T) // 无缓冲通道
ch := make(chan T, n) // 带缓冲通道,缓冲区大小为 n
- 在力扣的算法题中,单纯使用 channel 的情况较少,除非题目明确要求并发。
总结与对比 (make vs new)
| 特性 | make |
new |
|---|---|---|
| 适用类型 | 仅用于内建引用类型:slice, map, channel |
用于任何类型(包括值类型和自定义结构体) |
| 返回值 | 类型 T 本身 (e.g., []int, map[int]int) |
指向该类型的指针 (*T) |
| 初始化 | 会初始化内存(置零值),并设置好内部数据结构(如 slice 的 len/cap) | 只分配内存,并将内存置为零值(nil for references, 0 for numbers, "" for strings),返回指向这块零值内存的指针 |
错误示例:
var s *[]int = new([]int) // s 现在是一个指向 nil 切片的指针
*s = make([]int, 10) // 必须这样使用:先 new 出一个指针,再让指针指向 make 出来的切片
// 通常我们直接写更简单: s := make([]int, 10)
力扣题刷技巧
- 看到数组、字符串、动态规划题:第一反应想到
make([]T, len, cap)来创建你的切片(DP 表、结果集)。 - 看到需要查找、去重、计数的题:第一反应想到
make(map[K]V)来创建你的映射。 - 养成预分配容量的习惯:在知道或能估算数据规模时,始终为
make函数指定capacity参数。这是一个简单且有效的性能优化手段,能让你的解法跑得更快。
append方法
追加单个元素
numbers := []int{
1, 2, 3}
numbers = append(numbers, 4) // [1, 2, 3, 4]
追加多个元素
numbers := []int{
1, 2, 3}
numbers = append(numbers, 4, 5, 6) // [1, 2, 3, 4, 5, 6]
追加另一个切片
slice1 := []int{
1, 2, 3}
slice2 := []int{
4, 5, 6}
slice1 = append(slice1, slice2...) // [1, 2, 3, 4, 5, 6]
下面内容了解一下即可
重要特性
-
自动扩容:当切片容量不足时,
append会自动分配一个新的底层数组,通常是原容量的2倍(当长度小于1024时)。 -
返回值:
append总是返回一个新的切片,必须将返回值赋给原切片变量。 -
零值切片:可以向
nil切片追加元素:var s []int s = append(s, 1) // [1]
性能考虑
-
频繁的
append操作可能导致多次内存分配和复制 -
如果知道最终大小,可以预先分配容量:
s := make([]int, 0, 100) // 长度为0,容量为100
常见错误
s := []int{
1, 2, 3}
_ = append(s, 4) // 错误:没有使用返回值
// s 仍然是 [1, 2, 3]
append 是 Go 中处理动态数组增长的主要方式,理解它的工作原理对于编写高效的 Go 代码非常重要。
copy方法
copy 是 Go 语言中用于复制切片内容的内置函数,常用于将一个切片的内容复制到另一个切片中。
copy(dst, src []T) int
- 返回实际复制的元素个数(取 dst 和 src 长度的较小值)
用法
nums1 := make([]int, 10) // 假设 nums1 有足够容量
nums2 := []int{
4, 5, 6}
m := 5 // 从 nums1 的第 m 个位置开始复制
n := copy(nums1[m:], nums2) // 将 nums2 复制到 nums1[m:] 的位置
// n 是实际复制的元素数
以下内容了解一下即可
- 不会自动扩容,目标切片必须有足够空间
- 可以部分复制(取决于目标切片的剩余空间)
- 常用于合并切片或覆盖切片部分内容
示例:合并两个有序切片
nums1 := make([]int, len(nums1Original)+len(nums2)
copy(nums1, nums1Original)
copy(nums1[len(nums1Original):], nums2)
sort包
sort 包是 Go 标准库中用于排序和搜索的包,提供了对切片和用户定义集合进行排序的功能。
基本类型排序方法
- 整型排序
func Ints(x []int) // 对 int 切片进行升序排序
func IntsAreSorted(x []int) bool // 检查 int 切片是否已排序
sort.Ints(nums)//升序
sort.Sort(sort.Reverse(sort.IntSlice(nums)))//降序
- 浮点数排序
func Float64s(x []float64) // 对 float64 切片进行升序排序
func Float64sAreSorted(x []float64) bool // 检查 float64 切片是否已排序
- 字符串排序
func Strings(x []string) // 对 string 切片进行升序排序(按字典序)
func StringsAreSorted(x []string) bool // 检查 string 切片是否已排序
通用排序接口
- Slice 排序 (Go 1.8+)
func Slice(x interface{
}, less func(i, j int) bool) // 根据 less 函数对切片排序
func SliceStable(x interface{
}, less func(i, j int) bool) // 稳定排序
func SliceIsSorted(x interface{
}, less func(i, j int) bool) bool // 检查是否已排序
- 自定义排序接口
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
func Sort(data Interface) // 排序
func Stable(data Interface) // 稳定排序
func IsSorted(data Interface) bool // 检查是否已排序
搜索方法
func SearchInts(a []int, x int) int // 在已排序的 int 切片中搜索 x
func SearchFloat64s(a []float64, x float64) int // 在已排序的 float64 切片中搜索
func SearchStrings(a []string, x string) int // 在已排序的 string 切片中搜索
func Search(n int, f func(int) bool) int // 通用二分搜索
逆序排序
Go 1.21+ 引入了新的逆序排序方式:
// 对整型切片降序排序
sort.Sort(sort.Reverse(sort.IntSlice(nums)))
// Go 1.21+ 更简洁的方式
slices.SortFunc(nums, func(a, b int) int {
return b - a // 降序排序
})
实用示例
- 自定义结构体排序
type Person struct {
Name string
Age int
}
people := []Person{
{
"Alice", 25},
{
"Bob", 20},
}
// 按年龄排序
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
- 搜索示例
nums := []int{
1, 3, 5, 7, 9}
x := 5
pos := sort.SearchInts(nums, x) // 返回 2
- 检查切片是否已排序
if sort.IntsAreSorted(nums) {
fmt.Println("切片已排序")
}
性能提示
- 对于基本类型,优先使用
sort.Ints等特定函数,它们比通用sort.Slice更快 - 需要保持相等元素原始顺序时,使用
Stable系列方法 - 对大型数据集,考虑使用
sort.Slice而非实现sort.Interface接口,代码更简洁 sort包提供了强大而灵活的排序功能,是 Go 语言中处理有序数据的核心工具之一。
移除元素(9.15.2)
方法1:前后指针法
func removeElement(nums []int, val int) int {
//创建前后指针以及最终元素个数的记录
follow := 0
result := 0
for front,_ := range nums{
if nums[front] != val{
nums[follow]=nums[front]
result++
front++
follow++
}else{
front++
}
}
return result
}
知识点
快慢指针写法
func removeElement(nums []int, val int) int {
slow := 0 // 慢指针,指向下一个要放置非val元素的位置
for fast := 0; fast < len(nums); fast++ {
if nums[fast] != val {
nums[slow] = nums[fast]
slow++
}
}
return slow
}
range方法
好的!为您总结Go语言range方法在力扣题目中的使用要点,特别针对算法题目的特点:
for index, value := range collection {
// 循环体
}
数组/切片(最常用)
nums := []int{
1, 2, 3, 4, 5}
// 方式1:获取索引和值(推荐)
for i, num := range nums {
fmt.Printf("nums[%d] = %d\n", i, num)
}
// 方式2:只获取值
for _, num := range nums {
fmt.Printf("值: %d\n", num)
}
// 方式3:只获取索引
for i := range nums {
fmt.Printf("索引: %d, 值: %d\n", i, nums[i])
}
字符串(字符遍历)
s := "hello"
// 遍历字符串(按rune遍历,支持中文)
for i, char := range s {
fmt.Printf("位置%d: %c (Unicode: %U)\n", i, char, char)
}
Map(无序遍历)
m := map[string]int{
"a": 1, "b": 2, "c": 3}
// 遍历map(顺序随机)
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
力扣题目中的实用技巧
-
原地修改数组
// 错误:修改的是副本,不影响原数组 for _, num := range nums { num = num * 2 // 无效! } // 正确:通过索引修改原数组 for i := range nums { nums[i] = nums[i] * 2 // 有效! } -
双指针技巧中的range
func twoSum(nums []int, target int) []int { // 使用range遍历,配合map快速查找 seen := make(map[int]int) for i, num := range nums { complement := target - num if idx, exists := seen[complement]; exists { return []int{ idx, i} } seen[num] = i } return nil } -
字符串处理
func countCharacters(s string) int { count := 0 for _, char := range s { if char != ' ' { count++ } } return count } -
矩阵遍历(二维数组)
matrix := [][]int{ { 1,2,3}, { 4,5,6}, { 7,8,9}} // 遍历二维数组 for i, row := range matrix { for j, value := range row { fmt.Printf("matrix[%d][%d] = %d\n", i, j, value) } }
常见陷阱与注意事项
-
值拷贝问题
nums := []int{ 1, 2, 3} for _, num := range nums { num++ // 这只是修改副本,原数组不变! } // nums仍然是[1, 2, 3] -
性能考虑
// 对于大数组,直接使用索引可能更快 bigArray := make([]int, 1000000) // 较慢:每次迭代都有值拷贝 for _, value := range bigArray { _ = value } // 较快:直接通过索引访问 for i := range bigArray { _ = bigArray[i] } -
边界情况处理
// 空数组/切片不会panic var empty []int for i, num := range empty { fmt.Println(i, num) // 不会执行 } // nil切片也不会panic var nilSlice []int = nil for i, num := range nilSlice { fmt.Println(i, num) // 不会执行 }
力扣实战示例
-
移除元素
func removeElement(nums []int, val int) int { slow := 0 // 使用range遍历,但通过索引修改原数组 for _, num := range nums { if num != val { nums[slow] = num slow++ } } return slow } -
反转字符串
func reverseString(s []byte) { // 使用range获取索引,进行双指针交换 for i := range s[:len(s)/2] { j := len(s) - 1 - i s[i], s[j] = s[j], s[i] } } -
寻找重复元素
func findDuplicate(nums []int) int { seen := make(map[int]bool) for _, num := range nums { if seen[num] { return num } seen[num] = true } return -1 }
总结
- 优先使用索引:在需要修改原数组时,使用
for i := range形式 - 值遍历只读:
for _, value := range中的value是副本,不能修改原数据 - map遍历无序:不要依赖map的遍历顺序
- 性能敏感时:大数组考虑直接使用索引访问
- 安全遍历:range对nil和空集合是安全的
(须回看)删除有序数组中的重复项(9.15.3)
快慢指针法
func removeDuplicates(nums []int) int {
//异常情况
length:=len(nums)
if length == 0{
return 0
}
//慢指针
slow := 0
//快指针起始遍历
for fast:=1;fast<length;fast++{
if nums[fast]!=nums[slow]{
slow++
nums[slow]=nums[fast]
}
}
return slow+1
}
注意1
下面这段代码存在问题:
对于输入 [1,1,2],当 fast = 1(指向第二个1)时:
nums[fast] == nums[fast+1]→1 == 2→ false- 进入
else分支:fast++→fast = 2 - 现在
fast = 2,但数组长度是3(索引0,1,2) - 下一次循环:
if fast == length-1→2 == 2,应该跳出循环
但是,如果数组是 [1,1,1]:
- 内层循环会一直
fast++,直到fast = 2 - 然后检查
nums[2] != nums[3]→ 访问nums[3]导致越界
func removeDuplicates(nums []int) int {
//慢指针
fast,slow := 0,0
length := len(nums)
for {
//弹出条件
if fast == length-1{
break
}
if nums[fast]==nums[fast+1]{
for{
if nums[fast]!=nums[fast+1]{
break
}
fast++
}
fast++
slow++
nums[slow]=nums[fast]
}else{
fast++
slow++
nums[slow]=nums[fast]
}
}
return slow+1
}
注意2
return slow++ // 错误!
- 语法错误:Go 的
return语句不能包含表达式,只能返回值 - 逻辑错误:即使语法正确,
slow++是后置自增,会先返回slow的值,然后再自增
因此要写成:
return slow+1
哈希表(如果不是非严格递增就要用到)
基本操作
// 创建哈希表
m := make(map[keyType]valueType)
// 字面量初始化
m := map[string]int{
"a": 1, "b": 2}
// 基本操作
m["key"] = value // 插入/更新
value := m["key"] // 读取
delete(m, "key") // 删除
len(m) // 获取大小
力扣常用哈希表模式
频率统计(最常用)
// 统计元素频率
func countFrequency(nums []int) map[int]int {
freq := make(map[int]int)
for _, num := range nums {
freq[num]++
}
return freq
}
// 字符串字符统计
func charCount(s string) map[rune]int {
count := make(map[rune]int)
for _, char := range s {
count[char]++
}
return count
}
快速查找存在性
// 检查重复元素
力扣数组刷题技巧总结

最低0.47元/天 解锁文章
589

被折叠的 条评论
为什么被折叠?



