*内容来自leetcode
设计问题*2
1.二叉树的序列化与反序列化
题目要求
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
思路
最开始的时候,考虑到不能通过任意一种遍历方式来得到原本的二叉树,因此考虑需要在序列化的时候同时得到两种遍历方式的结果,好像很麻烦。
但是一看到例子给出的输入输出,发现序列化和遍历并非一回事,给出的序列中含有null值,那也可以在树的序列中加入null值,这样就相当于在遍历的基础上增加了额外的信息,就可以反序列来得到树的结构。(层序遍历实际就是广度优先搜素)
在序列化时考虑的是基于层序遍历来实现,而反序列化就是根据序列化得到字符串来依次构建。
很坑爹的一点是,按照上述思路实现后,提示返回值不符合要求,在本地检查了后发现反序列化的返回值又是满足要求的。后来发现是在进行序列化的时候,要求最后得到的是字符串,而在一开始时没有注意这个,一直返回的是列表。这样虽然在后续进行反序列化时也能正常完成,但是不能通过leetcode。考虑可能是会检测序列化的数据类型。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode <-- 输入的root是树格式
:rtype: str
由任意的一种遍历方式,无法确定一个树
"""
if not root:
return str('')
stk = [root]
data = [root.val]
while stk:
root = stk.pop(0)
if root.left:
stk.append(root.left)
data.append(root.left.val)
else:
data.append(None)
if root.right:
stk.append(root.right)
data.append(root.right.val)
else:
data.append(None)
#删除后面多余的None
while data[-1] == None:
data.pop()
return str(data)
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data == '':
return []
data_list = eval(data)
root = TreeNode(data_list.pop(0))
build = root
stk = []
while data_list:
if data_list[0] != None:
build.left = TreeNode(data_list.pop(0))
else:
data_list.pop(0)
if not data_list:
break
if data_list[0] != None:
build.right = TreeNode(data_list.pop(0))
else:
data_list.pop(0)
if build.left:
stk.append(build.left)
if build.right:
stk.append(build.right)
build = stk.pop(0)
return root
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
官方提到的解法有深度优先搜索和括号表示编码 + 递归下降解码
括号表示编码+递归下降解码(用newbing对C++的代码用python进行重写后修改得到)
newbing写的代码输出不对,因为原来的C++代码中传递的是ptr地址,在函数内对ptr进行修改后,在函数外部也ptr也会随着修改。而在python中,函数只能传递ptr的值,在函数内进行修改并不会影响函数外的值,所以newbing改的代码无法成功对序列进行遍历。做出的修改是将ptr声明为类中的一个全局变量。(此题两种解法在本地留有备份)
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
ptr = 0
def serialize(self, root):
if not root:
return "X"
left = "(" + self.serialize(root.left) + ")"
right = "(" + self.serialize(root.right) + ")"
print(left + str(root.val) + right)
return left + str(root.val) + right
def parseSubtree(self, data):
#print("ptr:",self.ptr)
self.ptr += 1 # skip left parenthesis
subtree = self.parse(data)
self.ptr += 1 # skip right parenthesis
return subtree
def parseInt(self, data):
x = 0
sgn = 1
if not data[self.ptr].isdigit():
sgn = -1
self.ptr += 1
while data[self.ptr].isdigit():
x = x * 10 + ord(data[self.ptr]) - ord('0')
self.ptr += 1
return x * sgn
def parse(self, data):
if data[self.ptr] == 'X':
self.ptr += 1
return None
cur = TreeNode(0)
cur.left = self.parseSubtree(data)
cur.val = self.parseInt(data)
cur.right = self.parseSubtree(data)
return cur
def deserialize(self, data: str) -> TreeNode:
#ptr = 0
return self.parse(data)
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
2.常数时间插入、删除和获取随机元素
题目要求
实现RandomizedSet 类:
RandomizedSet() 初始化 RandomizedSet 对象
bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。
示例:
输入
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
输出
[null, true, false, true, 2, true, false, 2]
思路
因为python中自带集合,所以只需要维护一个集合即可,比较简单。不过出现的问题在于在随机返回函数时,使用集合的pop()函数无法通过。随的不是很机。
class RandomizedSet:
def __init__(self):
self.newSet = set()
def insert(self, val: int) -> bool:
if val not in self.newSet:
self.newSet.add(val)
return True
else:
return False
def remove(self, val: int) -> bool:
if val in self.newSet:
self.newSet.remove(val)
return True
else:
return False
def getRandom(self) -> int:
'''
randNum = self.newSet.pop()
self.newSet.add(randNum)
'''
randNum = random.choice(list(self.newSet))
return randNum
深究一下set.pop()的随机性:
在本地进行测试发现,当set中存在3种元素时,循环n次,无论运行多少次,三种元素取的次数都是一样的,相较于random.choice得到的,不具有随机性。
import random
from tkinter import N
newSet = {1,2,3}
a1,a2 = 0, 0
b1,b2 = 0, 0
c1,c2 = 0, 0
for i in range(10):
num = newSet.pop()
if num == 1:
a1 += 1
if num == 2:
b1 += 1
if num == 3:
c1 += 1
newSet.add(num)
print(a1,b1,c1)
for i in range(10):
num = random.choice(list(newSet))
if num == 1:
a2 += 1
if num == 2:
b2 += 1
if num == 3:
c2 += 1
print(a2,b2,c2)
按理来说本题应该是需要自己构建数据结构来实现的,而不是用现成的集合set()
class RandomizedSet:
#hashmap 和 变长数组
def __init__(self):
self.store = []
self.verify = {}
def insert(self, val: int) -> bool:
if val in self.verify:
return False
self.store.append(val)
self.verify[val] = len(self.store) - 1
return True
def remove(self, val: int) -> bool:
if val not in self.verify:
return False
index = self.verify[val]
self.store[index] = self.store[-1]
self.verify[self.store[-1]] = index
del self.verify[val]
self.store.pop()
return True
def getRandom(self) -> int:
return random.choice(self.store)