Python动态规划解决数字三角形问题(兼纪录copy的一次坑)

本文介绍了如何使用Python动态规划方法解决数字三角形问题,强调了重叠子问题和最优子结构的概念。在实现过程中,作者遇到了因误用浅复制导致的问题,详细阐述了这一错误及解决方案,提醒读者在处理包含子列表的列表复制时应使用深复制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python动态规划解决数字三角形问题(兼纪录copy的一次坑)

照例先上代码1

import copy


lines = [
    [1],
    [3, 2],
    [4, 5, 6],
    [8, 9, 8, 7],
    [7, 6, 5, 4, 3],
    [0, 10, 0, 0, 0, 5],
    [1, 2, 3, 4, 5, 6, 7],
    [8, 7, 6, 5, 4, 3, 2, 1],
    [1, 1, 2, 3, 4, 5, 6, 7, 10]
]

# lines = [
#     [1],
#     [3, 0],
#     [0, 5, 0],
#     [0, 9, 0, 0],
# ]


def maxSum(list, n, i, j):
    if i == n:
        return list[i][j]
    x = maxSum(list, n, i + 1, j)
    y = maxSum(list, n, i + 1, j + 1)
    return max(x, y) + list[i][j]


# 动态规划
def maxSumDP(list, listDP, n, i, j):
    if listDP[i][j] != list[i][j]:
        return listDP[i][j]
    if i == n:
        listDP[i][j] = list[i][j]
    else:
        x = maxSumDP(list, listDP, n, i + 1, j)
        y = maxSumDP(list, listDP, n, i + 1, j + 1)
        listDP[i][j] = max(x, y) + list[i][j]
    return listDP[i][j]


def pathFind(list):
    return maxSum(list, len(list) - 1, 0, 0)


def pathFindDP(list):
    listDP = copy.deepcopy(list)
    return maxSumDP(list, listDP, len(list) - 1, 0, 0)

# 个人趣味
print('乃木坂', pathFind(lines))
print('乃木坂', pathFindDP(lines))

动态规划,含有重叠子问题和最优子结构的问题可以使用DP算法解决

重叠子问题

即解决方案中的一些子问题被多次重复解决,并需要花费大量时间,则将其第一次计算的结果保存,供下次使用。

最优子结构

即最优解能够通过多个最优子解求出。

上方解决方案采取递归,其实相当于从后往前递推,上方节点每个都选取下方两个节点的极大值(最优子解),又因为上方节点比下方节点少,所以可以求出最大值(最优解),其中,有很多值如果不被记录则需计算多次(重叠子问题),花费的时间复杂度为o(2n),而采用记录的方式,则时间复杂度为O(n 2)

原理如上,而踩的坑主要是这里:

def pathFindDP(list):
    listDP = copy.deepcopy(list)
    return maxSumDP(list, listDP, len(list) - 1, 0, 0)

开始我用的其实是

def pathFindDP(list):
    listDP = list.copy()
    return maxSumDP(list, listDP, len(list) - 1, 0, 0)

后来发现计算出来的值一直很夸张,print了一下listDP和list.copy,发现两个表居然是一样的,立马记起来中了浅复制的坑,去复习了一遍,发现一直用错了copy

解坑知识如下:

Copy复习

深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 

而浅复制分两种情况进行讨论:
1)当浅复制的值是不可变对象(数值,字符串,元组)时和“等于赋值”的情况一样,对象的id值与浅复制原来的值相同。
2)当浅复制的值是可变对象(列表和元组)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的对象中无复杂子对象
原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂子对象 (例如列表中的一个子元素是一个列表)
如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。
但是改变原来的值中的复杂子对象的值则会影响浅复制的值。

对于简单的 object,例如不可变对象(数值,字符串,元组),用 shallow copy 和 deep copy 没区别

复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的子list,并未从原object中真的「独立」出来。
也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy就会跟着一起变。

代码示例:

import copy

test1 = [1, 2, 3, [4]]
test2 = test1
test3 = copy.copy(test1)
test4 = copy.deepcopy(test1)
test1.append([6])
test1[3].append([5])
print('test1:', test1)
print('test2:', test2)
print('test3:', test3)
print('test4:', test4)

运行结果:

test1: [1, 2, 3, [4, [5]], [6]]
test2: [1, 2, 3, [4, [5]], [6]]
test3: [1, 2, 3, [4, [5]]]
test4: [1, 2, 3, [4]]

所以,如果在想要保存原列表的同时复制一个列表来做修改,其中如果列表里有子列表,则必须使用copy包里的deepcopy来复制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值