Tag Archives: 位运算

LeetCode Convert a Number to Hexadecimal

LeetCode Convert a Number to Hexadecimal Given an integer, write an algorithm to convert it to hexadecimal. For negative integer, two’s complement method is used. Note:

  1. All letters in hexadecimal (a-f) must be in lowercase.
  2. The hexadecimal string must not contain extra leading 0s. If the number is zero, it is represented by a single zero character '0'; otherwise, the first character in the hexadecimal string will not be the zero character.
  3. The given number is guaranteed to fit within the range of a 32-bit signed integer.
  4. You must not use any method provided by the library which converts/formats the number to hex directly.
Example 1:
Input:
26
Output:
"1a"
Example 2:
Input:
-1
Output:
"ffffffff"

题意:把一个十进制数转换为十六进制数。如果是负数,则用补码的二进制转换为十六进制。简单题,因为计算机内存存储的就是补码,所以我们只需要取出每4bit,转换为十六进制就好。4bit对应的十六进制字符,可以提前用一个字典(或数组)存储好。 完整代码如下: [cpp] class Solution { public: string toHex(int num) { if (num == 0)return "0"; vector<string> dict = { "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" }; int mask = 0xf; string ans = ""; for (int i = 0; i < 8; ++i) { if (num == 0)break; ans = dict[num&mask] + ans; num >>= 4; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。]]>

LeetCode Total Hamming Distance

LeetCode Total Hamming Distance The Hamming distance between two integers is the number of positions at which the corresponding bits are different. Now your job is to find the total Hamming distance between all pairs of the given numbers. Example:

Input: 4, 14, 2
Output: 6
Explanation: In binary representation, the 4 is 0100, 14 is 1110, and 2 is 0010 (just
showing the four bits relevant in this case). So the answer will be:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.
Note:
  1. Elements of the given array are in the range of 0 to 10^9
  2. Length of the array will not exceed 10^4.

本题要求数组中所有数两两之间的汉明距离之和,最笨的方法就是两层循环,然后调用LeetCode Hamming Distance的方法计算每两个数之间的汉明距离。 网上还有一种更巧妙的方法,仔细观察下面三个数的二进制表示,我们可以位的角度计算所有汉明距离,而不是每次针对两个完整的数。比如最低位,三个数都是0,则任意两个数之间不会贡献汉明距离;次低位有一个为0,两个为1,要产生汉明距离,必须一个为0,一个为1,也就是(4,14)和(4,2)才能产生次低位的汉明距离,而(14,2)因为都是1没有汉明距离。所以我们可以统计每一位中0和1的个数zero[i]和one[i],在第i位上所有数的两两汉明距离之和为zero[i]*one[i]。所以总的汉明距离就是ans=Σ zero[i]*one[i]。
  • 4:  0100
  • 14:1110
  • 2:  0010
完整代码如下: [cpp] class Solution { public: int totalHammingDistance(vector<int>& nums) { int bit_len = sizeof(int) * 8; vector<int> one(bit_len, 0), zero(bit_len, 0); for (int i = 0; i < bit_len; ++i) { for (int j = 0; j < nums.size(); ++j) { if (nums[j] & 1)++one[i]; else ++zero[i]; nums[j] >>= 1; } } int ans = 0; for (int i = 0; i < bit_len; ++i) { ans += one[i] * zero[i]; } return ans; } }; [/cpp] 本代码提交AC,用时112MS。]]>

LeetCode Single Number III

260. Single Number III 260. Single Number III

Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.

Example:

Input:  [1,2,1,3,2,5]
Output: [3,5]

Note:

  1. The order of the result is not important. So in the above example, [5, 3] is also correct.
  2. Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity? 260. Single Number III

本题是LeetCode Single Number的升级版,即数组中有两个只出现一次的数,其他的数都出现了两次,现在要把这两个只出现一次的数找出来。
直接用LeetCode Single Number异或的方法是不行的,因为虽然出现两次的数异或等于0了,但是有两个只出现一次的数a和b,得到的结果是它们的异或a^b。
如果有办法把数组中的数分成两堆,a和b在不同的堆里,并且每堆除了a或b,其他数也都恰好出现了两次,即如果3出现两次,则两个3要么都在a堆中,要么都在b堆中。这样我们就可以对两个堆分别异或,找到每堆中只出现一次的数a和b。
那么根据a^b的结果能否把数分成两堆呢,答案是可以的。异或的规则是相同为0,不同为1。既然a和b分别只出现了1次,那么a和b肯定是不同的两个数,所以他们的异或结果的二进制中肯定有一位是1,记这一位为x,那么a和b在x位的二进制肯定是不一样的,这就找到了a和b的区别。
所以我们可以把数组中的所有数按x位为0或1分成两堆,这样就达到了开头的目的,然后利用LeetCode Single Number的方法,在两堆进行异或,最终就能找到a和b了。完整代码如下:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums)
    {
        int flag = 0;
        for (int i = 0; i < nums.size(); ++i) {
            flag ^= nums[i];
        }
        int mask = 1;
        for (int i = 0; i < 32; ++i) {
            if ((flag & mask) != 0)
                break;
            mask <<= 1;
        }
        vector<int> ans(2, 0);
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] & mask) {
                ans[0] ^= nums[i];
            }
            else {
                ans[1] ^= nums[i];
            }
        }
        return ans;
    }
};

本代码提交AC,用时16MS。

二刷。相同思路,很简洁的代码,对于n,直接n&(-n)就能得到其最后一位1的位置,请看面试笔试宝典第三版P97,代码如下:

class Solution {
public:
	vector<int> singleNumber(vector<int>& nums) {
		int c = 0, n = nums.size();
		for (int i = 0; i < n; ++i) {
			c ^= nums[i];
		}
		c = c & (-c); // 得到c最后一个1
		int a = 0, b = 0;
		for (int i = 0; i < n; ++i) {
			if ((nums[i] & c) == 0) { // 注意加括号,位运算优先级低于==
				a ^= nums[i];
			}
			else {
				b ^= nums[i];
			}
		}
		return { a,b };
	}
};

本代码提交AC,用时20MS。

LeetCode Single Number II

137. Single Number II

Given a non-empty array of integers, every element appears three times except for one, which appears exactly once. Find that single one.

Note:

Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

Example 1:

Input: [2,2,3,2]
Output: 3

Example 2:

Input: [0,1,0,1,0,1,99]
Output: 99

数组中有出现3次的数,但是也有一个出现1次的数,现在要把这个只出现1次的数找出来。要求是线性时间复杂度和常数空间复杂度。 相比于LeetCode Single Number有难度。网上看到一个很不错的题解,分析得很详细。 初级解法是,把数组中每个数字的对应二进制位加起来,因为3个相同的数对应二进制位加起来之后模3一定等于0,比如5的二进制为101,三个101加起来就是303,每一位模3就是000。所以如果混入了一个只出现一次的数,这样取模之后,剩余位为1的就是我们要找的数的二进制形式。 这种解法需要一个32位的数组,用来统计所有数每一位出现1的次数之和,所以空间复杂度是O(32)=O(1)。时间复杂度的话,相当于O(32n)=O(n),是线性的。完整代码如下:

class Solution {
public:
    int singleNumber(vector<int>& nums)
    {
        int bit_len = sizeof(int) * 8;
        vector<int> bit(bit_len, 0);
        for (int i = 0; i < bit_len; ++i) {
            for (int j = 0; j < nums.size(); ++j) {
                bit[i] += (nums[j] & 1);
                nums[j] >>= 1;
            }
        }
        int ans = 0;
        for (int i = 0; i < bit_len; ++i) {
            int r = bit[i] % 3;
            ans |= (r << i);
        }
        return ans;
    }
};

本代码提交AC,用时12MS。

高级解法是,使用三个int的每一位表示所有数每一位出现1次数之和的情况,one对应位为1表示这一位出现了1次1,two对应位为1表示这一位出现了2次1,three对应位为1表示这一位出现了3次1。但是one,two,three对应位不可能都同时为1。

通过分析对应位的关系(请看原博客),发现one,two,three可以快速通过位运算计算得到。完整代码如下:

class Solution {
public:
    int singleNumber(vector<int>& nums)
    {
        int one = 0, two = 0, three = 0;
        for (int i = 0; i < nums.size(); ++i) {
            two |= (one & nums[i]);
            one ^= nums[i];
            three = one & two;
            one -= three;
            two -= three;
        }
        return one;
    }
};

本代码提交AC,用时15MS。

初级解法还是好理解一些,会不会是一个通用的解法呢,比如数组中出现1次和出现4次(甚至k次)的数混在一起,都可以通过模k求解呢?

LeetCode Counting Bits

LeetCode Counting Bits Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array. Example: For num = 5 you should return [0,1,1,2,1,2]. Follow up:

  • It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
  • Space complexity should be O(n).
  • Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.
Hint:
  1. You should make use of what you have produced already.
  2. Divide the numbers in ranges like [2-3], [4-7], [8-15] and so on. And try to generate new range from previous.
  3. Or does the odd/even status of the number help you in calculating the number of 1s?

这一题很有意思,给定数字num,要把0~num之间每个数的二进制中1的个数列出来,比如num=5时,需要计算0,1,2,3,4,5这6个数字每个数字的二进制中,1的个数,分别是0,1,1,2,1,2。 这样就不能用LeetCode Number of 1 Bits的方法对每个数都求一遍了,复杂度肯定太高了。这题一看就知道是找规律的题,所以我们先把前几个数的二进制1的个数列出来看看。   观察的时候需要分组观察,比如[0,1]的结果为0,1,[2,3]的结果为1,2,也就是在[0,1]的结果上加了1;[4,7]的结果为1,2,2,3,也就是在[0,3]的结果上加了1;[8,15]的结果为1,2,2,3,2,3,3,4,也就是在[0,7]的结果上加了1。 所以重复的周期是以2的幂递增的,初始的时候,我们可以在ans数组中只存[0,1]的结果,然后每次添加2的幂个结果进ans,直到ans的大小大于num为止。 完整代码如下: [cpp] class Solution { public: vector<int> countBits(int num) { vector<int> ans = { 0,1 }; while (ans.size() <= num) { int old_size = ans.size(); for (int i = 0; i < old_size; ++i) { ans.push_back(ans[i] + 1); } } return vector<int>(ans.begin(), ans.begin() + num + 1); } }; [/cpp] 本代码提交AC,用时73MS。 ]]>

LeetCode Number Complement

LeetCode Number Complement Given a positive integer, output its complement number. The complement strategy is to flip the bits of its binary representation. Note:

  1. The given integer is guaranteed to fit within the range of a 32-bit signed integer.
  2. You could assume no leading zero bit in the integer’s binary representation.
Example 1:
Input: 5
Output: 2
Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2.
Example 2:
Input: 1
Output: 0
Explanation: The binary representation of 1 is 1 (no leading zero bits), and its complement is 0. So you need to output 0.

求一个数的二进制补,比如整数5的二进制是101,那么它的二进制补就是010,也就是整数2。求补的时候不能考虑前导零(leading zero)。 这个题好像AC率很高的,但是我还想了几天,之前的思路一直不对,我想的是判断到num中的一个0之后,ans就置1,然后往左移,但是这样的顺序和正确结果正好反了。 后来开窍了,num可以用移位的思想,但是ans可以不用移位,直接与上某个数,把对应位置1就好了。所以先要用一个变量i记录当前num已经移了多少位了,如果发现num当前位为0,则把ans中第i位置为1就好了,直到num为0。完整代码如下: [cpp] class Solution { public: int findComplement(int num) { int ans = 0, i = 0; while (num > 0) { if ((num & 1) == 0)ans |= (1 << i); num >>= 1; ++i; } return ans; } }; [/cpp] 本代码提交AC,用时3MS,居然只打败了5%的人,肯定还有其他方法。 网上找到这样一种解法。位运算的波浪号~本身就是取反操作,其实可以借用的。(我隐隐约约也感觉,这个位翻转的操作应该会有直接的位运算吧,可能是太久没用过~了,想不起来。) 但是~是对所有位进行翻转,包括翻转leading zero,所以需要对~稍加改造。~num翻转完之后,我们还要把leading zero翻回来,所以得知道哪些是leading zero。当num右移到0时,之前移过的肯定不是leading zero,所以之后的位肯定就是leading zero了,根据这个思路有下面的代码: [cpp] class Solution { public: int findComplement(int num) { int mask = 0xffffffff, tmp = num; while (tmp > 0) { mask <<= 1; tmp >>= 1; } return (~mask)&(~num); } }; [/cpp] 本代码提交AC,用时6MS,居然变慢了。。。不过多掌握一种思路也是好的。]]>

LeetCode Power of Four

LeetCode Power of Four Given an integer (signed 32 bits), write a function to check whether it is a power of 4. Example: Given num = 16, return true. Given num = 5, return false. Follow up: Could you solve it without loops/recursion?


判断一个数是否是4的幂,之前那些通用方法还是可以用的。 首先4的幂肯定是2的幂,所以可以先用LeetCode Power of Two的方法判断是否为2的幂,如果是2的幂,进一步找4的幂和2的幂的差别。 首先,4的幂减1肯定能够被3整除,所以有下面的解法: [cpp] class Solution { public: bool isPowerOfFour(int n) { return (n > 0) && ((n & (n-1)) == 0) &&((n – 1) % 3 == 0); } }; [/cpp] 本代码提交AC,用时3MS。 其次,观察一下4的幂的二进制表示:
  • 1:1
  • 4:100
  • 16:10000
从右往左数,其最高位1所在的位置都是奇数位,所以如果把4的幂num和$$0x55555555_2=1010101010101010101010101010101$$相与,得到的还是num本身,所以有下面的解法: [cpp] class Solution { public: bool isPowerOfFour(int n) { return (n > 0) && ((n & (n-1)) == 0) && ((n & 0x55555555) == n); } }; [/cpp] 本代码提交AC,用时3MS。 以上解法参考这个题解。]]>

LeetCode Power of Two

231. Power of Two

Given an integer, write a function to determine if it is a power of two.

Example 1:

Input: 1
Output: true 
Explanation: 20 = 1

Example 2:

Input: 16
Output: true
Explanation: 24 = 16

Example 3:

Input: 218 Output: false

判断一个数是否是2的幂次方。 递归或迭代等常规方法就不说了,可以参考LeetCode Power of Three网上看到两个比较有意思的解法,和位运算有关。下面列出了2的幂次方以及对应的二进制表示,先找找规律。

  • 1:1
  • 2:10
  • 4:100
  • 8:1000
  • 16:10000

发现所有2的幂的二进制中都只有1位是1,所有可以统计二进制中1出现的次数,等于1表示是2的幂。完整代码如下:

class Solution {
public:
    bool isPowerOfTwo(int n)
    {
        int cnt = 0;
        while (n > 0) {
            cnt += (n & 1);
            n >>= 1;
        }
        return cnt == 1;
    }
};

本代码提交AC,用时3MS。
另外,不考虑前导零(leading zero),这些2的幂只有最高位是1,如果把这个数减1,那么最高位变为0了,后面的位全变为1。把前后的两个二进制相与,正好等于0,比如$4_2=100$,$4-1=3_2=011$,$4\&3=100\&011=000$。这个思路的完整代码只需一行,如下:

class Solution {
public:
    bool isPowerOfTwo(int n) { return (n > 0) && ((n & (n - 1)) == 0); }
};

本代码提交AC,用时6MS,按理应该要比上一种解法快,不知为何。

二刷。解法太多了,直接1移位判断相等即可:

class Solution {
public:
    bool isPowerOfTwo(int n) {
        int one=1;
        for(int i=0;i<31;++i){
            int cur = one << i;
            if(cur==n)return true;
        }
        return false;
    }
};

本代码提交AC,用时0MS。

LeetCode Find the Difference

LeetCode Find the Difference Given two strings s and t which consist of only lowercase letters. String t is generated by random shuffling string s and then add one more letter at a random position. Find the letter that was added in t. Example:

Input:
s = "abcd"
t = "abcde"
Output:
e
Explanation:
'e' is the letter that was added.

有两个字符串s和t,其中t是由s shuffle而来,并且在随机位置上随机加上了一个字符,现在要找出这个随机加上的字符是哪个。 第一思路是,t由s shuffle而来的部分和s的组成是一样的,只是字符排列顺序不一样,所以每个字符出现的次数是一样的,而加上的那个字符导致该字符的出现次数在s和t中不一样,所以用一个hash就可以找到这个出现次数不一样的字符。 完整代码如下: [cpp] class Solution { public: char findTheDifference(string s, string t) { unordered_map<char, size_t> mpS, mpT; for (int i = 0; i < s.size(); i++) { mpS[s[i]]++; } for (int i = 0; i < t.size(); i++) { mpT[t[i]]++; } unordered_map<char, size_t>::iterator it = mpT.begin(); while (it != mpT.end()) { if (mpS.find((*it).first) == mpS.end() || mpS[(*it).first] != (*it).second)return (*it).first; it++; } return 0; // never reach here } }; [/cpp] 本代码提交AC,用时13MS。 后来想到之前做的一个题LeetCode Single Number,把s和t组合起来,不就相当于除了加入的字符,其他字符都重复出现了偶数次吗,所以把s和t中的所有字符异或一下,得到的结果就是那个插入的字符,真是高明。 完整代码如下: [cpp] class Solution { public: char findTheDifference(string s, string t) { char ans = 0; for (int i = 0; i < s.size(); i++) { ans ^= s[i]; } for (int i = 0; i < t.size(); i++) { ans ^= t[i]; } return ans; } }; [/cpp] 本代码提交AC,用时3MS,相当于前一个版本的零头,果然位运算的效率就是高。]]>

LeetCode Hamming Distance

LeetCode Hamming Distance The Hamming distance between two integers is the number of positions at which the corresponding bits are different. Given two integers x and y, calculate the Hamming distance. Note: 0 ≤ x, y < 231. Example:

Input: x = 1, y = 4
Output: 2
Explanation:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑
The above arrows point to positions where the corresponding bits are different.

求两个整数的汉明距离,即两个整数的二进制串中不一样的位数。马上联想到之前LeetCode Number of 1 Bits求二进制中1的个数,这里是一样的,相当于求两个整数的二进制位,每次比较一下,如果不同,则汉明距离加1;当发现两个数已经相等时,汉明距离不可能再增加了。 完整代码如下: [cpp] class Solution { public: int hammingDistance(int x, int y) { int dist = 0; for (int i = 0; i < 32; i++) { if (x == y)break; if ((x & 1) != (y & 1))dist++; x >>= 1; y >>= 1; } return dist; } }; [/cpp] 本代码提交AC,用时0MS,击败55.12%,加了那个break效率会高很多。]]>