最大总和子序列与阈值

huangapple go评论80阅读模式
英文:

Max Sum Subsequence with Threshold

问题

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

限制条件:

  • 数组的最大大小为 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()
        );
    }

}

需要验证这个解决方案是否正确。

是否有更好的解决方案?

英文:

Given an array of Integers find the Max Sum of Subsequence which is less than or equal to given Threshold.

Constrants:
Max size of Array is 10^5
Max size if element in array is 10^9
Threshold is in range of 1 to 10^9

e.g.
Input:
1 2 4 5
10

Output:
10

In above example array is [ 1, 2, 4, 5 ] and threshold is 10. Max sum subsequence is 10 formed by (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 &lt;= 0) return 0;
if (threshold - A[pos] &lt; 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 &lt; 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 &lt; 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()
);
}
}

Need help in verifying if this solution is correct.

Any better solution ?

答案1

得分: 2

为了获得更好的性能,我只会遍历一次数值,并在一个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());
}

<sup>注意:我们不能在迭代子集时添加到sums中,所以我们需要一个子集的副本。toArray()是获取这样一个副本的最快方法。</sup>

测试

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
英文:

For better performance, I would only loop through the values once, and would keep track of all previous sums in a TreeSet.

Being a Set automatically eliminates duplicate sums. E.g. if input is [1, 3, 4, 7], then by the time we process 7, the sums previously seen will be [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], where the duplicate sum 4 has been eliminated. Prevents redundant processing.

We should stop looking if we find a sum equal to threshold. This is known as short-circuting. No need to keep looking if we found the max possible sum already. E.g. if threshold = 8, we would skip the processing of the 7 value, since we already found a sum equal to threshold, i.e. 1+3+4 = 8.

Being a SortedSet allows us to find a subset that can be added to the current value we're processing. E.g. if threshold = 10, when we process 7 we look for 3 for potential short-circuit, then only need to consider previous sums in range 1-2, so we can call subSet(1, 3) to get those.

Although not explicitly stated in the question, we would consider the empty sub-sequence valid, which means that 0 is a valid result, if we otherwise don't find a solution. Also, while not explicitly stated, we will assume that values cannot be negative. Whether values can exceed threshold doesn't matter much, since that's easy to guard against.

With the above logic we wouldn't add sums that exceed threshold. E.g. if threshold = 6, the above set would contain [1, 3, 4, 5], when we are done. We can then call last() to find the max sum.

Here is the code for all of that:

static int maxSum(int threshold, int... values) {
TreeSet&lt;Integer&gt; sums = new TreeSet&lt;&gt;();
for (int value : values) {
if (value == threshold)
return threshold; // short-circuit
if (value &lt; 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());
}

<sup>Note: We can't add to sums while iterating the subset, so we need a copy of the subset. toArray() is the fastest way to get such a copy.</sup>

Tests

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));

Output

10
8
10
5
0

huangapple
  • 本文由 发表于 2020年9月19日 01:52:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/63960608.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定