给定一个非负数组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!");

    }
}
心之所向,素履以往 生如逆旅,一苇以航