題目描述
給你一個長度爲 n 的整數數組 nums,其中 n > 1,返回輸出數組 output ,其中 output[i] 等於 nums 中除 nums[i] 之外其餘各元素的乘積。
提示:題目數據保證數組之中任意元素的全部前綴元素和後綴(甚至是整個數組)的乘積都在 32 位整數範圍內。
要求:在 O(n) 時間複雜度內完成此題。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/product-of-array-except-self
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
算法
第一個想到的最簡單的方法:
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n=1 # 保存整個數組除0以外的元素乘積
k=0 # 保存數組中0的數量
for i in nums:
if i:
n*=i
else:
k+=1
if k>=2:
return [0]*len(nums)
if k==1:
ls=[]
for i in nums:
if i:
ls.append(0)
else:
ls.append(n)
else:
ls=[]
for i in nums:
if i:
ls.append(n//i)
return ls
這種方法需要兩次遍歷,時間複雜度是O(n),需要O(n)的空間複雜度,但是第一次需要兩個變量保存值,第二次遍歷所使用的空間其實可以用原數組。
要求:禁止使用除法
# 動態規劃,開闢兩類空間,分別存儲索引i左邊(右邊)所有數的乘積,
# 所以,output[i]即爲i左邊所有數的乘積*i右邊所有數的乘積.
大佬就是大佬,一句話就豁然開朗。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
# 動態規劃,開闢兩類空間,分別存儲索引i左邊(右邊)所有數的乘積,
# 所以,output[i]即爲i左邊所有數的乘積*i右邊所有數的乘積.
left=nums.copy()
right=nums.copy()
for i in range(1,n):
left[i]*=left[i-1]
for i in range(n-2,-1,-1):
right[i]*=right[i+1]
res=[]
for i in range(n):
res.append((left[i-1] if i>=1 else 1)*(right[i+1] if i+1<n else 1))
return res
執行用時 :88 ms, 在所有 Python3 提交中擊敗了17.32%的用戶
內存消耗 :19.7 MB, 在所有 Python3 提交中擊敗了9.09%的用戶
進階!
你可以在常數空間複雜度內完成這個題目嗎?( 出於對空間複雜度分析的目的,輸出數組不被視爲額外空間。)
對上面的算法進行優化,事實是我們不需要左右兩個數組來保存乘積,只需要其中一個即可。在計算另一個數組時就可同時計算出結果數組。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
# 動態規劃,開闢兩類空間,分別存儲索引i左邊(右邊)所有數的乘積,
# 所以,output[i]即爲i左邊所有數的乘積*i右邊所有數的乘積.
# 爲了滿足O(1)空間複雜度要求(輸出數組不被視作額外空間),
# left和right數組只保留一個,爲了方便計算,最好是保留right
right=nums.copy()
for i in range(n-2,-1,-1):
right[i]*=right[i+1]
res=[]
left=1
for i in range(n-1):
res.append(left*right[i+1])
left*=nums[i]
res.append(left)
return res
執行用時 :64 ms, 在所有 Python3 提交中擊敗了56.66%的用戶
內存消耗 :18.4 MB, 在所有 Python3 提交中擊敗了100.00%的用戶
再進階!
你可以在常數空間複雜度內完成這個題目嗎?
寫出如上的算法時又突然發現,其實可以實現真的**在常數空間複雜度內完成這個題目!**因爲:
res=[]
left=1
for i in range(n-1):
res.append(left*right[i+1])
left*=nums[i]
res.append(left)
這裏的結果數組res其實不是必須的,我們完全可以用原數組nums替代,那麼如果要替代問題是什麼?
在下面的left*=nums[i]
會被覆蓋掉,顯然,用一個額外的變量提前保存會被覆蓋的位置的值即可!
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
# 動態規劃,開闢兩類空間,分別存儲索引i左邊(右邊)所有數的乘積,
# 所以,output[i]即爲i左邊所有數的乘積*i右邊所有數的乘積.
right=nums.copy()
for i in range(n-2,-1,-1):
right[i]*=right[i+1]
res=[]
left=1
pre=nums[i]
for i in range(n-1):
nums[i]=left*right[i+1]
left*=pre
pre=nums[i+1]
nums[-1]=left
return nums
執行用時 :60 ms, 在所有 Python3 提交中擊敗了70.12%的用戶
內存消耗 :17.5 MB, 在所有 Python3 提交中擊敗了100.00%的用戶
參考算法
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
# 動態規劃,開闢兩類空間,分別存儲索引i左邊(右邊)所有數的乘積,
# 所以,output[i]即爲i左邊所有數的乘積*i右邊所有數的乘積.
# 爲了滿足O(1)空間複雜度要求(輸出數組不被視作額外空間),
# 首先,一類空間的dp數據(i右邊所有數的乘積)用輸出數組存儲,
# 二類空間的dp數據(i左邊所有數的乘積)用兩個變量存儲,
# 因爲這兩個變量會一直變化, 所以應該實時更新輸出數組.
dp_right = [1 for i in range(n)]
pre_left = 1
for i in range(n - 2, -1, -1):
dp_right[i] = dp_right[i + 1] * nums[i + 1]
for i in range(1, n):
cur_left = pre_left * nums[i - 1]
dp_right[i] = cur_left * dp_right[i]
pre_left = cur_left
return dp_right
拓展
最後補充一個很酷的拓展:
官方的一句話系列:
import functools
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
return list(map(lambda x,y:x*y,functools.reduce(lambda x,y:x.append(x[-1]*y) or x,nums[:-1],[1]),functools.reduce(lambda x,y:x.append(x[-1]*y) or x,nums[-1:0:-1],[1])[::-1]))
import functools
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
return list(
map(
lambda x,y:x*y,
functools.reduce(
lambda x,y:x.append(x[-1]*y) or x,nums[:-1],[1]),
functools.reduce(
lambda x,y:x.append(x[-1]*y) or x,nums[-1:0:-1],[1])[::-1]))
分析了一下,是利用標準庫functools,這裏順便提一句以前內置函數裏好像是有reduce
的,但後來就沒了,這裏用functools.reduce()
做替代。
lambda函數:lambda x,y:x.append(x[-1]*y) or x
但是我貌似還是沒看懂算法的構建啊,吐血。