跳转至

3196. 最大化子数组的总成本

题目描述

给你一个长度为 n 的整数数组 nums

子数组 nums[l..r](其中 0 <= l <= r < n)的 成本 定义为:

cost(l, r) = nums[l] - nums[l + 1] + ... + nums[r] * (−1)r − l

你的任务是将 nums 分割成若干子数组,使得所有子数组的成本之和 最大化,并确保每个元素 正好 属于一个子数组。

具体来说,如果 nums 被分割成 k 个子数组,且分割点为索引 i1, i2, ..., ik − 1(其中 0 <= i1 < i2 < ... < ik - 1 < n - 1),则总成本为:

cost(0, i1) + cost(i1 + 1, i2) + ... + cost(ik − 1 + 1, n − 1)

返回在最优分割方式下的子数组成本之和的最大值。

注意:如果 nums 没有被分割,即 k = 1,则总成本即为 cost(0, n - 1)

 

示例 1:

输入: nums = [1,-2,3,4]

输出: 10

解释:

一种总成本最大化的方法是将 [1, -2, 3, 4] 分割成子数组 [1, -2, 3][4]。总成本为 (1 + 2 + 3) + 4 = 10

示例 2:

输入: nums = [1,-1,1,-1]

输出: 4

解释:

一种总成本最大化的方法是将 [1, -1, 1, -1] 分割成子数组 [1, -1][1, -1]。总成本为 (1 + 1) + (1 + 1) = 4

示例 3:

输入: nums = [0]

输出: 0

解释:

无法进一步分割数组,因此答案为 0。

示例 4:

输入: nums = [1,-1]

输出: 2

解释:

选择整个数组,总成本为 1 + 1 = 2,这是可能的最大成本。

 

提示:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109

解法

方法一:记忆化搜索

根据题目描述,如果当前数没取反,那么下一个可以取反,也可以不取反;如果当前数取反了,那么下一个只能不取反。

因此,我们定义一个函数 $\textit{dfs}(i, j)$,表示从第 $i$ 个数开始,第 $i$ 个数是否能取反,其中 $j$ 表示第 $i$ 个数是否取反。如果 $j = 0$,表示第 $i$ 个数不能取反,否则可以取反。答案为 $\textit{dfs}(0, 0)$。

函数 $dfs(i, j)$ 的执行过程如下:

  • 如果 $i \geq \textit{len}(nums)$,表示已经遍历完了数组,返回 $0$;
  • 否则,第 $i$ 个数可以不取反,此时答案为 $nums[i] + \textit{dfs}(i + 1, 1)$;如果 $j = 1$,表示第 $i$ 个数可以取反,此时答案为 $\max(\textit{dfs}(i + 1, 0) - nums[i])$。我们取两者的最大值即可。

为了避免重复计算,我们可以使用记忆化搜索,将已经计算过的结果保存起来。

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $nums$ 的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution:
    def maximumTotalCost(self, nums: List[int]) -> int:
        @cache
        def dfs(i: int, j: int) -> int:
            if i >= len(nums):
                return 0
            ans = nums[i] + dfs(i + 1, 1)
            if j == 1:
                ans = max(ans, -nums[i] + dfs(i + 1, 0))
            return ans

        return dfs(0, 0)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
    private Long[][] f;
    private int[] nums;
    private int n;

    public long maximumTotalCost(int[] nums) {
        n = nums.length;
        this.nums = nums;
        f = new Long[n][2];
        return dfs(0, 0);
    }

    private long dfs(int i, int j) {
        if (i >= n) {
            return 0;
        }
        if (f[i][j] != null) {
            return f[i][j];
        }
        f[i][j] = nums[i] + dfs(i + 1, 1);
        if (j == 1) {
            f[i][j] = Math.max(f[i][j], -nums[i] + dfs(i + 1, 0));
        }
        return f[i][j];
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
    long long maximumTotalCost(vector<int>& nums) {
        int n = nums.size();
        long long f[n][2];
        fill(f[0], f[n], LLONG_MIN);
        auto dfs = [&](auto&& dfs, int i, int j) -> long long {
            if (i >= n) {
                return 0;
            }
            if (f[i][j] != LLONG_MIN) {
                return f[i][j];
            }
            f[i][j] = nums[i] + dfs(dfs, i + 1, 1);
            if (j) {
                f[i][j] = max(f[i][j], -nums[i] + dfs(dfs, i + 1, 0));
            }
            return f[i][j];
        };
        return dfs(dfs, 0, 0);
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func maximumTotalCost(nums []int) int64 {
    n := len(nums)
    f := make([][2]int64, n)
    for i := range f {
        f[i] = [2]int64{-1e18, -1e18}
    }
    var dfs func(int, int) int64
    dfs = func(i, j int) int64 {
        if i >= n {
            return 0
        }
        if f[i][j] != -1e18 {
            return f[i][j]
        }
        f[i][j] = int64(nums[i]) + dfs(i+1, 1)
        if j > 0 {
            f[i][j] = max(f[i][j], int64(-nums[i])+dfs(i+1, 0))
        }
        return f[i][j]
    }
    return dfs(0, 0)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function maximumTotalCost(nums: number[]): number {
    const n = nums.length;
    const f: number[][] = Array.from({ length: n }, () => Array(2).fill(-Infinity));
    const dfs = (i: number, j: number): number => {
        if (i >= n) {
            return 0;
        }
        if (f[i][j] !== -Infinity) {
            return f[i][j];
        }
        f[i][j] = nums[i] + dfs(i + 1, 1);
        if (j) {
            f[i][j] = Math.max(f[i][j], -nums[i] + dfs(i + 1, 0));
        }
        return f[i][j];
    };
    return dfs(0, 0);
}

方法二:动态规划

我们可以将方法一中的记忆化搜索转换为动态规划。

定义 $f$ 和 $g$ 为两个状态,其中 $f$ 表示当前数不取反的最大值,而 $g$ 表示当前数取反的最大值。

遍历数组 $nums$,对于第 $i$ 个数,我们可以根据 $f$ 和 $g$ 的状态更新 $f$ 和 $g$ 的值:

  • 如果当前数不取反,那么 $f$ 的值为 $\max(f, g) + x$,表示当前数不取反,那么前一个数可以取反或不取反;
  • 如果当前数取反,那么 $g$ 的值为 $f - x$,表示当前数取反,那么前一个数不能取反

最终答案为 $\max(f, g)$。

时间复杂度 $O(n)$,其中 $n$ 是数组 $nums$ 的长度。空间复杂度 $O(1)$。

1
2
3
4
5
6
class Solution:
    def maximumTotalCost(self, nums: List[int]) -> int:
        f, g = -inf, 0
        for x in nums:
            f, g = max(f, g) + x, f - x
        return max(f, g)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution {
    public long maximumTotalCost(int[] nums) {
        long f = Long.MIN_VALUE / 2, g = 0;
        for (int x : nums) {
            long ff = Math.max(f, g) + x;
            long gg = f - x;
            f = ff;
            g = gg;
        }
        return Math.max(f, g);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Solution {
public:
    long long maximumTotalCost(vector<int>& nums) {
        long long f = LLONG_MIN / 2, g = 0;
        for (int x : nums) {
            long long ff = max(f, g) + x, gg = f - x;
            f = ff;
            g = gg;
        }
        return max(f, g);
    }
};
1
2
3
4
5
6
7
func maximumTotalCost(nums []int) int64 {
    f, g := math.MinInt64/2, 0
    for _, x := range nums {
        f, g = max(f, g)+x, f-x
    }
    return int64(max(f, g))
}
1
2
3
4
5
6
7
function maximumTotalCost(nums: number[]): number {
    let [f, g] = [-Infinity, 0];
    for (const x of nums) {
        [f, g] = [Math.max(f, g) + x, f - x];
    }
    return Math.max(f, g);
}

评论