给定一个非负数组arr,和一个正数m 返回arr的所有子序列中累加和%m之后的最大值。
import java.util.Arrays;
import java.util.HashSet;
import java.util.TreeSet;
public class Main {
public static int max1(int[] arr, int m) {
HashSet<Integer> set = new HashSet<>();
process(arr, 0, 0, set);
int max = 0;
for (Integer sum : set) {
max = Math.max(max, sum % m);
}
return max;
}
public static void process(int[] arr, int index, int sum, HashSet<Integer> set) {
if (index == arr.length) {
set.add(sum);
} else {
process(arr, index + 1, sum, set);
process(arr, index + 1, sum + arr[index], set);
}
}
public static int max2(int[] arr, int m) {
if (arr == null || arr.length == 0) {
return 0;
}
int n = arr.length;
int sum = Arrays.stream(arr).sum();
/**
* dp[i][j]:是否能使用第0~i个数,组成和为j
*/
boolean[][] dp = new boolean[n][sum + 1];
/**
* base case
*
* dp[i][0] = true
* dp[0][arr[i]] = true
*/
dp[0][0] = true;
dp[0][arr[0]] = true;
for (int i = 1; i < n; ++i) {
dp[i][0] = true;
/**
* 此处,j应该到哪里截止呢
* 显然,j到0最稳妥
* 对于没有状态压缩的dp,dp[i][j]不会继承dp[i-1][j]的状态,所以需要到0
* 而对于状态压缩的的滚动数组,dp[i]继承自上一层的dp[i],所以可以省略dp[i][j] = dp[i - 1][j],
* 从而 j可以到arr[i]截止。
*
*
* 简而言之,但dp不能继承(不管主动还是被动)上一层的状态,需要到0截止。
*
* 而由于不使用滚动数组,逆序或正序遍历都可以
*/
for (int j = sum; j >= 0; --j) {
/**
* 不选用第i个数
*/
dp[i][j] = dp[i - 1][j];
/**
* 选用第i个数
*/
if (j - arr[i] >= 0) {
dp[i][j] = dp[i][j] || dp[i - 1][j - arr[i]];
}
}
}
int ret = 0;
for (int i = sum; i >= 0; --i) {
if (dp[n - 1][i]) {
if (i % m > ret) {
ret = i % m;
}
}
}
return ret;
}
public static int max3(int[] arr, int m) {
if (arr == null || arr.length == 0) {
return 0;
}
int n = arr.length;
/**
* dp[i][j]:是否能使用第0~i个数,组成摸和为j
*/
boolean[][] dp = new boolean[n][m];
/**
* base case
* dp[i][0] = true
*
* dp[0][arr[0] % m] = true
*/
dp[0][0] = true;
dp[0][arr[0] % m] = true;
for (int i = 1; i < n; ++i) {
dp[i][0] = true;
for (int j = m - 1; j >= 0; --j) {
/**
* 不使用第i个元素
*/
dp[i][j] = dp[i - 1][j];
/**
* 使用第i个元素
*
* dp[i][j] = dp[i - 1][?]
*
* 那么?应该是什么呢
*
* 假设 0 ~ (i-1)选择的数的和为x
*
* 则有:
* 1. x % m = ?
* 2. (x + arr[i]) % m = j => (x % m + arr[i] % m) % m = j => (? + arr[i] % m) % m = j
*
* =>
*
* 1. ? = j - arr[i] % m
* 2. ? = m + j - arr[i] % m
*
*/
if (j - arr[i] % m >= 0) {
dp[i][j] |= dp[i - 1][j - arr[i] % m];
}
if (m + j - arr[i] % m < m) {
dp[i][j] |= dp[i - 1][m + j - arr[i] % m];
}
}
}
int ret = 0;
for (int i = m - 1; i >= 0; --i) {
if (dp[n - 1][i]) {
ret = i;
break;
}
}
return ret;
}
// 如果arr的累加和很大,m也很大
// 但是arr的长度相对不大
public static int max4(int[] arr, int m) {
if (arr == null || arr.length == 0) {
return 0;
}
int n = arr.length;
if (n == 1) {
return arr[0] % m;
}
int mid = (n - 1) >> 1;
TreeSet<Integer> treeSet1 = new TreeSet<>();
TreeSet<Integer> treeSet2 = new TreeSet<>();
process(arr, 0, mid, m, 0, treeSet1);
process(arr, mid + 1, n - 1, m, 0, treeSet2);
/**
* 设前部分和为 x,后部分和为 y
* 则
*
* (x + y) % m = (x % m + y % m) % m
*
* 设treeSet1取e1,treeSet2取e2
*
* 1. e1 + e2 < m (e1 + e2) % m = e1 + e2 => 尽可能逼近 m - 1 => e1 + floor(m - 1 - e1)
* 2. e1 + e2 > m 这种情况下,(e1 + e2) % m = e1 + e2 - m
* 而
* e1 + e2 - m < e1 且 e1 + e2 - m < e2
* 所以这种情况不如直接选 e1 和 e2 中的任何一个,故不考虑该情况
*
*/
int ret = 0;
for (Integer e : treeSet1) {
ret = Math.max(ret, e + treeSet2.floor(m - e - 1));
}
return ret;
}
public static void process(int[] arr, int index, int to, int m, int sum, TreeSet<Integer> set) {
if (index == to + 1) {
set.add(sum % m);
} else {
process(arr, index + 1, to, m, sum, set);
process(arr, index + 1, to, m, sum + arr[index], set);
}
}
public static int[] generateRandomArray(int len, int value) {
int[] ans = new int[(int) (Math.random() * len) + 1];
for (int i = 0; i < ans.length; i++) {
ans[i] = (int) (Math.random() * value);
}
return ans;
}
public static void main(String[] args) {
int len = 10;
int value = 100;
int m = 76;
int testTime = 500000;
System.out.println("test begin");
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(len, value);
int ans1 = max1(arr, m);
// int ans2 = max2(arr, m);
// int ans3 = max3(arr, m);
int ans4 = max4(arr, m);
// if (ans1 != ans2) {
// System.out.println("Oops!");
// }
//
// if (ans1 != ans3) {
// System.out.println("Oops!");
// }
if (ans1 != ans4) {
System.out.println("Oops!");
}
}
System.out.println("test finish!");
}
}