数据结构与算法----复习Part 15 ()

news2025/1/12 22:58:07

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

目录

一,二叉搜索树(Binary Search Tree)

二叉搜索树的查找

二叉搜索树的插入

二叉搜索树的创建

二叉搜索树的删除

二,线段树(Segment Tree):

线段树的构建

线段树的单点更新

线段树的区间查询

线段树的区间更新


  

一,二叉搜索树(Binary Search Tree)

        如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;

        如果任意节点的右子树不为空,则右子树上所有结点的值均大于它的根节点的值;

        任意节点的左子树、右子树均为二叉搜索树;

        根据二叉搜索树,即左子树的节点值 < 根节点值 < 右子树的节点值 这一特性,如果以中序遍历的方式遍历整个二叉树,则会得到一个递增序列。

二叉搜索树的查找

        在二叉搜索树中查找值为 val 的节点:

        如果二叉搜索树为空,则返回 None;

        如果二叉搜索树不为空,则将要查找的值 val 与 root.val 比较:

                如果 val == root.val ,则查找成功,返回找到的节点;

                如果 val < root.val ,则递归查找左子树;

                如果 val > root.val ,则递归查找右子树。

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

class Solution:
    def searchBST(self, root: TreeNode, val: int):
        if not root:
            return None

        if val == root.val:
            return root
        elif val < root.val:
            return self.searchBST(root.left, val)
        else:
            return self.searchBST(root.right, val)
        

二叉搜索树的插入

        在二叉搜索树中插入一个值为 val 的节点:

        如果二叉搜索树为空,则创建一个值为 val 的节点,并作为根节点;

        如果二叉搜索树不为空,则将插入的值 val 与二叉搜索树根节点的值 root.val 比较:

                如果 val < root.val ,则递归将值插入左子树;

                如果 val > root.val ,则递归将值插入右子树。

PS:二叉搜索树不存在重复节点,否则将违反定义。如果 val 已经存在,则直接返回。

    def insertIntoBST(self, root: TreeNode, val: int):
        if root == None:
            return TreeNode(val)
        
        if val < root.val:
            root.left = self.insertIntoBST(root.left, val)
        if val > root.val:
            root.right = self.insertIntoBST(root.right, val)
        return root

二叉搜索树的创建

        根据数组序列中的元素值,建立一棵二叉搜索树:

        初始化二叉搜索树为空树;

        遍历数组元素,将数组元素 nums[i] 依次插入到二叉搜索树中;

        将数组中全部元素值插入到二叉搜索树中后,返回二叉搜索树的根节点。

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

class Solution:
    def searchBST(self, root: TreeNode, val: int):
        if not root:
            return None

        if val == root.val:
            return root
        elif val < root.val:
            return self.searchBST(root.left, val)
        else:
            return self.searchBST(root.right, val)

    def buildBST(self, nums):
        root = TreeNode(nums[0])
        for num in nums[1:]:
            self.insertIntoBST(root, num)
        return root

二叉搜索树的删除

        在二叉搜素树种删除值为 val 的节点:

        删除节点并不困难,难点在于保持删除后的仍然是二叉搜索树,可以分为三种情况

        1,被删除节点的左子树为空:

                则令其右子树代替被删除节点的位置;

        2,被删除节点的右子树为空:

                则令其左子树代替被删除节点的位置;

        3,被删除节点的左右子树都不为空:

                考虑二叉搜索树的特点:中序遍历结果是一个升序排列,那么将被删除节点的中序遍历

        前驱或后继替换当前节点即可,这样保证了新树的中序遍历仍为升序。

                

        观察 C 节点我们可以发现,中序遍历的前驱是其左子树的最右侧叶子节点,而中序遍历的后继是其右子树最左侧的叶子节点。

        如果当前节点为空,则返回当前节点;

        如果当前节点的值大于 val,则递归去左子树搜索并删除,因为左子树可能有变化,所以 root.left 也要跟着递归更新;

        如果当前节点的值小于 val,则递归去右子树搜索并删除,root.right 也要递归更新;

        如果当前节点的值等于 val:

                如果当前节点的左子树为空,则删除之后,右子树代替当前节点位置,返回右子树;

                如果当前节点的右子树为空,则删除之后,左子树代替当前节点位置,返回左子树;

                如果当前节点左右子树后不为空,则将左子树转移到右子树最左侧叶子节点的位置,然

        后右子树代替当前节点的位置。

    def deleteNode(self, root: TreeNode, val: int):
        if root == None:
            return None
        
        if root.val > val:
            root.left = self.deleteNode(root.left, val)
        elif root.val < val:
            root.right = self.deleteNode(root.right, val)
        else:
            if root.left == None and root.right == None:
                root = None
                return root
            elif root.left != None and root.right == None:
                return root.left
            elif root.left == None and root.right != None:
                return root.right
            else:
                node = root.right
                while node.left:
                    node = node.left
                root.val = node.val
                # 删除右子树的最小值
                root.right = self.deleteNode(root.right, node.val)
        return root

98. 验证二叉搜索树

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        ans = []
        def inorder(root):
            if not root:
                return 
            inorder(root.left)
            ans.append(root.val)
            inorder(root.right)
            return ans
        ans = inorder(root)
        for i in range(1, len(ans)):
            if ans[i] <= ans[i - 1]:
                return False
        return True

173. 二叉搜索树迭代器

class BSTIterator:

    def __init__(self, root: TreeNode):
        self.stack = []
        self.in_order(root)

    def in_order(self, node):
        while node:
            self.stack.append(node)
            node = node.left

    def next(self) -> int:
        node = self.stack.pop()
        if node.right:
            self.in_order(node.right)
        return node.val

    def hasNext(self) -> bool:
        return len(self.stack) != 0

        用显式栈维护二叉搜索树,要注意节点的访问顺序。 

二,线段树(Segment Tree):

        一种基于分治思想的二叉树,用于在区间上进行信息统计,它的每一个节点都对应一个区间[left, right],left,right 通常是整数。每一个叶子节点表示了一个单位区间(长度为 1 ),叶子节点对应区间上 left = right。每一个非叶子节点 [left, right] 的左叶子节点都为 [left, (left + right) / 2],右叶子节点表示的区间都为 [(left + right) / 2 + 1, right]。

        线段树是一棵平衡二叉树(高度差不超过 1),树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父节点区间二等分之后的其中一个子区间。当有 n 个元素时,对区间操作可以在 O(logn) 的时间复杂度内完成。

区间为 [0, 7]的线段树

线段树的特点:

        线段树的每个节点都代表一个区间;

        线段树具有唯一的根节点,代表的区间时整个统计范围;

        线段树的每个叶子节点都代表一个长度为 1 的区间[x, x];

        对于每个内部节点[left, right],它的左子节点是 [left, mid],右子节点是 [mid + 1, right]。其中 mid = (left + right) / 2。

线段树的构建

        由于线段树近乎是完全二叉树,所以使用 顺序存储结构。

        我们可以采用与完全二叉树类似的编号方法来对线段树进行编号:

                根节点的编号为 0;

                如果某二叉树节点(非叶子节点)的下标为 i,那么其左孩子的下标为 2 * i + 1,右孩子

        节点的下标为 2 * i + 2;

                如果某二叉树节点(非根节点)的下标为 i,那么其父节点的下标为 (i - 1) // 2。

        上图可知,下标为 i 节点的子节点下标为 2 * i + 1 和 2 * i + 2,所以线段树适合采用递归的方法来创建:

        如果是叶子节点(left = right),则节点的值就是对应位置的元素值;

        如果是非叶子节点,则递归创建左右子树;

        节点的区间值(区间和、区间最大值、区间最小值)等于该节点左右子节点元素值的对应计算结果。

class SegmentTreeNode:
    def __init__(self, val = 0):
        self.left = -1
        self.right = -1
        self.val = val
        self.lazy_tag = None

class SegmentTree:
    def __init__(self, nums, function):     # function 根据题目要求确定该节点的值
        self.size = len(nums)
        self.tree = [SegmentTreeNode() for _ in range(4 * self.size)]
        self.nums = nums
        self.function = function
        if self.size > 0:
            self.__build(0, 0, self.size -1)

    def __build(self, index, left, right):
        self.tree[index].left = left
        self.tree[index].right = right
        if left == right:
            self.tree[index].val = self.nums[left]
            return

        mid = left + (right - left) // 2    # mid 记录节点存储的区间长度
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        self.__build(left_index, left, mid)
        self.__build(right_index, mid + 1, right)
        self.__pushup(index)

    def __pushup(self, index):
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        # 按照节点值的计算方式,更新当前节点的值
        # 因为是递归地构造线段树,所以叶子节点已经构造好
        self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val)

线段树的单点更新

        修改一个元素的值,例如将 nums[ i ] 修改为 val:

                如果是叶子节点,满足 left = right,则更新该节点的值;

                如果是非叶子节点,则判断应该在左子树还是在右子树中更新;

                在对应的子树中更新节点值;

                左右子树更新返回之后,向上更新节点的区间值( function 中实现的 区间和、区间最大

        值或是区间最小值等)。

    def update_point(self, i, val):
        self.nums[i] = val
        self.__update_point(i, val, 0, 0, self.size - 1)

    def __update_point(self, i, val, index, left, right):
        if self.tree[index].left == self.tree[index].right:
            self.tree[index].val = val
            return

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        if i <= mid:
            self.__update_point(i, val, left_index, left, mid)
        else:
            self.__update_point(i, val, right_index, mid + 1, right)
        
        self.__pushup(index)

线段树的区间查询

        查询一个区间为 [q_left, q_right] 的区间值:

                如果区间 [q_left, q_right] 完全覆盖了当前节点所在的区间 [left, right] ,即 left >= q_leftt         并且 right <= q_right,则返回该节点的区间值,即该节点的 val;

                如果区间 [q_left, q_right] 与当前节点毫无关系,则返回 0;

                如果区间 [q_left, q_right] 与当前节点所在区间有交集,则:

                        如果区间 [q_left, q_right] 与左子树所在区间 [left, mid] 有交集,则在当前节点的左

                子树中进行查询,并保存查询结果 res_left;

                        如果区间 [q_left, q_right] 与右子树所在区间 [mid + 1, right] 有交集,则在当前节

                点的右子树中进行查询,并保存查询结果 res_right;

                        最后返回左右子树元素区间值的聚合计算结果。

    def query_interval(self, q_left, q_right):
        return self.__query_interval(q_left, q_right, 0, 0, self.size - 1)

    def __query_interval(self, q_left, q_right, index, left, right):
        if left >= q_left and right <= q_right:
            return self.tree[index].val
        if right < q_left or left > q_right:
            return 0

        self.__pushdown(index)      # 在访问节点时调用

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        res_left = 0
        res_right = 0
        if q_left <= mid:
            res_left = self.__query_interval(q_left, q_right, left_index, left, mid)
        if q_right > mid:
            res_right = self.__query_interval(q_left, q_right, right_index, mid + 1, right)
        
        return self.function(res_left, res_right)

        其中 self.__pushdown() 函数的作用在线段树的区间更新中

线段树的区间更新

        对 [q_left, q_right] 区间进行更新,如将 [q_left, q_right] 区间内所有元素更新为 val:

        在区间更新操作中,如果某个节点区间P [left, right] 被修改区间 [q_left, q_right] 完全覆盖,则以该节点 P 为根的整棵子树中的节点,都要修改。但是如果后序的查找操作中,没有用到修改后的节点 P 作为候选答案,那么更新以节点 P 为根的子树这一操作就是徒劳的。

        因此在节点中

class SegmentTreeNode:
    def __init__(self, val = 0):
        self.left = -1
        self.right = -1
        self.val = val
        self.lazy_tag = None

        最后一行增加了一个【延迟标记】,意为 该区间曾经被修改为 val,但其子节点的值尚未更新。也就是说,将区间节点 P 子节点的更新操作延迟到 在后续操作中递归进入子节点 时再执行。

使用 延迟标记 的区间更新步骤为:

        如果区间 [q_left, q_right] 完全覆盖了当前节点所在区间 [left, right],则更新当前节点所在区间的值,并将当前节点的延迟标记为区间值;

        如果区间 [q_left, q_right] 与当前节点所在区间毫无关系,则直接返回;

        如果区间 [q_left, q_right] 与当前节点所在区间有交集:

                如果当前节点使用了【延迟标记】,即延迟标记不为 None,则将当前区间的更新操作应

        用到该节点的子节点上,即向下更新;

                如果区间 [q_left, q_right] 与左子节点所在区间有交集,则在左子树中更新区间值;

                如果区间 [q_left, q_right] 与右子节点所在区间有交集,则在右子树中更新区间值;

                左右子树更新返回后,向上更新区间值。

PS:向下更新

        更新左子节点和左子节点的 lazy_tag = val;

        更新右子节点和右子节点的 lazy_tag = val;

        将当前节点的 lazy_tag = None;

        

    def update_interval(self, q_left, q_right, val):
        self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1)

    def __update_interval(self, q_left, q_right, val, index, left, right):

        if left >= q_left and right <= q_right:
            interval_size = (right - left + 1)
            self.tree[index].val = interval_size * val
            self.tree[index].lazy_tag = val
            return
        if right < q_left or left > q_right:
            return 0

        self.__pushdown(index)

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        if q_left <= mid:
            self.__update_interval(q_left, q_right, val, left_index, left, mid)
        if q_right > mid:
            self.__update_interval(q_left, q_right, val, right_index, mid + 1, right)

        self.__pushup(index)

    # 向下更新下标为 index 的节点所在区间的左右子节点的值个 lazy_tag
    def __pushdown(self, index):
        lazy_tag = self.tree[index].lazy_tag
        if not lazy_tag:
            return

        left_index = index * + 1
        right_index = index * 2 + 2

        self.tree[left_index].lazy_tag = lazy_tag
        left_size = self.tree[left_index].right - self.tree[left_index].left + 1
        self.tree[left_index].val = lazy_tag * left_size
        
        self.tree[right_index].lazy_tag = lazy_tag
        right_size = self.tree[right_index].right - self.tree[right_index].left + 1
        self.tree[right_index].val = lazy_tag * right_size
        
        self.tree[index].lazy_tag = None

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1519741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go语言中的make和new:内存分配与对象创建的巧妙之道

Go语言中的make和new&#xff1a;内存分配与对象创建的巧妙之道 Go语言作为一门简洁而强大的编程语言&#xff0c;提供了多种用于动态内存分配和对象创建的关键词。其中&#xff0c;make和new是两个常见且常被混淆的关键词。本文将深入讲解Go语言中make和new的区别&#xff0c;…

[AIGC] Spring Boot中的切面编程和实例演示

切面编程&#xff08;Aspect Oriented Programming&#xff0c;AOP&#xff09;是Spring框架的关键功能之一。通过AOP&#xff0c;我们可以将代码下沉到多个模块中&#xff0c;有助于解决业务逻辑和非业务逻辑耦合的问题。本文将详细介绍Spring Boot中的切面编程&#xff0c;并…

你是否知道到今年315到来 大数据杀熟还存在吗?

随着315消费者权益日的临近&#xff0c;关于大数据杀熟的话题再次引起了广泛关注。在当今这个数字化时代&#xff0c;大数据杀熟现象是否仍然存在呢&#xff1f; 首先&#xff0c;我们需要明确什么是大数据杀熟。简单来说&#xff0c;大数据杀熟是指企业利用消费者的个人信息和…

Inception网络以及GoogleNet

一个inception模块所做的 多个inception模块组成一个inception网络 上图中有多个inception模块&#xff0c;组成了一个inception网络&#xff0c;在有的隐藏层的地方还会输出还会做softmax。 Google开发的inception网络&#xff0c;因此把它叫做GoogleNet&#xff0c;详细说明…

wordpress博客趣主题个人静态网页模板

博客趣页面模板适合个人博客&#xff0c;个人模板等内容分享。喜欢的可以下载套用自己熟悉的开源程序建站。 博客趣主题具有最小和清洁的设计&#xff0c;易于使用&#xff0c;并具有有趣的功能。bokequ主题简约干净的设计、在明暗风格之间进行现场切换。 下载地址 清新个人…

【Canvas与艺术】时尚钟表

【实现效果图示】 【实现代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>时尚钟表</title></head><body onload&…

iptables详细介绍

在 CentOS 中,iptables 是一种用于配置和管理网络防火墙的工具,它提供了一种灵活和强大的方式来控制进出服务器的网络流量。以下是 CentOS 中 iptables 的主要内容: 规则链(Chains): iptables 使用规则链来组织规则,常见的链包括: INPUT:处理进入服务器的数据包。OUTP…

Binance Labs领投的安全赛道龙头GoPlus Security零撸教程

简介&#xff1a;SecWarex是Goplus推出的个人安全产品&#xff0c;可以理解为web3版的360安全卫士&#xff0c;它通过提供开放、无权限、用户驱动的安全服务&#xff08;包括代币检测、NFT 检测、恶意地址、审批安全 API 和 dApp 合约安全等&#xff09;&#xff0c;打造 Web3 …

OpenCV与AI深度学习 | 实战 | 基于YOLOv9和OpenCV实现车辆跟踪计数(步骤 + 源码)

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;实战 | 基于YOLOv9和OpenCV实现车辆跟踪计数&#xff08;步骤 源码&#xff09; 导 读 本文主要介绍使用YOLOv9和OpenCV实现车辆跟踪计数&a…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:PatternLock)

图案密码锁组件&#xff0c;以九宫格图案的方式输入密码&#xff0c;用于密码验证场景。手指在PatternLock组件区域按下时开始进入输入状态&#xff0c;手指离开屏幕时结束输入状态完成密码输入。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#…

人工智能原理:探索智能的奥秘

人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI。是新一轮科技革命和产业变革的重要驱动力量&#xff0c;是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 人工智能是智能学科重要的组成部分&a…

java-ssm-jsp基于java的校园疫情管理系统

java-ssm-jsp基于java的校园疫情管理系统 获取源码——》公主号&#xff1a;计算机专业毕设大全

Android Studio实现内容丰富的安卓宠物用品管理系统

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 项目编号128 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.系统公告 3.宠物社区&#xff08;可发布宠物帖子&#xf…

BFS 最短路径

目录 原理剖析&#xff1a; 1、 1926. 迷宫中离入口最近的出口 2、 433. 最小基因变化 3、 127. 单词接龙 4、 675. 为高尔夫比赛砍树 原理剖析&#xff1a; 为什么BFS能够解决最短路径问题&#xff1f; 对于无权图(边权为1)或所有边权重相等的情况&#xff0c;BFS是一种有…

PTA题解 --- N个数求和(C语言)

今天是PTA题库解法讲解的第二天&#xff0c;今天我们要讲解N个数求和&#xff0c;题目如下&#xff1a; 要解决这个问题&#xff0c;我们可以用C语言编写一个程序来处理和简化分数。程序的基本思路如下&#xff1a; 1. 定义一个函数来计算两个数的最大公约数&#xff08;GCD&a…

【Python爬虫基础教程 | 第一篇】URL、HTTP基础必知必会

前言 该专栏开设的目的在于给初学者提供一个学习爬虫的成长平台&#xff0c;文章涉及内容均为必备知识。 可订阅专栏&#xff1a;【Python爬虫教程】 | CSDN秋说 文章目录 前言URL概念及组成结构HTTP概念简述浏览器接收资源HTTP协议的结构请求结构请求行请求头请求体请求差异及…

win10虚拟机安装驱动教程

在虚拟机菜单栏中选择安装VMware Tools&#xff1a; 安装好后&#xff0c;在虚拟机中打开此电脑&#xff0c;双击DVD驱动器进行安装&#xff1a; 一直点击下一步&#xff1a; 安装完成&#xff1a; 此时重启虚拟机&#xff0c;发面小屏幕页面的虚拟机自动占满了全部屏幕&#x…

Java Web开发从0到1

文章目录 总纲第1章 Java Web应用开发概述1.1 程序开发体系结构1.1.1 C/S体系结构介绍1.1.2 B/S体系结构介绍1.1.3 两种体系结构的比较1.2 Web应用程序的工作原理1.3 Web应用技术1.3.1 客服端应用技术1.3.2 服务端应用技术1.4 Java Web应用的开发环境变量1.5 Tomcat的安装与配置…

OpenCV(八)——基本线条操作

基本线条操作 OpenCV中提供了基本的线条的操作&#xff0c;包括画直线、画矩形、画圆形等。 &#xff08;1&#xff09;画直线&#xff0c;在OpenCV中利用line()画直线&#xff0c;形式为image_with_line cv2.line(image, start_point, end_point, color, thickness)。line(…

使用 Docker Compose 快速搭建监控网站 uptime-kuma

有时候需要监控自己搭建的一些网站、服务是否正常运行&#xff0c; 这时候可以考虑使用一个监控网站&#xff0c; 定时的进行检测&#xff0c; 记录网站、服务的运行状态&#xff0c; 在这推荐使用 uptime-kuma。 博主博客 https://blog.uso6.comhttps://blog.csdn.net/dxk539…