首页 > 其他分享 >LeetCode 3165.不包含相邻元素的子序列的最大和:单点修改的线段树(动态规划)

LeetCode 3165.不包含相邻元素的子序列的最大和:单点修改的线段树(动态规划)

时间:2024-11-02 21:20:26浏览次数:5  
标签:index right 单点 nums int tree 3165 LeetCode left

【LetMeFly】3165.不包含相邻元素的子序列的最大和:单点修改的线段树(动态规划)

力扣题目链接:https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements/

给你一个整数数组 nums 和一个二维数组 queries,其中 queries[i] = [posi, xi]

对于每个查询 i,首先将 nums[posi] 设置为 xi,然后计算查询 i 的答案,该答案为 nums不包含相邻元素 的 子序列 的 最大 和。

返回所有查询的答案之和。

由于最终答案可能非常大,返回其对 109 + 7 取余 的结果。

子序列 是指从另一个数组中删除一些或不删除元素而不改变剩余元素顺序得到的数组。

 

示例 1:

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

输出:21

解释:
执行第 1 个查询后,nums = [3,-2,9],不包含相邻元素的子序列的最大和为 3 + 9 = 12
执行第 2 个查询后,nums = [-3,-2,9],不包含相邻元素的子序列的最大和为 9 。

示例 2:

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

输出:0

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

 

提示:

  • 1 <= nums.length <= 5 * 104
  • -105 <= nums[i] <= 105
  • 1 <= queries.length <= 5 * 104
  • queries[i] == [posi, xi]
  • 0 <= posi <= nums.length - 1
  • -105 <= xi <= 105

解题方法:线段树 + DP

对于单次操作,我们可以使用分治的方法来求解。对于一个子区间,我们比较关注的有:区间第一个元素是否被选取、区间最后一个元素是否被选取。也就是说,对于一个子区间,一共有以下4种情况:

  • 开头一定不选,结尾一定不选,记为 f 00 f00 f00
  • 开头一定不选,结尾可选(也可不选),记为 f 01 f01 f01
  • 开头可选(也可不选),结尾一定不选,记为 f 10 f10 f10
  • 开头可选(也可不选),结尾可选(也可不选),记为 f 11 f11 f11

那么对于区间 [ l e f t , r i g h t ] [left, right] [left,right],如何进行分治操作呢?

  • 如果 l e f t = = r i g h t left==right left==right,那么这个区间就只有一个元素,这个区间的 f 00 = f 01 = f 10 = 0 f00=f01=f10=0 f00=f01=f10=0, f 11 = max ⁡ ( 0 , n u m s [ l e f t ] ) f11=\max(0, nums[left]) f11=max(0,nums[left])。

  • 否则,令 m i d = ⌊ l e f t + r i g h t 2 ⌋ mid = \lfloor\frac{left+right}{2}\rfloor mid=⌊2left+right​⌋,递归计算 [ l e f t , m i d ] [left, mid] [left,mid]和 [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right]两个子区间的4个值并汇总得到这个区间的4个值。

    假设左区间为 p p p,右区间为 q q q,则汇总方式为:

    • f 00 = max ⁡ ( f p 00 + f q 10 , f p 01 + f q 00 ) f00 = \max(f_p00+f_q10, f_p01+f_q00) f00=max(fp​00+fq​10,fp​01+fq​00)
    • f 01 = max ⁡ ( f p 00 + f q 11 , f p 01 + f q 01 ) f01 = \max(f_p00+f_q11, f_p01+f_q01) f01=max(fp​00+fq​11,fp​01+fq​01)
    • f 10 = max ⁡ ( f p 10 + f q 10 , f p 11 + f q 00 ) f10 = \max(f_p10+f_q10, f_p11+f_q00) f10=max(fp​10+fq​10,fp​11+fq​00)
    • f 11 = max ⁡ ( f p 10 + f q 11 , f p 11 + f q 01 ) f11 = \max(f_p10+f_q11, f_p11+f_q01) f11=max(fp​10+fq​11,fp​11+fq​01)

那么如何应对 l e n ( q u e r i e s ) len(queries) len(queries)次的修改呢?那就需要引入线段树了。

  • 对于修改操作,使用线段树实现单点修改,线段树的每个节点维护对应区间的4个值
  • 对于查询操作,线段树根节点(整个区间)的 f 11 f11 f11记为所求

时空复杂度分析

  • 时间复杂度 O ( n + q log ⁡ n ) O(n+q\log n) O(n+qlogn),其中 n = l e n ( n u m s ) n=len(nums) n=len(nums), q = l e n ( q u e r i e s ) q=len(queries) q=len(queries)
  • 空间复杂度 O ( n ) O(n) O(n)

AC代码

C++
const unsigned int mod = 1e9 + 7;

class Solution {
private:
    vector<array<unsigned int, 4>> tree;  // 00, 01, 10, 11

    void maintain(int index) {
        int leftIndex = 2 * index + 1;
        int rightIndex = 2 * index + 2;
        tree[index] = {
            max(tree[leftIndex][1] + tree[rightIndex][0], tree[leftIndex][0] + tree[rightIndex][2]),
            max(tree[leftIndex][0] + tree[rightIndex][3], tree[leftIndex][1] + tree[rightIndex][1]),
            max(tree[leftIndex][2] + tree[rightIndex][2], tree[leftIndex][3] + tree[rightIndex][0]),
            max(tree[leftIndex][2] + tree[rightIndex][3], tree[leftIndex][3] + tree[rightIndex][1])
        };
    }

    void buildTree(vector<int>& nums, int index, int left, int right) {
        if (left == right) {
            tree[index] = {0, 0, 0, (unsigned int)max(nums[left], 0)};
            return;
        }
        int mid = (left + right) / 2;
        buildTree(nums, 2 * index + 1, left, mid);
        buildTree(nums, 2 * index + 2, mid + 1, right);
        maintain(index);
    }

    void update(int index, int left, int right, int modifiedI, int val) {
        if (left == right) {
            tree[index][3] = max(0, val);
            return;
        }
        int mid = (left + right) / 2;
        if (modifiedI <= mid) {
            update(2 * index + 1, left, mid, modifiedI, val);
        } else {
            update(2 * index + 2, mid + 1, right, modifiedI, val);
        }
        maintain(index);
    }
public:
    int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {
        tree.resize(nums.size() * 4);
        buildTree(nums, 0, 0, nums.size() - 1);
        unsigned int ans = 0;
        for (vector<int>& query : queries) {
            update(0, 0, nums.size() - 1, query[0], query[1]);
            ans = (ans + tree[0][3]) % mod;
        }
        return ans;
    }
};
Python
from typing import List

MOD = 1_000_000_007
class Solution:
    def maintain(self, index: int) -> None:
        leftNode = self.tree[2 * index + 1]
        rightNode = self.tree[2 * index + 2]
        self.tree[index] = [
            max(leftNode[0] + rightNode[2], leftNode[1] + rightNode[0]),
            max(leftNode[0] + rightNode[3], leftNode[1] + rightNode[1]),
            max(leftNode[2] + rightNode[2], leftNode[3] + rightNode[0]),
            max(leftNode[2] + rightNode[3], leftNode[3] + rightNode[1])
        ]
    
    def build(self, index: int, left: int, right: int) -> None:
        if left == right:
            self.tree[index][3] = self.nums[left]
            return
        mid = (left + right) >> 1
        self.build(2 * index + 1, left, mid)
        self.build(2 * index + 2, mid + 1, right)
        self.maintain(index)

    def update(self, index: int, left: int, right: int, modifiedI: int, val: int) -> None:
        if left == right:
            self.tree[index][3] = max(0, val)
            return
        mid = (left + right) >> 1
        if modifiedI <= mid:
            self.update(2 * index + 1, left, mid, modifiedI, val)
        else:
            self.update(2 * index + 2, mid + 1, right, modifiedI, val)
        self.maintain(index)

    def maximumSumSubsequence(self, nums: List[int], queries: List[List[int]]) -> int:
        self.tree = [[0, 0, 0, 0] for _ in range(len(nums) * 4)]  # 00, 01, 10, 11
        self.nums = nums
        self.build(0, 0, len(nums) - 1)
        ans = 0
        for q, v in queries:
            self.update(0, 0, len(nums) - 1, q, v)
            ans = (ans + self.tree[0][3]) % MOD
        return ans

Java
class Solution {
    private long[][] tree;  // 诶,如果不是long的话会有两组数据无法通过
    private final int mod = 1000000007;

    private void maintain(int index) {
        long[] leftNode = tree[2 * index + 1];
        long[] rightNode = tree[2 * index + 2];
        tree[index][0] = Math.max(leftNode[0] + rightNode[2], leftNode[1] + rightNode[0]);
        tree[index][1] = Math.max(leftNode[0] + rightNode[3], leftNode[1] + rightNode[1]);
        tree[index][2] = Math.max(leftNode[2] + rightNode[2], leftNode[3] + rightNode[0]);
        tree[index][3] = Math.max(leftNode[2] + rightNode[3], leftNode[3] + rightNode[1]);
    }

    private void build(int[] nums, int index, int left, int right) {
        if (left == right) {
            tree[index][3] = Math.max(0, nums[left]);
            return;
        }
        int mid = (left + right) / 2;
        build(nums, 2 * index + 1, left, mid);
        build(nums, 2 * index + 2, mid + 1, right);
        maintain(index);
    }

    private void update(int index, int left, int right, int modifiedI, int val) {
        if (left == right) {
            tree[index][3] = Math.max(0, val);
            return;
        }
        int mid = (left + right) / 2;
        if (modifiedI <= mid) {
            update(2 * index + 1, left, mid, modifiedI, val);
        } else {
            update(2 * index + 2, mid + 1, right, modifiedI, val);
        }
        maintain(index);
    }

    public int maximumSumSubsequence(int[] nums, int[][] queries) {
        tree = new long[4 * nums.length][4];  // 00, 01, 10, 11
        build(nums, 0, 0, nums.length - 1);
        long ans = 0;
        for (int[] query : queries) {
            update(0, 0, nums.length - 1, query[0], query[1]);
            ans = (ans + tree[0][3]) % mod;
        }
        return (int)ans;
    }
}
Go
package main

type data struct {
    _00 int
    _01 int
    _10 int
    _11 int
}

type seg []data

func max(a int, b int) int {
    if a >= b {
        return a
    }
    return b
}

func maintain(tree seg, index int) {
    leftNode := tree[index * 2 + 1]
    rightNode := tree[index * 2 + 2]
    tree[index] = data{
        max(leftNode._00 + rightNode._10, leftNode._01 + rightNode._00),
        max(leftNode._00 + rightNode._11, leftNode._01 + rightNode._01),
        max(leftNode._10 + rightNode._10, leftNode._11 + rightNode._00),
        max(leftNode._10 + rightNode._11, leftNode._11 + rightNode._01),
    }
}

func build(tree seg, nums []int, index int, left int, right int) {
    if left == right {
        tree[index]._11 = max(0, nums[left])
        return
    }
    mid := (left + right) >> 1
    build(tree, nums, 2 * index + 1, left, mid)
    build(tree, nums, 2 * index + 2, mid + 1, right)
    maintain(tree, index)
}

func update(tree seg, index int, left int, right int, modified int, val int) {
    if left == right {
        tree[index]._11 = max(0, val)
        return
    }
    mid := (left + right) >> 1
    if modified <= mid {
        update(tree, 2 * index + 1, left, mid, modified, val)
    } else {
        update(tree, 2 * index + 2, mid + 1, right, modified, val)
    }
    maintain(tree, index)
}

func maximumSumSubsequence(nums []int, queries [][]int) int {
    tree := make(seg, len(nums) * 4)
    build(tree, nums, 0, 0, len(nums) - 1)
    ans := 0
    for _, query := range queries {
        update(tree, 0, 0, len(nums) - 1, query[0], query[1])
        ans = (ans + tree[0]._11) % 1_000_000_007
    }
    return ans
}

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

Tisfy:https://letmefly.blog.csdn.net/article/details/143452715

标签:index,right,单点,nums,int,tree,3165,LeetCode,left
From: https://blog.csdn.net/Tisfy/article/details/143452715

相关文章

  • 代码随想录算法训练营第十天|leetcode232.用栈实现队列、leetcode225. 用队列实现栈、
    1leetcode232.用栈实现队列题目链接:232.用栈实现队列-力扣(LeetCode)文章链接:代码随想录视频链接:栈的基本操作!|LeetCode:232.用栈实现队列哔哩哔哩bilibili自己的思路:真的第一次接触这个概念,完全没有任何思路,甚至不知道从何下手1.1基本概念栈就是相当于砌墙的砖头,先......
  • 代码随想录算法训练营第九天|leetcode151.翻转字符串里的单词、卡码网55.右旋字符串、
    1leetcode151.翻转字符串里的单词题目链接:151.反转字符串中的单词-力扣(LeetCode)文章链接:代码随想录视频链接:字符串复杂操作拿捏了!|LeetCode:151.翻转字符串里的单词哔哩哔哩bilibili自己的思路:直接将空格去掉,然后分割字符串为列表,在列表中进行翻转,不在字符串内部操作......
  • LeetCode24:两两交换链表中的节点
    原题地址:.-力扣(LeetCode)题目描述给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]输出:[1]......
  • LeetCode25:K个一组翻转链表
    原题地址:.-力扣(LeetCode)题目描述给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需......
  • LeetCode100之滑动窗口最大值(239)--Java
    1.问题描述        给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。        示例1输入:nums=[1,3,-1,-3,5,3,6......
  • [LeetCode] 3226. Number of Bit Changes to Make Two Integers Equal
    Youaregiventwopositiveintegersnandk.Youcanchooseanybitinthebinaryrepresentationofnthatisequalto1andchangeitto0.Returnthenumberofchangesneededtomakenequaltok.Ifitisimpossible,return-1.Example1:Input:n=13......
  • LeetCode题练习与总结:矩形区域不超过 K 的最大数值和--363
    一、题目描述给你一个 mxn 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。题目数据保证总会存在一个数值和不超过 k 的矩形区域。示例1:输入:matrix=[[1,0,1],[0,-2,3]],k=2输出:2解释:蓝色边框圈出来的矩形区域 [[......
  • LeetCode题练习与总结:水壶问题--365
    一、题目描述有两个水壶,容量分别为 x 和 y 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 target 升。你可以:装满任意一个水壶清空任意一个水壶将水从一个水壶倒入另一个水壶,直到接水壶已满,或倒水壶已空。示例1: 输入:x=3,y=5,target=4输出......
  • 2024-11-1-leetcode每日一题-3259. 超级饮料的最大强化能量
    题目描述来自未来的体育科学家给你两个整数数组 energyDrinkA 和 energyDrinkB,数组长度都等于 n。这两个数组分别代表A、B两种不同能量饮料每小时所能提供的强化能量。你需要每小时饮用一种能量饮料来 最大化 你的总强化能量。然而,如果从一种能量饮料切换到另一种,你......