目录
一.首先对于第一种情况(如果arr中每个数字不大怎么做),有两种解决方案
二. 前两种情况针对的都是求和较小的情况,如果arr中m值很小,怎么做
三.如果arr长度很短,但arr每个数字比较大,并且m比较大呢?
题目:给定一个非负数组arr,和一个正数m。返回arr的所有子序列中累加和(%m)之后的最大值。
①:如果arr中每个数字不大怎么做:
②:如果arr中m值很小,怎么做:
③:如果arr长度很短,但arr每个数字比较大,并且m比较大呢?
一.首先对于第一种情况(如果arr中每个数字不大怎么做),有两种解决方案
1.1第一种就是暴力解决
碰到数组里面的每一个元素,我们有两种选择,累加他,和不累加他。之后把所以的累加结果放进一个集合中,取余求最大值。
public class SubsquenceMaxModM {
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;
}
//arr[index...]能形成多少个不同的累加和,全部存到set里面
/*
* arr
* index 要不要
* sum -> arr[0...index-1]所做的决定已经形成的累加和是多少
* */
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);//要
}
}
}
1.2使用背包解决,通过一个boolean类型的数组,行表示数组元素,列表示0-m所有值,在行里面随便组合,看累加和能否达到列值,得到了这个列就为true,最后只需要在最后一行里面找是true的列,取余得到最大值就可以了。
public class SubsquenceMaxModM {
//使用背包算法解决
public static int max2(int[] arr,int m){
int sum =0;
int N = arr.length;//得到行数
for (int i = 0;i<N;i++){
sum += arr[i];//得到列数
}
boolean[][] dp = new boolean[N][sum+1];//0~sum
for (int i =0;i<N;i++){
dp[i][0] = true;//第一列不用数字也能达到
}
dp[0][arr[0]] = true;//第一行的arr[0]这个位置可以达到,因为刚好就是他自己,他也只能组成他自己
//到这里第一行已经解决了,第一行就只可能有两个true。
//0行 不用管了
for (int i=1;i<N;i++){
for (int j=1;j<=sum;j++){
dp[i][j] = dp[i-1][j];//不使用arr[i]
if (j-arr[i]>=0){//arr[i] = 500万 j=7,根本用不了arr[i]
dp[i][j] = dp[i][j] | dp[i-1][j-arr[i]];//使用arr[i],其余的组成j-arr[i].
}
}
}
int ans = 0;
for (int j=0;j<=sum;j++){
if (dp[N-1][j]){
ans = Math.max(ans,j%m);//前面能到达的,最后一行肯定能到达,所以只看最后一行就可以了
}
}
return ans;
}
}
二. 前两种情况针对的都是求和较小的情况,如果arr中m值很小,怎么做
package 算法题;
import java.util.HashSet;
import java.util.TreeSet;
/*
*给定一个非负数组arr,和一个正数m,返回arr的所有子序列中累加和%m之后的最大值。
*/
public class SubsquenceMaxModM {
//上面两种方法只考虑累加和不大的情况,不可能有那么多列
public static int max3(int[] arr,int m){
int N = arr.length;
boolean[][] dp = new boolean[N][m];//0~m
for (int i =0;i<N;i++){
dp[i][0] = true;//第一列指的是,arr[0...i],任意选择,相加之后对m取余之后,等于零,所以第一列都为true
}
dp[0][arr[0]%m] = true;//第一行,只能选择选不选arr[0],所以arr[0]对m取余是arr[0],所以arr[0]取余那一列就是true
for (int i=1;i<N;i++){
for (int j=1;j<m;j++){
dp[i][j] = dp[i-1][j];//不用arr[i]的值,arr[0...i-1]能不能对m取余得到j
//获取arr[i]%m,就有两种情况,一是,arr[i]%m值比j小,那么只需要arr[i]和arr[0..i]任意选取得到的余数之和为j就可以
//另一种就是得到的余数比j还大,那么就需要证明arr[0..i]任意选取得到的余数和arr[i]%m得到的余数之和对m取余为j
int cur = arr[i]%m;
if (j-cur>=0){
dp[i][j] = dp[i][j] | dp[i-1][j-cur];//用arr[i]的值,arr[i]%m是cur,arr[0...i-1]任意选取取余得到j-cur就可以
}
if (m+j-cur<m){
dp[i][j] = dp[i][j] | dp[i-1][m+j-cur];
}
}
}
int ans = 0;
for (int i=0;i<m;i++){
if (dp[N-1][i]){
ans = i;//最后一个为true的地方的列值,就是最大的余数
}
}
return ans;
}
}
三.如果arr长度很短,但arr每个数字比较大,并且m比较大呢?
把arr分成两部分,分别求出两部分各自对应的求和之后对m取余的结果,放在一个TreeSet里面,排好序。
例如,假设N为35,可以分成arr[0]-arr[18],arr[19]-arr[34]两部分,之后两部分余数相加,得到一个最靠近m的值,就是最大的余数,因为两部分各自得到的值,表示各自决定求和用到数组的哪些元素(合起来就表示得到最大的余数需要哪些元素求和之后对m取余)。
package 算法题;
import java.util.HashSet;
import java.util.TreeSet;
/*
*给定一个非负数组arr,和一个正数m,返回arr的所有子序列中累加和%m之后的最大值。
*/
public class SubsquenceMaxModM {
//m和累加和都达到了10的9次方,都很大,上面就不适用了。
public static int max4(int[] arr,int m){
if (arr.length == 1)
return arr[0] %m;
int mid = (arr.length-1)/2;
TreeSet<Integer> sortSet1 = new TreeSet<>();//把装进来的余数都排好序
process4(arr,0,0,mid,m,sortSet1);
TreeSet<Integer> sortSet2 = new TreeSet<>();
process4(arr,mid+1,0,arr.length-1,m,sortSet2);
int ans = 0;
for (Integer left:sortSet1){
//<=m-1-left 最大的元素
//sortSet2.floor(m-1-left) floor方法返回的是集合里小于等于给定元素的最大值
ans = Math.max(ans,left+sortSet2.floor(m-1-left));
}
return ans;
}
/*
* int index,int end决定了arr[index,end]范围,选择多少个数据,进行任意选择求和sum之后对m取余,之后将余数都放进集合里
* */
public static void process4(int[]arr,int index,int sum,int end,int m,TreeSet sortSet){
if (index ==end+1){
sortSet.add(sum%m);
}else{
process4(arr,index+1,sum,end,m,sortSet);
process4(arr,index+1,sum+arr[index],end,m,sortSet);
}
}
}