leetcode位运算专题

前篇知识:

位运算得用途:

&运算

一个数 and 1的结果就是取二进制的最末位,用于判断数得奇偶性

一个数and 该数的负数,保留位中最右边 1 ,且将其余的 1 设位 

|运算

可以给二进制指定位置得值变成1,例如or 1,就是把最末位改成1,然后再减一就是0。这样可以这个数强行变成最接近的偶数。当然or (1 << n)就是末位得前 n  - 1 位变成1。

^运算

两次异或同一个数最后结果不变,即(a xor b) xor b = a。这样可以用来加密文件,把a当作目标值,b当作密钥,c(a^b)为加密值

a xor a = 0;

0 xor a = a;

 

交换a,b值

a:=a xor b;

b:=a xor b;

a:=a xor b;

~运算

如果not的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差,即-res = ~res + 1

<< 和 >>运算

<<二进制数向左移动多少位  >>向右移动多少位

或运算的最小翻转次数

题目:

给你三个正整数 a、b 和 c。

你可以对 a 和 b 的二进制表示进行位翻转操作,返回能够使按位或运算   a OR b == c  成立的最小翻转次数。

「位翻转操作」是指将一个数的二进制表示任何单个位上的 1 变成 0 或者 0 变成 1 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

每个位置的求值之间没有存在联系,对比每个二进制各个位中得位数,不用管a,b,c变成多少,只要翻转得最小次数即可。

代码:

class Solution {
public:
    int minFlips(int a, int b, int c) {
        int res=0;
        while(c!=0 || b!=0 || a!=0) {    //易略点1
            int n1=a%2,n2=b%2,n3=c%2;
            if( (n1 | n2) != n3) {    //难点1
                if(n3 == 1) {
                    res++;
                } else if(n1==0 || n2==0) {
                    res++;
                } else {
                    res+=2;
                }
            }
            a = a >> 1;
            b = b >> 1;
            c = c >> 1;
        }
        return res;
    }
};

总结:

易略点1:这里我们对a,b,c进行判断,不能只对单一数进行判断就跳出循环,因为三个数得位数量可能不同

难点1:如果n1,n2之间没有括号,这时得优先级就不对,表达式中会先计算n2!=n3,再计算n1得或运算

 

只出现一次的数字 III

题目:

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

能AC很简单,直接使用map即可。但是要用位运算就很难了

代码:

map方法:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        map<int,int> mii;
        vector<int> res;
        for(int i=0;i<nums.size();i++) {
            mii[nums[i]]++;
        }
        map<int,int>::iterator iter;
        for(iter=mii.begin();iter!=mii.end();iter++) {
            if(iter->second == 1) {
                res.push_back(iter->first);
            }
            if(res.size()==2) {
                return res;
            }
        }
        return res;
    }
};

位运算法1:

//优化leetcode上的输入输出
static const auto _ = [](){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    return nullptr;
}();

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int xorVal=0;
        for(int num : nums) xorVal ^= num;  //难点1
        int diff = xorVal & (-xorval);    //难点2
        int a=0 , b=0;
        for(int val : nums) {
            //难点3
            if(val & diff) a ^= val;
            else b ^= val;
        }
        return {a,b};
    }
};

位运算2:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int res = 0;
        for(int num : nums) res ^= num;
        int x = 0, diff = res & (-res);
        for(int num : nums) {
            if(num & diff) x ^= num;
        }
        return vector<int>{x, res ^ x};    //难点4
    }
};

总结:

map方法没啥好说的,就是map[数值]=出现次数,这个方法时间空间复杂度都比位运算大

难点1:题目中只存在2个不同数,同一个数 xor 该数为 0,0 xor 任何数为该数,所以xorVal值为2个不同数的异或运算值

难点2:求得xorVal中第一次出现1的位置,例如 xorVal = 276(1010100),那么diff = 0000100 

难点3:用diff进行分类,假设两个不同数为a、b,val & diff中,a,b在diff位上的数一定是不同的,所以就可以进行分类。

res求出了两个唯一数的不同位数,我们以最右边出现1的位置来记作标识位 diff 来进行分类

难点4:第二种方法和第一个方法类似,就是使用的(a xor b) xor b = a 来求值,res为a ^ b ,只要求出a,即可得到b

 

得分最高的单词集合

题目:

你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。

请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

1.回溯法:

使用递归思路,每个单词就是选择与不选的关系,然后我们就分别求出该单词选与不选的大小,对于后面的就使用递归函数计算,最后就是比较选与不选的大小进行返回

2.位压缩法:

规则:二进制位数为0,表示不使用该单词,反则使用。一共有(1 << words.size() )种情况,对于第i 种情况,使用 i & (1 << j )验证在i情况中,第 j 个单词有没有被使用到。用来求得这种情况符不符合题目条件

3.动态规划法:

这个就是最直观得方法,就是对于每个letter字典中有得字符都加上求值,若在中间发现不符合条件后,就再使用循环来回溯为原始状态。思路简单但是需要注意得细节很多

代码:

1.回溯法:

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        vector<int> m(26);
        for(int i=0;i<letters.size();i++) {
            m[letters[i]-'a']++;
        }
        return backtrack(0 , m , words , score);
    }
    int backtrack(int index , vector<int> &m , vector<string>& words , vector<int>& score) {
            if(index >= words.size()) return 0;
            vector<int> t(m);
            int ret = spell(t , words[index] , score);
            int left = ret==0 ? 0 : ret + backtrack(index+1 , t , words , score);
            int right = backtrack(index+1 , m , words , score);
            return max(left , right);
    }
    int spell(vector<int> &t , string str , vector<int>& score) {
        int res=0;
        for(char c : str) {
            if(!t[c - 'a'])  return 0;
            res += score[c - 'a'];
            t[c - 'a']--;
        }
        return res;
    }
};

2.位压缩法:

class Solution {
public:
    vector<int> group(vector<string> &words , int bit) {
        vector<int> g(26,0);
        for(int i=0;i<words.size();i++) {
            if(! (bit & (1 << i)) ) continue;
            for(char c : words[i]) {
                g[c - 'a']++;
            }
        }
        return g;
    }

    int calaScore(vector<int> &group , vector<int> &letterscnt , vector<int>& score) {
        int res=0;
        for(int i=0;i<26;i++) {
            if(letterscnt[i] < group[i]) return 0;
            res += group[i] * score[i];
        }
        return res;
    }

    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        vector<int> letterscnt(26,0);
        int res=0;
        for(int i=0;i<letters.size();i++) {
            letterscnt[letters[i] - 'a']++;
        }
        for(int i=0;i< (1 << words.size() ); i++) {
            auto g = group(words , i);
            res = max(res , calaScore(g , letterscnt , score));
        }
        return res;
    }
};

3.动态规划法:

int lines[26],nums[26];
class Solution {
public:
    int res=0;
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        //memset(lines, 0, sizeof(lines));
        //memset(nums, 0, sizeof(nums));
        for(int i=0;i<letters.size();i++) {
            lines[letters[i] - 'a']++;
        }
        dfs(0 , score , words , 0);
        return res;
    }

    void dfs(int p , vector<int> &score , vector<string> &words,int s) {
        res = max(res , s);
        while(p < words.size()) {
            bool flag = true;
            int news = s;
            for(char c : words[p]) {
                int ch = c - 'a';
                news += score[ch];
                if(++nums[ch] > lines[ch])  flag = false;
            }
            if(flag) {
                dfs(p + 1 , score , words , news);
            }
            for(char c : words[p])   nums[c - 'a']--;
            p++;
        }
    }
};

 

UTF-8 编码验证

题目:

UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:

对于 1 字节的字符,字节的第一位设为0,后面7位为这个符号的unicode码。
对于 n 字节的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

输入是整数数组。只有每个整数的最低 8 个有效位用来存储数据。这意味着每个整数只表示 1 字节的数据。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/utf-8-validation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

1.字符串法:

思路很简单,就是把十进制整数转化成二进制得字符串类型,然后在第一个数判断0得位置,以后得数判断是否以10开头即可

2.位运算法:

位运算就使用到了&操作来判断第一个数的头部有多少个1,然后一样用&操作判断以后得数判断是否以10开头。

代码:

1.字符串法:

class Solution {
    public boolean validUtf8(int[] data) {
        int res=0;
        for(int n : data) {
            //String bir = Integer.toBinayString(n);
            String bir = Integer.toBinaryString(n);     
            bir = bir.length() >= 8 ? bir.substring(bir.length()-8) : "00000000".substring(8 - bir.length()) +  bir;    //易错点1
            if( res == 0 ) {
                for(int i=0;i<bir.length();i++) {
                    if(bir.charAt(i) == '0')    break;
                    res++;
                }
                if(res == 0)    continue;       //易漏点1
                if(res > 4 || res == 1)     return false;
            } else {
                if(! (bir.charAt(0) == '1' && bir.charAt(1) == '0'))    return false;
            }
            res--;      //易错点2
        }
        return res == 0;
    }
}

2.位运算法:

class Solution {
public:
    bool validUtf8(vector<int>& data) {
        int res = 0;
        for(int n : data) {
            if(res == 0) {
                int mask1 = 1 << 7;
                while(mask1 & n) {
                    res++;
                    mask1 >>= 1;        //易错点1
                }
                if(res == 0)    continue;
                if(res > 4 || res == 1)     return false;
            } else {
                //int mask1 = 1 << 7 , mask2 = 1 << 6;      优化
                if( (!((n & (1 << 7)) != 0 && (n & (1 << 6)) == 0)) )     return false;
            }
            res--;
        }
        return res == 0;
    }
};

总结:

第一个方法用的是java编写代码,因为java更加方便,有一些直接符合题意得源函数

1.字符串法:

易错点1:length()方法是针对字符串得,length属性是针对数组得。当不足8位时,需要在前面补0。补位数量也可以使用(bir.length() % 8)

易漏点1:边界情况:若数组以若干个0开头,直接跳过即可

易错点2:字节数得计算,不要忘记了在第一个数计算完后,也要减一。

2.位运算法:

易错点1:mask1对num从左到右判断1得数量,所以循环条件应该是mask>>1

 

插入

题目:

给定两个32位的整数N与M,以及表示比特位置的i与j。编写一种方法,将M插入N,使得M从N的第j位开始,到第i位结束。假定从j位到i位足以容纳M。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/insert-into-bits-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

只要把N 的 i到j位全部重置然后加上M << i 即可

代码:

class Solution {
public:
    int insertBits(int N, int M, int i, int j) {
        for(int k = i;k <= j; k++) {
            if(N & (1 << k))    N -= (1 << k);
        }
        N += (M << i);
        return N;
    }
};
class Solution {
public:
    int insertBits(int N, int M, int i, int j) {
        int temp = (1 << (j - i + 1)) - 1 << i;
        return (N & ~temp) | M << i;
    }
};

总结:

1.我们先考虑会不会溢出的问题,例如1143207437 1017033 11 31,可以看出我们是在N位上替换成M的数,所以不存在溢出INT_MAX的情况。

2.把N 的 i到j位全部重置的方法,当然我们可以像第一个代码一样使用循环,用&运算判断是否为1,再减去即可。但是用"00..00"&N来重置更加快捷。

幂集

题目:

编写一种方法,返回某集合的所有子集。集合中不包含重复的元素

思路:

1.回溯法:

num:记录一个集合的数量,以此为结束回溯的条件。每次都在上次回溯的基础上添加新的元素

2.位运算法:

一共有1<<nums.size()种情况,一样的每一个元素都是只有选与不选。用&运算来做选择条件

代码:

1.回溯法:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        return backtrack(nums , 0);
    }
    vector<vector<int>> backtrack(vector<int> &nums , int num) {
        if(num >= nums.size())  return {{}};
        vector<vector<int>> ivec = backtrack(nums , num+1);
        int sz = ivec.size();
        for(int i=0; i<sz; i++) {
            vector<int> vec = ivec[i];
            vec.push_back(nums[num]);
            ivec.push_back(vec);
        }
        return ivec;
    }
};

2.位运算法:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        const int sz = nums.size();
        const int binCount = 1 << sz;

        vector<vector<int>> res;
        vector<int> vec;

        for(int i=0; i<binCount; i++) {
            for(int j=0;j<sz;j++) 
                if((i >> j) & 1)    vec.push_back(nums[j]);
            res.push_back(vec);
            vec.clear();
        }
        
        return res;
    }
};

总结:

回溯法没啥好说的,就是逻辑问题,什么时候以什么参数来完成回溯条件。

在回溯法转化位运算法中,都是用 1 << size 来记录一共有多少种情况,然后根据题意使用& 或者 | 运算来完成运算。 

 

子数组按位或操作

题目:

我们有一个非负整数数组 A。

对于每个(连续的)子数组 B = [A[i], A[i+1], ..., A[j]] ( i <= j),我们对 B 中的每个元素进行按位或操作,获得结果 A[i] | A[i+1] | ... | A[j]。

返回可能结果的数量。 (多次出现的结果在最终答案中仅计算一次。)

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bitwise-ors-of-subarrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

求出每个子集全部的或操作,直接暴力法简单,但是要使用剪枝技巧来提高效率。

代码:

class Solution {
public:
    int subarrayBitwiseORs(vector<int>& A) {
        int sz = A.size();
        unordered_set<int> res;
        if(sz < 2) return sz;

        for(int i = 0; i < sz; i++) {
            res.insert(A[i]);
            for(int j = i - 1; j >= 0; j--) {
                if((A[j] | A[i]) == A[j])   break;        //剪枝条件
                A[j] |= A[i];
                res.insert(A[j]);
            }
        }
        return res.size();
    }
};

总结:

 

 

字母大小写全排列

题目:

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

思路:

在前面已经修改好了的基础上,每次改变一个字母,以达到递归的效果

代码:

class Solution {
public:
    vector<string> letterCasePermutation(string S) {
        vector<string> res{S};
        for(int i = 0; i<S.size(); i++) {
            if(isalpha(S[i])) {
                for(int j = res.size()-1; j >= 0;j--) {
                    res.push_back(res[j]);
                    if(isupper(res[j][i]))  res[j][i] = tolower(res[j][i]);
                    else    res[j][i] = toupper(res[j][i]);
                }
            }
        }
        return res;
    }
};

总结:

思路很简单,循环体的条件定义和什么时候添加string。这题与位运算好像没啥关系,但是这个代码编写一开始没有想到,所以也记录一下。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值