LeetCode 2172. 数组的最大与和

一、题目

1、题目描述

  给你一个长度为 n n n 的整数数组 a [ i ] a[i] a[i] 和一个整数 m m m,满足 2 m ≥ n 2m \ge n 2mn。总共有 m m m 个篮子,编号为 1 1 1 m m m。你需要把所有 n n n 个整数分到这些篮子中,且每个篮子至多有 2 2 2 个整数。一种分配方案的 与和 定义为每个数与它所在篮子编号的 按位与运算 结果之和。
  样例输入: a = [1,2,3,4,5,6], m = 3
  样例输出: 9

2、基础框架

  • C语言 版本给出的基础框架代码如下:
int maximumANDSum(int* a, int n, int m){

}

3、原题链接

LeetCode 2172. 数组的最大与和

二、解题报告

1、思路分析

   ( 1 ) (1) (1) 看到题目以后,云里雾里,不知道从哪里下手,哎?突然发现,数据范围竟然这么小,难道是暴力枚举吗?
   ( 2 ) (2) (2) 但是仔细一下,哦,原来是状态压缩。
   ( 3 ) (3) (3) 每个篮子至多只能放2个整数。换言之,每个篮子可以放的整数的个数是 0 0 0 1 1 1 2 2 2
   ( 4 ) (4) (4) 也就是说,如果一个篮子里面已经有 2 2 2 个整数了,那么无论这个篮子里之前是什么整数,都不能再放进来了;如果一个篮子里面有 1 1 1 个整数,那么无论它是什么,都只能放 1 个整数进来。
   ( 5 ) (5) (5) 所以说,一个篮子里到底放了几个整数,我不需要关心,我只关心它放了几个整数,并且与和最大。
   ( 6 ) (6) (6) 于是,我们想到了 三进制状态压缩。对于每个篮子,是三进制上的一个数位,比如说总共 4 个篮子,第一个篮子有 1 个数,第二个篮子有2个数,第三个篮子没有数,第四个篮子有2个数,我们用一个三进制数来表示如下: ( 1202 ) 3 (1202)_3 (1202)3
   ( 7 ) (7) (7) 因为总共有 9 9 9 个篮子,所以总的状态数等于 3 9 = 19683 3^9 = 19683 39=19683,状态数很少,所以我们可以枚举所有状态,然后对于当前状态的篮子情况,我们可以选择任意一个篮子进行状态转移。
   ( 8 ) (8) (8) 例如, 状态 ( 1202 ) 3 (1202)_3 (1202)3 ,我们从第 2 个篮子取出一个数,状态会变成 ( 1102 ) 3 (1102)_3 (1102)3。再从第 4 个篮子取出一个数,状态就变成 ( 1101 ) 3 (1101)_3 (1101)3,那么一直取,一直爽,最后状态会变成 ( 0000 ) 3 (0000)_3 (0000)3,这是初始状态。
   ( 9 ) (9) (9) 这不就是动态规划吗???
   ( 10 ) (10) (10) 直接上记忆化搜索!

2、时间复杂度

   O ( n 2 m ) O(n2^m) O(n2m)

3、代码详解

#define maxs 19684                // (1)
int pow3[11];                     // (2)
int cnt[maxs];                    // (3)
int dp[18][maxs];                 // (4)

#define inf -1                    // (5)

int getStateCount(int state) {    // (6)
    int cnt = 0;
    while(state) {
        cnt += state % 3;
        state /= 3;
    }
    return cnt;
}

int max(int a, int b) {
    return a > b ? a : b;
}

void init() {
    int i;
    pow3[0] = 1;
    for(i = 1; i <= 10; ++i) {
        pow3[i] = pow3[i-1] * 3;
    }
    for(i = 0; i < pow3[9]; ++i) {
        cnt[i] = getStateCount(i);
    }
}

int getStateBit(int state, int pos) {   // (7)
    return state / pow3[pos] % 3;
}

int getNextState(int state, int pos) {  // (8)
    return state - pow3[pos];
}

int dfs(int m, int *a, int cur, int state) {  // (9)
    int x, i;
    int nstate;
    if(cur == -1 && state == 0) {
        return 0;
    }

    if(dp[cur][state] != inf) {
        return dp[cur][state]; 
    }
    dp[cur][state] = -12345;

    for(i = 1; i <= m; ++i) {
        if( 0 == getStateBit(state, i-1) ) {
            continue;
        }
        nstate = getNextState(state, i-1);
        x = dfs(m, a, cur-1, nstate) + (a[cur] & i);
        dp[cur][state] = max(dp[cur][state], x);
    }
    return dp[cur][state];
}

int maximumANDSum(int* a, int n, int m){
    int i, j;
    int ret = -12345;

    init();
    memset(dp, inf, sizeof(dp));

    for(i = 0; i < n; ++i) {
        for(j = 0; j < pow3[m]; ++j) {
            if(cnt[j] != (i+1)) {       // (10)
                continue;
            }
            
            dfs(m, a, i, j);           // (11)
            if(i == n-1) {             // (12)
                ret = max(ret, dp[i][j]);
            }
        }
    }
    return ret;
}


  • ( 1 ) (1) (1) 代表状态总数,总共 3 9 3^9 39 个状态;
  • ( 2 ) (2) (2) pow3[i]代表 3 i 3^i 3i,预先初始化出来;
  • ( 3 ) (3) (3) cnt[state]代表 s t a t e state state 这个状态下用了多少个数;
  • ( 4 ) (4) (4) dp[i][state]代表前面 i i i 个数放到 m m m个篮子中组合出的状态 s t a t e state state 从而得到的最大与和;
  • ( 5 ) (5) (5) inf代表非法状态;
  • ( 6 ) (6) (6) int getStateCount(int state)求的是state这个状态下用了多少个数;
  • ( 7 ) (7) (7) int getStateBit(int state, int pos)表示求state这个状态的从低到高第pos的三进制位对应的值是 0 还是 1 还是 2;
state  -> (12201)_3
index      43210 
pos    ->   3
return ->   2
  • ( 8 ) (8) (8) int getNextState(int state, int pos)表示state这个状态对应的篮子从第pos位取掉一个数得到的状态;
state  -> (12201)_3
index      43210 
pos    ->   3
return -> (11201)_3
  • ( 9 ) (9) (9) dfs实现的就是状态转移,配合记忆化搜索;
    d p [ i ] [ s t a t e ] = m a x x = 1 m ( d p [ i − 1 ] [ n s t a t e ] + ( a [ i ] & x ) ) dp[i][state] = max_{x=1}^{m}(dp[i-1][nstate] + (a[i] \& x)) dp[i][state]=maxx=1m(dp[i1][nstate]+(a[i]&x))
  • ( 10 ) (10) (10) dp状态的第二维计算出来的数字个数和第一维数字个数不相等,代表是一个冲突的状态,直接不用计算;
  • ( 11 ) (11) (11) 计算状态 d p [ i ] [ j ] dp[i][j] dp[i][j]
  • ( 12 ) (12) (12) n n n个数用完的时候,取最大与和;

三、本题小知识

  一个题目看着复杂的时候,我们先看下它的数据范围,如果数据范围小的话,很可能就是 暴力 或者 状态压缩


四、加群须知

  相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」
  那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:

🌌《算法入门指引》🌌

  如果链接被屏蔽,或者有权限问题,可以私聊作者解决。

  大致题集一览:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述



在这里插入图片描述


  为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
  不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」
  🔥联系作者,或者扫作者主页二维码加群,加入刷题行列吧🔥


🔥让天下没有难学的算法🔥

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

让你养成九天持续刷题的习惯
🔥《九日集训》🔥

入门级C语言真题汇总
🧡《C语言入门100例》🧡

组团学习,抱团生长
🌌《算法零基础100讲》🌌

几张动图学会一种数据结构
🌳《画解数据结构》🌳

竞赛选手金典图文教程
💜《夜深人静写算法》💜
  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值