前言
前缀积思想,题前抽象一段子数组(积/和/自定义处理方式),通过空间换时间。前缀和的本质就是简单的动态规划,有时可以空间压缩,这样就不浪费空间了。
逻辑转换思想,在一些内在规律的限定下,可以另辟蹊径,就是一种逻辑转换,以达到走捷径,减运算消耗时间&空间的目的。达到一个目的的直观路径有一条,但观察其规律,将问题转换 / 将过程转换,用更简洁的逻辑来表达,便发现了多条殊途同归的路,也算是算法的目的吧。
一、乘积的最大子数组
二、前缀积&逻辑转换
1、idea
子数组的任何前缀乘积都不超过32位;
数肯定是越乘越大,除非碰到0,开启新一段数据;
每段数组处理方式,记录当前乘积,记录当前段最前面的负数积,如果当前乘积是负数,将最前面的负数积除掉,再更新最大乘积max,反之直接用来更新max最大乘积。
2、golang简洁版
func maxProduct(nums []int) int {
m := nums[0]
all,neg := 1,1
for _,n := range nums {
// 记录当前乘积
all *= n
// 取最大乘积
m = max(max(all,all / neg),m) // 用时4ms -> 0ms
// 开启下一段数组的neg 和 all
if n == 0 {
neg = 1
all = 1
}
// 记录该段第一个负数积
if n < 0 && neg == 1 {
neg = all
}
}
return m
}
func max(x,y int) int {
if x > y {
return x
}
return y
}
3、golang解析版
func maxProduct(nums []int) int {
m := nums[0]
all,neg := 1,1
for _,n := range nums {
// 记录当前乘积
all *= n
// 记录最大乘积,2种情况,负数/负数 & 正数
/*
k := 0
if all >= 0 {
k = all
}else {
k = all / neg
}
if k > max {
max = k
}
*/
// 抽象过程,逻辑等价替换。
// 这个if/else可用max函数替换,通过内在规律限定,无非就是取最大。
m = max(max(all,all / neg),m) // 用时4ms -> 0ms
// 开启下一段数组的neg 和 all
if n == 0 {
neg = 1
all = 1
}
// 记录该段第一个负数
if n < 0 && neg == 1 {
// 真正的负数必须带上前面所有的正数乘积
neg = all
}
}
return m
}
func max(x,y int) int {
if x > y {
return x
}
return y
}
// 逻辑整理,检验基本功是否准备好?
// 子数组的任何前缀乘积都不超过32位
// 越乘肯定越大,除非碰到0,每段找到最前面的那个负数,以便后续需要。
// case:从左到右遍历,同时记录当前乘积,以及最前面那个负数,负数初始化为1,不更新最大乘积即可。
总结
1)前缀思想,一种抽象的思想,可以是前缀积/前缀和,也可以是自定义前缀xx.
2)逻辑转换思想,目的清晰,暴力路径一条,但可以挖掘内在规律,发现其他路径。这条路径可以是抽象问题,改变程序整体结构,降低整个程序的时空复杂度;也可以是抽象其中一个小目的,改变一小段代码,提高运算速度。
参考文献
[1] LeetCode 乘积最大子数组