【问题标题】:Max Sum Subsequence with Threshold带阈值的最大和子序列
【发布时间】:2021-01-05 16:18:29
【问题描述】:

给定一个整数数组,找出小于或等于给定阈值的子序列的最大和。

限制:
数组的最大大小为 10^5
数组中元素为 10^9 时的最大大小
阈值范围为 1 到 10^9

例如
输入:
1 2 4 5
10

输出:
10

在上面的例子中,数组是[1,2,4,5],阈值是10。最大和子序列是10,由(1,4,5)组成

import java.util.Arrays;

public class MaxSumSubsequenceWithThreshold {

    class Solution {
        int[] A;
        int threshold;
        Integer[][] dp;
        Solution(int[] A, int threshold) {
            this.A = A; this.threshold = threshold;
            dp = new Integer[A.length][threshold + 1];
        }

        int sum(int pos, int threshold) {
            if (threshold <= 0) return 0;
            if (threshold - A[pos] < 0) return dp[pos][threshold] = Integer.MIN_VALUE;
            if (dp[pos][threshold] != null) return dp[pos][threshold];
            int sum = 0;
            int max = Integer.MIN_VALUE;
            for (int i = pos + 1; i < A.length; i++) {
                sum = sum(i, threshold - A[pos]);
                max = Math.max(max, sum);
            }
            return dp[pos][threshold] = max == Integer.MIN_VALUE ? A[pos] : max + A[pos];
        }

        int solve() {
            Arrays.sort(A);
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < A.length; i++) {
                dp[i][threshold] = sum(i, threshold);
                max = Math.max(max, dp[i][threshold]);
            }
            return max;
        }

    }

    public static void main(String[] args) {
        int[] A = InputUtils.nextIntLine();
        int threshold = InputUtils.nextInt();
        System.out.println(
                new MaxSumSubsequenceWithThreshold().new Solution(A, threshold).solve()
        );
    }

}

在验证此解决方案是否正确时需要帮助。

有更好的解决方案吗?

【问题讨论】:

  • 为了更好的解决方案,不要使用递归。
  • 阈值是否有限制,例如1 &lt;= threshold &lt;= 100 之类的?可以有多少个值?
  • 在描述中添加阈值范围。

标签: java arrays algorithm recursion dynamic-programming


【解决方案1】:

为了获得更好的性能,我只会循环遍历这些值一次,并会在 TreeSet 中跟踪所有以前的总和。

成为Set 会自动消除重复的总和。例如。如果输入是[1, 3, 4, 7],那么当我们处理 7 时,之前看到的总和将是 [1, 3, 4, 1+3, 1+4, 3+4, 1+3+4] = [1, 3, 4, 4, 5, 7, 8] = [1, 3, 4, 5, 7, 8],其中重复的总和 4 已被消除。防止冗余处理。

如果我们找到等于threshold 的总和,我们应该停止查找。这被称为短路。如果我们已经找到了最大可能的总和,则无需继续查找。例如。如果threshold = 8,我们将跳过7值的处理,因为我们已经找到了一个等于threshold的总和,即1+3+4 = 8。

作为SortedSet,我们可以找到一个可以添加到我们正在处理的当前值的子集。例如。如果threshold = 10,当我们处理7时,我们寻找3潜在的短路,那么只需要考虑之前在1-2范围内的和,所以我们可以调用subSet(1, 3)来得到那些.

虽然问题中没有明确说明,但如果我们没有找到解决方案,我们会认为空子序列有效,这意味着 0 是有效结果。此外,虽然没有明确说明,但我们将假设值不能为负。值是否可以超过threshold 并不重要,因为这很容易防范。

使用上述逻辑,我们不会添加超过threshold 的总和。例如。如果threshold = 6,当我们完成时,上面的集合将包含[1, 3, 4, 5]。然后我们可以调用last() 来查找最大值。

这是所有这些的代码:

static int maxSum(int threshold, int... values) {
    TreeSet<Integer> sums = new TreeSet<>();
    for (int value : values) {
        if (value == threshold)
            return threshold; // short-circuit
        if (value < threshold) {
            if (sums.contains(threshold - value))
                return threshold; // short-circuit
            for (int prevSum : sums.subSet(1, threshold - value).toArray(new Integer[0]))
                sums.add(prevSum + value);
            sums.add(value);
        }
    }
    return (sums.isEmpty() ? 0 : sums.last().intValue());
}

注意:我们不能在迭代子集时添加到sums,所以我们需要一个子集的副本。 toArray() 是获取此类副本的最快方式。

测试

System.out.println(maxSum(10, 1,2,4,5));
System.out.println(maxSum(8, 1,3,4,7));
System.out.println(maxSum(10, 1,3,4,7));
System.out.println(maxSum(6, 1,3,4,7));
System.out.println(maxSum(3, 5,7,9));

输出

10
8
10
5
0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多