LeetCode 二进制在枚举法上的应用

本文探讨了如何利用二进制位运算和状态压缩技巧解决78子集、90子集II、1434独特帽子组合、1125最小必要团队和847最短路径问题。通过实例展示了如何用二进制表示枚举状态,优化dp状态并剪枝,以及在不同场景下的多源BFS应用。

总结

二进制位运算可以用来枚举,dp状态压缩的时候也可以派上用场。还有像是树状数组就是基于位运算。
考虑用二进制表示枚举状态的情况,有这几个特点,(1)对每个对象可以写成两种状态(2)对象数目不多,一般16个以内(3)需要枚举这几个对象任意状态的组合

题解

78. 子集

对有n个数的的数组,他的子集个数应该是 2 n 2^n 2n,在 [ 0 , 2 n ) [0,2^n) [0,2n)中遍历 i i i,不难发现,将数组中每个数字对应到 i i i 的二进制表示。假设 i i i 为5,即101,那么第 i i i个集合中放入数组中第0和第2个数字。可以发现,由于 i i i 不重复,得到的也正是不重复的 2 n 2^n 2n的集合。

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> ans;
        vector<int> t;
        for(int mask = 0; mask < (1 << n); ++ mask){
            t.clear();
            for(int i = 0; i < n; ++ i){
                if(mask & (1 << i))
                    t.push_back(nums[i]);
            }
            ans.push_back(t);
        }
        return ans;
    }
};

90. 子集 II

对于有重复元素的,先对数组排序,然后要去除的是,上一个数字跟现在数字相同,且上一个数字不在集合中。

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<int> t;
        vector<vector<int>> ans;
        for(int mask = 0; mask < (1 << n); ++ mask){
            t.clear();
            bool flag = true;
            for(int i = 0; i < n; ++ i){
                if(mask & (1 << i)){
                    if(i > 0 && nums[i-1] == nums[i] && (mask & (1 << (i-1)))== 0){
                        flag = false;
                        break;
                    }
                    t.push_back(nums[i]);
                }
            }
            if(flag)
                ans.push_back(t);
        }
        return ans;
    }
};

1434. 每个人戴不同帽子的方案数

这题暴力回溯的话会超时。先来看看暴搜的思路。遍历每个人喜欢的帽子,用一个visit数组存储该序号的帽子是否被人戴了,没有的话就当前人戴上,遍历下一个。遍历到最后一个人的时候方案数就加一。
这样暴搜会超时,想想怎么剪枝?
先对比一下重复运算在哪一块。其实递归只用到两个状态,(1)每个位置的帽子是否有人戴了(2)每个人是否有戴帽子。
假设1号2号人都喜欢a号b号帽子,那么1戴上a,2戴上b与1戴上b,2戴上a这两种情况,对于其他人而言,这个时候其实是重复运算的。那其实我们要有的就是1,2戴上帽子,ab帽子也都被使用的状态下方法数。
那怎么存储这两种状态?考虑到有一个可以放在循环中隐式存储,只用考虑一种状态,发现n是小于10,用n位的二进制表示每个位置的人是否戴上帽子不就可以了!!

class Solution {
public:
    const int mod = 1e9+7;
    int res = 0;
    void dfs(vector<vector<int>> &hats, vector<bool>& visit, int cur){
        if(cur == hats.size()){
            ++ res;
            return;
        }
        for(int i = 0; i < hats[cur].size(); ++ i){
            if(visit[hats[cur][i]] == false){
                visit[hats[cur][i]] = true;
                dfs(hats, visit, cur+1);
                visit[hats[cur][i]] = false;
            }
        }
    }
    int numberWays(vector<vector<int>>& hats) {
        int n = hats.size(), m = 0;
        for(int i = 0; i < hats.size(); ++ i){
            for(int h : hats[i])
                m = max(h, m);
        }
        vector<bool> visit(m+1, false);
        dfs(hats, visit, 0);
        return res;
    }
};

d p [ i ] [ j ] dp[i][j] dp[i][j]表示用了前 [ 1 , i ] [1,i] [1,i]个帽子(不一定这些都被戴上),然后人戴帽子的二进制状态为 j j j此时的方案数。

class Solution {
public:
    const int mod = 1e9+7;
    int numberWays(vector<vector<int>>& hats) {
        int n = hats.size(), m = 0;
        for(int i = 0; i < hats.size(); ++ i){
            for(int h : hats[i])
                m = max(h, m);
        }
        vector<vector<int>> hatsToperson(m+1);
        for(int i = 0; i < hats.size(); ++ i){
            for(int h : hats[i])
                hatsToperson[h].push_back(i);
        }
        vector<vector<int>> dp(m+1, vector<int>(1 << n, 0));
        dp[0][0] = 1;
        for(int i = 1; i <= m; ++ i){
            for(int j = 0; j < (1 << n); ++ j){
                dp[i][j] = dp[i-1][j];
                for(int h : hatsToperson[i]){
                    if(j & (1 << h)){
                        dp[i][j] += (dp[i-1][j^(1<<h)]);
                        dp[i][j] %= mod;
                    }
                }
            }
        }
        return dp[m][(1<<n)-1];
    }
};

当然老规矩,可以压缩成一维的。

class Solution {
public:
    const int mod = 1e9+7;
    int numberWays(vector<vector<int>>& hats) {
        int n = hats.size(), m = 0;
        for(int i = 0; i < hats.size(); ++ i){
            for(int h : hats[i])
                m = max(h, m);
        }
        vector<vector<int>> hatsToperson(m+1);
        for(int i = 0; i < hats.size(); ++ i){
            for(int h : hats[i])
                hatsToperson[h].push_back(i);
        }
        vector<int> dp(1 << n, 0);
        dp[0] = 1;
        for(int i = 1; i <= m; ++ i){
            for(int j = (1 << n)-1; j >= 0; -- j){
                for(int h : hatsToperson[i]){
                    if(j & (1 << h)){
                        dp[j] += (dp[j^(1<<h)]);
                        dp[j] %= mod;
                    }
                }
            }
        }
        return dp[(1<<n)-1];
    }
};

1125. 最小的必要团队

技能数在16以内,考虑用二进制表示每个技能是否达到了。
不过这里的dp的时候,跟之前不太一样,一般来讲需要改变的当前遍历到的dp位置,但是这里对每个人来说,其实受影响的位置是,这个人拥有的技能都点满的那些状态。所以这里其实改变的只是(j|mask),j表示当前遍历到的状态,mask表示的是这个人拥有的技能状态。
如果想改变当前状态的dp的话,需要比较当前状态去除这个人拥有的某些技能的dp,这里不方便写代码。

class Solution {
public:
    vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
        int n = req_skills.size(), m = people.size();
        unordered_map<string,int> mp;
        for(string req_skill : req_skills)
            mp[req_skill] = mp.size();
        vector<int> dp(1<<n, 0x3f3f3f3f);
        vector<vector<int>> team(1<<n);
        dp[0] = 0;
        for(int i = 1; i <= m; ++ i){
            int mask = 0;
            for(string skill : people[i-1])
                mask |= (1 << mp[skill]);
            for(int j = (1<<n) - 1; j >= 0; -- j){
                int x = j | mask;
                if(dp[x] > dp[j] + 1){
                    team[x] = team[j];
                    team[x].push_back(i-1);
                    dp[x] = dp[j]+1;
                }
            }
        }
        return team[(1<<n)-1];
    }
};

847. 访问所有节点的最短路径

这题题意是遍历完所有的节点经历的最短路径,可以从任意点开始。节点数目在12以内。
最短路径想到BFS
任意点开始想到多源BFS
节点数目12以内想到状态压缩,二进制表示状态
本题就是多源BFS+状态压缩

class Solution {
public:
    int shortestPathLength(vector<vector<int>>& graph) {
        int n = graph.size();
        vector<vector<int>> visit(n, vector<int>(1 << n, -1));
        queue<pair<int,int>> q;
        for(int i = 0; i < n; ++ i){
            q.emplace(i, 1 << i);
            visit[i][1<<i] = 0;
        }
        int ans = 0;
        while(!q.empty()){
            auto [u, mask] = q.front();
            q.pop();
            if(mask == (1 << n) - 1){
                ans = visit[u][mask];
                break;
            }
            int dist = visit[u][mask];
            for(int v : graph[u]){
                int mask_v = mask | (1 << v);
                if(visit[v][mask_v] == -1){
                    q.emplace(v, mask_v);
                    visit[v][mask_v] = dist+1;
                }
            }
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值