内容简介:在 Leetcode 上实现这个算法时,总是运行超时,于是深入的学习了下先上性能测试结果:golang 实现源码及逻辑分析
在 Leetcode 上实现这个算法时,总是运行超时,于是深入的学习了下
先上性能测试结果:
Benchmark_numSubarraysWithSum1 20 78277388 ns/op 0 B/op 0 allocs/op Benchmark_numSubarraysWithSum2 2000 869859 ns/op 8192 B/op 1 allocs/op Benchmark_numSubarraysWithSum3 5000 298845 ns/op 0 B/op 0 allocs/op Benchmark_numSubarraysWithSum4 30000 43751 ns/op 40993 B/op 2 allocs/op
四种方法
golang 实现源码及逻辑分析
package main
import (
"fmt"
"math/rand"
)
var (
// SourceArray: 源数组
SourceArray = make([]int, 1000)
// K: 目标值
K = len(SourceArray) / 10
)
func init() {
// 为了避免文档过大,数组未静态定义,而在此包初始化时生成
// 随机填充 0、1,作为数组元素
for i := range SourceArray {
SourceArray[i] = rand.Intn(2)
}
}
// 方法#1
// * 时间复杂度: O(n^3)
// 逻辑最直观,但复杂度度最高,性能很差
func numSubarraysWithSum1(SourceArray []int, K int) int {
var count int
for i := 0; i < len(SourceArray); i++ {
for j := i; j < len(SourceArray); j++ {
sum := 0
for _, v := range SourceArray[i : j+1] {
sum += v
}
if sum == K {
count++
}
}
}
return count
}
// 方法#2
// * 时间复杂度: O(n^2)
// 理解起来相对复杂,但还可以接受,复杂度一般
func numSubarraysWithSum2(SourceArray []int, K int) int {
count := 0
// 生成累计数组 (复杂度: O(n))
// sumArray[0] -> 0
// sumArray[1] -> 0 + SourceArray[0]
// sumArray[2] -> 0 + SourceArray[0] + SourceArray[1] = sumArray[1] + SourceArray[1]
// ...
// sumArray[n] -> sumArray[n-1] + SourceArray[n]
sumArray := make([]int, len(SourceArray)+1)
sumArray[0] = 0
for i := range SourceArray {
sumArray[i+1] = sumArray[i] + SourceArray[i]
}
// 计算连续累计值间的差值,就等价于子数组元素之和,如果等于目标值target,则为满足条件 (复杂度: O(n^2))
// sumArray[1] - sumArray[0] -> mathSum(SourceArray[0:1])
// sumArray[2] - sumArray[0] -> mathSum(SourceArray[0:2])
// ...
// sumArray[2] - sumArray[1] -> mathSum(SourceArray[1:2])
// ...
// sumArray[n] - sumArray[m] -> mathSum(SourceArray[n:m])
for i := range SourceArray {
for j := range SourceArray {
if sumArray[j+1]-sumArray[i] == K {
count++
}
}
}
return count
}
// 方法#3: 遍历过程中直接求和
// * 时间复杂度: O(n^2)
// 是对方法#1的优化,避免了求和那层遍历,比较好理解,性能也还可以
func numSubarraysWithSum3(SourceArray []int, K int) int {
count := 0
for i := 0; i < len(SourceArray); i++ {
sum := 0
for j := i; j < len(SourceArray); j++ {
sum += SourceArray[j]
if sum == K {
count++
}
}
}
return count
}
// 方法#4: 最优算法
// * 时间复杂度: O(n)
// 逻辑很难理解,但性能好的一逼,尝试理解下~
// 按着代码的思路,貌似没啥毛病,但是这种计算方式,并不符合人类的正常思维,而是一个抽象的数学公式,还是死记硬背记下它吧
func numSubarraysWithSum4(SourceArray []int, K int) int {
count, sum := 0, 0
// sumMap: MAP[连续累计值 -> 这个累计值出现的次数]
sumMap := make(map[int]int, len(SourceArray))
// 首先这个定义从人类思维上就没法理解,但后面确实满足了公式计算的需求
sumMap[0] = 1
for i := range SourceArray {
// sum为当前累计值 = 之前元素之和
// - 如果下个元素非0,则当前累计值将会发生变化,当前累计值的出现的次数只会为1
// - 如果出现连续的元素为0,sum不会变,在sumMap[sum]++时,累计值出现的次数会大于1
// 所以:累计值出现的次数 = 1 + 连续出现0的次数,等价于和为当前累计值对于的子数组的数量
sum += SourceArray[i]
// sum-K 为距离目标值的差值
// - 如果=0,代表正好满足,匹配计数加一,所以可以看出开头定义的`sumMap[0] = 1`是为了此处
// - 如果<0或者超出Map,代表已经超出目标值,不符合条件,从golang数组里取负数索引会得到0
// - 如果>0且未超出Map,代表需要补充的差值,从sumMap中获得此差值的数量,同时意味着匹配数量
count += sumMap[sum-K]
sumMap[sum]++
}
return count
}
func main() {
fmt.Println("#1:", numSubarraysWithSum1(SourceArray, K))
fmt.Println("#2:", numSubarraysWithSum2(SourceArray, K))
fmt.Println("#3:", numSubarraysWithSum3(SourceArray, K))
fmt.Println("#4:", numSubarraysWithSum4(SourceArray, K))
}
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 菜鸡的算法修炼:数组(旋转数组的最小数字)
- 算法-计算小数组在大数组中的索引
- 数组查询算法(JavaScript)
- JavaScript数组-排序算法
- 算法面试:数组编码面试问题
- 数据结构与算法: 数组
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Remote
Jason Fried、David Heinemeier Hansson / Crown Business / 2013-10-29 / CAD 26.95
The “work from home” phenomenon is thoroughly explored in this illuminating new book from bestselling 37signals founders Fried and Hansson, who point to the surging trend of employees working from hom......一起来看看 《Remote》 这本书的介绍吧!