携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第35天,构建乘积数组[前后缀&优化] - 掘金 (juejin.cn)
前言
前后缀是一种典型的空间换时间的做法,通过记录每个位置的状态来达到减少计算,而且当有前后遍历时,还可以用变量替代数组,因为前后缀是滚动的,就像是动态规划一样。
一、构建乘积数组
二、前后缀
1、前后缀直接相乘
// 构建乘积数组
public class ConstructArr {
public int[] constructArr(int[] a) {
/*
计算每个位置的前后缀,每个位置的值 = 前缀 * 后缀。
*/
int n = a.length;
// 特殊情况,防止空数组。
if (n == 0) return new int[]{};
// 初始化前后缀。
int[] prefix = new int[n];
prefix[0] = 1;
int[] suffix = new int[n];
suffix[n - 1] = 1;
// 为前后缀中间可同时计算的部分赋值。
for (int i = 1; i < n - 1; i++) {
prefix[i] = a[i - 1] * prefix[i - 1];
suffix[n - i - 1] = a[n - i] * suffix[n - i];
}
// 为前后缀不可同时赋值的特殊位置赋值。
prefix[n - 1] = prefix[n - 2] * a[n - 2];
suffix[0] = suffix[1] * a[1];
// 计算前后缀乘积。
int[] r = new int[n];
for (int i = 0; i < n; i++) r[i] = prefix[i] * suffix[i];
// 返回计算结果。
return r;
}
}
2、状态压缩
// 改进:由于计算前后缀乘积时,需要往一个方向遍历,所以可少一个前/后缀。
class ConstructArr2 {
public int[] constructArr(int[] a) {
/*
计算每个位置的前后缀,每个位置的值 = 前缀 * 后缀。
*/
int n = a.length;
// 特殊情况,防止空数组。
if (n == 0) return new int[]{};
// 从左往右遍历,可用prefix代替prefix[].
int prefix = 1;
// 初始化后缀。
int[] suffix = new int[n];
suffix[n - 1] = 1;
// 为后缀每个位置赋值。
for (int i = n - 2; i >= 0; i--) suffix[i] = a[i + 1] * suffix[i + 1];
// 计算前后缀乘积。
int[] r = new int[n];
for (int i = 0; i < n; i++) {
r[i] = prefix * suffix[i];
// 更新前缀。
prefix *= a[i];
}
// 返回计算结果。
return r;
}
}
总结
1)前后缀可以空间换时间,当结合遍历时,还可以进行空间压缩。
参考文献
[1] LeetCode 构建乘积数组