有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
示例 1:
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40]
输出:5
示例 3:
输入:stones = [1,2]
输出:1
错误解法
每次都将最大和次大的石头进行消除,此目的在于每次都将最大的石头消除。解法为:每次循环之前先将数组进行从小到大排序,然后取最大和次大的相减,将得到的值重新放入到数组中,并且将最大和次大的值从数组中删除。直到数组的长度等于1或者零,结束循环。
func lastStoneWeightII(stones []int) int {
if len(stones)==0 {
return 0
}
if len(stones)==1{
return stones[0]
}
sort.Ints(stones)
a := len(stones)-1
b := a - 1
tmp := 0
tmp = stones[a] -stones[b]
if tmp!=0{
stones = stones[:len(stones)-2]
stones = append(stones,tmp)
}
return lastStoneWeightII(stones)
}
结果错误:一种情况是,当出现两个最大的时候,然后其他的又比较靠近的时候,这种解法就会出现错误。
正确解法
从计算表达式上看,结合减数和被减数,只要这两个值相减的结果越小越好。假设减数为n,则被减数为sum-n。因此所求的结果为:sum-2n。因此我们只要求出n越接近sum/2越好。对于从一堆数组中求出越接近n的问题跟01背包问题的解法一样。
dp[i]/[j]表示前i个数能凑成和不超过j的最大值。每个物品都有选和不选的两种决策。对于前i(从1开始)个物品来说,如果stones[i-1]>j的话,此时的dp[i]/[j] = dp[i-1]/[j],即不选。如果前i个物品,如果stones[i-1] <= j,此时可以选和可以不选,我们只要这两个决策中的最大值:dp[i]/[j] = max(dp[i-1]/[j],dp[i-1]/[j-stons[i-1]]+stones[i-1])。
代码实现:
package main
import (
"log"
)
func lastStoneWeightII(stones []int) int {
sum :=0
for i:=0;i<len(stones);i++{
sum += stones[i]
}
sum2 := sum / 2
dp := make([][]int,len(stones)+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,sum2+1)
}
n := len(stones)
for i:=1;i<=n;i++{
for j:=0;j<=sum2;j++{
if j<stones[i-1]{
dp[i][j] = dp[i-1][j]
}else if j>=stones[i-1] {
// 这里有点迷惑:为什么是取最大值呢
dp[i][j] = max(dp[i-1][j],dp[i-1][j-stones[i-1]] + stones[i-1])
}
}
}
return sum - 2 * dp[n][sum2]
}
func max(a,b int)int{
if a>b{
return a
}
return b
}
func main(){
log.Println(lastStoneWeightII([]int{31,26,33,21,40}))
}