4.4 分解让复杂问题简单化
参考:
面试题26:复杂链表的复制
题目:请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling 指向链表中的任意结点或者NULL。返回结果为复制后复杂链表的head。
思路梳理
方法一:两次遍历
第一次遍历:复制节点与m_pNext;
第二次遍历:寻找m_pSibling的位置并复制;
方法二:一次遍历+哈希表
第一次遍历:复制节点与m_pNext,保存所有的节点值及其位置于哈希表中;
第二次:在哈希表中寻找m_pSibling的位置并赋值;O(1)时间+O(N)空间。
方法三:在原链表的基础上复制,然后拆分链表
- 在原链表上复制节点与m_pNext,A’为复制的节点,实线为next指针;
- 复制m_pSibling,虚线;
- 挑出奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表,即为想要的返回结果。
代码实现
# -*- coding:utf-8 -*-
class RandomListNode:
def __init__(self, x):
self.label = x
self.next = None
self.random = None
class Solution:
def CloneNodes(self, pHead):
pPre = pHead
while pPre:
pNode = RandomListNode(pPre.label)
pNode.next = pPre.next
pPre.next = pNode
pPre = pNode.next
def ConnectSibling(self, pHead):
pCur = pHead
while pCur:
pCloned = pCur.next
# 必须要先判断原链表中,该元素是否有random指针
if pCur.random:
pCloned.random = pCur.random.next
pCur = pCloned.next
# 取出奇数位上的节点作为返回结果
def ReconnectNodes(self, pHead):
pCur = pHead
pClonedHead = pCloned = pCur.next
pCur.next = pCloned.next
pCur = pCur.next
while pCur:
pCloned.next = pCur.next
pCloned = pCloned.next
pCur.next = pCloned.next
pCur = pCur.next
return None or pClonedHead
# 返回 RandomListNode
def Clone(self, pHead):
if not pHead:
return None
self.CloneNodes(pHead)
self.ConnectSibling(pHead)
return self.ReconnectNodes(pHead)
面试题27:二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个有序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。比如输入图4.12中左边的二叉搜索树,则输出转换之后的排序双向链表。
思路梳理
树节点的访问顺序类似于中序遍历。
- 为了把这个复杂的问题分析清楚,我们可以把树分为三个部分:根结点、左子树和右子树,然后把左子树中最大的结点、根结点、右子树中最小的结点链接起来。
- 至于如何把左子树和右子树内部的结点链接成链表,那和原来的问题的实质是一样的,因此可以递归解决。
- 解决这个问题的关键在于把一个大的问题分解成几个小问题,并递归地解决小问题。
按照左右子树分治,递归实现。根的左边连接左子树的最右边结点,右边连接右子树的最左边结点。
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def Convert(self, root):
# 无效输入
if root==None:
return None
# 如果是叶子节点
if not root.left and not root.right:
return root
# 递归处理左子树
self.Convert(root.left)
left = root.left
# 如果左子树存在,则找到左子树最右边的叶子节点并更新指针
if left:
while left.right:
left = left.right
root.left, left.right = left, root
# 递归处理右子树
self.Convert(root.right)
right = root.right
# 如果右子树存在,找到右子树最左边的叶子节点并更新指针
if right:
while right.left:
right = right.left
root.right, right.left = right, root
# 找到整棵树最左边的叶子节点,作为遍历结果的头结点
head = root
while head.left:
head = head.left
return head
回顾中序遍历
递归版本
-
Time complexity : O(n). The time complexity is O(n) because the recursive function is T ( n ) = 2 ⋅ T ( n / 2 ) + 1 T(n) = 2 \cdot T(n/2)+1 T(n)=2⋅T(n/2)+1.
-
Space complexity : The worst case space required is O(n), and in the average case it’s O ( log n ) O(\log n) O(logn) where n is number of nodes.
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
res = []
if not root:
return res
res.extend(self.inorderTraversal(root.left))
res.append(root.val)
res.extend(self.inorderTraversal(root.right))
return res
非递归版本:
采用栈的数据结构,存储每一层遍历到的根节点。从而实现整个代码。
- Time complexity : O(n).
- Space complexity : O(n).
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
res, stack = [], []
while True:
while root:
stack.append(root)
root = root.left
if not stack:
return res
node = stack.pop()
res.append(node.val)
# 下一次循环的根是当前弹出节点的右儿子
root = node.right
面试题28:字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
思路梳理
- 将字符串转换为一个字符列表;
- 对所有字符进行排序;
- 建立循环遍历每个字母:
- 遍历每一个非重复的字符,如果遇到重复字符,则跳过;
- 递归调用本方法,对本循环对应字符之前的字符列表排序;并且与本循环字符之后的字符列表连接;
- 使用本轮循环的字母作为上一次的结果字符串的首字母。
依次取一个元素,然后依次和之前递归形成的所有子串组合,形成新的字符串。
class Solution:
def Permutation(self, ss):
# 处理特殊情况
if not len(ss):
return []
if len(ss)==1:
return list(ss)
# 处理原始字符串
s_list = list(ss)
s_list.sort()
length = len(s_list)
ans = []
for i in range(length):
# 跳过重复字符
if i>0 and s_list[i] == s_list[i-1]:
continue
# 对剩余字符排序
tmp = self.Permutation(''.join(s_list[:i])+''.join(s_list[i+1:]))
# 添加首字母
for t in tmp:
ans.append(s_list[i] + t)
return ans
本题扩展:
如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢?还是输入三个字符a、b、c,则它们的组合有a、b、c、ab、ac、bc、abc。当交换字符串中的两个字符时,虽然能得到两个不同的排列,但却是同一个组合。比如ab和ba是不同的排列,但只算一个组合。
依次取一个元素,然后依次和之前递归形成的所有子串组合,形成新的字符串。
class Solution:
def group(self, ss):
# 处理特殊情况
if not len(ss):
return []
if len(ss)==1:
return list(ss)
# 处理原始字符串
s_list = list(ss)
s_list.sort()
length = len(s_list)
ans = []
for i in range(length):
ans.append(s_list[i])
# 跳过重复字符
if i>0 and s_list[i] == s_list[i-1]:
continue
# 对剩余字符排序
tmp = self.group(''.join(s_list[i+1:]))
# 添加首字母
for t in tmp:
ans.append(s_list[i] + t)
ans = list(set(ans))
ans.sort()
return ans
本章小结
解决复杂问题的三种方法:画图、举例子和分解。
把复杂问题分解成若干个小问题,是解决很多复杂问题的有效方法。如果我们遇到的问题很大,可以尝试先把大问题分解成小问题,然后再递归地解决这些小问题。
- 分治法、动态规划等方法都是应用分解复杂问题的思路。