剑指offer 4 解决面试题的思路
参考:
4.2 画图让抽象问题形象化
面试题19:二叉树的镜像
题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
- 先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子结点。
- 当交换完所有非叶子结点的左右子结点之后,就得到了树的镜像。
# 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 invertTree(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
### 方法一:递归,最快
# if not root:
# return root
# root.left, root.right = root.right, root.left
# if root.left:
# self.invertTree(root.left)
# if root.right:
# self.invertTree(root.right)
# return root
### 方法二:简易递归
# if root:
# root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
# return root
### 方法三:使用stack实现, 最慢
stack = [root]
while stack:
r = stack.pop()
if r:
r.left, r.right = r.right, r.left
stack.extend([r.left, r.right])
return root
面试题20:顺时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
思路梳理
- 从外圈到内圈的顺序依次打印;
- 打印每一圈时,首先需要判断每一步开始坐标点是否满足小于行数的一半且小于列数的一半,
- 在矩阵中选取左上角为(start, start)的一圈作为我们分析的目标。
- 让循环继续的条件是
columns>startX×2 && rows>startY×2
。
- 考虑如何打印一圈:
- 把打印一圈分为四步:第一步从左到右打印一行,第二步从上到下打印一列,第三步从右到左打印一行,第四步从下到上打印一列。
- 这四步的是否需要进行,取决于(start, start)与(endX,endY)的相对位置,判断后再执行。
- 在最后一圈中(如下图所示),可能出现仅能向右走一行,仅能向右走一行向下走一列,向右走一行向下走一列向左走一行,能走完整一圈,一共四种情况。其中只有能向右走一行必然发生,不必判断,剩余的都需要判断发生条件(根据本圈左上角起始坐标点与右下角终止坐标点的相对位置来判断)。
# -*- coding:utf-8 -*-
class Solution:
def printMatrixInCircle(self, matrix, col, row, start):
endX = col-1-start
endY = row-1-start
# 从左到右打印一行
for i in range(start, endX+1):
print(matrix[start][i])
# 从上到下打印一行
if start < endY:
for j in range(start+1, endY+1):
print(matrix[j][endX])
# 从右到左打印一行
if start < endX and start < endY:
for k in range(endX-1, start-1, -1):
print(matrix[endY][k])
# 从下到上打印一行
if start < endX and start < endY-1:
for l in range(endY-1, start, -1):
print(matrix[l][start])
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
# 矩阵为空
if matrix == None:
return
row = len(matrix)
col = len(matrix[0])
start = 0
while (row > start * 2) & (col > start * 2):
self.printMatrixInCircle(matrix, col, row, start)
start += 1
4.3 举例让抽象问题具体化
面试题21:包含min函数的栈
题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。
思路梳理
如果设置一个数值型变量保存最小值,则会出现问题:当该最小值出栈后,无法找到栈中剩余元素的次小元素;
- 所以考虑构建一个辅助栈,依次将当前最小元素压入,最终最小元素会位于辅助栈中的栈顶,其余次小元素依次位于其他位置。
注意:
# 必须加上“=”号,不然如果栈中压入过两次最小元素,弹出其中一个会导致辅助栈中的最小元素被弹出
elif x <= self.minS[-1]:
class MinStack(object):
def __init__(self):
"""
initialize your data structure here.
"""
self.val = []
self.minS = []
def push(self, x):
"""
:type x: int
:rtype: None
"""
self.val.append(x)
if not len(self.minS):
self.minS.append(x)
# 必须加上“=”号,不然如果栈中压入过两次最小元素,弹出其中一个会导致辅助栈中的最小元素被弹出
elif x <= self.minS[-1]:
self.minS.append(x)
def pop(self):
"""
:rtype: None
"""
if len(self.minS) and self.val[-1] == self.minS[-1]:
self.minS.pop()
return self.val.pop()
def top(self):
"""
:rtype: int
"""
return self.val[-1]
def getMin(self):
"""
:rtype: int
"""
return self.minS[-1]
面试题22:栈的压入、弹出序列
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1、2、3、4、5 是某栈的压栈序列,序列 4、5、3、2、1 是该压栈序列对应的一个弹出序列,但 4、3、5、1、2 就不可能是该压栈序列的弹出序列。
思路梳理
- 建立一个辅助栈,把push序列的数字依次压入辅助栈;
- 每次压入后,比较辅助栈的栈顶元素和pop序列的首元素是否相等,
- 相等的话就推出pop序列的首元素和辅助栈的栈顶元素;
- 若最后** pop序列为空**,则push序列可以对应于pop序列,返回True;否则,返回False。
class Solution(object):
def validateStackSequences(self, pushed, popped):
"""
:type pushed: List[int]
:type popped: List[int]
:rtype: bool
"""
stack = []
for push in pushed:
stack.append(push)
while stack and popped and stack[-1] == popped[0]:
stack.pop()
del popped[0]
return popped==[]
面试题23:从上往下打印二叉树
题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。例如输入图4.5中的二叉树,则依次打印出8、6、10、5、7、9、11。
思路梳理
按层遍历树的节点,本质上来说就是广度优先遍历二叉树,由根->儿子->孙子的顺序进行访问,符合“先入先出”的原则,所以采用队列的数据结构进行存储。
不管是广度优先遍历一个有向图还是一棵树,都要用到队列。
- 第一步我们把起始结点(对树而言是根结点)放入队列中。
- 接下来每一次从队列的头部取出一个结点,遍历这个结点之后把从它能到达的结点(对树而言是子结点)都依次放入队列。
- 重复这个遍历过程,直到队列中的结点全部被遍历为止。
class Solution:
# 返回从上到下每个节点值列表,例:[1,2,3]
def PrintFromTopToBottom(self, root):
# write code here
if not root:
return []
queue = [root]
res = [root.val]
while queue:
r = queue.pop(0)
if r.left:
queue.append(r.left)
res.extend([r.left.val])
if r.right:
queue.append(r.right)
res.extend([r.right.val])
return res
或者
class Solution:
# 返回从上到下每个节点值列表,例:[1,2,3]
def PrintFromTopToBottom(self, root):
# write code here
if not root:
return []
queue = [root]
res = []
while queue:
r = queue.pop(0)
res.extend([r.val])
if r.left:
queue.append(r.left)
if r.right:
queue.append(r.right)
return res
面试题24:二叉搜索树的后序遍历序列
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。
思路梳理
后序遍历数组中前面的数字可以分为两部分:
- 第一部分是左子树结点的值,它们都比根结点的值小;
- 第二部分是右子树结点的值,它们都比根结点的值大。
- 先找到根、左子树、右子树的节点列表;
- 根据规则得到了左子树,所以无需判断左边是否合理;
- 判断右边元素是否合法;
- 递归调用方法,判断左右子树是否合法。
- 返回左右子树结果与操作。
class Solution:
def VerifySquenceOfBST(self, sequence):
# 判断特殊无效输入
if not sequence:
return False
left = right = []
root = sequence[-1]
index = 0
for i in range(len(sequence)-1):
index = i
if sequence[i] > root:
break
res = True
right = sequence[index:-1]
if right:
for i in range(index+1, len(sequence)-1):
if sequence[i] < root:
return False
ri = True
if index < len(sequence)-1:
ri = self.VerifySquenceOfBST(sequence[index:-1])
le = True
if index:
le = self.VerifySquenceOfBST(sequence[:index])
return ri and le
面试题25:二叉树中和为某一值的路径
题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
思路梳理
递归函数:
- 将 距离目标值还需要的值 保存为参数,传递给子方法。
- 总体思路为深度优先遍历的递归。
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
ret = []
def dfs(root,sum_,tmp):
if root:
if root.left==None and root.right == None:
if root.val == sum_:
tmp.append(root.val)
ret.append(tmp[:])
else:
tmp.append(root.val)
dfs(root.left,sum_-root.val,tmp[:])
dfs(root.right,sum_-root.val,tmp[:])
dfs(root,expectNumber,[])
return ret
方法二:
当用前序遍历的方式访问到某一结点时,我们把该结点添加到路径上,并累加该结点的值。
- 如果该结点为叶结点并且路径中结点值的和刚好等于输入的整数,则当前的路径符合要求,我们把它打印出来。
- 如果当前结点不是叶结点,则继续访问它的子结点。
- 当前结点访问结束后,递归函数将自动回到它的父结点。
因此我们在函数退出之前,要在路径上删除当前结点并减去当前结点的值,以确保返回父结点时路径刚好是从根结点到父结点的路径。
- 我们不难看出保存路径的数据结构实际上是一个栈,因为路径要与递归调用状态一致,而递归调用的本质就是一个压栈和出栈的过程。
注意:需要设置一个变量 pre ,记录前一个访问的节点,防止重复访问右节点。
见LeetCode113:
# 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 pathSum(self, root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype: List[List[int]]
"""
if root is None:
return
res = []
stack = []
sum1 = 0
node = root
pre = TreeNode(0)
while node or stack:
while node:
stack.append(node)
sum1 += node.val
node = node.left
node = stack[-1]
# 是否符合返回值的条件
if not node.left and not node.right and sum==sum1:
res.append([i.val for i in stack])
# 如果有右节点
if node.right and node.right!=pre:
node = node.right
# 更新pre与其他变量
else:
pre = node
sum1 -= node.val
stack.pop()
node = None
return res