【LeetCode 1349】Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务

109 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务活动详情

动态规划 + 状态压缩

一、题目描述

给定一个M*N的矩阵表示教室的座位,好的用'.'表示,坏的用'#'表示。规定每个学生在其九宫格的左、左上、右、右上四个位置不能放其他学生,求最多可以放多少个学生。

数据范围

N、M <= 8

二、思路分析

朴素想法: 直接暴搜,复杂度为指数级,不可行。考虑剪枝,由题目的摆放规则,冲突的情况很多,所以能减掉绝大部分情况,且N、M也不是特别大,有可能过时间限制。但这算是下策,由于时间限制没有去尝试,考虑其他方式。

进阶想法: 我们思考下学生之间的影响关系,可知只有前后排的人会互相影响,也就是说学生的摆放是有后效性的。

可以消除这种后效性吗?解决方法就是保存当前一行学生的摆放状态,后续就能根据之前保存的状态去转移,从而避免已经摆放了的前排对后排的影响。

摆放状态怎么保存呢?一行学生就只有8个,很明显,这只需要一个状态压缩就行了。用二进制下的每一位去表示每个位置是否摆放学生,一个不大于 2^8 的数字就能表示所有状态。

题解: 故我们设 f[i][j] 表示做到第 i 行,且第 i 行的学生摆放状态是 j 的最大摆放数量。转移方程就是【当前这行的个数】 + 【做到上一行时的最大数量】

f[i][k] = max( f[i-1][j] ) ,其中 kj 两个状态不冲突(依题意)

三、AC代码

class Solution {
public:
    int f[8][256];

    // 求单独一个状态是否合法,合法的话有几个学生
    int js1(int x, int r, vector<vector<char>>& seats){
        int ans=0;
        for (int i=1,d=0; i<=x; i*=2,d++) {
            if (i&x) {
                if ((i*2)&x) return 0;
                if (seats[r][d] == '#') return 0;
            }
        }
        while (x) {
            ans+=(x%2);
            x/=2;
        }
        return ans;
    }

    // 判断两个状态是否冲突
    int js2(int x, int y) {
        for (int i=1; i<=y; i*=2) {
            if (!(i&y)) continue;
            if (((i*2)&x) || ((i/2)&x)) return 0;
        }
        return 1;
    }

    int maxStudents(vector<vector<char>>& seats) {
        int ans=0;
        int n = seats.size();
        int m = seats[0].size();

        int fm = (1<<m),fn = (1<<n);
        for (int i=0; i<fm; i++) {
            f[0][i] = js1(i,0,seats);
            ans = max(ans, f[0][i]);
        } 
        for (int i=1; i<n; i++) {
            for (int j=0; j<fm; j++) {
                // 做的时候这里遇到了坑,没有把 0 的状态转移好,直接判成continue了
                f[i][0] = max(f[i][0], f[i-1][j]);
                for (int k=0; k<fm; k++) {
                    int now = js1(k,i,seats);
                    if (k==0 || (!js2(j,k))) {
                        continue;
                    }
                    f[i][k] = max(f[i][k], f[i-1][j] + now);
                    ans = max(ans, f[i][k]);
                }
            }
        }
        return ans;
    }
};

四、总结

此类题目需要注重观察题目的要求,弄清楚转移条件再下手去写,这样才能避免写着写着把自己弄乱了。