剑指offer 第7章 两个面试案例
参考:
- 从基本功能、边界条件和错误处理等方面确保代码的完整性和鲁棒性;
- 把大的问题分解成两个或者多个小问题再递归地解决小问题;
- 从时间复杂度和空间复杂度两个方面选择最优的解法。
面试题49 把字符串转换成整数
思路梳理
- 空字符串""和只有一个正号或者负号的情况
- 正整数的最大值是0x7FFF FFFF(1+31个1),表示:2**31-1
- 最小的负整数是0x8000 0000(1+31个0),表示:-2**31
- 分两种情况来分别判断整数是否发生上溢出或者下溢出。
class Solution(object):
def myAtoi(self, str):
INT_MIN = 2**31
num = ''
# 记录索引
st = 0
# 去掉空格,找到起始位置
str = str.strip()
# 判断正负
if len(str)==0:
return 0
sign = -1 if str[st] == '-' else 1
if str[0] in ['-','+']:
st+=1
# 找出数字
for c in str[st:]:
if c.isdigit():
num += c
elif not c.isdigit():
return 0
num = len(num)>0 and int(num)
ans = num*sign
#判断是否超过正数上界时,采用min;判断超过负数下界时,采用max
return max(-2**31,min(ans,2**31-1))
面试题50:树中两个节点的最低公共祖先
- 如果树中有指向父节点的指针,则该问题等价于寻找两个链表中第一个公共节点;
- 如果没有指向父节点的指针,则见思路梳理。
思路梳理
迭代版本1
- 使用stack保存树的遍历,使用parent字典保存树中所有节点(p 与 q的祖先们,或者直到找到p与q的所有祖先为止)的父节点;
- 从p节点开始,一直遍历p的祖先,并保存为一个集合 set();
- 从q节点开始,一直遍历q的祖先,直到找到第一个在set 中的节点,即为第一个公共祖先。
class Solution:
def lowestCommonAncestor(self, root, p, q):
# 保存树的遍历
stack = [root]
# 保存路径,目的:保存p与q 的祖先
parent = {root:None}
while p not in parent or q not in parent:
node = stack.pop()
if node.left:
stack.append(node.left)
parent[node.left] = node
if node.right:
stack.append(node.right)
parent[node.right] = node
# 找出q的祖先中,第一个出现在p的祖先中的节点。
ancesters = set()
while p in parent:
ancesters.add(p)
p = parent[p]
while q not in ancesters:
q = parent[q]
return q
递归版本
-
从根节点开始遍历树。
-
如果当前节点本身是p或q中的一个,我们将变量mid标记为True并继续搜索左右分支中的另一个节点。
-
如果左侧或右侧分支中的任何一个返回True,则表示下面找到了两个节点中的一个。
-
如果在遍历中的任何一点,左,右或中三个标志中的任何两个变为真,这意味着我们找到了节点p和q的最低共同祖先。
-
Complexity Analysis
-
Time Complexity: O(N), where N is the number of nodes in the binary tree. In the worst case we might be visiting all the nodes of the binary tree.
-
Space Complexity: O(N) This is because the maximum amount of space utilized by the recursion stack would be N since the height of a skewed binary tree could be N.
# 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 lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
# 查看当前节点的子树中是否同时包含两个节点,p和q
# 使用布尔变量记录遍历结果
def recurse_tree(current_node):
# 如果到达叶子节点末端
if not current_node:
return False
# 左递归
left = recurse_tree(curent_node.left)
# 右递归
right = recurse_tree(curent_node.right)
mid = True if current_node in (p, q) else False
if mid + left + right >= 2:
self.ans = current_node
# 如果三者之一是True,则返回True
return mid or right or left
recurse_tree(root)
return self.ans
迭代版本2—— 结果错误
- 先从树的根节点开始,找到根节点到结点m和结点n的路径,这时候我们就有两个List或者两个链表,
- 然后就像寻找两个链表的公共结点一样,从后往前遍历两个List找到最靠后的第一个公共结点即可。
- 需要遍历两次树,每遍历一次的时间复杂度是O(n)。
- 得到的两条路径的长度在最差情况时是O(n),通常情况下两条路径的长度是O(logn),即 树的高度。
# 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 preOrder(self, root, obj):
stack = []
path1 = []
while root or stack:
while root:
stack.append(root)
path1.append(root)
if root == obj:
return path1
root = root.left
root = stack.pop()
root = root.right
return path1 if path1[-1] == obj else []
def lowestCommonAncestor(self, root, A, B):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if root is None or A is None or B is None:
return None
pathA = self.preOrder(root, A)
pathB = self.preOrder(root, B)
lenA = len(pathA)
lenB = len(pathB)
minlen = min(lenA, lenB)
res = -1
for i in range(-1, -minlen-1, -1):
if pathA[i] == pathB[i]:
res = pathA[i]
else:
return res
return res