带阈值的最大和子序列
Max Sum Subsequence with Threshold
给定一个整数数组,找到小于或等于给定阈值的子序列的最大和。
限制因素:
数组的最大大小为 10^5
如果数组中的元素为 10^9
,则最大大小
阈值在 1 到 10^9
范围内
例如
输入:
1 2 4 5
10
输出:
10
在上面的例子中,数组是[1,2,4,5],阈值是10。最大和子序列是由(1,4,5)
组成的10
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()
);
}
}
需要帮助验证此解决方案是否正确。
有更好的解决方案吗?
为了获得更好的性能,我只会循环一次这些值,并会在 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
,我们应该停止寻找。这被称为 short-circuting。如果我们已经找到最大可能的总和,则无需继续寻找。例如。如果 threshold = 8
,我们将跳过 7
值的处理,因为我们已经找到了等于 threshold
的总和,即 1+3+4 = 8
.
作为 SortedSet
允许我们找到一个子集,该子集可以添加到我们正在处理的当前值中。例如。如果threshold = 10
,当我们处理7
时,我们寻找3
潜在的short-circuit,那么只需要考虑1
-[=28=范围内的先前总和],所以我们可以调用 subSet(1, 3)
来获取这些。
虽然问题中没有明确说明,但我们会认为空 sub-sequence 有效,这意味着 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
给定一个整数数组,找到小于或等于给定阈值的子序列的最大和。
限制因素:
数组的最大大小为 10^5
如果数组中的元素为 10^9
,则最大大小
阈值在 1 到 10^9
例如
输入:
1 2 4 5
10
输出:
10
在上面的例子中,数组是[1,2,4,5],阈值是10。最大和子序列是由(1,4,5)
组成的10import 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()
);
}
}
需要帮助验证此解决方案是否正确。
有更好的解决方案吗?
为了获得更好的性能,我只会循环一次这些值,并会在 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
,我们应该停止寻找。这被称为 short-circuting。如果我们已经找到最大可能的总和,则无需继续寻找。例如。如果 threshold = 8
,我们将跳过 7
值的处理,因为我们已经找到了等于 threshold
的总和,即 1+3+4 = 8
.
作为 SortedSet
允许我们找到一个子集,该子集可以添加到我们正在处理的当前值中。例如。如果threshold = 10
,当我们处理7
时,我们寻找3
潜在的short-circuit,那么只需要考虑1
-[=28=范围内的先前总和],所以我们可以调用 subSet(1, 3)
来获取这些。
虽然问题中没有明确说明,但我们会认为空 sub-sequence 有效,这意味着 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