刷题过程中踩坑(152. 乘积最大子数组),第一次解题代码中将下面代码中的二维dp数组的初始化中通过dp = [[nums[0]] * 2] * len(nums)
实现的:
py
class Solution:
def maxProduct(self, nums: List[int]) -> int:
dp = [[nums[0]] * 2 for _ in range(len(nums))] # dp[i][0]:最大值 dp[i][1]:最小值
for i in range(1, len(nums)):
if nums[i] == 0:
dp[i][0] = 0
dp[i][1] = 0
elif nums[i] > 0:
dp[i][0] = max(dp[i - 1][0] * nums[i], nums[i])
dp[i][1] = min(dp[i - 1][1] * nums[i], nums[i])
else:
dp[i][0] = max(dp[i - 1][1] * nums[i], nums[i])
dp[i][1] = min(dp[i - 1][0] * nums[i], nums[i])
ans = float('-inf')
for i in range(len(dp)):
if dp[i][0] > ans:
ans = dp[i][0]
return ans
结果测试用例总是不通过,解题思路似乎也看不出问题,gpt检查是dp数组初始化的问题。
python中,*
的方式对列表数乘扩容,如果列表元素是可变类型,修改一个元素的值会影响其它元素,可从下面cpython的源代码中证明:
https://github.com/python/cpython/blob/3.12/Objects/listobject.c#L553
如果列表长度为1,源码574行直接开辟n个长度的空间并复制列表首元素的地址;如果列表长度>1,581~582行将原列表的前n个元素地址复制到新列表的前n个元素位置,585~586行直接将新列表中前n和位置元素的地址批量复制到后面的内存空间。
其实本质上来说,python列表的数乘运算,底层都是引用复制,区别在于引用对象是可变对象还是不可变对象,可变对象修改其中一个会影响其它所有副本,不可变对象修改会重新开辟内存空间,不影响副本。
验证demo:
py
a = [[1, 2]] * 3
print(a)
print([id(item) for item in a])
a[0][0] = 3
print(a)
print([id(item) for item in a])
# [[1, 2], [1, 2], [1, 2]]
# [4309240192, 4309240192, 4309240192]
# [[3, 2], [3, 2], [3, 2]]
# [4309240192, 4309240192, 4309240192]
py
a = [1] * 3
print(a)
print([id(item) for item in a])
a[0] = 3
print(a)
print([id(item) for item in a])
# [1, 1, 1]
# [4326971552, 4326971552, 4326971552]
# [3, 1, 1]
# [4326971616, 4326971552, 4326971552]
可通过列表推导式的方式初始化,这样对于可变类型对象每次都会重新开辟内存空间,修改不相互影响:
py
a = [[1, 2] for _ in range(3)]
print(a)
print([id(item) for item in a])
# [[1, 2], [1, 2], [1, 2]]
# [4343746688, 4345649152, 4345315968]