python_ACM模式《剑指offer刷题》二叉树1

本文介绍了如何在面试中提问关于使用双端队列实现二叉树Z字形层序遍历的问题,并详细解析了两种思路:一种是利用双端队列的伪Z字形遍历,另一种是利用两个栈的逆序存储策略。提供了代码实现和参考资源。

题目:

面试tips:

1. 询问是否可以使用双端队列 (看后面思路就可知为什么要问这个)

思路:

时复和空复都为O(n)

思路一:利用双端队列。总体思想是利用二叉树层序遍历(二叉树的层序遍历就是用队列dq,且从左往右每一层存入队列中),但这里的双端队列使用在path中,即存储路径path时,遇到奇数列,从dq中读出来的节点进行尾插入path;遇到偶数列,从dq中读出来的节点进行头插入。

例如:层序遍历对上述二叉树(因为是层序遍历,因此都是从左往右读取的)

第一层读取: 1 。 因为是奇数层,则存入path尾插,则[1]

第二层读取:2 3 。因为是偶数层,则存入path头插,则[3 2](注意先读取先插)

第三层读取:4 5 6 7 。因为是奇数层,则存入path尾插,则[4 5 6 7](注意先读取先插)

第二层读取:8 9 10 11 12 13 14 15 。因为是偶数层,则存入path头插,则[15 14 13 12 11 10 9 8]

        其实本质上思路一是伪Z字形遍历,因为其在第一次pop节点时还是层序的,只是加入路径path时对奇偶列的加入一个是尾插一个是头插。是leetcode上提供的思路。

        而思路二当在pop节点时就已经时Z字形遍历了。是剑指offer提供的思路。

思路二:利用两个栈。分别称为当前栈,下一栈。分析:若当前栈存储的是奇数行的节点时,则处理时将其左右孩子按顺序存入下一栈中(这样下一次就可以输出右左孩子);若当前栈存储的是偶数行的节点时,则处理时将其右左孩子按顺序存入下一栈(这样下一次就可以输出左右孩子)。

具体分析:

当前栈:第一行。-> 弹出节点1,将其左右孩子存入下一栈23。当前栈为空了则将下一栈作为当前栈,当前栈作为下一栈。result[1]

当前栈:此时节点为23,是第二行。->弹出节点3,将其右左孩子存入下一栈76,弹出节点2,将其右左孩子存入下一栈54,则下一栈为7654。当前栈为空了则将下一栈作为当前栈,当前栈作为下一栈。result[32]

当前栈:result[4567]

````直至两个栈都为空。

代码实现:

思路一:

from collections import deque

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def arr2tree(arr, index):
    # 满二叉树数组格式构造二叉树
    # 构造arr[index]的二叉树
    # 满二叉树数组格式: 依照满二叉树的结构,无论是否为空都会将数组填上
    if index >= len(arr) or arr[index] == None:
        return None
    root = TreeNode(val = arr[index])
    left = arr2tree(arr, 2 * index + 1)
    right = arr2tree(arr, 2 * index + 2)
    root.left = left
    root.right = right
    return root


def zigzagLevelOrder(root):
    # 使用双端队列。
    if not root:
        return []
    dq = deque([root])  # 这个作用只是层序遍历的迭代法
    result = []
    sign = True  # 表明当前是奇数行
    while dq:
        size = len(dq)
        path = deque()  # 这里使用双端队列
        while size:
            # 之所以说其是伪Z字形遍历 就是其取出来时还是层序遍历的从左往右,只是对结果集根据奇数列or偶数列头插或尾插
            node = dq.popleft()
            if sign:
                path.append(node.val)
            else:
                path.appendleft(node.val)
            # 下面都是层序遍历的套路 左右孩子往dq中存
            if node.left:
                dq.append(node.left)
            if node.right:
                dq.append(node.right)
            size -= 1
        result.append(list(path))
        sign = not sign
    return result

if __name__ == '__main__':
    arr = [3, 9, 20, None, None, 15, 7]
    root = arr2tree(arr, 0)
    print(zigzagLevelOrder(root))
    # [[3], [20, 9], [15, 7]]


思路二:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def arr2tree(arr, index):
    # 满二叉树数组格式构造二叉树
    # 构造arr[index]的二叉树
    # 满二叉树数组格式: 是指首先按层序遍历顺序,且二叉树的非空节点的左右孩子(尽管为空)都会打印出来,空节点的左右孩子则不打印
    if index >= len(arr) or arr[index] == None:
        return None
    root = TreeNode(val = arr[index])
    left = arr2tree(arr, 2 * index + 1)
    right = arr2tree(arr, 2 * index + 2)
    root.left = left
    root.right = right
    return root

def zigzagLevelOrder(root) :
    # 利用两个栈解决本题
    # 将奇数层1放入第一个栈中,且放左右孩子在第二个栈中(则下一次就可逆序打印)
    # 偶数层0时(第二个栈)放右左孩子
    if not root:
        return []
    # 定义一个二维数组 分别是两个栈
    stack = [[],[]]
    result = []
    path = []
    # 初始化奇数层、偶数层
    current, next_lay = 1, 0    # 先处理第一层 故当前层是奇数层
    stack[current].append(root)
    while stack[current] or stack[next_lay]:
        # 只要奇数层or偶数层还有节点 说明未遍历完毕
        node = stack[current].pop()
        path.append(node.val)
        if current:
            # 如果是奇数层则先放左孩子再放右孩子(因为下一层要逆序)
            if node.left:
                stack[next_lay].append(node.left)
            if node.right:
                stack[next_lay].append(node.right)
        else:
            # 若是偶数层则先放右孩子
            if node.right:
                stack[next_lay].append(node.right)
            if node.left:
                stack[next_lay].append(node.left)
        if not stack[current]:
            # 如果当前层空了则更换当前层
            result.append(path[:])
            path = []
            current = 1 - current  # 当前层从奇数层更换成偶数层,偶数层更换为奇数层
            next_lay = 1 - next_lay    # 下一层从偶数层更换成奇数层,奇数层更换为偶数层
    return result

if __name__ == '__main__':
    arr = [3, 9, 20, None, None, 15, 7]
    root = arr2tree(arr, 0)
    print(zigzagLevelOrder(root))
    # [[3], [20, 9], [15, 7]]

参考资料:

1. 《剑指offer》

2. 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维状数组的程序 34 3.4 线段 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问 67 5.5 0/1背包问 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值