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来复制。