# [leetcode] 位操作（Ⅰ）

## 子集

输入: nums = [1,2,3]

[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]


0 0 [ ]
0 1 [2]
1 0 [1]
1 1 [1,2]

class Solution
{
public:
vector<vector<int>> subsets(vector<int> &nums)
{
vector<vector<int>> vv;
int total = 1 << nums.size();
for (int i = 0; i < total; i++)
{
vv.push_back(getItem(i, nums));
}

return vv;
}

vector<int> getItem(int n, const vector<int> &nums)
{
vector<int> v;
for (int i = 0; i < nums.size(); i++)
{
if ((n >> i) & 0x1)
v.push_back(nums[i]);
}
return v;
}
};


class Solution:
def __init__(self):
self.ans = list()
self.ans.append([])

def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
cur = []
self.helper(cur, nums)
return self.ans

def helper(self, cur: list, nums: list):
for i in range(0, len(nums)):
cur.append(nums[i])
self.ans.append(list(cur))
self.helper(cur, nums[(i + 1):])
cur.pop()


C++ 回溯法

class Solution
{
public:
vector<vector<int>> ans;
vector<vector<int>> subsets(vector<int> &nums)
{
vector<int> cur;
vector<bool> table(nums.size(), false);
ans.push_back(vector<int>(cur));
backtrack2(nums, 0, cur, table);
return ans;
}

void backtrack(vector<int> &v, int start, vector<int> &cur, vector<bool> &table)
{
for (int i = start; i < v.size(); i++)
{
if (table[i])
return;
cur.push_back(v[i]), table[i] = true;
ans.push_back(cur), backtrack(v, start + 1, cur, table);
cur.pop_back(), table[i] = false;
}
}
void backtrack2(vector<int> &v, int start, vector<int> &cur, vector<bool> &table)
{
for (int i = start; i < v.size(); i++)
{
if (table[i])
continue;
cur.push_back(v[i]), table[i] = true;
ans.push_back(vector<int>(cur)), backtrack(v, start + 1, cur, table);
cur.pop_back(), table[i] = false;
}
}
};


## 只出现一次的数字

输入: [2,2,1]



• $$0 \oplus x = x$$
• $$x \oplus x = 0$$
• $$(x \oplus y) \oplus z = x \oplus (y \oplus z)$$

class Solution {
public:
int singleNumber(vector<int>& nums) {
int x = 0;
for (auto k:nums)
{
x^=k;
}
return x;
}
};


## 只出现一次的数字 II

输入: [2,2,3,2]



input: [2,2,3,2]
2: 0 0 1 0
2: 0 0 1 0
3: 0 0 1 1
-----plus-----
t: 0 0 0 1
2: 0 0 1 0
-----plus-----
t: 0 0 1 1


class Solution
{
public:
int singleNumber(vector<int> &nums)
{
int countBits[32] = {0};
for (auto x : nums)
{
for (int i = 0; i < 32; i++)
{
countBits[i] = (countBits[i] + ((x >> i) & 0x1)) % 3;
}
}
int ans = 0;
for (int i = 0; i < 32; i++)
{
ans |= ((countBits[i]) << i);
}
return ans;
}
};


## 只出现一次的数字 III

输入: [1,2,1,3,2,5]



• $$k \oplus a = b$$

• $$k \oplus b = a$$

 x    = 1 0 1 0 1 0
-x    = 0 1 0 1 1 0
x&(-x)= 0 0 0 0 1 0


$$x\&(-x)$$ 的作用是只保留 $$x$$ 中最低位的 1 。

$$mask=k\&(-k)$$ 中只有一个 1 ，这个 1 要么来自 $$a$$，要么来自 $$b$$ ，并且只会来自二者之一，也即是：

• (mask & a) != 0, (mask & b)==0
• (mask & b) != 0, (mask & a)==0

class Solution
{
public:
vector<int> singleNumber(vector<int> &nums)
{
int k = 0;
for (auto x : nums)
k ^= x;
int mask = k & (-k);
int y = 0;

// find one of {a,b}
for (auto x : nums)
{
if (mask & x) //ignore one of {a,b}
y ^= x;
}
return vector<int>({y, k ^ y});
}
};


## 多数元素

输入: [2,2,1,1,1,2,2]



nums: [7] 7 5 7 5 1 | [5] 7 | [5] 5 7 7 | [5] 5 5 5
cand: [7] 7 7 7 7 7 | [5] 5 | [5] 5 5 5 | [5] 5 5 5
flag: [1] 2 1 2 1 0 | [1] 0 | [1] 2 1 0 | [1] 2 3 4


class Solution
{
public:
int majorityElement(vector<int> &nums)
{
int candidate = nums.front();
int flag = 0;
for (auto x : nums)
{
if (flag == 0)
candidate = x;
flag += (candidate == x) ? (1) : (-1);
}
return candidate;
}
};


## 位1的个数

• 循环和位移计数。

for i=0 to 31
ans += (n>>i)&0x1

• 利用 $$n \& (n-1)$$ 优化：该操作总是把 $$n$$ 的最低位的 1 置为 0 ，显然该方法直接「命中要害」。

/*
n      = [... 1 0 0 ... 0]
(n-1)  = [... 0 1 1 ... 1]
n&(n-1)= [... 0 0 0 ... 0]
*/
int hammingWeight(uint32_t n)
{
int ans = 0;
while (n)
{
ans++;
n &= (n - 1);
}
return ans;
}


## 数字范围按位与

class Solution
{
public:
int rangeBitwiseAnd(uint32_t m, uint32_t n)
{
while (m < n)
n &= (n - 1);
return n;
}
};


## 2的幂

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


## 4的幂

1  = 0000 0000 0001
4  = 0000 0000 0100
16 = 0000 0001 0000
64 = 0000 0100 0000
256= 0001 0000 0000


bool isPowerOfFour(int n)
{
return (n >= 1) && ((n & (n - 1)) == 0) && ((n & 0xAAAAAAAA) == 0);
}


## 缺失数字

• 解法1：
$$k = \frac{n(n-1)}{2} - \sum_{i=0}^{len-1}nums[i]$$

• 解法2：
下面以 [3,2,0] 例子，假如在数组最后补上缺失的数字 $$1$$

$$Index$$ 0 1 2 3
$nums$ 3 2 0 [1]

显然，通过异或的交换律，可以得到：$$\oplus_{i=0}^{len-1} (i \oplus nums[i]) = 0$$，即：k ^= nums[i]^i (i=0,...,len-1) => k=0

如果缺失 1 ，我们在数组的最后补上 0 ：

// index xor num[index]
k = (0 xor 3) xor (1 xor 2) xor (2 xor 0) xor (3 xor 0)
= (0 xor 0) xor (1 xor 0) xor (2 xor 2) xor (3 xor 3)
= 0 xor 1 xor 0 xor 0 xor 0
= 1


代码：

class Solution
{
public:
int missingNumber(vector<int> &nums)
{
nums.push_back(0);
int k = 0;
for (size_t i = 0; i < nums.size(); i++)
k ^= (i ^ nums[i]);
return k;
}
};


## 最大单词长度乘积

输入: ["abcw","baz","foo","bar","xtfn","abcdef"]



#define max(a, b) ((a) > (b) ? (a) : (b))
class Solution
{
public:
int maxProduct(vector<string> &words)
{
vector<int> v;
for (auto &s : words)
v.push_back(hash(s));

size_t ans = 0;
size_t len = words.size();
for (size_t i = 0; i < len; i++)
{
for (size_t j = i + 1; j < len; j++)
{
if ((v[i] & v[j]) == 0)
ans = max(ans, words[i].size() * words[j].size());
}
}
return ans;
}

int hash(string &s)
{
int n = 0;
for (auto x : s)
{
n |= (1 << (x - 'a'));
}
return n;
}
};


## 比特位计数

输入: 2



• $$dp[i] = dp[i>>1] + (i\&1)$$
• $$dp[i] = dp[i\&(i-1)] + 1$$
class Solution
{
public:
vector<int> countBits(int num)
{
vector<int> v(num + 1);
for (int i = 0; i <= num; i++)
{
v[i] = v[i >> 1] + (i & 0x1);
}
return v;
}
};


## 两整数之和

a   = 0 1 1
b   = 0 0 1
-----------
a^b = 0 1 0


#define getBit(a, i) (((a) >> i) & 0x1)
int getSum(int a, int b)
{
int in = 0;
int c = 0;
for (int i = 0; i < 32; i++)
{
c |= (getBit(a, i) ^ getBit(b, i) ^ in) << i;
in = (getBit(a, i) && getBit(b, i)) || (in && getBit(a, i)) || (in && getBit(b, i));
}
return c;
}


## 找不同

输入：
s = "abcd"
t = "abcde"


• 解法1（其实是错误解法

原来的想法：将字符串映射为一个 $$int$$ ，与最大单词长度乘积做法一致。但需要考虑出现字符串为"" 的特殊情况。

出错原因：如果输入 s="aaa", t="aa"，显然无法做映射了。

• 解法2

异或。显然，除了「添加的字母」，其他的都在 $$s,t$$ 中成对出现。

class Solution
{
public:
char findTheDifference(string s, string t)
{
char k = 0;
for (auto x : s)
k ^= x;
for (auto x : t)
k ^= x;
return k;
}
};


## UTF-8 编码验证

class Solution
{
public:
bool validUtf8(vector<int> &data)
{
for (size_t i = 0; i < data.size();)
{
uint8_t byte = data[i] & 0xff;
int t = unitLength(byte);
if (t == -1)
return false;
if (t == 0 && (byte >> 7) == 0x1)
return false;
// read the after t bytes, but i can not out of the data.size()
while (t--)
{
i++;
if (i >= data.size())
return false;
data[i] &= 0xff;
if (!((data[i] >> 6) == 2))
{
return false;
}
}
i++;
}
return true;
}

int unitLength(uint8_t n)
{
if ((n & ~0x7F) == 0)
return 0;
else if ((n & ~0xDF) == 0)
return 1;
else if ((n & ~0xEF) == 0)
return 2;
else if ((n & ~0xF7) == 0)
return 3;
return -1;
}
};


## 整数替换

1. 如果 n 是偶数，则用 n / 2替换 n。
2. 如果 n 是奇数，则可以用 n + 1或n - 1替换 n。
n 变为 1 所需的最小替换次数是多少？

输入:8



• 解法1：递归

显然，题目描述就是递归的结构。值得注意的地方就是递归函数的参数不能为 $$int$$ ，因为需要进行 $$n+1$$ 的操作，当 $$n = 2147483647$$ 的时候，leetcode 就会抛出溢出异常。

class Solution
{
public:
int integerReplacement(int n)
{
return helper(n);
}

int helper(uint32_t n)
{
if (n == 1)
return 0;
if (n % 2 == 0)
return 1 + helper(n / 2);
return 1 + min(helper(n + 1), helper(n - 1));
}
};

• 解法2：位操作

不用递归的话，关键在于找出 $$n+1$$$$n-1$$ 的决策条件。显然，我们想数字 $$n$$ 的二进制尽可能地接近 $$(...100...00)_2$$ 这种形式（让末尾有尽可能多的 $$0$$ ，这样除以 $$2$$ 之后仍然是一个偶数，不断进入偶数的分支）。

$$n$$ 为奇数时，其末尾的 $$2$$ 个比特为 $$(01)_2$$ 或者 $$(11)_2$$ 。显然，对于前者应该减一，后者应该加一，这样才能够让「末尾有尽可能多的 $$0$$ 」。

但是，提交之后发现输入为 $$3$$ 时，输出应为 $$2(3 \rightarrow 2 \rightarrow 1)$$。根据上述算法，$$n$$ 的变化过程是 $$3 \rightarrow 4 \rightarrow 2 \rightarrow 1$$，这是需要考虑的特殊情况。

int integerReplacement(int num)
{
uint32_t n = num;
int k = 0;
while (n > 1)
{
if (n & 0x1)
n = ((n & 0x2) == 0) || (n == 3) ? (n - 1) : (n + 1);
else
n >>= 1;
k++;
}
return k;
}


## 二进制手表

• uint16_t bits 来表示时间。第 $$0 - 5$$ 比特表示分钟，第 $$6 - 9$$ 比特表示小时。那么 bits >> 6 可求出小时，bits & 0x3f 可求出分钟。
• 遍历 0x0 - 0x3FF ，只有二进制中 $$1$$ 的个数为 $$n$$ 的，才求出字符串计入答案。（同时需要考虑小时数和分钟数要在合理的范围内）
class Solution
{
public:
{
vector<string> vs;
for (uint16_t bits = 0; bits < 1024; bits++)
{
int h = bits >> 6;
int m = (bits & 0x3f);
if (0 <= h && h <= 11 && 0 <= m && m <= 59 && countOne(bits) == num)
vs.push_back(format(h, m));
}
return vs;
}

string format(int h, int m)
{
stringstream ss;
ss << h << ":";
ss << setfill('0') << setw(2) << m;
return ss.str();
}

int countOne(int n)
{
int k = 0;
while (n)
n &= (n - 1), k++;
return k;
}
};


## 数字转换为十六进制数

• 解法1：循环迭代

经典做法，但效率低下，复杂度为 $$O(log_{16}{N})$$

while (n)
{
s+=tochar(n%16);
n/=16;
}
return reverse(s);

• 解法2：位操作

针对 $$n$$ 的每 4 个 bit（字节顺序从高到低）进行转换，算法复杂度 $$O(1)$$

class Solution
{
public:
const char *stdstr = "0123456789abcdef";
string toHex(int n)
{
string hex;
int k = sizeof(int) * 8 / 4 - 1;
while (k > 0 && (((n >> (4 * k)) & 0x0f) == 0))
k--;
while (k >= 0)
hex.append(1, stdstr[(n >> (4 * k--)) & 0x0f]);
return hex;
}
};


## 数字的补数

输入: 5



class Solution
{
public:
int findComplement(int n)
{
int k = ~n, t = 0;
while (n)
n >>= 1, t++;
return k & ~((-1) << t);
}
};


## 汉明距离总和

输入: 4, 14, 2



int hammingDistance(int a, int b)
{
int k = a ^ b;
int t = 0;
while (k)
t++, k &= (k - 1);
return t;
}


• 暴力的 $$O(n^2)$$ 解法：当然是超时了。

• 位操作

我们不妨从每一个 $$bit$$ 的角度来看汉明距离。假如输入 nums = [4,14,2,12,0]

4  = 0 1 0 0
14 = 1 1 1 0
2  = 0 0 1 0
12 = 1 1 0 0
0  = 0 0 0 0


看每个数字的第 $$1$$ 比特（倒数第二列），可见有 2 个 1，3 个 0 。那么在这一个 bit 上，产生的汉明距离是多少呢？显然是 $C_{2}^{1} \cdot C_{3}^{1} = 6$ ，每个数字都是 32 bit ，总的汉明距离就是：

$total = \sum_{i=0}^{31}{one_i} \cdot {zero_i}$

$$one_i$$ 表示该 bit 上 1 的数目，而显然 $$zero_i = len(nums) - one_i$$

代码：

class Solution
{
public:
int totalHammingDistance(vector<int> &nums)
{
size_t len = nums.size();
vector<int> countOne(32, 0);
for (auto x : nums)
{
for (int i = 0; i < 32; i++)
countOne[i] += (x >> i) & 0x1;
}
int sum = 0;
for (int i = 0; i < 32; i++)
sum += countOne[i] * (len - countOne[i]);
return sum;
}
};


时间复杂度为 $$O(N)$$

## 交替位二进制数

输入: 5



• 解法1：循环位移比较

bool hasAlternatingBits(int n)
{
while (n)
{
if (((n >> 1) & 0x1) == (n & 0x1))
return false;
n >>= 1;
}
return true;
}

• 解法2：构造掩码，检查 n ^ ( n >> 1 ) 的有效二进制位是否为全 1 。注意特殊情况 $$n=2147483647$$ 时，leetcode 会抛出溢出异常。

bool hasAlternatingBits2(int num)
{
unsigned int n = num;
n = n ^ (n >> 1);
return ((n + 1) & n) == 0;
}


## 子数组按位或操作

输入：[1,1,2]



int subarrayBitwiseORs(vector<int> &A)
{
unordered_set<int> s;
int len = A.size();
vector<vector<int>> dp(len + 1, vector<int>(len + 1, 0));
for (int i = 0; i < len; i++)
{
dp[i][i] = A[i], s.insert(dp[i][i]);
for (int j = i + 1; j < len; j++)
{
dp[i][j] = dp[i][j - 1] | A[j], s.insert(dp[i][j]);
}
}
return s.size();
}


int subarrayBitwiseORs2(vector<int> &A)
{
unordered_set<int> s(A.begin(), A.end());
int len = A.size();
vector<int> dp(len + 1, 0);
for (int i = 0; i < len; i++)
{
dp[i] = A[i];
for (int j = i + 1; j < len; j++)
dp[j] = dp[j - 1] | A[j], s.insert(dp[j]);
}
return s.size();
}


int subarrayBitwiseORs3(vector<int> &A)
{
unordered_set<int> s(A.begin(), A.end());
int len = A.size();
int dp = 0;
for (int i = 0; i < len; i++)
{
dp = A[i];
for (int j = i + 1; j < len; j++)
dp = dp | A[j], s.insert(dp);
}
return s.size();
}


## 串联字符串的最大长度

输入：arr = ["un","iq","ue"]



#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
class Solution
{
public:
int ans = 0;
int maxLength(vector<string> &arr)
{
vector<string> v;
for (auto &x : arr)
{
if (check2(x))
v.push_back(x);
}
string s = "";
backtrack(v, s, 0);
return ans;
}
void backtrack(vector<string> &v, string &cur, int start)
{
for (int i = start; i < v.size(); i++)
{
if (!check(cur, v[i]))
continue;
cur += v[i];
ans = max(ans, cur.length());
backtrack(v, cur, i + 1);
for (int j = 0; j < v[i].length(); j++)
cur.pop_back();
}
}
bool check(string &a, string &b)
{
for (auto x : b)
if (a.find(x) != string::npos)
return false;
return true;
}

bool check2(string &s)
{
bool table[26] = {0};
for (auto x : s)
{
if (table[x - 'a'])
return false;
table[x - 'a'] = true;
}
return true;
}
};


## 或运算的最小翻转次数

输入：a = 2, b = 6, c = 5



• 如果 bit(c,i) == 1 ，说明 bit(a|b, i) == 0，即 bit(a, i) == 0 && bit(b, i) == 0，只需要对 a 或者 b 进行一次翻转操作.
• 如果 bit(c,i) == 0 ，说明 bit(a|b, i) == 1 ，这时有 2 种情况: bit(a,i), bit(b,i) 的其中之一为 1或者**两者都为 1 **。 如果 bit(a,i) == 1 那么 a 需要翻转；bit(b,i)==1 同理。只有这样才能保证 bit(a|b,i) = 0
class Solution
{
public:
int minFlips(int a, int b, int c)
{
int k = (a | b) ^ c;
int ans = 0;
for (int i = 0; i < 32; i++)
{
if ((k >> i) & 0x1)
{
if ((c >> i) & 0x1)
{
ans++;
}
else
{
ans += (a >> i) & 0x1;
ans += (b >> i) & 0x1;
}
}
}
return ans;
}
};


## 面试题专栏

### 插入

 输入：N = 10000000000, M = 10011, i = 2, j = 6
输出：N = 10001001100


int insertBits(int N, int M, int i, int j)
{
uint32_t mask = ~((0xffffffff >> (32 - (j - i + 1))) << i);
return (N & mask) | (M << i);
}


### 配对交换

 输入：num = 2（或者0b10）
输出 1 (或者 0b01)


int exchangeBits(int num)
{
for (int i = 30; i >= 0; i -= 2)
{
int t = (num >> i) & 0x3;
if (t == 1 || t == 2)
num ^= 0x3 << i;
}
return num;
}


### 最大数值

• $$x,y$$ 异号。 sign(x) == 1 && sign(y) == 0 ，即：sign(x) & (sign(y) ^ 1)，其中 sign(x) = (x >> 1) & 1

• $$x,y$$ 同号

1. sign(x) == sign(y) => (sign(x)^sign(y)^1)
2. x - y <= 0
=> x - y < 1
=> x + (~y + 1) < 1
=> x + (~y) < 0
=> sign(x+(~y))


因此，$$x,y$$ 同号的情况下，$$x \le y$$ 当且仅当 (sign(x) ^ sign(y) ^ 1) & sign(x + (~y))

flag1 = sign(x) & (sign(y) ^ 1)flag2 = (sign(x) ^ sign(y) ^ 1) & sign(x + (~y)) ，那么 flag1 | flag2 就表示 $$x \le y$$

• $$x=2147483647, y=-1$$
• $$x=-2147483648, y=1$$

x <= y iff (flag1 | flag2)


#define sign(x) ((((x) >> 31) & 0x1))
class Solution
{
public:
int maximum(int a, int b)
{
uint8_t x1 = sign(a) & (sign(b) ^ 1);
uint8_t x2 = (sign(a) ^ sign(b) ^ 1) & sign((a + (~b)));
uint8_t le = x1 | x2;
return (le ^ 1) * a + b * le;
}
};


int maximum2(int x, int y)
{
long a = x, b = y;
int less = (((a - b) >> 63) & 0x1);
return (less ^ 1) * a + less * b;
}


## 总结

• n & (n-1)：将最右边的 1 置为 0 。
• x & (-x)：只保留最右边的 1 。
• 构造掩码
• 异或的性质
• $x \oplus 0 = x$
• $$x \oplus (11...11)_2 = not(x)$$
• $$x \oplus x = 0$$
• $$(x \oplus y) \oplus z = x \oplus (y \oplus z)$$
• $$k=a \oplus b$$，那么 $$k \oplus a = b, k \oplus b = a$$

posted @ 2020-02-18 00:30  sinkinben  阅读(383)  评论(0编辑  收藏  举报