813. 最大平均值和的分组(Python)

Python014

813. 最大平均值和的分组(Python),第1张

难度:★★★☆☆

类型:数组

方法:动态规划

力扣链接请移步 本题传送门

更多力扣中等题的解决方案请移步 力扣中等题目录

我们将给定的数组 A 分成 K 个相邻的非空子数组 ,我们的分数由每个子数组内的平均值的总和构成。计算我们所能得到的最大分数是多少。

注意我们必须使用 A 数组中的每一个数进行分组,并且分数不一定需要是整数。

示例:

输入:

A = [9,1,2,3,9]

K = 3

输出: 20

解释:

A 的最优分组是[9], [1, 2, 3], [9]. 得到的分数是 9 + (1 + 2 + 3) / 3 + 9 = 20.

我们也可以把 A 分成[9, 1], [2], [3, 9].

这样的分组得到的分数为 5 + 2 + 6 = 13, 但不是最大值.

说明:

1 <= A.length <= 100.

1 <= A[i] <= 10000.

1 <= K <= A.length.

答案误差在 10^-6 内被视为是正确的。

数组问题常用动态规划来解决。动态规划的精髓在于,把一个难以解决的大问题转化为若干个可以通过有限次重复解决的简单问题。接下来从动态规划的几个要素进行介绍:

【数组定义】

我们定义一个二维数组dp,维度为输入数组A的维度×K,dp[i][k]表示数组A[:i+1]被分成k+1组时,可以得到的最大分数(各组平均值的和)。这里尤其需要注意python下标和它代表的变量的物理含义之间正好相差1的。

【初始状态】

我们可以很快得知的信息是,如果数组只被分为一组的情况。因此可以先把k=0的一列填好。这一列各个位置dp[i][0]的填充规则,就是A[:i]的均值。

【递推公式】

我们以分组数k和下标i递增的方式,构造嵌套循环。这里还需要留意一个情况,就是填充dp[i][k]时,需要考虑到获得当前位置的各种情况的分组,因此借助一个中间下标变量j,范围从0到i,也就是说,我们把数组A[:i+1]从下标j砍成了两半,因此当前位置处的值可以填充为:

dp[i][k] = max(dp[i][k], dp[j][k-1]+average(j+1, i+1))

这里average是一个函数,用来计算A[i:j]子数组的均值。这里把i和j+1的原因是我们定义dp数组的物理意义决定的。

有了这个递推公式,就可以迭代进行计算了,

注意k的范围是从1到K,因为k=0的一列已经算好了;

i的范围是从k到len(A),因为组的个数不能比数组中数字的个数还要多;

j的范围是小于i,因为必须要保证可以把A[:i+1]分开。

【最后结果】

最终范围dp[-1][-1]也就是最后计算出的值,就可以代表题目所要求的结果。

如有疑问或建议,欢迎评论区留言~

有关更多力扣中等题的python解决方案,请移步 力扣中等题解析

动态规划算法中是将复杂问题递归分解为子问题,通过解决这些子问题来解决复杂问题。与递归算法相比,动态编程减少了堆栈的使用,避免了重复的计算,效率得到显著提升。

先来看一个简单的例子,斐波那契数列.

斐波那契数列的定义如下。

斐波那契数列可以很容易地用递归算法实现:

上述代码,随着n的增加,计算量呈指数级增长,算法的时间复杂度是 。

采用动态规划算法,通过自下而上的计算数列的值,可以使算法复杂度减小到 ,代码如下。

下面我们再看一个复杂一些的例子。

这是小学奥数常见的硬币问题: 已知有1分,2分,5分三种硬币数量不限,用这些硬币凑成为n分钱,那么一共有多少种组合方法。

我们将硬币的种类用列表 coins 定义;

将问题定义为一个二维数组 dp,dp[amt][j] 是使用 coins 中前 j+1 种硬币( coins[0:j+1] )凑成总价amt的组合数。

例如: coins = [1,2,5]

dp[5][1] 就是使用前两种硬币 [1,2] 凑成总和为5的组合数。

对于所有的 dp[0][j] 来说,凑成总价为0的情况只有一种,就是所有的硬币数量都为0。所以对于在有效范围内任意的j,都有 dp[0][j] 为1。

对于 dp[amt][j] 的计算,也就是使用 coins[0:j+1] 硬币总价amt的组合数,包含两种情况计算:

1.当使用第j个硬币时,有 dp[amt-coins[j]][j] 种情况,即amt减去第j个硬币币值,使用前j+1种硬币的组合数;

2.当不使用第j个硬币时,有 dp[amt][j-1] 种情况,即使用前j种硬币凑成amt的组合数;

所以: dp[amt][j] = dp[amt - coins[j]][j]+dp[amt][j-1]

我们最终得到的结果是:dp[amount][-1]

上述分析省略了一些边界情况。

有了上述的分析,代码实现就比较简单了。

动态规划算法代码简洁,执行效率高。但是与递归算法相比,需要仔细考虑如何分解问题,动态规划代码与递归调用相比,较难理解。

我把递归算法实现的代码也附在下面。有兴趣的朋友可以比较一下两种算法的时间复杂度有多大差别。

上述代码在Python 3.7运行通过。