leetcode--二叉树模板总结(python3)

1、为何要从树下手

回溯、分治、动态规划,在刷了这么多题之后的体会就是其实都是树的遍历,大多数算法与树都脱不了干系,在东哥的指引下,开始树的模板总结:

2、树的遍历的框架特点

# 所谓的树的遍历的几行破代码
def find_next(root):
    if xxx:
        return
    # 前序遍历
        do_some()
    find_next(root.left)
    # 中序遍历
        do_some()
    find_next(root.right)
    # 后序遍历
        do_some()

3、树框架的经典案例

东哥举了两个典型的例子,虽然直观上没有联系,但都暗藏玄机,如下

3.1快速排序

简单概要一下快排的精髓,从当前需要排序的数组nums中取一个截断点(取数组第一个也好,随机取也好),这个截断点的作用就是分割数组,小于该点的放前面,大于该点的放后面,便产生左右两个子数组,之后对左右数组递归执行上述逻辑,当数组的size不足1时则停止。

#  快速排序伪代码
def quick_sort(nums, l, r):
    if r-l < 1:return
    node = get_node(nums, l, r)
    quick_sort(nums, l, node-1)
    quick_sort(nums, node+1, r)

妥妥的前序遍历的节奏,先找根,再依次处理左右节点,伪代码不过瘾,上干货

    #  图方便用了费内存的写法,可以试试在原数组上修改
def quick_sort(nums):
    if len(nums) < 2:
        return nums
    else:
        node = nums[0]
        left_nums = [i for i in list[1:] if i<=node]
        right_nums = [i for i in list[1:] if i > node]
        return quick_sort(left_nums)+[node]+quick_sort(right_nums)

3.2归并排序

同样简单概括一下归并排序的精髓,其实也就是所谓的分治思想,把原有数组一分为二,分别排序,最后再合并,递归则体现在分别排序里,分治的思想体现在,把问题设计成重复子问题,我要排一个大的,只要排两个小的,最终通过后续遍历的回溯特性依次把小的数组,组装起来复原成初始要求的大数组。

提炼一下:

以我的理解串一下就是后续遍历这种递归模式,易构成回溯的特性,从而能实现利用分治思想设计的解决方案,往后提前拓展一下可以体会到,递归可以实现逆向分治(自上而下的拆解),而动态规划则是分治的正向体现(自下而上的组装)。

#  归并排序伪代码
def merge_sort(nums, l, r):
    if r-l <=1:return
    middle = (left+right) >> 1
    merge_sort(nums, left, middle)
    merge_sort(nums, middle+1, right)
    merge(nums, left, middle, right)

干货

def merge_sort(nums):
    if len(nums) <= 1:
        return nums
    mid = len(nums) >> 1  
    left = merge_sort(nums[:mid])
    right = merge_sort(nums[mid:])
    return merge(left, right)
def merge(left, right):
    result = []
    i =j = 0 
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result += left[i:]
    result += right[j:]
    return result

4、写法对比

随便举一个刷题的例子,每次写递归总觉的自己写的不优美,这是为啥呢?
个人觉得这和我们平常的思维方式有关,比如这题路径总和II我写出了如下两种递归解法:
递归1:

class Solution:
    def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
        if not root:
            return []
        ans = []
        path = [root.val]
        def dfs(node):
            if not (node.left or node.right):
                if sum(path) == target:
                    ans.append(path.copy())
                return
            if node.left:
                path.append(node.left.val)
                dfs(node.left)
                path.pop()
            if node.right:
                path.append(node.right.val)
                dfs(node.right)
                path.pop()
        dfs(root)
        return ans

递归2:

class Solution:
    def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
        ans = []
        path = []
        def dfs(node):
            # 退出条件
            if not node:
                return
            # 处理当前节点
            path.append(node.val)
            if not (node.left or node.right):
                if sum(path) == target:
                    ans.append(path.copy())
            # 进入下层
            dfs(node.left)
            dfs(node.right)
            # 清理当前节点
            path.pop()
        dfs(root)
        return ans

递归2的方法是不是比第一种要好很多呢,代码看着思路也清晰,而实际上我第一次顺着思路写出来的递归是递归1,这是为什么呢?

思考:

因为递归通常是要先去找退出条件的,那么由路径总和我们知道终止条件就是当左右子树都为None时,则不用再往下递归了,所以我的递归1的终止条件就如同我说的一样,终止条件设定好了,那么进入下层递归的条件也就来了,不能仅以node作为判断条件,因为以node.left、node.right进行判断时就默认了node不能为None,所以必须对左右分支提前进行预先判断,确保传入的节点不能为None,所以这种写法看起来就是同时处理了两个分支,所以相对于正常的递归写法(递归2)的每次处理一个节点来说逻辑较为繁琐一点。

提炼一下:

按照正常逻辑提出的终止条件对于程序来说并不优美,对于只要关注当前节点的正常递归来说,变成了需要关注当前节点的所有子节点了,所以并不能被终止条件所迷惑,看透程序执行顺序的本质,严格按照递归模板走,仅关注当前节点则能使代码更简洁。

参考文献:
https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/er-cha-shu-xi-lie-1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342