Leetcode Contest 25
昨天晚上有红烧牛排骨,白灼罗非鱼,烤翅和我喜欢的草莓蛋糕。
这次比赛我表现比较弱,只做出了前三道,而且第三道还WA了5次。最后一题是很有趣的DP,可惜比赛的时候做不出来。
第一题Perfect Number
思路:关键的观察是正因子是成对出现的,例如d是num的一个因子,那么num/d也是num的一个因子,所以我们只要搜索到\(\sqrt{num}\)就行。
class Solution(object):
def checkPerfectNumber(self, num):
"""
:type num: int
:rtype: bool
"""
if num <= 1:
return False
s = 1
for i in xrange(2, int(num**(0.5))+1):
if num % i == 0:
s += i
if i != num/i:
s += num/i
return s == num
第二题Complex Number Multiplication
简单的字符串处理,就不多说了。
class Solution(object):
def complexNumberMultiply(self, a, b):
"""
:type a: str
:type b: str
:rtype: str
"""
def getri(s):
p = s.find('+')
r = int(s[:p])
i = int(s[p+1:-1])
return r, i
ra, ia = getri(a)
rb, ib = getri(b)
r = ra*rb - ia*ib
i = ra*ib + ia*rb
return '{}+{}i'.format(r, i)
第三题Boundary of Binary Tree
我这题的思路是这样的:先求左右边界和所有叶子,然后按照左边界,叶子,右边界(逆序)的顺序将边界输出。WA了5次的原因大概是一开始的时候我没有正确求得左右边界,或者说是没有意识到根节点的特殊性。
左边界:从根节点的左子树p开始(不能从根节点开始!!!)。如果p不为空,那么将其加进左边界,并且如果p的左子树非空,将p更新为p的左子树,否则将p更新为p的右子树,直到p为空停止。
右边界:根左边界求法一样,只是在更新p时要先考虑右子树。
叶子:这个比较简单,用DFS。对于当前节点root,如果root有子树,那么它不可能是叶子,递归先后考虑它的左右子树。相反地,如果root没有子树,那么root就是叶子了。
在输出边界的时候要注意判重。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def boundaryOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
p = root.left
left = []
while p:
left.append(p)
p = p.left if p.left else p.right
p = root.right
right = []
while p:
right.append(p)
p = p.right if p.right else p.left
def dfs(root, lst):
if not root:
return
if root.left or root.right:
dfs(root.left, lst)
dfs(root.right, lst)
else:
lst.append(root)
leaves = []
dfs(root, leaves)
ans = [root.val]
visited = set([root])
for p in left:
if p not in visited:
ans.append(p.val)
visited.add(p)
for p in leaves:
if p not in visited:
ans.append(p.val)
visited.add(p)
for p in reversed(right):
if p not in visited:
ans.append(p.val)
visited.add(p)
return ans
第四题Remove Boxes
思路:动态规划,我是用记忆化搜索实现的。dp(l, r)表示消除位置l到r之间所有元素的最大得分。边界条件有两种情况,一是l>r,返回0;二是l=r,返回1。
我们考虑如何消除位置l上的元素。首先扫描l到r之间的元素,标记所有跟l位置一样的元素。现在消除位置l大致上分成两种方法,一是单独消除位置l的元素,得分为1+dp(l+1, r);二是选某些之前标记过的元素与位置l一起消除,这种情况的最大得分需要另外一个动态规划算法来快速求出。这就是这题有趣的地方!
假设标记出来跟位置l一样的元素位置组成的位置数组为[j0=l, j1, ..., jn]。如果我们让f(j, i)表示使得j位置成为跟l位置一起消除时变成第i个的最大得分。那么转移方程是:
具体实现看下面代码。下面的实现还有个简单的优化,没有这个优化会TLE。
class Solution(object):
def removeBoxes(self, boxes):
"""
:type boxes: List[int]
:rtype: int
"""
memo = {}
def dp(l, r):
if l > r:
return 0
if l == r:
return 1
if (l, r) in memo:
return memo[l, r]
k = boxes[l]
lp = []
for i in xrange(l, r+1):
if k == boxes[i]:
lp.append(i)
n = len(lp)
if n == r-l+1:
memo[l, r] = ans = n * n
return ans
ans = 1 + dp(l+1, r)
tmp = [[0]*n for _ in xrange(n)]
for i in xrange(1, n):
for j in xrange(i, n):
for k in xrange(i-1, j):
if k+1 < j and lp[k]+1 == lp[k+1]:
continue
tmp[j][i] = max(tmp[j][i], tmp[k][i-1]+dp(lp[k]+1, lp[j]-1))
ans = max(ans, tmp[j][i]+(i+1)*(i+1)+dp(lp[j]+1,r))
memo[l, r] = ans
return ans
return dp(0, len(boxes)-1)