路径总和
题目难度显示是简单,但是这道题并不容易,并且背后可以说道的东西不少。多次代码优化的过程让我收获不少,特此记录。
首先分析我最初的思路,考虑从上往下计算,每个节点保存可能的路径和。如,10这节点可以保存[10],5这个节点保存[10,5],3这个节点保存[3,5,10]。如此类推。代码如下:
class Solution(object):
def pathSum(self, root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype: int
"""
node = []
return self.nodepath(node, root, sum)
def nodepath(self,node,root,sum):
count = 0
if not root:
return 0
for i in range(len(node)):
node[i] += root.val
if sum == node[i]:
count += 1
node.append(root.val)
if root.val == sum:
count += 1
count = count + self.nodepath(node[:], root.right,sum) + self.nodepath(node[:], root.left,sum)
return count
深浅复制傻傻搞不清
首先值得说的就是这个深浅复制问题。浅复制只复制了原来的地址。没有复制内容。之前我们已经遇到过一次这个问题在DP问题初始化DP表的时候。
DP = [[0]*n]*m
就是一种错误的初始化方式。因为每一列都不是独立的,对随之发生变化。
应该是DP = [[0 for i i range(n)]for j in range(m)]
这样才可以保存独立。
这次我们是在递归上遇到了,由于list是可变对象,不同递归出口的path列表会相互影响。也就是说,在完成node.left
的递归后list发生了变化,导致node.right
开始运算时用的是上一次递归结束的list。
解决这一问题的方式是传进子递归函数的数组写为p[:]
这样可以解决
对于深浅复制有以下实验:
>>> import copy
>>> a = [[10], 20]
>>> b = a[:]
>>> c = list(a)
>>> d = a * 1
>>> e = copy.copy(a)
>>> f = copy.deepcopy(a)
>>> a.append(21)
>>> a[0].append(11)
>>> print id(a), a
30553152 [[10, 11], 20, 21]
>>> print id(b), b
44969816 [[10, 11], 20]
>>> print id(c), c
44855664 [[10, 11], 20]
>>> print id(d), d
44971832 [[10, 11], 20]
>>> print id(e), e
44833088 [[10, 11], 20]
>>> print id(f), f
44834648 [[10], 20]
从以上可以看出,使用 a[:], list(a), a*1, copy.copy(a)
四种方式复制列表结果都可以得到一个新的列表,但是如果列表中含有列表,所有b, c, d, e四个新列表的子列表都是指引到同一个对象上。
浅拷贝:只是复制了父元素,对于子元素还是收到原来列表的影响
只有使用copy.deepcopy(a)
方法得到的新列表f才是包括子列表在内的完全复制。
hashmap的运用(dic)
可以看到上一版代码的主要循环在每次生成了一个节点的所有可能,并查询是否存在sum。如果用hash表就可以消除这个操作。因此从别人那里学习到的代码如下:
class Solution(object):
def pathSum(self, root, sum):
dic = {0:1}
self.res = 0
cur = 0
self.helper(root,sum,cur,dic)
return self.res
def helper(self,root,sum,cur,dic):
if not root:
return
cur += root.val
self.res += dic.get(cur-sum,0)
dic[cur] = dic.get(cur,0)+1
self.helper(root.left,sum,cur,dic)
self.helper(root.right,sum,cur,dic)
dic[cur] -= 1
这代码妙啊,一下子看出了和自己的差距。我一点一点分析可以学到的东西。
- 全局变量
可以看到代码里定义了一个self.res
,这变量是可以在这个类下面使用的,在每次迭代中保持统一。 - 字典的示用
首先是字典的定义dic = {}
,赋值方法可以dic[x] = y
在访问字典的值时候,最好是用dic.get(key, x)
,其中x是当这个键不存在在字典中,返回的数值。 dic[cur] -= 1
为了防止在这一次递归完成(达到叶节点)后,后面在另外一枝上循环是使用到。测试的反例为[1,-2,-3] 1
关于字典的使用我还会慢慢的进一步补充。