首页 > 编程语言 >Leetcode刷题Python之3165.不包含相邻元素的子序列的最大和

Leetcode刷题Python之3165.不包含相邻元素的子序列的最大和

时间:2024-10-31 13:20:03浏览次数:3  
标签:nums Python 线段 元素 查询 复杂度 节点 3165 Leetcode

提示:利用线段树解决不包含相邻元素的子序列最大和问题。

文章目录


一、题目描述

给定一个整数数组 nums 和一个二维数组 queries,其中 queries[i] = [posi, xi] 表示将 nums[posi] 的值更新为 xi。对于每次查询,要求重新计算 nums 中不包含相邻元素的子序列的最大和,并返回所有查询结果的累加和。由于累加和可能很大,最后结果需要对 109+7 取模。


示例

示例一:

输入: nums = [3,5,9], queries = [[1,-2],[0,-3]]
输出: 21

解释:
第 1 次查询后,nums = [3, -2, 9],不包含相邻元素的最大和为 3 + 9 = 12。
第 2 次查询后,nums = [-3, -2, 9],不包含相邻元素的最大和为 9。
查询的总和为 12 + 9 = 21。

示例二:

输入: nums = [0, -1], queries = [[0, -5]]
输出: 0

解释:
第 1 次查询后,nums = [-5, -1],不包含相邻元素的最大和为 0(选择空子序列)。

二、解题思路

1.思路分析

题目要求计算 nums 中不包含相邻元素的子序列最大和,这类似于经典的打家劫舍问题,即在一组元素中挑选非相邻的元素,求其和的最大值。问题的难点在于,每次查询需要在更新数组的某个位置后,迅速重新计算整个数组的最大和。若采用暴力的方式重新计算每次的结果,时间复杂度将高达 O(n⋅q),其中 n 是数组长度,q 是查询次数,这对于数据规模较大的情况来说难以接受。

为了优化时间复杂度,我们可以利用 线段树 的思路。线段树适合解决 区间查询和更新 的问题,可以在对数组某个元素进行修改后,以 O(logn) 的时间复杂度重新计算受影响的部分,从而避免全局计算。

2.线段树的状态设计

我们可以通过四种状态来存储每个区间的最大和:

**f00:**表示左子区间和右子区间都没有选中的最大和。
**f01:**表示左子区间没有选中,右子区间选中的最大和。
**f10:**表示左子区间选中,右子区间没有选中的最大和。
**f11:**表示左子区间和右子区间都选中的最大和。
通过这四种状态,我们可以在合并左右子树时,动态更新区间的最大和。

3.线段树的操作

建树(build):
初始化线段树时,从叶子节点开始,将数组中的每个元素值填入对应的线段树节点中。
维护(maintain):
在更新某个元素后,需要重新计算该元素所在区间及其父区间的最大和状态。
更新(update):
当某个元素发生更新时,只需更新对应的线段树节点,并维护与其相关的父节点状态。


三、代码实现

class Solution(object):
    def maximumSumSubsequence(self, nums, queries):
        """
        :type nums: List[int]
        :type queries: List[List[int]]
        :rtype: int
        
        通过线段树维护数组不包含相邻元素的最大和。每次查询只更新受影响的部分,避免全局计算。
        """
        MOD = 10**9 + 7  # 用于结果取模

        # 线段树节点保存四种状态:f00, f01, f10, f11
        n = len(nums)
        t = [[0] * 4 for _ in range(2 << n.bit_length())]

        # 手写 max 函数提高效率
        def max(a, b):
            return b if b > a else a

        # 合并左右子树的状态,更新当前节点的状态
        def maintain(o):
            a, b = t[o * 2], t[o * 2 + 1]
            t[o][0] = max(a[0] + b[2], a[1] + b[0])  # 左右都没选,取最大值
            t[o][1] = max(a[0] + b[3], a[1] + b[1])  # 左没选右选了
            t[o][2] = max(a[2] + b[2], a[3] + b[0])  # 左选了右没选
            t[o][3] = max(a[2] + b[3], a[3] + b[1])  # 左右都选了

        # 初始化线段树
        def build(o, l, r):
            if l == r:  # 叶子节点
                t[o][3] = max(nums[l], 0)  # 只需考虑当前元素是否大于 0
                return
            m = (l + r) // 2
            build(o * 2, l, m)
            build(o * 2 + 1, m + 1, r)
            maintain(o)  # 合并左右子树的状态

        # 更新线段树,修改 nums[i] 为 val
        def update(o, l, r, i, val):
            if l == r:  # 找到叶子节点
                t[o][3] = max(val, 0)  # 只更新 f11 的值,确保非负
                return
            m = (l + r) // 2
            if i <= m:
                update(o * 2, l, m, i, val)  # 更新左子树
            else:
                update(o * 2 + 1, m + 1, r, i, val)  # 更新右子树
            maintain(o)  # 更新父节点状态

        # 构建初始线段树
        build(1, 0, n - 1)

        ans = 0
        for i, x in queries:  # 遍历每个查询
            update(1, 0, n - 1, i, x)  # 将 nums[i] 更新为 x
            ans += t[1][3]  # 每次查询后累加 f11
            ans %= MOD  # 对结果取模
        return ans

代码详细解释

初始化与建树:

我们用 build 函数递归初始化线段树,每个节点存储四种状态,分别表示不同的子序列选择状态。
叶子节点初始化时,根据数组元素值设置状态,若元素为负数,则最大和为 0。

线段树的维护:

maintain 函数用于在修改某个节点后,合并左右子树的状态。我们考虑四种情况:
1.左右子树都不选;
2.左子树不选,右子树选;
3.左子树选,右子树不选;
4.左右子树都选。

处理查询:

每次查询时,通过 update 函数在对应位置更新 nums 数组的值,线段树在更新过程中仅修改受影响的节点及其相关区间。
计算当前查询的结果时,只需取根节点的 f11 值,这代表当前整个数组的最大和。

最终输出:

我们将每次查询的最大和累加,并对结果取模 109+7,保证返回的结果在题目要求范围内。

四、总结

时间复杂度分析

**线段树的初始化:**构建线段树的时间复杂度为 O(n),其中 n 是数组长度。

**每次查询的复杂度:**由于线段树每次更新的复杂度为 O(logn),处理每个查询的时间复杂度也是 O(logn)

**总复杂度:**对于 q 个查询,整个算法的时间复杂度为 O(n+qlogn),其中 q 是查询次数。
该算法可以有效处理 nq 均为 5104*规模的情况,能够满足时间要求。


通过利用线段树,我们成功地将原问题的暴力解法优化到了 O(n+qlogn) 的时间复杂度,使得在面对大规模数据时依然可以高效地处理。本题展示了线段树在区间查询与更新问题中的强大应用,尤其在需要高效维护复杂状态的情况下,线段树是一种很好的解决方案。
在这里插入图片描述

标签:nums,Python,线段,元素,查询,复杂度,节点,3165,Leetcode
From: https://blog.csdn.net/qq_53086905/article/details/143392639

相关文章

  • Python+Django框架淘宝家用电器销售数据可视化系统作品截图和开题报告参考
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩,提供核心代码讲解,答辩指导。项目配有对应开发......
  • python安装
    系统:win11安装版本:3.13.0一、检查检查电脑内是否已存在python,打开cmd,指令python或者python-V如果已存在旧版本的python,先删除旧版本二、下载安装程序官网地址下载地址默认下载window环境下的最新版本,可自行选择其他系统和其他版本。三、开始安装1.选择addtoPATH......
  • Python中list列表的所有方法
    Python中list列表的所有方法方法描述返回值list.append()在列表末尾添加指定元素,如果增加的是序列会形成嵌套。无返回,直接修改。list.extend()在列表末尾逐个添加指定序列中的所有元素。无返回,直接修改。list.insert()将对象插入列表指定位置,如果增加的是序列会形成嵌套。无返......
  • Python中str字符串的所有方法
    Python中str字符串的所有方法方法描述返回值str.capitalize()将字符串的第一个字符转换为大写,其余字符转换为小写。返回一个新字符串str.casefold()将字符串转换为小写,并移除所有音调标记。识别的内容比str.lower()多返回一个新字符串str.center()返回指定宽度的新字符串,原字......
  • Python+Django框架山西太原二手房数据可视化大屏系统开题报告参考
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩,提供核心代码讲解,答辩指导。项目配有对应开发......
  • Linux安装Python 3.11
    Linux安装python在Linux上安装Python3.11,你可以按照以下步骤进行。这些步骤以CentOS为例,但其他Linux发行版的过程大同小异,可能只需稍作调整。1.检查Python版本首先,打开终端,检查系统上是否已安装Python3.11:python3.11--version#或者python3--version如果系统返回的是......
  • 【20241030】【Python基础教程】第二章 列表和元组 I
    第二章列表和元组I2.1序列概述数据结构是以某种方式(如通过编号)组合起来的数据元素(如数、字符乃至其他数据结构)集合元组是特殊的序列,列表和元组的主要不同在于,列表是可以修改的,而元组不可以。几乎在所有情况下都可使用列表来代替元组。一种例外情况是将元组用作字典键。序......
  • python 备份文件,从 D盘 到Z盘。并且保留15天的文件
    备份文件,从D盘到Z盘。并且保留15天的文件importosimportshutilfromdatetimeimportdatetime,timedeltadefmove_and_clean_folders(a_folder,b_folder,keep_count=15):try:#获取前两天的日期yesterday=datetime.now()-timedelta(days=......
  • 轻松掌握在AirtestIDE中切换为本地Python环境的详细指南
    此文章来源于项目官方公众号:“AirtestProject”版权声明:允许转载,但转载必须保留原链接;请勿用作商业或者非法用途一、前言最近有一些新入门的小伙伴们都在问如何在AirtestIDE内使用更多的依赖库,为了解决这个问题,我们建议将AirtestIDE的Python环境切换为本地Python环境,并在本地......
  • 在 Odoo 中,确实可以通过 SQL 语句来提升一些功能逻辑的处理效率。将 SQL 转为 Python
    1.使用env.cr.execute执行SQL语句OdooORM提供的env.cr.execute()可以直接执行SQL语句,这样可以在Python代码中调用SQL逻辑,结合Odoo的业务模型实现复杂的逻辑操作。execute()方法适合处理批量数据更新、复杂查询等。示例:批量更新customer_id字段defupdate_......