1250. 检查「好数组」 Hard 数学 最大公约数 2023/2/15
给你一个正整数数组 nums,你需要从中任选一些子集,然后将子集中每一个数乘以一个 任意整数,并求出他们的和。
假如该和结果为 1,那么原数组就是一个「好数组」,则返回 True;否则请返回 False。
示例:
输入:nums = [12,5,7,23]
输出:true
解释:挑选数字 5 和 7。
53 + 7(-2) = 1
本题看上去无从下手,其实是个数学题。
有定理:多元一次不定方程
A
1
X
1
+
A
2
X
2
+
.
.
.
+
A
n
X
n
=
C
A_1X_1+A_2X_2+...+A_nX_n=C
A1X1+A2X2+...+AnXn=C(
A
1
,
A
2
,
.
.
.
A
n
,
C
A_1,A_2,...A_n,C
A1,A2,...An,C 均为正整数)存在整数解的充要条件是:
(
A
1
,
A
2
.
.
.
A
n
)
∣
C
(A_1,A_2...A_n)|C
(A1,A2...An)∣C,即
C
C
C 能够被
A
1
,
A
2
.
.
.
A
n
A_1,A_2...A_n
A1,A2...An 的最大公约数整除;
题目中,
C
=
1
C=1
C=1,因此
A
1
,
A
2
.
.
.
A
n
A_1,A_2...A_n
A1,A2...An 的最大公约数只能为1;
class Solution {
public:
bool isGoodArray(vector<int>& nums) {
int g = nums[0];
for (int i = 1; i < nums.size(); i++) {
g = gcd(g, nums[i]);
}
return g == 1;
}
// 求最大公约数
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
};
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 Easy 位运算 类动态规划 2023/2/22
给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
示例:
输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
本题最直观的想法是先构造一个计算每个数中二进制为1的数字个数,再遍历一遍即可。但这样每次计算的时间复杂度为 O ( log n ) O(\log{n}) O(logn),总时间复杂度为 O ( n log n ) O(n\log{n}) O(nlogn)。
class Solution {
public:
vector<int> countBits(int n) {
vector<int> c;
for (int i = 0; i <= n; i++)
c.push_back(count(i));
return c;
}
int count(int x) {
int res = 0;
while (x) {
res += x % 2;
x /= 2;
}
return res;
}
};
可以考虑使用动态规划的思想:如果一个数是偶数,那么它二进制含1的个数等于其右移1位含1的个数;如果一个数是偶数,那么它二进制含1的个数等于其右移1位含1的个数+1;所以有: r e s [ i ] = r e s [ i > > 1 ] + ( i & 1 ) res[i] = res[i >> 1] + (i \& 1) res[i]=res[i>>1]+(i&1)
class Solution {
public:
vector<int> countBits(int n) {
vector<int> res(n + 1, 0);
for (int i = 0; i <= n; i++) {
res[i] = res[i >> 1] + (i & 1);
}
return res;
}
};
剑指 Offer II 004. 只出现一次的数字 Medium 位运算 2023/2/22
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
示例:
输入:nums = [0,1,0,1,0,1,100]
输出:100
常规思路是哈希表遍历一次,但会消耗额外的空间复杂度,可以考虑使用位运算:累加所有数字的每一位,对于出现3次的数字来说,累加值必定是3的倍数,如果累加值÷3余1,说明该数字的二进制的该位为1,用1左移i位去与即可。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans = 0;
for (int i = 0; i < 32; i++) {
// 计算每一位二进制的和
int total = 0;
for (int num: nums) {
total += ((num >> i) & 1);
}
if (total % 3) ans |= (1 << i);
}
return ans;
}
};
剑指 Offer II 005. 单词长度的最大乘积 Medium 位掩码 2023/2/22
给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。
示例:
输入: words = [“abcw”,“baz”,“foo”,“bar”,“fxyz”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “fxyz”。它们不包含相同字符,且长度的乘积最大。
对于每个字符串,肯定都要与所有其他字符串比较1次,因此 O ( n 2 ) O(n^2) O(n2) 的时间复杂度是跑不了了,那么如何将每次比较的时间复杂度降到 O ( 1 ) O(1) O(1) 呢?
预处理构建位掩码:对于字符串的每个字符 c c c ,把1左移 c − ′ a ′ c-'a' c−′a′位再与掩码相或。
比较时,只需将两个字符串的位掩码相与,为0则表示不包含相同字符。
class Solution {
public:
int maxProduct(vector<string>& words) {
vector<int> masks(words.size());
// 位掩码构建
for (int i = 0; i < words.size(); i++)
for (char c: words[i])
masks[i] |= 1 << (c - 'a');
int res = 0;
// 比较掩码
for (int i = 0; i < words.size(); i++)
for (int j = i + 1; j < words.size(); j++)
if (!(masks[i] & masks[j])) res = max(res, (int)(words[i].size() * words[j].size()));
return res;
}
};
89. 格雷编码 Medium 格雷码构造 2023/2/24
n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
1.每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
2.第一个整数是 0
3.一个整数在序列中出现 不超过一次
4.每对 相邻 整数的二进制表示 恰好一位不同 ,且
5.第一个 和 最后一个 整数的二进制表示 恰好一位不同
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
示例:
输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10] 。
需要熟悉格雷码构造的性质:将含有 2 i − 1 2^{i-1} 2i−1 个元素的格雷码扩容成 2 i 2^{i} 2i 个元素,只需要将其倒序排列,再在其首位添1即可。
原格雷码 ⇒ 倒序 ⇒ 添1 ⇒ 扩充后的格雷码
(
0
)
0
,
(
0
)
1
(0)0,(0)1
(0)0,(0)1 ⇒
(
0
)
1
,
(
0
)
0
(0)1,(0)0
(0)1,(0)0 ⇒
11
,
10
11,10
11,10 ⇒
00
,
01
,
11
,
10
00,01,11,10
00,01,11,10
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> ret;
ret.reserve(1 << n); // 预留2^n位
ret.push_back(0);
for (int i = 1; i <= n; i++) {
// 当前数组含有2^(i-1)个元素,需扩成2^i个元素
int m = ret.size();
// 倒序
for (int j = m - 1; j >= 0; j--) {
// 倒序并将首位添1
ret.push_back(ret[j] | (1 << (i - 1)));
}
}
return ret;
}
};
另一种方法需要记住格雷码的公式 g i = i ⊕ ⌊ ı 2 ⌋ g_i=i \oplus\left\lfloor\frac{\imath}{2}\right\rfloor gi=i⊕⌊2⌋ 不推荐。
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> ret(1 << n);
for (int i = 0; i < ret.size(); i++) {
ret[i] = (i >> 1) ^ i;
}
return ret;
}
};