0x01.问题
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示: 题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
来源:力扣(LeetCode)
0x02.假如这是面试题,你如何给出最令面试官满意的答案?
题目确实很简单,最原始的想法肯定是先对整个数组做乘积然后后面只要逐个相除即可,但是有一个小小的限制,不能使用除法。最原始的思路被打断了,自然开始了下一步的思考,不使用除法如何得到这些乘积,难道对每一个数去枚举?
减分的代码:
class Solution {
public int[] productExceptSelf(int[] nums) {
int n=nums.length;
int[] ans=new int[n];
for(int i=0;i<n;i++){
ans[i]=1;
for(int j=0;j<n;j++){
if(j!=i){
ans[i]*=nums[j];
}
}
}
return ans;
}
}
-
这代码着实暴力,而且应该大家都清楚,这只要是个程序员,就能写出这样子的代码,不写还好,一旦面试中给出了这样的代码,可能就是【回家等通知了】。
-
在紧张的面试过程中,你可以有这样的想法,但最多是一瞬间,转瞬而过,主要要思考的就是,如何去改进?
-
以一种正常化的思维去想一下,这段代码为什么这么慢,或者说这么慢的原因是什么?
-
不难发现,就是其中做了大量的重复运算,有一些数,总是在反复的相乘,这是毫无意义的。
扫描二维码关注公众号,回复: 11444056 查看本文章 -
那么解决这种大量重复的最简单的思路是什么?
-
就是采用一个中间数组,将这些中间结果存储起来。具体的想法:
- 对于每一个
ans[i]
,它的值应该是等于
nums[0]*...*nums[i-1]*nums[i+1]*...nums[n-1]
,通俗一点,就是左边乘积与右边乘积再相乘。 - 对于每一次使用的左边的数,是不是会存在大量重复的数,那么你就会发现,其实这就是dp,左边的数可以用dp计算出来,同理,右边的数,也可以计算出来。
- 对于每一个=
i
,维护left[i]
和right[i]
表示左右两边的乘积。 left[i]
和right[i]
其实就是dp。
- 对于每一个
-
那么对于最终的答案,就是
ans[i]=left[i]*right[i]
,时间复杂度成功降为了O(N)。
普通的代码:
class Solution {
public int[] productExceptSelf(int[] nums) {
int n=nums.length;
int[] ans=new int[n];
int[] left=new int[n];
int[] right=new int[n];
left[0]=1;
for(int i=1;i<n;i++){
left[i]=nums[i-1]*left[i-1];
}
right[n-1]=1;
for(int i=n-2;i>=0;i--){
right[i]=nums[i+1]*right[i+1];
}
for(int i=0;i<n;i++){
ans[i]=left[i]*right[i];
}
return ans;
}
}
-
这段代码确实已经把时间复杂度降低了一个维度,那为什么仍然说是普通的代码呢?
-
因为在减低时间维度的时候,增加了空间的开销,除了必要的
ans
外,空间复杂度确实增加了。 -
此时的你很有可能认为这就是最佳的答案了,因为你稍微想了一下空间复杂度,发现没有优化的地方。但如果你把这段代码给了面试官,也只能算一个正常的答案。
-
事实上,再仔细想一下,上面的思路不就是dp嘛,dp的数组是可以优化的啊。此时的你可能又会陷入疑惑:一个dp可以优化,两个dp怎么办?????
-
我们不是还有个ans数组嘛,拿来用嘛,它本身就可以当做一个dp,然后再用普通的优化方法优化掉另一个dp数组。
- 我们将ans本身看成一个
left
的dp。优化掉left
。 - 然后我们发现,
right
每一个值只与它后一个数有关,所以可以用一个常数将其优化掉。
- 我们将ans本身看成一个
加分的代码:
class Solution {
public int[] productExceptSelf(int[] nums) {
int n=nums.length;
int[] ans=new int[n];
ans[0]=1;
for(int i=1;i<n;i++){
ans[i]=ans[i-1]*nums[i-1];
}
int right=1;
for(int i=n-1;i>=0;i--){
ans[i]=ans[i]*right;
right*=nums[i];
}
return ans;
}
}
-
仔细反思一下,面对这种简单的问题,到底如何给出最佳答案呢?
- 脑袋里蹦出的第一思路,肯定是你最擅长的做法,但你最擅长的,并不一定是最好的(当然很多大佬的确是最好的,哈哈哈),如果我们用极快的方法做出了我们的第一思路,那么,你可以利用余下的时间,思考优化的可能性:
- 原始思路慢的原因是什么?
- 时间空间维度上如何去解决这个问题?
- 有些空间是否多余?
- 是否存在赘余的代码?
-
最后,能交出什么样的答案,还是取决于【平时积累+面试时的冷静思考能力】。