刷题过程中踩坑(152. 乘积最大子数组),第一次解题代码中将下面代码中的二维dp数组的初始化中通过dp = [[nums[0]] * 2] * len(nums)实现的:

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:

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]
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]

可通过列表推导式的方式初始化,这样对于可变类型对象每次都会重新开辟内存空间,修改不相互影响:

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]
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐