这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战
除自身以外数组的乘积(题号238)
题目
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
**提示:**题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
**说明: 请不要使用除法,**且在 O(n) 时间复杂度内完成此题。
进阶: 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
链接
解释
这题啊,这题是DP变种。
说实话,看到这题的第一眼,感觉用两层循环就可以解决了。
首先第一层循环,循环每个元素,内层循环就是计算整个数组种除了当前数字的乘积,在JavaScript可以使用reduce来简化代码,但这样的时间复杂度就是O(n * n)了,显然不符合题目的说明。
后续也证明了这样的做法确实没法AC,因为在测试用例靠后的位置会有个巨长的数组来让你超时。
那除了这种做法还有什么方法呢?想了10分钟后,发现DP可以解决这个问题。
dp[i][j]可以代表一个数组中从i到j位置上所有数字的乘积,那么要求一个除一个数字以外的其它数字乘积就很简单了,举个例子:
[1,2,3,4]
比方说现在取2个这个位置上的其它数字的乘积,它的index是1,那么假设已经有DP数组了,那么当前位置的乘积就是:
dp[0][0] * dp[2][3]
dp[0][0]代表着1,dp[2][3]代表着3 * 4,两个一乘就是1 * 3 * 4,也就是我们需要的结果了。
那怎么拿到这个DP数组呢?在求之前我们需要知道这个DP数组应该包含怎样的数据。
因为如果是平常的DP数组,拿到数组的方法肯定是两个for循环,这样的时间复杂度就已经是O(n * n)了,已经超出了题目的要求,所以需要简化一下这个DP数组。
回到刚才的问题上?这个DP数组需要为我们提供怎样的数据?
因为都是从头到尾的计算,所以它只需要有两层:
-
第一层表示从数组的第一个元素到每个元素位置的乘积
也就是
dp[i]表示从0开始到i位置的所有数字的乘积 -
第二层表示从数组的最后一个元素到每个元素位置的乘积
也就是
dp[i]表示从nums.length - 1到i位置的所有数字乘积,也就是反向的第一层数据
了解这两层后就好办了,可以循环数组两次,分别求除DP数组的两层,也可以只循环一层,利用nums.length和i的关系可以在一个for循环中求出两层数组的值。
拿到值之后再遍历下每个元素,就可以拿到最后的结果了。
自己的答案(双for循环)
var productExceptSelf = function(nums) {
const len = nums.length
const res = new Array(len)
for (let i = 0; i < len; i++) {
res[i] = nums.reduce((total, num, index) => {
if (index === i) return total
return total * num
}, 1)
}
return res
};
代码很简单,就双层for循环呗,也很简单的失败了,倒在了19/20个测试用例。
自己的答案(DP+双数组)
这就是解释中说的方法了👇:
var productExceptSelf = function(nums) {
const len = nums.length
const dp = Array.from({length: 2}, () => new Array())
const res = new Array(len)
dp[0][0] = 1
dp[1][0] = 1
for (let i = 1; i <= len; i++) {
dp[0].push(dp[0][i - 1] * nums[i - 1])
dp[1].push(dp[1][i - 1] * nums[len - i])
}
for (let i = 1; i <= len; i++) {
res[i - 1] = dp[0][i - 1] * dp[1][len - i]
}
return res
};
这里有几点需要注意的地方:
-
初始化DP数组时没有给数组长度
因为下面用的
push方法,此处给长度没啥意义,给了长度也可以,不过就没必要用push了,都可以 -
DP数组的初始化赋值
这里的DP数组是比
nums数组长一位的,Why?因为第一个元素和最后一个元素的问题,如果是求第一个元素的值,那么显然dp[0][i - 1]是不存在的,此时DP公式就会出现问题,这里在前面多放一个1,就是为了防止取不到值的问题,dp[1]作为反向数组,自然也是要做一样的操作,否则也会出现取不到值的问题
在成功拿到DP数组后,最后for循环一次,就能拿到最后的res了,清晰明了
更好的方法(DP+单数组)
👆的解法虽然AC了,单依然不符合题意,因为空间复杂度是O(n)的,毕竟有DP数组的存在。
那如何优化到O(1)呢?注意题目说到res其实不算O(n),因为毕竟是返回值。
那就得在res上做文章了,是不是可以这么做
- 给
res初始化赋值的时候赋值的是上面dp[0]的数组 - 之后利用一个变量来累计
dp[1]数组的变化,依次给res重新赋值
这样就节省掉了DP双数组的空间,让答案符合O(1)的空间复杂度。
var productExceptSelf = function(nums) {
const len = nums.length
const res = new Array(len)
res[0] = 1
for (let i = 1; i < len; i++) {
res[i] = res[i - 1] * nums[i - 1]
}
let count = 1
for (let i = len - 1; i >= 0; i--) {
res[i] = res[i] * count
count *= nums[i]
}
return res
};
整体解法就是这样,第一个for循环用来给res初始化赋值,之后累计count变量的值,依次赋值给res中的元素,这样两次for循环之后,res就可以拿到最后的结果了,还是比较巧妙的。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇