4.4 分解让复杂问题简单化

参考:

  1. 所有offer题目的LeetCode链接及python实现
  2. github Target offer

面试题26:复杂链表的复制

题目:请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling 指向链表中的任意结点或者NULL。返回结果为复制后复杂链表的head。

思路梳理

方法一:两次遍历

第一次遍历:复制节点与m_pNext;
第二次遍历:寻找m_pSibling的位置并复制;

方法二:一次遍历+哈希表

第一次遍历:复制节点与m_pNext,保存所有的节点值及其位置于哈希表中;
第二次:在哈希表中寻找m_pSibling的位置并赋值;O(1)时间+O(N)空间。

方法三:在原链表的基础上复制,然后拆分链表
  1. 在原链表上复制节点与m_pNext,A’为复制的节点,实线为next指针;
  2. 复制m_pSibling,虚线
  3. 挑出奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表,即为想要的返回结果。
    原始复杂链表
    step1
    step2

step3

代码实现
# -*- 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中左边的二叉搜索树,则输出转换之后的排序双向链表。

27题

思路梳理

树节点的访问顺序类似于中序遍历。

  • 为了把这个复杂的问题分析清楚,我们可以把树分为三个部分:根结点、左子树和右子树,然后把左子树中最大的结点、根结点、右子树中最小的结点链接起来。
  • 至于如何把左子树和右子树内部的结点链接成链表,那和原来的问题的实质是一样的,因此可以递归解决
  • 解决这个问题的关键在于把一个大的问题分解成几个小问题,并递归地解决小问题。

按照左右子树分治递归实现。根的左边连接左子树的最右边结点,右边连接右子树的最左边结点。
分治法

# -*- 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)=2T(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。

思路梳理

  1. 将字符串转换为一个字符列表;
  2. 对所有字符进行排序;
  3. 建立循环遍历每个字母:
    1. 遍历每一个非重复的字符,如果遇到重复字符,则跳过;
    2. 递归调用本方法,对本循环对应字符之前的字符列表排序;并且与本循环字符之后的字符列表连接;
    3. 使用本轮循环的字母作为上一次的结果字符串的首字母。

依次取一个元素,然后依次和之前递归形成的所有子串组合,形成新的字符串。

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

本章小结

解决复杂问题的三种方法:画图、举例子和分解。

把复杂问题分解成若干个小问题,是解决很多复杂问题的有效方法。如果我们遇到的问题很大,可以尝试先把大问题分解成小问题,然后再递归地解决这些小问题。

  • 分治法、动态规划等方法都是应用分解复杂问题的思路。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值