二进制和位运算

目录

一元运算

一元运算模板

力扣 2749. 得到整数零需要执行的最少操作数

左移右移

CSU 1318 Small change

CSU 1530 Gold Rush

力扣 190. 颠倒二进制位

力扣 476. 数字的补数

力扣 1009. 十进制整数的反码

二元运算

异或

CSU 1217 奇数个的那个数

力扣 1375. 二进制字符串前缀一致的次数

力扣 1177. 构建回文串检测

力扣 2425. 所有数对的异或和

力扣 剑指 Offer 56 - I. 数组中数字出现的次数(寻找仅出现一次的2个数)

与、或

CSU 2055 Wells‘s Lottery

力扣 2568. 最小无法得到的或值

综合运用

力扣 191. 位1的个数(补码、位与、异或)


一元运算

一元运算模板

//二进制运算,n>0
class Bits {
public:
	//二进制最低位
	static inline long long getLowBit(long long n) {
		return n & (-n);
	}
	//二进制最高位
	static inline long long getHighBit(long long n) {
		while (n != getLowBit(n)) n ^= getLowBit(n);
		return n;
	}
	//是否是2的幂
	static inline bool isPowerOf2(long long n) {
		return n == getLowBit(n);
	}
	//二进制总位数
	static inline int getBitLength(long long n) {
		int ans = 0;
		while (n)
		{
			n >>= 1;
			ans++;
		}
		return ans;
	}
	//二进制中1的个数
	static inline int getNum1(long long n) {
		int ans = 0;
		while (n)
		{
			n ^= getLowBit(n);
			ans++;
		}
		return ans;
	}
	//二进制中0的个数,只算最高位1后面的0
	static inline int getNum0(long long n) {
		return getBitLength(n) - getNum1(n);
	}
	//二进制反转,01互换,仅限最高位1后面的位
	static inline long long getReverseNum(long long n) {
		long long a = 1, len = getBitLength(n);
		return (a << len) - 1 ^ n;
	}
};

力扣 2749. 得到整数零需要执行的最少操作数

给你两个整数:num1 和 num2 。

在一步操作中,你需要从范围 [0, 60] 中选出一个整数 i ,并从 num1 减去 2i + num2 。

请你计算,要想使 num1 等于 0 需要执行的最少操作数,并以整数形式返回。

如果无法使 num1 等于 0 ,返回 -1 。

示例 1:

输入:num1 = 3, num2 = -2
输出:3
解释:可以执行下述步骤使 3 等于 0 :
- 选择 i = 2 ,并从 3 减去 22 + (-2) ,num1 = 3 - (4 + (-2)) = 1 。
- 选择 i = 2 ,并从 1 减去 22 + (-2) ,num1 = 1 - (4 + (-2)) = -1 。
- 选择 i = 0 ,并从 -1 减去 20 + (-2) ,num1 = (-1) - (1 + (-2)) = 0 。
可以证明 3 是需要执行的最少操作数。

示例 2:

输入:num1 = 5, num2 = 7
输出:-1
解释:可以证明,执行操作无法使 5 等于 0 。

提示:

  • 1 <= num1 <= 109
  • -109 <= num2 <= 109
class Solution {
public:
	int makeTheIntegerZero(int x, int y) {
		for (long long k = 1; k < 33; k++) {
			long long x2 = x - k * y;
			if (k <= x2 && FgetNum1(x2) <= k )return k;
		}
		return -1;
	}
};

左移右移

CSU 1318 Small change

题目:

Description
打完网赛,就到了晚饭的时间,但CSU_ACM的同学们都已经没力气出去了,这时CX建议大伙一起点餐吧,因为正是饭点,CX为了不让大家等太久,找了一个承诺20分钟送到超时要打折的外卖。但CX的RP都在网赛上用光了,果然送餐的迟到了,按规定咱们是要少给钱的。可是那些送餐员十分的狡猾,他们没有带零钱,于是乎,原价为N元的饭,由于他们的迟到可能需要降价,这些狡猾的送餐员会随机报一个数∈(1,N),如果CSU_ACM的小基友没有恰好这么多钱的话,送餐员还是按原价收取饭钱。为了CSU_ACM的最大利益,想知道最少由多少张钞票可以应对送餐员的任意要求(每张钞票的价值可为任意正整数),不论送餐员报的数字为多少总能给出相应的零钱。

Input
多组数据(不超过20组),输入到文件结束。

输入为CSU_ACM的小基友们点餐的总价N.(1<=N<=100000)

Output
输出为CSU_ACM的小基友们准备的零钱的最少张数。每个测试数据一行。

Sample Input
1
2
5
Sample Output
1
2
3

代码:
 

#include
using namespace std;
 
int main()
{
	int n, a;
	while (cin >> n)
	{
		a = 1;
		while ((1 << a) < n + 1)a++;
		cout << a << endl;
	}
	return 0;
}

CSU 1530 Gold Rush

题目:

二进制和位运算_第1张图片

Input

Output

Sample Input

3
2 2 2
2 1 3
10 1000 24

Sample Output

1
2
7

代码:

#include
using namespace std;
 
int main()
{
	int t, n;
	long long a, b;
	cin >> t;
	while (t--)
	{
		cin >> n >> a >> b;
		while (a % 2 == 0)n--, a /= 2;
		cout << n << endl;
	}
	return 0;
}

力扣 190. 颠倒二进制位

题目:

颠倒给定的 32 位无符号整数的二进制位。

示例 1:

输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
      因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:

输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
      因此返回 3221225471 其二进制表示形式为 10101111110010110010011101101001。
 

提示:

请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。

进阶:
如果多次调用这个函数,你将如何优化你的算法?

代码:

class Solution {
public:
	uint32_t reverseBits(uint32_t n) {
		uint32_t ans = 0;
		for (int i = 0; i < 32; i++)
		{
			ans = ans * 2 + n % 2, n /= 2;
		}
		return ans;
	}
};

力扣 476. 数字的补数

对整数的二进制表示取反(0 变 1 ,1 变 0)后,再转换为十进制表示,可以得到这个整数的补数。

  • 例如,整数 5 的二进制表示是 "101" ,取反后得到 "010" ,再转回十进制表示得到补数 2 。

给你一个整数 num ,输出它的补数。

示例 1:

输入:num = 5
输出:2
解释:5 的二进制表示为 101(没有前导零位),其补数为 010。所以你需要输出 2 。

示例 2:

输入:num = 1
输出:0
解释:1 的二进制表示为 1(没有前导零位),其补数为 0。所以你需要输出 0 。

提示:

  • 1 <= num < 231
class Solution {
public:
    int findComplement(int num) {
        return Bits::getReverseNum(num);
    }
};

力扣 1009. 十进制整数的反码

每个非负整数 N 都有其二进制表示。例如, 5 可以被表示为二进制 "101"11 可以用二进制 "1011" 表示,依此类推。注意,除 N = 0 外,任何二进制表示中都不含前导零。

二进制的反码表示是将每个 1 改为 0 且每个 0 变为 1。例如,二进制数 "101" 的二进制反码为 "010"

给你一个十进制数 N,请你返回其二进制表示的反码所对应的十进制整数。

示例 1:

输入:5
输出:2
解释:5 的二进制表示为 "101",其二进制反码为 "010",也就是十进制中的 2 。

示例 2:

输入:7
输出:0
解释:7 的二进制表示为 "111",其二进制反码为 "000",也就是十进制中的 0 。

示例 3:

输入:10
输出:5
解释:10 的二进制表示为 "1010",其二进制反码为 "0101",也就是十进制中的 5 。

提示:

  1. 0 <= N < 10^9
class Solution {
public:
    int bitwiseComplement(int num) {
        return num?Bits::getReverseNum(num):1;
    }
};

二元运算

与运算、或运算是环非域,异或运算是域。

参考数学与泛型编程(4)环

异或

CSU 1217 奇数个的那个数

Description

给定些数字,这些数中只有一个数出现了奇数次,找出这个数。

Input

每组数据第一行n表示数字个数,1 <= n <= 2 ^ 18 且 n % 2 == 1。

接下来n行每行一个32位有符号整数。

Output

出现奇数次那个数,每组数据对应一行。

Sample Input

5
1
1
2
2
3

7
1
2
1
2
2
3
3
Sample Output

3
2

事实证明,异或的计算往往与二进制有关。

本题就是用二进制解决的。

代码:
 

#include
int main()
{
    int n;
    int m;
    int r;
    while(scanf("%d",&n)==1)
    {
        r=0;
        while(n--)
        {
            scanf("%d",&m);
            r^=m;
        }       
        printf("%d\n",r);
    }
    return 0;
}

如果本题改成,有1个数出现了3k+1次,其他的数都出现了3的某个倍数次,要找这个数。

那么解法就是,把所有的数表示成三进制,然后把所有的数加起来,最后把每一位模3,即可得到结果。

把题目如此修改的创意,以及三进制的解法,来自我的朋友http://my.csdn.net/good_night_sion_

更一般的,如果是除了某个数之外,所有的数都出现了n的倍数次,那么用k进制就可以求解,其中k是不小于n的任何整数

力扣 1375. 二进制字符串前缀一致的次数

给你一个长度为 n 、下标从 1 开始的二进制字符串,所有位最开始都是 0 。我们会按步翻转该二进制字符串的所有位(即,将 0 变为 1)。

给你一个下标从 1 开始的整数数组 flips ,其中 flips[i] 表示对应下标 i 的位将会在第 i 步翻转。

二进制字符串 前缀一致 需满足:在第 i 步之后,在 闭 区间 [1, i] 内的所有位都是 1 ,而其他位都是 0 。

返回二进制字符串在翻转过程中 前缀一致 的次数。

示例 1:

输入:flips = [3,2,4,1,5]
输出:2
解释:二进制字符串最开始是 "00000" 。
执行第 1 步:字符串变为 "00100" ,不属于前缀一致的情况。
执行第 2 步:字符串变为 "01100" ,不属于前缀一致的情况。
执行第 3 步:字符串变为 "01110" ,不属于前缀一致的情况。
执行第 4 步:字符串变为 "11110" ,属于前缀一致的情况。
执行第 5 步:字符串变为 "11111" ,属于前缀一致的情况。
在翻转过程中,前缀一致的次数为 2 ,所以返回 2 。
示例 2:

输入:flips = [4,1,2,3]
输出:1
解释:二进制字符串最开始是 "0000" 。
执行第 1 步:字符串变为 "0001" ,不属于前缀一致的情况。
执行第 2 步:字符串变为 "1001" ,不属于前缀一致的情况。
执行第 3 步:字符串变为 "1101" ,不属于前缀一致的情况。
执行第 4 步:字符串变为 "1111" ,属于前缀一致的情况。
在翻转过程中,前缀一致的次数为 1 ,所以返回 1 。
 

提示:

n == flips.length
1 <= n <= 5 * 104
flips 是范围 [1, n] 中所有整数构成的一个排列

class Solution {
public:
	int numTimesAllBlue(vector& flips) {
		mapm;
		int num = 0, ans = 0;
		for (int i = 0; i < flips.size(); i++) {
			if (flips[i] == i + 1) {
				ans += (num == 0);
				continue;
			}
			m[i + 1] ^= 1;
			m[flips[i]] ^= 1;
			if (m[i + 1])num++; else num--;
			if(m[flips[i]])num++; else num--;
			ans += (num == 0);
		}
		return ans;
	}
};

力扣 1177. 构建回文串检测

给你一个字符串 s,请你对 s 的子串进行检测。

每次检测,待检子串都可以表示为 queries[i] = [left, right, k]。我们可以 重新排列 子串 s[left], ..., s[right],并从中选择 最多 k 项替换成任何小写英文字母。 

如果在上述检测过程中,子串可以变成回文形式的字符串,那么检测结果为 true,否则结果为 false。

返回答案数组 answer[],其中 answer[i] 是第 i 个待检子串 queries[i] 的检测结果。

注意:在替换时,子串中的每个字母都必须作为 独立的 项进行计数,也就是说,如果 s[left..right] = "aaa" 且 k = 2,我们只能替换其中的两个字母。(另外,任何检测都不会修改原始字符串 s,可以认为每次检测都是独立的)

示例:

输入:s = "abcda", queries = [[3,3,0],[1,2,0],[0,3,1],[0,3,2],[0,4,1]]
输出:[true,false,false,true,true]
解释:
queries[0] : 子串 = "d",回文。
queries[1] : 子串 = "bc",不是回文。
queries[2] : 子串 = "abcd",只替换 1 个字符是变不成回文串的。
queries[3] : 子串 = "abcd",可以变成回文的 "abba"。 也可以变成 "baab",先重新排序变成 "bacd",然后把 "cd" 替换为 "ab"。
queries[4] : 子串 = "abcda",可以变成回文的 "abcba"。
 

提示:

1 <= s.length, queries.length <= 10^5
0 <= queries[i][0] <= queries[i][1] < s.length
0 <= queries[i][2] <= s.length
s 中只有小写英文字母

class Solution {
public:
	vector canMakePaliQueries(string s, vector>& queries) {
		vectorvsum(s.length()+1);
		vsum[0] = 0;
		for (int i = 0; i < s.length(); i++) {
			vsum[i + 1] = (1 << s[i] - 'a')^ vsum[i];
		}
		vectorans(queries.size());
		for (int i = 0; i < ans.size(); i++) {
			int dif = vsum[queries[i][1] + 1] ^ vsum[queries[i][0]];
			int num = NumInBinary(dif);
			ans[i] = num / 2 <= queries[i][2];
		}
		return ans;
	}
};

力扣 2425. 所有数对的异或和

给你两个下标从 0 开始的数组 nums1 和 nums2 ,两个数组都只包含非负整数。请你求出另外一个数组 nums3 ,包含 nums1 和 nums2 中 所有数对 的异或和(nums1 中每个整数都跟 nums2 中每个整数 恰好 匹配一次)。

请你返回 nums3 中所有整数的 异或和 。

示例 1:

输入:nums1 = [2,1,3], nums2 = [10,2,5,0]
输出:13
解释:
一个可能的 nums3 数组是 [8,0,7,2,11,3,4,1,9,1,6,3] 。
所有这些数字的异或和是 13 ,所以我们返回 13 。

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:0
解释:
所有数对异或和的结果分别为 nums1[0] ^ nums2[0] ,nums1[0] ^ nums2[1] ,nums1[1] ^ nums2[0] 和 nums1[1] ^ nums2[1] 。
所以,一个可能的 nums3 数组是 [2,5,1,6] 。
2 ^ 5 ^ 1 ^ 6 = 0 ,所以我们返回 0 。

提示:

  • 1 <= nums1.length, nums2.length <= 105
  • 0 <= nums1[i], nums2[j] <= 109

思路:利用异或和的交换性即可。

class Solution {
public:
    int xorAllNums(vector& nums1, vector& nums2) {
        int ans=0;
        if(nums1.size()%2)for(auto x:nums2)ans^=x;
        if(nums2.size()%2)for(auto x:nums1)ans^=x;
        return ans;
    }
};

力扣 剑指 Offer 56 - I. 数组中数字出现的次数(寻找仅出现一次的2个数)

 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
 

限制:

2 <= nums.length <= 10000

int func(int x)
{
    return x;
}
int xorr;
int func2(int x)
{
    return (x&xorr)?x:0;
}
int xorSum(vectorv,int(*f)(int))
{
    int ans=0;
    for(int i=0;i singleNumbers(vector& nums) {
        int xo=xorSum(nums,func);
        xorr=xo&-xo;
        vectorans(2);
        ans[0]=xorSum(nums,func2);
        ans[1]=ans[0]^xo;
        return ans;
    }
};


 

与、或

CSU 2055 Wells‘s Lottery

题目:

Description
As is known to all, Wells is impoverished. 
When God heard that, God decide to help the poor Wells from terrible condition.
One day Wells met God in dream, God gave Wells a secret number which will bought Wells a great fortune and gifted Wells an ablility of transforming two lottery tickets x, y into a lottery ticket z (meet the condition that z==x or y).

Wells realized that the number must be the result of lottery, which would be worth ¥5,000,000! Wells was very excited, and can't help sharing this thought. The secret number is X, and Wells has purchase N lottery tickets  but has not received them yet. And if lucky enough, Wells could pick some numbers of them satisfy that , k is the amount of picked numbers.

How ever the owner of the lottery shop called SDJ, who decided to modify the lottery tickets and made Wells lost his fortune. In order to save energy to modify the lottery tickets, SDJ want to know the minimum amount of modification of lottery tickets.

Input
The input only contains one line.
First line contains two positive integers N ,X  ,N means as described above Second line contains N number  means the number on i-th lottery tickets.

Output
Output a line only contains minimum amount of modification of lottery tickets.

Sample Input
3 7 
4 2 1
Sample Output
1

题意:

有n个数,问需要删掉多少个数才能使得剩下的数无论怎么选,都不能使得或运算的总结果为x

思路:

对于(a|x) != x来说,a是肯定不会被选的,所以不用管,

剩下的数,每一个对x的某些位都有贡献,

计算x的不为0的每一位,看哪一位的数最少,这个数量就是答案了。

代码:

#include
using namespace std;
 
int main()
{
	int n, x, a, num[32];
	cin >> n >> x;
	int ans = n;
	for (int i = 0; i < 32; i++)num[i] = 0;
	while (n--)
	{
		cin >> a;
		if ((a|x) != x)continue;
		for (int i = 0; i < 32; i++)num[i] += a % 2, a /= 2;
	}	
	for (int i = 0; i < 32; i++)
	{
		if (x % 2 && ans>num[i])ans = num[i];
		x /= 2;
	}
	cout << ans;
	return 0;
}

力扣 2568. 最小无法得到的或值

给你一个下标从 0 开始的整数数组 nums 。

如果存在一些整数满足 0 <= index1 < index2 < ... < indexk < nums.length ,得到 nums[index1] | nums[index2] | ... | nums[indexk] = x ,那么我们说 x 是 可表达的 。换言之,如果一个整数能由 nums 的某个子序列的或运算得到,那么它就是可表达的。

请你返回 nums 不可表达的 最小非零整数 。

示例 1:

输入:nums = [2,1]
输出:4
解释:1 和 2 已经在数组中,因为 nums[0] | nums[1] = 2 | 1 = 3 ,所以 3 是可表达的。由于 4 是不可表达的,所以我们返回 4 。

示例 2:

输入:nums = [5,3,2]
输出:1
解释:1 是最小不可表达的数字。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109

思路:

显然答案一定是2的幂

class Solution {
public:
	int minImpossibleOR(vector& nums) {
		mapm;
		for (auto x : nums)m[x]++;
		for (int i = 1;; i <<= 1) {
			if (m[i] == 0)return i;
		}
		return 0;
	}
};

综合运用

力扣 191. 位1的个数(补码、位与、异或)

 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。

思路:

最简单的思路是除2,最搞效的是位运算

代码:

class Solution {
public:
	int hammingWeight(uint32_t n) {
		int ans = 0;
		while (n)
		{
			n ^= (n&(-n));
			ans++;
		}
		return ans;
	}
};

力扣 2317. 操作后的最大异或和(异或,位与,位或)

给你一个下标从 0 开始的整数数组 nums 。一次操作中,选择 任意 非负整数 x 和一个下标 i ,更新 nums[i] 为 nums[i] AND (nums[i] XOR x) 。

注意,AND 是逐位与运算,XOR 是逐位异或运算。

请你执行 任意次 更新操作,并返回 nums 中所有元素 最大 逐位异或和。

示例 1:

输入:nums = [3,2,4,6]
输出:7
解释:选择 x = 4 和 i = 3 进行操作,num[3] = 6 AND (6 XOR 4) = 6 AND 2 = 2 。
现在,nums = [3, 2, 4, 2] 且所有元素逐位异或得到 3 XOR 2 XOR 4 XOR 2 = 7 。
可知 7 是能得到的最大逐位异或和。
注意,其他操作可能也能得到逐位异或和 7 。

示例 2:

输入:nums = [1,2,3,9,2]
输出:11
解释:执行 0 次操作。
所有元素的逐位异或和为 1 XOR 2 XOR 3 XOR 9 XOR 2 = 11 。
可知 11 是能得到的最大逐位异或和。

提示:

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 108
class Solution {
public:
	int maximumXOR(vector& nums) {
		int ans = 0;
		for (auto x : nums)ans |= x;
		return ans;
	}
};

你可能感兴趣的:(算法)