剑指 Offer 15. 二进制中1的个数
知识点:数学;位运算
题目描述
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
示例
输入:n = 11 (控制台输入 00000000000000000000000000001011)
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
输入:n = 128 (控制台输入 00000000000000000000000010000000)
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
解法一:位运算
直接统计每一位上1的个数,注意java中无符号右移是>>>;只要n为0后就可以停止了。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while(n != 0){ //n为0就可以停止了
res += n & 1;
n = n >>> 1; //无符号右移;
}
return res;
}
}
相关链接
剑指 Offer 65. 不用加减乘除做加法
知识点:数学;位运算
题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例
输入: a = 1, b = 1
输出: 2
解法一:位运算
不能用四则运算,那其实可以用的只要逻辑运算和位运算了,这里很明显要用位运算。关键怎么用呢。
我们列一张表其实就可以发现,两个元素如果没有进位也就是不是对应位都是1的时候,相加就和异或运算的结果是一样的;如果有进位,也就是两位上都是1的时候,相加就是两者相与然后左移一位;所以我们就可以做了。
可以依次得到两个数的异或结果和与结果;
sum=a+b=sum+carry;但是求得这两个后还是要加,由于不能用加法,所以可以再求这两个结果的与结果和异或结果。直到最后进位的为0,sum就是答案了。
class Solution {
public int add(int a, int b) {
int sum = a^b; //无进位的;
int carry = (a&b) << 1; //有进位的;
while(carry != 0){ //进位为0的时候返回sum;
a = sum;
b = carry;
sum = a^b;
carry = (a & b) << 1;
}
return sum;
}
}
相关链接
136. 只出现一次的数字
知识点:哈希表;set;消消乐;位运算
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例
输入: [2,2,1]
输出: 1
输入: [4,1,2,1,2]
输出: 4
异或运算有几个很重要的性质:
1.任何数和0异或,仍为本身:a⊕0 = a
2.任何数和本身异或,为0:a⊕a = 0
3.异或运算满足交换律和结合律:a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b
可以利用第2条和第3条性质将整个数组异或运算,那最后剩下的那个就是只出现一次的。
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(Integer i : nums){
ans ^= i;
}
return ans;
}
}
体会
上面的解法整体上分为2种:一种就是将每个元素出现的次数统计出来,然后我们找到出现一次的;另一种就是将重复的两两抵消,那最后剩下的就是我们要的。其实方法2-6都是用的这种思路,两两抵消.剩下的就是。其中位运算是最快的,而且不用开辟新的空间。
掌握位运算的解决方法:这种题目往往要按位与、按位异或等操作;
异或运算有几个很重要的性质:
1.任何数和0异或,仍为本身:a⊕0 = a
2.任何数和本身异或,为0:a⊕a = 0
3.异或运算满足交换律和结合律:a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b
此外还会有左移<<;右移>>等;比如说:
a & 1 : a的其他位全为0,最后一位不变:即取a最后一位;
a | (1 << i) : a的其他位不变,把a的第i位置为1;
(a >> i) & 1 : 取出a第i位上的值;
目前掌握的题型中触发位运算的是:题目中含有出现次数的问题;
260. 只出现一次的数字 III(剑指 Offer 56 -I)
知识点:数组;位运算;消消乐
题目描述
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
示例
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
输入:nums = [-1,0]
输出:[-1,0]
输入:nums = [0,1]
输出:[1,0]
解法一:位运算
这个题是LeetCode 136的升级,从一个元素变成了两个元素,所以我们可以采用分组的方法来解决,那么在每个组上再采用和136题一样的思路按位异或就可以了,分组的关键在于两点:
- 1.两个只有一个的分在不同组;
- 2.重复的数字要在一个组;
所以问题的关键就在于如何分组:将整个数组异或一遍后的结果其实就是那两个数字的异或结果,然后可以看这两个异或后的结果哪一位是1,是1的话就证明这两个数在这位上是不等的,那就可以根据这位进行分组,就能满足以上两个条件; - 1 两个相同的数字对应位是相等的,一定被分到一组里;
- 2 两个只有一个的数字在这位一个是1,一个是0,肯定被分到不同组里;
整体流程如下图:
class Solution {
public int[] singleNumber(int[] nums) {
int n = 0; //计算整个数组异或后的结果;
int m = 1; //分组依据,看异或后结果在哪位上为1;
int x = 0; //第一组;
int y = 0; //第二组;
for(Integer i : nums){
n ^= i;
}
//n要是在第i位为0,证明两个数字在第i位上相同,与m进行与操作后为0
//n要是在第i位为1,证明两个数字在第i位上不同,与m进行与操作后为1
while((n & m) == 0){
m = m << 1; //找到n在哪位上为1;以此为依据分组;
}
for(Integer i : nums){
if((i & m) == 0) x ^= i; //第一组;
else y ^= i; //第二组;
}
return new int[]{x,y};
}
}
时间复杂度:O(N);
空间复杂度:O(N);
137. 只出现一次的数字 II(剑指offer 56-II)
知识点:哈希表;位运算
题目描述
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例
输入:nums = [2,2,3,2]
输出:3
输入:nums = [0,1,0,1,0,1,99]
输出:99
解法一:位运算
这题不能再用异或去解了,因为变成了三个没有办法抵消,但是想象一下,如果有三个一模一样的数字,那这三个数字二进制相加后,所有位上要么是0;要么全是3的倍数;然后我们的多余元素,要么加上去为0;要么加上去多了一个1,所以可以依次求每位的和,然后%3,如果值为1,那证明我们在这位上的值为1;否则为0;
如下图所示;
class Solution {
public int singleNumber(int[] nums) {
//在java中int类型是32位,我们需要统计所有数字在某一位置的和能不能被3整除,
// 如果不能被3整除,说明那个只出现一次的数字的二进制在那个位置是1……把32位全部统计完为止
int ans = 0;
for(int i = 0; i < 32; i++){
int count = 0; //统计1的个数;
for(int j = 0; j < nums.length; j++){
count += (nums[j] >> i) & 1; //统计所有数在第i位上1的个数;
}
if(count % 3 != 0){
ans = ans | (1 << i); //其他位不变,第i位置为1;
}
}
return ans;
}
}
时间复杂度:O(N);
空间复杂度:O(1);
体会
掌握位运算的解决方法:这种题目往往要按位与、按位异或等操作;
此外还会有左移<<;右移>>等;比如说:
a & 1 : a的其他位全为0,最后一位不变:即取a最后一位;
a | (1 << i) : a的其他位不变,把a的第i位置为1;
(a >> i) & 1 : 取出a第i位上的值;
389.找不同
知识点:哈希表;抵消思想;
题目描述
给定两个字符串 s 和 t,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
示例
输入;s='abcdac'; t='adaccbb';
输出;'b';
解释;因为b是被添加的元素,不一定在最后位置;
异或;
这种思路是很巧妙的:这道题可以转换思路,将s和t合为一个字符串,然后呢,每两个字符两两按位取异或,如果两个字符一样,那取完异或就是0(字符0)。因为异或运算具有交换律,所以这样都按位异或完之后肯定有一个多余的,0和任意字符异或都等于那个字符,这样就把那个字符取出来了。其实这也是抵消思想的一种,就是按位异或去抵消,只要相同,求完就为0;
class Solution {
public char findTheDifference(String s, String t) {
char flag = 0;
for(char c : (s+t).toCharArray()){
flag ^= c;
}
return flag;
}
}
以上两种解法都只需要将两个字符串遍历一遍就可以;
体会
一定要学会这种抵消的思想,很常用;
记住两个元素按位异或后为0;0与元素A按位异或后为A;
作者:[curryxin]