Dongxing's Wiki Dongxing's Wiki
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
  • 剑指offer

    • 剑指 Offer - python 题解
    • 剑指Offer(1)
    • 剑指Offer(2)
    • 剑指Offer(3)
      • 知识点
      • 题目:重建二叉树
        • 要求
        • 思路
        • 代码
      • 题目:用两个栈模拟队列
        • 要求
        • 思路
        • 代码
        • 扩展
      • 题目:旋转数组的最小数字
        • 要求
        • 思路
        • 代码
        • 思路2
        • 代码2
    • 剑指Offer(4)
    • 剑指Offer(5)
    • 剑指Offer(6)
    • 剑指Offer(7)
    • 剑指Offer(8)
    • 剑指Offer(9)
    • 剑指Offer(10)
    • 剑指Offer(11)
    • 剑指Offer(12)
    • 剑指Offer(13)
    • 剑指Offer(14)
    • 剑指Offer(15)
  • 剑指Offer
  • 剑指offer
anthony
2018-04-06
目录

剑指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
1
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
1
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)
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]
1
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]
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这个题还是有点绕的,看到了几个不同风格的代码,牛客用例都能跑过,时间有限这次难以深究。如果发现哪里有问题欢迎评论区留言指出,感谢。

上次更新: 2022/11/11, 2:11:00
剑指Offer(2)
剑指Offer(4)

← 剑指Offer(2) 剑指Offer(4)→

Theme by Vdoing | Copyright © 2017-2023 anthony 京ICP备17072417-3
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式