LeetCode/分隔数组以得到最大和

给你一个整数数组 arr,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。
分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。
返回将数组分隔变换后能够得到的元素最大和

一. 动态规划(正向递推)

分析:数组的最大和是和子数组最大和关联的,对子问题的优化可以带来对整个问题的优化
在求取子数组最优解的同时,可以将其应用到全局最优解中,表现出了无后效性和重叠性,考虑使用动态规划

记dp[i]为以i为结尾的数组最大元素和,列举各个分段长度k取最大值dp[i] = max(dp[i-k]+arrmax(i-k,k)*k)

粗糙但清晰
class Solution {
public:
    int maxSumAfterPartitioning(vector<int>& arr, int k) {
        int n = arr.size();
        vector<int> dp(n);//dp[i]表示以i结尾元素最大和
        for(int i=0;i<n;i++){
            int index = i-k<0?-1:i-k;//最大分段前一个位置
            for(int j=index+1;j<=i;j++){//从最长分段k遍历到最小分段1
                int count = i-j+1;//当前分段元素个数
                int cur;//当前分段最大和
                if(j>0) cur = dp[j-1]+arrmax(j,i,arr)*count;
                else  cur = arrmax(0,i,arr)*count;
                dp[i] = max(dp[i],cur);//更新
            }
        }

        return dp[n-1];
    }
        int arrmax(int j,int i,vector<int>& arr){
            return *max_element(arr.begin()+j,arr.begin()+i+1);//注意该函数调用是左闭右开
        }
};

增加额外空间优化边界
class Solution {
public:
    int maxSumAfterPartitioning(vector<int>& arr, int k) {
        int n = arr.size();
        vector<int> dp(n+1);//dp[i]表示以i结尾元素最大和
        for(int i=0;i<n;i++){
            int index = i-k<0?-1:i-k;//最大分段前一个位置
            for(int j=index+1;j<=i;j++){//从最长分段k遍历到最小分段1
                int count = i-j+1;//当前分段元素个数
                dp[i+1] = max(dp[i+1],dp[j]+arrmax(j,i,arr)*count);//更新
            }
        }
        return dp[n];
    }
        int arrmax(int j,int i,vector<int>& arr){
            return *max_element(arr.begin()+j,arr.begin()+i+1);//注意该函数调用是左闭右开
        }
};

二. 优雅优化写法

增加额外空间不用处理越界,倒序搜索维护最大值(不用额外搜索)

class Solution {
public:
    int maxSumAfterPartitioning(vector<int> &arr, int k) {
        int n = arr.size(); 
        vector<int> dp(n+1);//增加额外空间处理越界
        for (int i = 0; i < n; i++) {//遍历数组的n个位置
            int curmax = 0;
            //i-k为最大段的前一个位置
            for (int j = i; j > i - k && j >= 0; j--) {//从后往前维护最大值
                curmax = max(curmax, arr[j]); // 一边枚举 j,一边计算子数组的最大值
                //dp数组要比i大一个位置,这里就不用取处理越界了
                dp[i+1] = max(dp[i+1], dp[j]+(i-j+1)*curmax);
            }
        }
        return dp[n];
    }
};

三. 反向递归

与动态规划等价

记忆化搜索
class Solution {
public:
    vector<int> memo;
    int maxSumAfterPartitioning(vector<int>& arr, int k) {
        int n = arr.size();
        memo.resize(n,-1);
        return backtrack(arr,n-1,k);
    }
    int backtrack(vector<int>& arr,int i,int k){
        if(i<0) return 0;//边界条件
        if(memo[i]!=-1) return memo[i];//不需要重复计算
        //int index = i-k;//最大分段前一个位置
        int curmax = 0;
         for(int j = i;j > i - k && j >= 0; --j){//这里反向遍历,可以顺便求最大值
             int count = i-j+1;
             curmax = max(curmax,arr[j]);
             memo[i] = max(memo[i], backtrack(arr,j-1,k) + count * curmax);
         }
         return memo[i];
    }
};

四. 线段树(练手)

对这道题来说是增加时间复杂度,但比第一种方法好

线段树
class Solution {
public:
struct TreeNode {
        TreeNode(int x = 0): value(x){}
        int left; // 节点区间左边界
        int right; // 节点区间右边界
        int value; // 节点存储的信息,这里以区间最值为例
};
    vector<TreeNode> tree;
    int maxSumAfterPartitioning(vector<int>& arr, int k) {
        int n = arr.size();
        tree.resize(n*4);
        build(arr,0,0,n-1);
        vector<int> dp(n+1);//dp[i]表示以i结尾元素最大和
        for(int i=0;i<n;i++){
            int index = i-k<0?-1:i-k;//最大分段前一个位置
            for(int j=index+1;j<=i;j++){//从最长分段k遍历到最小分段1
                int count = i-j+1;//当前分段元素个数
                dp[i+1] = max(dp[i+1],dp[j]+query(0,j,i)*count);//更新
            }
        }
        return dp[n];
    }
    void build(vector<int>& arr, int index, int left, int right) {
        tree[index].left = left;
        tree[index].right = right;
        // 叶子节点,存储输入数组的元素值
        if (left == right) {
            tree[index].value = arr[left];
            return;
        } 
        // 非叶子节点,递归构建左子树和右子树
        int mid = (left + right) / 2;
        build(arr,index * 2 + 1, left, mid);//递归建左树
        build(arr, index * 2 + 2, mid + 1, right);//递归建右树
        // 更新当前节点的信息,这里以区间最值为例
        tree[index].value = max(tree[index * 2 + 1].value, tree[index * 2 + 2].value);
}
    int query(int index, int left, int right) {
        // 当前节点区间与查询区间完全重合,直接返回节点存储的信息
        if (tree[index].left == left && tree[index].right == right) 
            return tree[index].value;
        else {
            // 否则,根据查询区间与节点区间的关系递归查询左子树或右子树
            int mid = (tree[index].left + tree[index].right) / 2;
            // 查询区间完全位于左子树
            if (right <= mid) 
                return query(index * 2 + 1, left, right);
            // 查询区间完全位于右子树
            else if (left > mid) 
                return query(index * 2 + 2, left, right);
            // 查询区间同时位于左子树和右子树,取两者查询结果的最值
            else 
                return max(query(index * 2 + 1, left, mid), query(index * 2 + 2, mid + 1, right));
        }
}
};
posted @ 2023-04-19 21:52  失控D大白兔  阅读(31)  评论(0编辑  收藏  举报