 剑指Offer(3)
剑指Offer(3)
  更新:此题解 markdown 源文件已放到 Github https://github.com/bytetopia/offer-coding-interviews-python (opens new window),欢迎前去给个小星星鸭!
知识点:二叉树、栈和队列、查找
# 二叉树
# 知识点
- 树的概念:分支节点、叶节点、度、深度……
- 二叉树的定义,满二叉树,完全二叉树
- 二叉树的五条性质
- 二叉树的存储方式(二叉链表广泛采用)
- 二叉树的遍历(前序、中序、后序)(递归、非递归),以及层次遍历(采用队列)
# 题目:重建二叉树
# 要求
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
# 思路
前序序列能确定根节点,用根节点,把中序划分为左右子树 基于递归的思路,重复上述步骤
# 代码
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回构造的TreeNode根节点
    def reConstructBinaryTree(self, pre, tin):
        # 给定前序和中序,重建二叉树
        # 思路:前序结果可以确定树根,通过树根可以将中序切成左右两半
        if(len(pre)==0):
            return None
        root = TreeNode(pre[0])
        tin_root_index = tin.index(root.val)
        if tin_root_index > 0:  # 有左子树
            new_tin = tin[0: tin_root_index]
            new_pre = pre[1: len(new_tin)+1]
            root.left = self.reConstructBinaryTree(new_pre, new_tin)
        if tin_root_index != len(tin) - 1:  # 有右子树
            new_tin = tin[tin_root_index+1:]
            new_pre = pre[len(pre)-len(new_tin):]
            root.right = self.reConstructBinaryTree(new_pre, new_tin)
        return root
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这个题主要是清楚前序和中序的特点,理清思路以后,算准确切分的下标,递归实现就行。 下标可以写的更简洁一点:
        index = tin.index(root.val)
        if index > 0:  # 有左子树
            root.left = self.reConstructBinaryTree(pre[1: index+1], tin[0: index])
        if index != len(tin) - 1:  # 有右子树
            root.right = self.reConstructBinaryTree(pre[index+1: ], tin[index+1: ])
        return root
2
3
4
5
6
# 栈和队列
# 题目:用两个栈模拟队列
# 要求
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
# 思路
一个栈处理push,新节点放入该栈,一个栈处理pop,初始为空 当需要pop时,如果pop栈不空,则直接出栈,如果空,则从push栈弹出全部内容放到pop栈,pop栈出栈一个
# 代码
# -*- coding:utf-8 -*-
class Solution:
    # 思路:
    # 一个栈处理push,新节点放入该栈,一个栈处理pop,初始为空
    # 当需要pop时,如果pop栈不空,则直接出栈,如果空,则从push栈弹出全部内容放到pop栈,pop栈出栈一个
    
    def __init__(self):
        self.push_s = []
        self.pop_s = []
    
    def push(self, node):
        # write code here
        self.push_s.append(node)
     
    def pop(self):
        # return xx
        if len(self.pop_s) == 0:
            if len(self.push_s) == 0:
                return None
            for i in range(0, len(self.push_s)):
                self.pop_s.append(self.push_s.pop(-1))
        return self.pop_s.pop(-1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
思路很简单,主要是实现的时候,下标不要写越界。
今天看到一个题,说用两个栈(长度分别为m、n,m大于n)来模拟队列,最大容量是多少。答案是 2n+1, 可以参考 https://www.cnblogs.com/eniac12/p/4865158.html
# 扩展
如果用两个队列来模拟栈呢?
做法是用两个队列交替使用,一个存数字一个始终为空。如果入栈则存数字的队列继续往后入队;如果要出栈的话,则在有数字的队列进行一次倒腾,依次出队并写入原本为空的队列,只留下最后一个元素弹出。两个队列交替着用。
# 查找
# 题目:旋转数组的最小数字
# 要求
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
# 思路
简单直接的思路:从前往后扫,找到第一个比上一个元素小的元素,就说明是最小元素了。 【注意特例】整个数组未旋转
# 代码
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # 思路
        # 数组应该为非递减,找到第一个比上一个元素小的位置,就是最小值
        if len(rotateArray) == 0:
            return 0
        for i in range(len(rotateArray)-1):
            if rotateArray[i+1] < rotateArray[i]:
                return rotateArray[i+1]
        # 都没找到
        return rotateArray[0]
2
3
4
5
6
7
8
9
10
11
感觉写完一遍跑过美滋滋,然而这种方法的时间复杂度为O(n)。
鉴于旋转数组前后两半都是有序的,可以采用类似二分查找的方法,减少比较次数。
# 思路2
如果用二分的话,整个思路会比较复杂一些,核心是通过比较 mid 位置和 low/high 位置的元素大小,判断这个旋转点位于前半段还是后半段,然后调整 low或者high,缩小范围,直到最终 low和high位置重合,找到最小点。

具体思路需要理一下,可以参考这两篇文章详细
https://blog.nowcoder.net/n/dcb0f2e6ffd44e1895b7a5297e362778?f=comment (opens new window)
和 https://blog.nowcoder.net/n/1e5cd58cbd0c4c3b855b635c1230469c?f=comment (opens new window)
另外尤其需要注意的一点是 ,题目中只说非递减,但没有说递增,所以是可能会存在连续几个相等的数字的。

# 代码2
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if rotateArray is None or len(rotateArray) == 0:
            return 0
        low = 0
        high = len(rotateArray) - 1
        arr = rotateArray
        while(low < high):
            if arr[low] < arr[high]: # 说明这段未旋转,直接退出
                return arr[low]
            mid = (low+high)//2
            if arr[mid] < arr[low]: # 说明low< x <=mid肯定包含了最小点 (实际发现 mid跟low或者high比好像都行,总之就是一种是high往前缩,一种是low往后进)
                high = mid
            elif arr[mid] > arr[high]:  # 说明mid < x <= high肯定包含了最小点,且mid处肯定不是最小点
                low = mid +1
            else:  # 说明high 和 low相等了,此时low处不是数组最小点,low++继续找 (实际发现high--也能行)
                low += 1
        return arr[low]
    
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这个题还是有点绕的,看到了几个不同风格的代码,牛客用例都能跑过,时间有限这次难以深究。如果发现哪里有问题欢迎评论区留言指出,感谢。
