【1262. 可被三整除的最大和】

这篇具有很好参考价值的文章主要介绍了【1262. 可被三整除的最大和】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

来源:力扣(LeetCode)

描述:

给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。

示例 1:

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

示例 2:

输入:nums = [4]
输出:0
解释:4 不能被 3 整除,所以无法选出数字,返回 0

示例 3:

输入:nums = [1,2,3,4,4]
输出:12
解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。

提示:

  • 1 <= nums.length <= 4 * 104
  • 1 <= nums[i] <= 104

方法:贪心 + 正向思维

我们把数组中的数分成三部分 a, b, c,它们分别包含所有被 3 除余 0 , 1, 2 的数。显然,我们可以选取 a 中所有的数,而对于 b 和 c 中的数,我们需要根据不同的情况选取不同数量的数。

假设我们在 b 中选取了 cntb 个数,c 中选取了 cntc个数,那么这些数的和被 3 除的余数为:

(cnt b + 2 × cnt c)mod3 = (cnt b − cnt c)mod3

我们希望上式的值为 0,那么 cntb 和 cntc 模 3 同余。并且我们可以发现,cntb 一定至少为 ∣b∣ − 2,其中 ∣b∣ 是数组 b 中的元素个数。这是因为如果 cntb ≤ ∣b∣ − 3,我们可以继续在 b 中选择 3 个数,使得 cntb 和 cntc 仍然模 3 同余。同理,cntc 一定至少为 ∣c∣ − 2。

因此,cntb 的选择范围一定在 {∣b∣−2, ∣b∣−1, ∣b∣} 中,cntc 的选择范围一定在 {∣c∣−2, ∣c∣−1, ∣c∣} 中。我们只需要使用两重循环,枚举最多 3 × 3 = 9 种情况。在从 b 或 c 中选取数时,我们可以贪心地从大到小选取数,因此需要对 b 和 c 进行排序。

代码:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        // 使用 v[0], v[1], v[2] 分别表示 a, b, c
        vector<int> v[3];
        for (int num: nums) {
            v[num % 3].push_back(num);
        }
        sort(v[1].begin(), v[1].end(), greater<int>());
        sort(v[2].begin(), v[2].end(), greater<int>());

        int ans = 0;
        int lb = v[1].size(), lc = v[2].size();
        for (int cntb = lb - 2; cntb <= lb; ++cntb) {
            if (cntb >= 0) {
                for (int cntc = lc - 2; cntc <= lc; ++cntc) {
                    if (cntc >= 0 && (cntb - cntc) % 3 == 0) {
                        ans = max(ans, accumulate(v[1].begin(), v[1].begin() + cntb, 0) + accumulate(v[2].begin(), v[2].begin() + cntc, 0));
                    }
                }
            }
        }
        return ans + accumulate(v[0].begin(), v[0].end(), 0);
    }
};

执行用时:28 ms, 在所有 C++ 提交中击败了88.94%的用户
内存消耗:26.7 MB, 在所有 C++ 提交中击败了31.80%的用户
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。对 b 和 c 进行排序需要 O(nlogn) 的时间。两重循环枚举的 9 种情况可以看作常数,每一种情况需要 O(n) 的时间进行求和。
空间复杂度:O(n),即为 a, b, c 需要使用的空间。

方法二:贪心 + 逆向思维

在方法一中,我们使用的是「正向思维」,即枚举 b 和 c 中分别选出了多少个数。我们同样也可以使用「逆向思维」,枚举 b 和 c 中分别丢弃了多少个数。

设 tot 是数组 nums 中所有元素的和,此时 tot 会有三种情况:

  • 如果 tot 是 3 的倍数,那么我们不需要丢弃任何数;
  • 如果 tot 模 3 余 1,此时我们有两种选择:要么丢弃 b 中最小的 1 个数,要么丢弃 c 中最小的 2 个数;
  • 如果 tot 模 3 余 2,此时我们有两种选择:要么丢弃 b 中最小的 2 个数,要么丢弃 c 中最小的 1 个数。

我们同样可以对 b 和 c 进行排序,根据 tot 的情况来选出 b 或 c 中最小的 1 或 2 个数。

下面的代码中使用的是排序的方法。

代码:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        // 使用 v[0], v[1], v[2] 分别表示 a, b, c
        vector<int> v[3];
        for (int num: nums) {
            v[num % 3].push_back(num);
        }
        sort(v[1].begin(), v[1].end(), greater<int>());
        sort(v[2].begin(), v[2].end(), greater<int>());
        int tot = accumulate(nums.begin(), nums.end(), 0);
        int remove = INT_MAX;

        if (tot % 3 == 0) {
            remove = 0;
        }
        else if (tot % 3 == 1) {
            if (v[1].size() >= 1) {
                remove = min(remove, v[1].end()[-1]);
            }
            if (v[2].size() >= 2) {
                remove = min(remove, v[2].end()[-2] + v[2].end()[-1]);
            }
        }
        else {
            if (v[1].size() >= 2) {
                remove = min(remove, v[1].end()[-2] + v[1].end()[-1]);
            }
            if (v[2].size() >= 1) {
                remove = min(remove, v[2].end()[-1]);
            }
        }

        return tot - remove;
    }
};

执行用时:32 ms, 在所有 C++ 提交中击败了76.27%的用户
内存消耗:26.7 MB,在所有 C++ 提交中击败了32.03%的用户
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。对 b 和 c 进行排序需要 O(nlogn) 的时间。也可以不用排序,将时间复杂度优化至 O(n)。
空间复杂度:O(n),即为 a, b, c 需要使用的空间。如果不排序,可以不显示将 a, b, c 求出来,而是直接对数组 nums 进行一次遍历,找出模 3 余 1 和 2 的最小的两个数,将空间复杂度优化至 O(1)。

方法三:动态规划

在上面的两种方法中,我们都是基于贪心的思路,要么选择若干个较大的数,要么丢弃若干个较小的数。我们也可以使用动态规划的方法,不需要进行排序或者贪心,直接借助状态转移方程得出解。

记 f(i, j) 表示前 i (i ≥ 1) 个数中选取了若干个数,并且它们的和模 3 余 j (0 ≤ j < 3) 时,这些数的和的最大值。那么对于当前的数 nums[i],如果我们选取它,那么就可以通过 f(i − 1, (j − nums[i]) mod 3) 转移得来;如果我们不选取它,就可以通过 f(i − 1, j) 转移得来。因此我们可以写出如下的状态转移方程:

f(i, j) = max{ f(i − 1, j), f(i − 1, (j − nums[i]) mod 3) + nums[i] }

边界条件为 f(0, 0) = 0 以及 f(0, 1) = f(0, 2) = −∞。表示当我们没有选取任何数时,和为 0,并且模 3 的余数为 0。对于 f(0, 1) 和 f(0, 2) 这两种不合法的状态,由于我们在状态转移中维护的是最大值,因此可以把它们设定成一个极小值。

在某些语言中,(j − nums[i]) mod 3 可能会引入负数,因此这道题用递推的形式来实现动态规划较为方便,即:

{ f ( i − 1 , j ) → f ( i , j ) f ( i − 1 , j ) + n u m s [ i ] → f ( i , ( j + n u m s [ i ] ) m o d 3 ) \begin{cases} f(i−1, j) → f(i, j)\\ f(i−1,j)+nums[i]→f(i,(j+nums[i])mod3) \end{cases} {f(i1,j)f(i,j)f(i1,j)+nums[i]f(i,(j+nums[i])mod3)

我们还可以发现,所有的 f(i, ⋯) 只会从 f(i − 1, ⋯) 转移得来,因此在动态规划时只需要存储当前第 i 行以及上一行第 i − 1 行的结果,减少空间复杂度。

代码:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        vector<int> f = {0, INT_MIN, INT_MIN};
        for (int num: nums) {
            vector<int> g = f;
            for (int i = 0; i < 3; ++i) {
                g[(i + num % 3) % 3] = max(g[(i + num % 3) % 3], f[i] + num);
            }
            f = move(g);
        }
        return f[0];
    }
};

执行用时:76 ms, 在所有 C++ 提交中击败了15.21%的用户
内存消耗:32.3 MB, 在所有 C++ 提交中击败了28.34%的用户
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
author:LeetCode-Solution文章来源地址https://www.toymoban.com/news/detail-495648.html

到了这里,关于【1262. 可被三整除的最大和】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【1015. 可被 K 整除的最小整数】

    来源:力扣(LeetCode) 描述: 给定正整数 k ,你需要找出可以被 k 整除的、仅包含数字 1 的最 小 正整数 n 的长度。 返回 n 的长度。如果不存在这样的 n ,就返回 -1 。 注意: n 不符合 64 位带符号整数。 示例 1: 示例 2: 示例 3: 提示: 1 = k = 10 5 方法:遍历 思路与算法 题

    2024年02月03日
    浏览(71)
  • 和可被K整除的子数组(Java详解)

    目录 一、题目描述 二、题解 思路分析 具体实现 完整代码 给定一个整数数组  nums  和一个整数  k  ,返回其中元素之和可被  k  整除的(连续、非空)  子数组  的数目。 子数组  是数组的  连续  部分。 示例: 输入:nums = [4,5,0,-2,-3,1],k = 5 输出:7 输入:nums = [ 5 ],

    2024年02月01日
    浏览(36)
  • 【数字IC手撕代码】Verilog模三检测器(判断输入序列能否被三整除)|题目|原理|设计|仿真

    芯片设计验证社区·芯片爱好者聚集地·硬件相关讨论社区·数字verifier星球 四社区 联合力荐 !近500篇 数字IC精品文章收录 ! 【数字IC精品文章收录】学习路线·基础知识·总线·脚本语言·芯片求职·EDA工具·低功耗设计Verilog·STA·设计·验证·FPGA·架构·AMBA·书籍 本系列旨在提

    2024年02月16日
    浏览(41)
  • 【每日一题Day199】LC1010总持续时间可被 60 整除的歌曲 | 哈希表

    总持续时间可被 60 整除的歌曲【LC1010】 在歌曲列表中,第 i 首歌曲的持续时间为 time[i] 秒。 返回其总持续时间(以秒为单位)可被 60 整除的歌曲对的数量。形式上,我们希望下标数字 i 和 j 满足 i j 且有 (time[i] + time[j]) % 60 == 0 。 思路 由于需要求两首歌的总时间可被60整除

    2024年02月03日
    浏览(44)
  • [Algorithm][前缀和][和为K的子数组][和可被K整除的子数组][连续数组][矩阵区域和]详细讲解

    和为 K 的子数组 分析 :因为有 负数 的存在 无法使用\\\" 双指针 \\\"优化,因为 区间求和不具备单调性 可能已经找到前缀和为k的子数组了,但是后面仍然可能会有更长的和为k的子数组存在 思路 :前缀和 + 哈希表 前缀和 : 以 i 位置为结尾 的所有的子数组 将问题转化为 :在

    2024年04月28日
    浏览(33)
  • 力扣日记1262

    LeetCode 1262. 可被三整除的最大和 数组中取数,使得和可以整出3且最大,每个数最多选一次 看数据量,复杂度可以到O(nlogn)往上 1 = nums.length = 4 * 10^4 1 = nums[i] = 10^4 思考一下怎么选数, 先想贪心。看出来这个题的答案是需要选数的组合,贪心不出来。 想动态规划。 对于下标

    2024年02月11日
    浏览(36)
  • 算法——前缀和之除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和

    这几道题对于我们前面讲过的一维、二维前缀和进行了运用,包含了面对特殊情况的反操作 目录 4.除自身以外数组的乘积 4.1解析 4.2题解 5.和为K的子数组 5.1解析 5.2题解 6.和可被K整除的子数组 6.1解析 6.2题解 7.连续数组 7.1题解 7.2题解 8.矩阵区域和 8.1解析 8.2题解 4.除自身以外

    2024年04月14日
    浏览(44)
  • 【LeetCode力扣】189 53 轮转数组 | 最大子数组和

    目录 1、189. 轮转数组 1.1、题目介绍 1.2、解题思路 2、53. 最大子数组和 2.1、题目介绍 2.2、解题思路   原题链接: 189. 轮转数组 - 力扣(LeetCode) ​ 示例 1: 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转

    2024年02月08日
    浏览(37)
  • 670. 最大交换 --力扣 --JAVA

    给定一个非负整数,你 至多 可以交换一次数字中的任意两位。返回你能得到的最大值。 将数字转换成字符数组便于遍历; 寻找是否存在比当前元素大的元素,取最后匹配到的元素,进行交换并跳出循环;

    2024年01月25日
    浏览(33)
  • 力扣164最大间距

            因为昨天写了一个基数排序,今天我来写一道用基数排序实现的题解,希望可以帮助你理解基数排序。 这个题本身不难,就是线性时间和线性额外空间(O(n))的算法,有点难实现  基数排序的时间复杂度是O(d*(n+radix)),其中d是最大值的位数,n是数组长度,radix是基数(

    2024年02月07日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包