题目
代码
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
}
};
方法一:二维动态规划
分析
该题目与 20210607 每日一题 目标和 类似。假设所有石头和为
s
u
m
sum
sum,将石头分为大小两堆分别用
a
a
a、
b
b
b 表示,
t
a
r
g
e
t
target
target 为最后一块石头的重量,则有:
a
+
b
=
s
u
m
a
−
b
=
t
a
r
g
e
t
\begin{aligned} & a+b=sum \\ & a-b=target \end{aligned}
a+b=suma−b=target 因此我们可以得到:
b
=
s
u
m
−
t
a
r
g
e
t
2
,
b
∈
N
b=\dfrac{sum-target}{2},\ b \in N
b=2sum−target, b∈N 当最后一块石头的重量
t
a
r
g
e
t
target
target 取最小时,
b
b
b 应该取最大值,因此可以将问题转化成能在数组中抽取石头组成和不超过
b
b
b 的最大值。设定二维数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示在前
i
i
i 个元素中,组成和不超过
j
j
j 的最大值。
在二维数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 中,起始值均为
0
0
0。对于第
i
i
i 块石头有选和不选两种情况:
- 如果 j ≥ s t o n e s [ i ] j \geq stones[i] j≥stones[i] 时,如果不选 s t o n e s [ i ] stones[i] stones[i],最大值为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j];如果选 s t o n e s [ i ] stones[i] stones[i],最大值为 d p [ i − 1 ] [ j − s t o n e s [ i ] ] + s t o n e s [ i ] dp[i-1][j-stones[i]]+stones[i] dp[i−1][j−stones[i]]+stones[i],因此总的最大值为: m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − s t o n e s [ i ] ] + s t o n e s [ i ] ) max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]) max(dp[i−1][j],dp[i−1][j−stones[i]]+stones[i])。
- 如果 j < s t o n e s [ i ] j < stones[i] j<stones[i] 时,则不能选 s t o n e s [ i ] stones[i] stones[i],最大值为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]。
综上所述
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 的状态转移方程可表示为:
d
p
[
i
]
[
j
]
=
{
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
s
t
o
n
e
s
[
i
]
]
+
s
t
o
n
e
s
[
i
]
)
i
f
j
≥
s
t
o
n
e
s
[
i
]
d
p
[
i
−
1
]
[
j
]
i
f
j
<
s
t
o
n
e
s
[
i
]
dp[i][j] = \begin{cases} max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]) &if\ j \geq stones[i]\\ dp[i-1][j] &if\ j < stones[i] \end{cases}
dp[i][j]={max(dp[i−1][j],dp[i−1][j−stones[i]]+stones[i])dp[i−1][j]if j≥stones[i]if j<stones[i]
d
p
[
n
]
[
b
]
dp[n][b]
dp[n][b] 即为问题答案。
代码
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = accumulate(stones.begin(), stones.end(), 0);
int n = stones.size(), b = sum / 2;
vector<vector<int>> dp (n + 1, vector<int> (b + 1, 0));
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= b; ++j) {
if(j >= stones[i - 1]) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return sum - 2 * dp[n][b];
}
};
复杂度分析
- 时间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是选取石块的最大值和。
- 空间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是选取石块的最大值和,实现动态规划需要创建 n × b n \times b n×b 的二维数组 d p dp dp。
方法二:一维动态规划
分析
根据方法一的状态转移方程可知,更新
d
p
[
i
]
[
]
dp[i][]
dp[i][] 的每个元素值时,只依赖于
d
p
[
i
−
1
]
[
]
dp[i-1][]
dp[i−1][] 的元素值。因此,可以使用一维滚动数组
d
p
[
j
]
dp[j]
dp[j] 来替代二维数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 对方法三进行空间上的优化。
d
p
[
j
]
dp[j]
dp[j] 的状态转移方程可表示为:
d
p
[
j
]
=
d
p
[
j
]
+
d
p
[
j
−
s
t
o
n
e
s
[
i
]
]
dp[j] = dp[j]+dp[j-stones[i]]
dp[j]=dp[j]+dp[j−stones[i]]
d
p
[
b
]
dp[b]
dp[b] 即为问题答案。
要点
根据方法三的状态转移方程,实现时内层循环需采用倒序遍历的方式更新元素值。
代码
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = accumulate(stones.begin(), stones.end(), 0);
int b = sum / 2;
vector<int> dp (b + 1, 0);
for(auto stone : stones) {
for(int j = b; j >= stone; --j) {
dp[j] = max(dp[j], dp[j - stone] + stone);
}
}
return sum - 2 * dp[b];
}
};
复杂度分析
- 时间复杂度: O ( n × b ) O(n \times b) O(n×b),其中 n n n 是数组长度, b b b 是负值元素和。
- 空间复杂度: O ( b ) O(b) O(b),其中 b b b 是选取石块的最大值和,实现动态规划需要创建 b b b 的一维滚动数组 d p dp dp。