题目描述
小U在公司年会上赢得了一等奖,作为一等奖得主,他有机会在一排红包中进行两次切割,将红包分成三部分。要求第一部分和第三部分的红包总金额相等。小U的目标是通过合理的切割获得最大可能的金额。具体来说,题目给定一个红包金额数组,要求我们找到一种方式,切割红包使得第一部分和第三部分的金额相等,并返回第一部分的总金额。
输入:
一个整数列表 redpacks,其中每个整数表示一个红包的金额。
输出:
返回切割后第一部分红包的最大金额。如果没有满足条件的切割方式,返回0。
解题思路
我们要从红包数组中选出切割点,使得第一部分和第三部分的金额相等。为了求解这个问题,我们需要考虑以下几点:
-
前缀和与后缀和的使用:
为了快速计算每一部分的金额,首先我们可以计算前缀和和后缀和。前缀和是从数组的起始位置到某个位置的所有红包金额之和,后缀和则是从某个位置到数组末尾的所有红包金额之和。通过这两种和的预计算,我们可以在常数时间内获取任意区间的红包总金额。 -
两指针法:
为了找到第一部分和第三部分金额相等的切割方式,可以使用双指针(即两端指针法)。一个指针i表示第一部分的结束位置,另一个指针j表示第三部分的开始位置。初始时,指针i从数组的起始位置向右移动,指针j从数组的末尾向左移动。通过比较preSum(i)(第一部分金额)和postSum(j)(第三部分金额)来调整指针的位置。 -
边界条件:
- 如果第一部分的金额小于第三部分的金额,那么需要扩大第一部分的范围(即移动
i)。 - 如果第一部分的金额大于第三部分的金额,则需要缩小第三部分的范围(即移动
j)。 - 如果两者相等,则更新最大金额,并继续调整指针。
- 如果第一部分的金额小于第三部分的金额,那么需要扩大第一部分的范围(即移动
-
时间复杂度优化:
利用前缀和和后缀和的预计算,我们将计算每个区间和的时间复杂度从 O(n) 降低到 O(1),使得总体的时间复杂度为 O(n)。
代码实现
package org.example;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static int[] preSumRaw = new int[1];
private static int[] postSumRaw = new int[1];
public static int solution(List<Integer> redpacks) {
int n = redpacks.size();
// preSumRaw[i] means the sum of redpacks from 0 to i (inclusive)
preSumRaw = new int[n];
// postSumRaw[i] means the sum of redpacks from i to n-1 (inclusive)
postSumRaw = new int[n];
// Calculate preSumRaw and postSumRaw
int s = 0;
for (int i = 0; i < n; i++) {
s += redpacks.get(i);
preSumRaw[i] = s;
}
s = 0;
for (int i = n - 1; i >= 0; i--) {
s += redpacks.get(i);
postSumRaw[i] = s;
}
// i is the index of the last redpack in the first group
// j is the index of the first redpack in the second group
int i = -1;
int j = n;
int ret = 0;
while (i < j) {
if (preSum(i) < postSum(j)) {
// need more redpacks in the first group
i++;
} else if (preSum(i) > postSum(j)) {
// need more redpacks in the second group
j--;
} else {
// a valid solution is found
ret = preSum(i);
i++;
}
}
return ret;
}
// Get the sum of redpacks from 0 to i (inclusive). If i is -1, return 0
public static int preSum(int i) {
if (i == -1) {
return 0;
}
return preSumRaw[i];
}
// Get the sum of redpacks from i to n-1 (inclusive). If i is postSumRaw.length, return 0
public static int postSum(int i) {
if (i == postSumRaw.length) {
return 0;
}
return postSumRaw[i];
}
public static void main(String[] args) {
// Test cases
List<Integer> redpacks1 = new ArrayList<>();
redpacks1.add(1);
redpacks1.add(3);
redpacks1.add(4);
redpacks1.add(6);
redpacks1.add(7);
redpacks1.add(14);
System.out.println(solution(redpacks1) == 14); // Output: 14
List<Integer> redpacks2 = new ArrayList<>();
redpacks2.add(10000);
System.out.println(solution(redpacks2) == 0); // Output: 0
List<Integer> redpacks3 = new ArrayList<>();
redpacks3.add(52);
redpacks3.add(13);
redpacks3.add(61);
redpacks3.add(64);
redpacks3.add(42);
redpacks3.add(26);
redpacks3.add(4);
redpacks3.add(27);
redpacks3.add(25);
System.out.println(solution(redpacks3) == 52); // Output: 52
}
}
代码复杂度分析
-
时间复杂度:
- 计算前缀和和后缀和的时间复杂度是 O(n),其中
n是红包数组的长度。 - 双指针法的时间复杂度也是 O(n),因为每次移动指针时,指针
i和j只会向前或向后移动一次。 - 因此,总的时间复杂度为 O(n)。
- 计算前缀和和后缀和的时间复杂度是 O(n),其中
-
空间复杂度:
- 存储前缀和
preSumRaw和后缀和postSumRaw需要 O(n) 的空间。 - 除此之外,额外的空间开销是常数级别的,因此空间复杂度为 O(n)。
- 存储前缀和
总结
这个问题通过前缀和与后缀和的预计算,将区间和的查询优化到 O(1) 的时间复杂度。结合双指针法,可以有效地找到满足条件的切割方式。通过这种方法,我们能够在 O(n) 的时间复杂度内解决问题,适用于大规模输入数据的场景。