每日一题分隔数组以得到最大和
Posted 字节幺零二四
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一题分隔数组以得到最大和相关的知识,希望对你有一定的参考价值。
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];
每日一题670. 最大交换
给定一个非负整数,你至多可以交换一次数字中的任意两位。
返回你能得到的最大值。
输入: 2736
输出: 7236
解释: 交换数字2和数字7。
输入: 9973
输出: 9973
解释: 不需要交换。
排序 + 对比交换
要交换两个数,使其交换后得到最大值。
那么从高位开始,找到第一个没按照降序排列的数,就是我们需要替换的数了。所以,可以通过Arrays.sort()方法,将原有数组进行排序(默认是升序排序,当与原数组对比的时候,我们可以采用对排序后的数组执行倒序遍历即可)。
将原数组与排序数组进行对比,发现第3个位置是不对的,应该是8,但是原数组却是3,所以我们将原数组的3替换为8。
下一步,就是需要将原数组的8替换为3了。此时,为了保证交互后得到最大值,所以我们需要采用逆序遍历的方式,从数组的尾部开始找8,当找到后,将8替换为3即可。
cl
以上是关于每日一题分隔数组以得到最大和的主要内容,如果未能解决你的问题,请参考以下文章
《LeetCode之每日一题》:229.三个无重叠子数组的最大和