最大相等分割红包金额 | 豆包MarsCode AI刷题

92 阅读4分钟

题目描述

小U在公司年会上赢得了一等奖,作为一等奖得主,他有机会在一排红包中进行两次切割,将红包分成三部分。要求第一部分和第三部分的红包总金额相等。小U的目标是通过合理的切割获得最大可能的金额。具体来说,题目给定一个红包金额数组,要求我们找到一种方式,切割红包使得第一部分和第三部分的金额相等,并返回第一部分的总金额。

输入:
一个整数列表 redpacks,其中每个整数表示一个红包的金额。

输出:
返回切割后第一部分红包的最大金额。如果没有满足条件的切割方式,返回0。

解题思路

我们要从红包数组中选出切割点,使得第一部分和第三部分的金额相等。为了求解这个问题,我们需要考虑以下几点:

  1. 前缀和与后缀和的使用:
    为了快速计算每一部分的金额,首先我们可以计算前缀和和后缀和。前缀和是从数组的起始位置到某个位置的所有红包金额之和,后缀和则是从某个位置到数组末尾的所有红包金额之和。通过这两种和的预计算,我们可以在常数时间内获取任意区间的红包总金额。

  2. 两指针法:
    为了找到第一部分和第三部分金额相等的切割方式,可以使用双指针(即两端指针法)。一个指针 i 表示第一部分的结束位置,另一个指针 j 表示第三部分的开始位置。初始时,指针 i 从数组的起始位置向右移动,指针 j 从数组的末尾向左移动。通过比较 preSum(i)(第一部分金额)和 postSum(j)(第三部分金额)来调整指针的位置。

  3. 边界条件:

    • 如果第一部分的金额小于第三部分的金额,那么需要扩大第一部分的范围(即移动 i)。
    • 如果第一部分的金额大于第三部分的金额,则需要缩小第三部分的范围(即移动 j)。
    • 如果两者相等,则更新最大金额,并继续调整指针。
  4. 时间复杂度优化:
    利用前缀和和后缀和的预计算,我们将计算每个区间和的时间复杂度从 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)。
  • 空间复杂度:

    • 存储前缀和 preSumRaw 和后缀和 postSumRaw 需要 O(n) 的空间。
    • 除此之外,额外的空间开销是常数级别的,因此空间复杂度为 O(n)。

总结

这个问题通过前缀和与后缀和的预计算,将区间和的查询优化到 O(1) 的时间复杂度。结合双指针法,可以有效地找到满足条件的切割方式。通过这种方法,我们能够在 O(n) 的时间复杂度内解决问题,适用于大规模输入数据的场景。