概念
golang对整形切片的排序
import "sort"
func main() {
nums := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}
sort.Ints(nums)
fmt.Println(nums) // 输出:[1 1 2 3 3 4 5 5 5 6 9]
}
对字符串切片排序
package main
import (
"fmt"
"sort"
)
func main() {
strs := []string{"banana", "apple", "mango", "peach", "orange"}
sort.Strings(strs)
fmt.Println(strs) // 输出:[apple banana mango orange peach]
}
对结构体的排序,需要指定结构体函数Len, Less和Swap函数
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
type PersonSlice []Person
func (p PersonSlice) Len() int { return len(p) }
func (p PersonSlice) Less(i, j int) bool { return p[i].Age < p[j].Age }
func (p PersonSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func main() {
people := PersonSlice{
{"Alice", 31},
{"Bob", 29},
{"Charlie", 33},
}
sort.Stable(people)
fmt.Println(people) // 输出:[Bob 29 Alice 31 Charlie 33]
}
make 函数主要用于创建和初始化内置的数据结构,主要是slice,map和channel make函数的基本用法:
make(Type, len, cap) Type
// Type: 数据类型,只能slice,map和channel
// len: 数据类型实际占用的内存空间长度,对于slice是必要参数
// cap: 为数据类型提前预留的内存空间长度,可选参数, 可选参数,所谓的**提前预留**是当前为数据类型申请内存空间的时候,提前申请好额外的内存空间,这样可以避免二次分配内存带来的开销,大大提高程序的性能
make函数的具体场景
-
创建slice: make([]int, 5, 10) 创建一个长度为5,容量为10的int slice make([]int, 2, 4) 创建一个长度为2,容量为4的int slice
-
创建map: make(map[string]int,cap): 创建一个空的map,key类型是string,值类型是int 和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity
-
创建channel: make(chan int, cap) 创建一个缓冲大小为cap 的int channel
刷题
454 四数相加II
本题解题步骤:
- 首先定义 一个map,key放a和b两数之和,value 放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 再遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 count 就可以了
package main
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
// 使用make定义变量的长度,可以加快执行速度
before := make(map[int]int, len(nums1)*len(nums2))
result := 0
for _, i := range nums1 {
for _, j := range nums2 {
// int默认值为0,不需要判断是否存在key
before[i+j] ++
}
}
for _, i := range nums3 {
for _, j:= range nums4 {
result += before[0-i-j]
}
}
return result
}
383 赎金信
使用长度为26的 数组代替map,因为 ransomNote 和 magazine 由小写英文字母组成,索引就变成int(char)-int('a')
15 三数之和
双指针
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。
而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。
接下来我来介绍另一个解法:双指针法,这道题目使用双指针法 要比哈希法高效一些,那么来讲解一下具体实现的思路。
动画效果如下:
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)。
package main
import "sort"
func threeSum(nums []int) [][]int {
result := [][]int{}
sort.Ints(nums)
// 排序之后最后一个元素小于0,无论如何组合三个数都是小于0
if nums[len(nums)-1] < 0 {
return result
}
// 排序之后第一个元素大于0,无论如何组合三个数都大于0
for first:=0; first<len(nums) && nums[first]<=0 ; first++ {
// 结果中的第一个元素去重,因为已经排序过
if first != 0 && nums[first] == nums[first-1] {
continue
}
left := first + 1
right := len(nums) - 1
for left < right {
if nums[first] + nums[left] + nums[right] < 0 {
left++
}else if nums[first] + nums[left] + nums[right] > 0 {
right--
}else {
result = append(result, []int{nums[first], nums[left], nums[right]})
// 找到三元组之后,对nums[left] 和 nums[right] 去重
left++
for left < right && nums[left-1] == nums[left] {
left++
}
right--
for left < right && nums[right+1] == nums[right] {
right--
}
}
}
}
return result
}
问题: 在剪枝过程中,只判断了最小的四个数是否小于target,没有计算最大值 result 没有设置
18 四数之和
package main
import "sort"
func fourSum(nums []int, target int) [][]int {
result := [][]int{}
sort.Ints(nums)
// 剪枝: 如果长度小于4,直接返回空
if len(nums) < 4 {
return result
}
// 遍历a第一个数,从0到len(nums)-4
// 剪枝:如果最小的四个数加起来大于target,不再遍历
for a := 0; a < len(nums)-3 && nums[a] + nums[a+1] + nums[a+2] + nums[a+3] <= target; a++ {
// 去重: 如果遍历后的数和前一个数相等,跳过
if a != 0 && nums[a] == nums[a-1] {
continue
}
// 剪枝:如果最小的四个数加起来大于target,不再遍历
for b := a+1; b < len(nums)-2 && nums[a] + nums[b] + nums[b+1] + nums[b+2] <= target; b++ {
// 去重: 如果遍历后的数和前一个数相等,跳过
if b != a+1 && nums[b] == nums[b-1] {
continue
}
c := b+1
d := len(nums)-1
for c < d {
if nums[a] + nums[b] + nums[c] + nums[d] < target {
c++
}else if nums[a] + nums[b] + nums[c] + nums[d] > target {
d--
}else {
result = append(result, []int{nums[a], nums[b], nums[c], nums[d]})
// 双指针去重
c++
for c<d && nums[c] == nums[c-1] {
c++
}
d--
for c<d && nums[d] == nums[d+1] {
d--
}
}
}
}
}
return result
}
总结
这道题使用了双指针法,双指针将时间复杂度O(n^2)的解法优化为O(n)的解法,也就是降一个数量级,要理解快指针和慢指针的究竟是什么含义, 题目如下:
链表相关双指针题目: