算法专题1--给定一个数组,和一个正数m,返回数组中所有子序列中累加和(%m)之后的最大值

这篇博客介绍了如何解决给定非负数组arr和正数m,找到所有子序列累加和模m后的最大值。文章详细讨论了三种不同情况的解决方案:当数组元素较小、m值较小以及数组短但数值大且m也大时,分别采用了暴力搜索、动态规划背包算法以及分治策略。代码实现中展示了如何运用这些方法有效地找到最大余数值。
摘要由CSDN通过智能技术生成

目录

一.首先对于第一种情况(如果arr中每个数字不大怎么做),有两种解决方案

1.1第一种就是暴力解决

1.2使用背包解决,通过一个boolean类型的数组,行表示数组元素,列表示0-m所有值,在行里面随便组合,看累加和能否达到列值,得到了这个列就为true,最后只需要在最后一行里面找是true的列,取余得到最大值就可以了。

 二. 前两种情况针对的都是求和较小的情况,如果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);
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值