【每日一题】分隔数组以得到最大和

1043. 分隔数组以得到最大和

关键词:动态规划、递归

题目来源:1043. 分隔数组以得到最大和 - 力扣(Leetcode)

题目描述
T动态规划
T递归

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

返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。

输入:arr = [1,15,7,9,2,5,10], k = 3
输出:84
输入:arr = [1,4,1,5,7,3,6,1,9,9,3], k = 4
输出:83
输入:arr = [1], k = 1
输出:1
数据范围
1 <= arr.length <= 500
0 <= arr[i] <= 109
1 <= k <= arr.length
问题分析

很容易发现,当确定第一个子数组后,剩下的便是一个与原问题相似的子问题,于是便得到如下朴素解法。

朴素递归

第一个子数组的起点已知,为i(当前问题的第一个子数组的起点为0),枚举第一个子数组的终点j,注意区间长度不能超过k,于是第一个子数组就为[i, j],也即有如下等式

dfs(i) = max( (i-j+1)*maxV + dfs(j+1) )

其中,dfs(i)表示第一个子数组起点为i时的最大和,dfs(0) 便是最终结果。

解空间可近似看做一棵高度为n的k叉树,所以时间复杂度为O(k^n),空间复杂度为O(n)。

记忆化

考虑到递归过程存在许多重复计算,因此可采用记忆化搜索,使用f[i]来记录dfs(i)的结果。

对于每个i,只有f[i]还没计算过时,才会分叉计算,一共分成k叉,于是时间复杂度为O(nk),空间复杂度为O(n)。

动态规划

至此,已经有动态规划的雏形了。递归的时候,i在“递”时是从小到大,“归”时是从大到小,于是,动态规划中的第一层for循环的i便与“归”保持一致,第二层for循环同样是枚举第一个子数组终点。

动态规划的时间复杂度为O(nk),空间复杂度为O(n)。

以上动态规划的思路是从递归转换而来,如果不借助递归,我们可以按如下来思考。

第一个子数组的起点已经确定,只需确定第一个子数组的终点就可完全确定第一个子数组。一旦第一个子数组的终点确定,第二个子数组的起点就确定,只需确定第二个子数组的终点就可以完全确定第二个子数组。依次类推。

由于最后一个子数组的终点也是确定的,于是,可以从前往后枚举每一个起点i,f[i]表示第一个数组起点为i时的最大和,于是f[0]便是题目所求答案。

空间优化

由于本题的n不超过500,所以此步并不是很重要,但这种思路需要注意。

在动态规划中,计算f[i]时,只会用到f[i…i+k+1]共k+1个位置,在计算f[i-1]时,只会用到f[i-1…i+k]共k+1个位置,可以发现,每次用到的位置会后移,于是可采用一个滚动数组来优化空间,用f[i%k]来代替法f[i],但要注意,i与i+k+1是同余的。空间复杂度为O(k)。

代码实现

朴素递归

int maxSumAfterPartitioning(vector<int> &arr, int k) {
    int n = arr.size();
    function<int(int)> dfs = [&](int i) {
        int res = 0;
        // 枚举当前问题的第一个子数组的终点
        for (int j = i, maxV = 0; j < i + k && j < n; j++) {
            maxV = max(arr[j], maxV);
            res = max(dfs(j + 1) + (j - i + 1) * maxV, res);
        }
        return res;
    };
    return dfs(0);
}

记忆化

int maxSumAfterPartitioning(vector<int> &arr, int k) {
    int n = arr.size(), f[n];
    memset(f, -1, sizeof(f));
    function<int(int)> dfs = [&](int i) {
        if (i >= n)return 0;
        int &res = f[i];
        if (res != -1)return res;
        // 枚举当前问题的第一个子数组的终点
        for (int j = i, maxV = 0; j < i + k && j < n; j++) {
            maxV = max(arr[j], maxV);
            res = max(dfs(j + 1) + (j - i + 1) * maxV, res);
        }
        return res;
    };
    return dfs(0);
}

动态规划

int maxSumAfterPartitioning(vector<int> &arr, int k) {
    int n = arr.size(), f[n + 1];
    memset(f, 0, sizeof f);
    for (int i = n - 1; i >= 0; i--) {
        // 枚举当前问题的第一个子数组的终点
        for (int j = i, maxV = 0; j < i + k && j < n; j++) {
            maxV = max(arr[j], maxV);
            f[i] = max(f[j + 1] + (j - i + 1) * maxV, f[i]);
        }
    }
    return f[0];
}

空间优化

int maxSumAfterPartitioning(vector<int> &arr, int k) {
    int n = arr.size(), f[k];
    memset(f, 0, sizeof f);
    for (int i = n - 1; i >= 0; i--) {
        // 由于i和i+k+1同余k,所以在计算过程中不能直接覆盖
        int res = 0;
        // 枚举当前问题的第一个子数组的终点
        for (int j = i, maxV = 0; j < i + k && j < n; j++) {
            maxV = max(arr[j], maxV);
            res = max(f[(j + 1) % k] + (j - i + 1) * maxV, res);
        }
        f[i % k] = res;
    }
    return f[0];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值