算法:可被三(五、七)整除的子序列最大和

可被三(五、七)整除的最大和

题目描述

1262. 可被三整除的最大和 - 力扣

给出一个整数数组 nums, 找到其中可以被3 整除的最大子序列和。

输入:nums = [3,6,5,1,8]
输出:18
解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。

这个题目经常变,就是变成能被 6 整除,能被 7 整除这些。

流程分析

这乍一看是个0 1 背包问题,每个问题有选和不选两种可能性, 不过在01背包里面选了的话是影响了背包的容量。在这个题呢,选择了某个数之后,一个是影响已经选了的总和,另一个呢就是会影响这个和对3的余数(这个感觉还是很难想的,不过做过了就知道了),在本题呢,就选择余数作为 dp 的第二维度。因此dp 含义为:

dp[i][j]:[0…i) 个数,任意组合,余数为 j 的情况下,可得到的最大和。

主要的问题点呢,在于当选择了这个数后,余数这一维的状态是如何转移的呢,可以先试着暴力写下:

    public static int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[nums.length + 1][3];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][2] = Integer.MIN_VALUE;

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
                if (nums[i - 1] % 3 == 0) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][0] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][1] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][2] + nums[i - 1]);
                } else if (nums[i - 1] % 3 == 1) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + nums[i - 1]);
                } else if (nums[i - 1] % 3 == 2) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][2] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0] + nums[i - 1]);
                }
            }
        }
        return dp[n][0];
    }

for 循环其中的,又可以简化成:

public static int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[nums.length + 1][3];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][2] = Integer.MIN_VALUE;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
            	int x = nums[i-1] % 3;
            	dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][(3 - x + 0) % 3] + nums[i - 1]);
            	dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][(3 - x + 1) % 3] + nums[i - 1]);
            	dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][(3 - x + 2) % 3] + nums[i - 1]);
            }
        }
        return dp[n][0];
    }

又继续简化代码呢:

public static int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[nums.length + 1][3];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][2] = Integer.MIN_VALUE;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
            	dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][(j + 3 - nums[i-1] % 3) % 3] + nums[i - 1]);
            }
        }
        return dp[n][0];
    }

最后附上完整的代码,其中包含了,对数器,及dfs 的暴力解法:、

package leetcode;

import java.util.Collections;
import java.util.Map;

import util.Util;

public class BiggestThree {
    public static void main(String[] args) {
        int testTime = 150000;
        int maxVal = (int)1e5;
        int maxLen = 15;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            baolires = Integer.MIN_VALUE;
            // 生成随机数组
            int[] nums = Util.geneArr(maxVal, maxLen);
            dfs(nums, nums.length - 1, 0);
            int dpres = easyMaxSum(nums);
            if (baolires != dpres) {
                Util.print1DArray(nums);
                System.out.println("baoli: " + baolires);
                System.out.println("dp: " + dpres);
                System.out.println("出错了");
            }
        }
        System.out.println("测试结束");
    }

    static long baolires = Integer.MIN_VALUE;

    //    当前来到看 i 位置,i 及其 之前自由组合所能达到的最大和
    public static void dfs(int[] nums, int i, int path) {
        if (i == -1) {
            if (path % 3 == 0)
                baolires = Math.max(baolires, path);
        } else {
//            选当前数
            dfs(nums, i - 1, path);
//            不选当前数
            dfs(nums, i - 1, path + nums[i]);
        }

    }


    public static int easyMaxSum(int[] nums) {
        int n = nums.length;
        long[][] dp = new long[n + 1][3];

//        for (int i = 0; i < n + 1; i++) {
//            for (int j = 0; j < 3; j++) {
//                dp[i][j] = Integer.MIN_VALUE;
//            }
//        }
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][2] = $$;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
//                如果有负数的话
                long select = dp[i - 1][(j + 3 - nums[i - 1] % 3) % 3] + nums[i - 1];
                long notSelect = dp[i-1][j];
                dp[i][j] = Math.max(select, notSelect);
            }
        }
        return (int)dp[n][0];
    }

    public static int maxSumDivThree(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[nums.length + 1][3];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        dp[0][2] = Integer.MIN_VALUE;

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < 3; j++) {
        /*
            int x = nums[i-1] % 3;
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][(3 - x + 0) % 3] + nums[i - 1]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][(3 - x + 1) % 3] + nums[i - 1]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][(3 - x + 2) % 3] + nums[i - 1]);
         */
                if (nums[i - 1] % 3 == 0) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][0] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][1] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][2] + nums[i - 1]);
                } else if (nums[i - 1] % 3 == 1) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + nums[i - 1]);
                } else if (nums[i - 1] % 3 == 2) {
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + nums[i - 1]);
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][2] + nums[i - 1]);
                    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0] + nums[i - 1]);
                }
            }
        }
        return dp[n][0];
    }
}

其中有哪些需要注意的点呢?

  1. 可以看到其中的 dp 数组初始化为 long ,但是最小值的话赋值为 I n t e g e r . M I N _ V A L U E Integer.MIN\_VALUE Integer.MIN_VALUE, 是为了防止数组中可能有负数的话,如果 dp 数组还是int 类型的话,那么 I n t e g e r . M I N _ V A L U E Integer.MIN\_VALUE Integer.MIN_VALUE 加上一个负数,就变成正数,溢出了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值