内容简介:在 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数组-排序算法
- 算法面试:数组编码面试问题
- 数据结构与算法: 数组
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
设计原本
Frederick P. Brooks, Jr. / InfoQ中文站、王海鹏、高博 / 机械工业出版社 / 2011-1-1 / 55.00元
无论是软件开发、工程还是建筑,有效的设计都是工作的核心。《设计原本:计算机科学巨匠Frederick P. Brooks的思考》将对设计过程进行深入分析,揭示进行有效和优雅设计的方法。 本书包含了多个行业设计者的特别领悟。Frederick P. Brooks, Jr.精确发现了所有设计项目中内在的不变因素,揭示 了进行优秀设计的过程和模式。通过与几十位优秀设计者的对话,以及他自己在几个设计......一起来看看 《设计原本》 这本书的介绍吧!