首先放今天的力扣打卡题。
在第一次做的过程中,我忽略了“升序排列”这个条件,没有使用二分。
由于是最近才刚开始学golang,所以很有兴趣的在这道题里使用了go的大杀器——goroutine。思路就是使用两个goroutine,一个从头到尾遍历数组,找出开始位置,一个倒序遍历数组,找到结束位置。
代码如下:
func searchRange(nums []int, target int) []int {
//使用WaitGroup控制goroutine
var wg sync.WaitGroup
//wg加入两个任务
wg.Add(2)
length := len(nums)
//将start end直接赋值-1 当找不到时直接返回
start := -1
end := -1
//第一个goroutine
go func() {
//函数运行结束后wg任务-1
defer wg.Done()
//正序遍历数组,找到start就跳出
for i := 0; i < length; i++ {
if nums[i] == target {
start = i
break
}
}
}()
go func() {
defer wg.Done()
//倒序遍历数组,找到end就跳出
for i := length-1; i >= 0; i-- {
if nums[i] == target {
end = i
break
}
}
}()
//wg一直等待两个任务结束
wg.Wait()
return []int{start,end}
}
由于go出色的性能,结果还是挺令人满意。
在完成这道题后,我去看了看题解,这才发现数组是有序的,可以使用二分法完成。
而官方的Go题解非常简练优雅。
这里放上代码并加上注释。
func searchRange(nums []int, target int) []int {
/*
SearchInts函数会返回数组中target第一次出现的位置。
如果没有找到,则会返回数组长度n。
*/
//找起始位置
leftmost := sort.SearchInts(nums, target)
if leftmost == len(nums) || nums[leftmost] != target {
return []int{-1, -1}
}
//结束位置(这里是找target+1,比target大一的数字的起始位置就在target结束位置的右边)
rightmost := sort.SearchInts(nums, target + 1) - 1
return []int{leftmost, rightmost}
}
其中使用到了SearchInts这个函数。
跟踪进去,发现它调用了sort包下的Search函数。
这里对这个使用二分进行查找的函数进行分析。
func Search(n int, f func(int) bool) int {
i, j := 0, n
for i < j {
/*
这里使用了移位操作,其结果与(i+j)/2一样。
uint是无符号的int,范围是2^32即0到4294967295。使用uint可以避免因为i+j太大而造成的溢出
*/
h := int(uint(i+j) >> 1)
// 如果f(h)返回false,说明从i到h中没有目标值。这时更新i为h+1 从原先的i到现在的i之间的数就不会再次扫描了
//相反的,如果f(h)返回true,说明从i到h中有目标值。这时更新j为 h
if !f(h) {
i = h + 1
} else {
j = h // preserves f(j) == true
}
}
//当 i==j 时,说明找到了(或者找完了但是没有找到,这时返回的是数组长度)
return i
}
在阅读这段简单的源码时,其中的移位操作很有趣。在此之前,我并不知道还可以使用移位来进行/2的操作。其中原理也很简单:
在二进制中,每一位等于前面所有位的值之和再加一。
例如,7位二进制的最大值为1111111,即127。
给它加一即为10000000,128。
因此,对数字向右移位一位,就是其/2的结果。
而在查资料的时候,发现移位实现的乘除法比直接乘除的效率高很多。
总结:
1.遇到查找有序数组中的元素的时候应该反应出来使用二分。
2.遇到需要大量进行乘除法的操作时,考虑使用移位。