689. 三个无重叠子数组的最大和(dp)

2023-11-20 13:52
文章标签 数组 dp 最大 三个 重叠 689

本文主要是介绍689. 三个无重叠子数组的最大和(dp),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这道题使用动态规划

解法一:非常规的动态规划

这道题相当于选出3个长度为k的子数组,要求子数组和最大,返回的是每个子数组的第一个元素的下标。
首先,求所有长度为k的子数组的和,即sum,并将结果存储在对应首个元素索引下,例如,

输入:[1,2,1,2,6,7,5,1], 2
则:sum = {3,3,3,8,13,12,6}

如果nums数组长度为n,则sum数组长度为n-k+1
然后,思考找到3个长度为k的数组,并要求子数组和最大,在已经得到了sum的情况下,可以转化为求sum中3个原元素位置不重叠且3个sum元素值和最大。
这里假设选中sum[i],那么只需要求其左右两边最大的sum值,但是注意其下标要不和i重叠,即至少小于等于i-k和大于等于i+k

于是,设置两个数组leftrightleft[i]表示从0isum[i]最大值的下标right[i]表示从n-1isum[i]最大值的下标,注意这里的下标都是sum数组的下标而不是nums数组的。
于是3个选中的sum值的和就应该是
sum[left[i-k]] + sum[i] + sum[right[i+k]]

其中,以k = 2为例,sum[i]相当于nums[i], nums[i+1],而sum[left[i-k]]相当于nums[i-2], nums[i-1]sum[right[i+k]]相当于nums[i+2],nums[i+3],这样就保证了元素的不重叠。

另外,这里求sum数组的时候,并没有使用N*N复杂度的计算,而是采用了首先设定一个初始值然后依次相加相减得到,复杂度降低。

class Solution {
public:vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {vector<int> sum;int cnt = 0;for (int i = 0; i < k; ++i) {cnt += nums[i];}sum.push_back(cnt);for (int i = k; i < nums.size(); ++i) {cnt += nums[i] - nums[i - k];sum .push_back(cnt);}vector<int> ans(3);int n = sum.size();	// 注意是sum的长度vector<int> left(n, 0);vector<int> right(n, n - 1);for (int i = 1; i < n; ++i) {if(sum[i] > sum[left[i - 1]]) left[i] = i;else left[i] = left[i - 1];}for (int i = n - 2; i >= 0; --i) {if(sum[i] >= sum[right[i + 1]]) right[i] = i;else right[i] = right[i + 1];}int mx = 0;for (int i = k; i < n - k; ++i) {if (mx < sum[left[i - k]] + sum[i] + sum[right[i + k]]) {mx = sum[left[i - k]] + sum[i] + sum[right[i + k]];ans = {left[i - k], i, right[i + k]};}}return ans;}
};

解法二:倒序动态规划

首先思考正常的动态规划的思路:
i0开始遍历,分别计算f(i)的值,而f(i)表示以索引值为i的元素为结尾的区间,得出的结果是索引值i更大的区间
在这道题中,我们设置f[i][j]表示以第i个元素为结尾的区间中包含j个不重叠子数组的和,分两种情况讨论:

  1. 不包含Ai,则f[i][j] = f[i-1][j]
  2. 包含Ai,相当于包含了[i-k, i]之间的元素区间,则f[i][j] = f[i-k][j-1] + (Si - Sk)S表示前缀和

f[i][j]在其中取最大值。
上述过程有一种从右向左迭代的感觉

而由于题目里要求字典序从小到大,于是就要使用倒序DP
那么i就变为从末尾向前遍历,而f(i)也变成了以第i个元素为开头的区间,迭代从右向左变成了左向右,于是上述两种情况就变成了:

  1. 不包含Ai,则f[i][j] = f[i+1][j]
  2. 包含Ai,相当于包含了[i, i+k]之间的元素区间,则f[i][j] = f[i+k][j-1] + (Si+k - Si)S表示前缀和

然后再确定好x,区间上届,遍历即可找到答案。

class Solution {
public:vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {vector<int> ans;int n = nums.size();vector<int> s(n + 1);for (int i = 1; i < n + 1; ++i) {s[i] = s[i - 1] + nums[i - 1];}int x = n + 1;vector<vector<int>> f(n + 2, vector<int>(4));for (int i = n - k + 1; i > 0; --i) {for (int j = 1; j <= 3; ++j) {f[i][j] = max(f[i + 1][j], f[i + k][j - 1] + s[i + k - 1] - s[i - 1]);// 注意s的下标}if (f[x][3] <= f[i][3]) x = i;	// 这步是必须的,确定最小下标}int y = 3;while (y > 0) {while (f[x][y] != f[x + k][y - 1] + s[x + k - 1] - s[x - 1]) ++x;// 注意s的下标ans.push_back(x - 1);x += k;--y;}return ans;}
};

这篇关于689. 三个无重叠子数组的最大和(dp)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/395087

相关文章

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

无线路由器哪个品牌好用信号强? 口碑最好的三个路由器大比拼

《无线路由器哪个品牌好用信号强?口碑最好的三个路由器大比拼》不同品牌在信号覆盖、稳定性和易用性等方面各有特色,如何在众多选择中找到最适合自己的那款无线路由器呢?今天推荐三款路由器让你的网速起飞... 今天我们来聊聊那些让网速飞起来的路由器。在这个信息爆炸的时代,一个好路由器简直就是家庭网编程络的心脏。无论你

如何提高Redis服务器的最大打开文件数限制

《如何提高Redis服务器的最大打开文件数限制》文章讨论了如何提高Redis服务器的最大打开文件数限制,以支持高并发服务,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录如何提高Redis服务器的最大打开文件数限制问题诊断解决步骤1. 修改系统级别的限制2. 为Redis进程特别设置限制

vue如何监听对象或者数组某个属性的变化详解

《vue如何监听对象或者数组某个属性的变化详解》这篇文章主要给大家介绍了关于vue如何监听对象或者数组某个属性的变化,在Vue.js中可以通过watch监听属性变化并动态修改其他属性的值,watch通... 目录前言用watch监听深度监听使用计算属性watch和计算属性的区别在vue 3中使用watchE

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上