260. 只出现一次的数字 III
分析
同剑指offer73
先求所有数的异或和
因为不相同的数只有两个, 所以在异或和的第k位必定为1
然后根据第k位是否为1, 将原数组分成两部分, 就可以得到需要求的第1个数了
然后将sum ^ first
就能得到第二个数了
code
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int sum = 0;
for (auto& x : nums) sum ^= x;
int k = 0;
while (!(sum >> k & 1)) k ++;
int first = 0;
for (auto& x : nums)
if (x >> k & 1) first ^= x;
return {first, sum ^ first};
}
};
263. 丑数
分析
按照定义就行了
code
class Solution {
public:
bool isUgly(int n) {
if (n <= 0) return false;
while (n % 2 == 0) n /= 2;
while (n % 3 == 0) n /= 3;
while (n % 5 == 0) n /= 5;
return n == 1;
}
};
264. 丑数 II
分析
其实这题考察的是 “多路归并”
将所有的丑数分成三大类,
第1类是所有包含因子2的丑数,
S
2
S_2
S2
第2类是所有包含因子3的丑数,
S
3
S_3
S3
第3类是所有包含因子5的丑数,
S
5
S_5
S5
这3个集合并起来,
S
2
∪
S
3
∪
S
5
∪
1
=
S
S_2 \cup S_3 \cup S_5 \cup 1 = S
S2∪S3∪S5∪1=S
S是整个丑数的集合
如果想从小到大将丑数集合
S
S
S列出来,
1, … 后面这个序列是这3个集合的并集, 所以接下来需要做的是将这3个集合归并一下
其实可以发现
S
2
=
S
∗
2
,
S
3
=
S
∗
3
,
S
5
=
S
∗
5
S_2 = S * 2, S_3 = S * 3, S_5 = S * 5
S2=S∗2,S3=S∗3,S5=S∗5
因此
S
2
,
S
3
,
S
5
S_2, S_3, S_5
S2,S3,S5可以由
S
S
S序列构造出来
所以3个指针, 可以同时指向S序列, 最开始都指向1
因为可能同时有多个数取到最小值, 因此需要把所有指到最小值的指针往后移动一位
code
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> q(1, 1);
for (int i = 0, j = 0, k = 0; q.size() < n; ){
int t = min(q[i] * 2, min(q[j] * 3, q[k] * 5));
q.push_back(t);
if (q[i] * 2 == t) i ++ ;
if (q[j] * 3 == t) j ++ ;
if (q[k] * 5 == t) k ++ ;
}
return q.back();
}
};
268. 丢失的数字
分析
脑经急转弯, 总和减去现有数的和
code
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int s = n * (n + 1) / 2;
for (auto& x : nums) s -= x;
return s;
}
};
273. 整数转换英文表示
分析
因为英文是3位数, 3位数看的
所有最后面的3位是
xx hundred _ _, _ _表示几十, 几十的时候20~90正常, 1~19都有各自的表示
所有只需要写一个get(x)函数, 返回3位数
然后在最前面+单位, 比如billion, millon, thousand
零要特判下, 因为不会出现“zero million”
code
class Solution {
public:
string num0_19[20] = {
"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen",
"Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen",
"Nineteen",
};
string num20_90[8] = {
"Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy",
"Eighty", "Ninety",
};
string num1000[4] = {
"Billion ", "Million ", "Thousand ", "", // 因为最后3位没有任何单位, 所以接一个空字符就行了
};
string get(int x) {
string res;
if (x >= 100) {
res += num0_19[x / 100] + " Hundred ";
x %= 100;
}
if (x >= 20) {
res += num20_90[x / 10 - 2] + " ";
x %= 10;
if (x) res += num0_19[x] + ' ';
}else if (x) res += num0_19[x] + ' ';
return res;
}
string numberToWords(int num) {
if (!num) return "Zero";
string res;
for (int i = 1e9, j = 0; i >= 1; i /= 1000, j ++ ) // 最大是1e9, 单位是Billion开始计算
if (num >= i){
res += get(num / i) + num1000[j];
num %= i;
}
res.pop_back(); // 多余的空格, pop_back()
return res;
}
};
274. H 指数
分析
首先为了方便, 将所有数排序
然后按照题意去枚举, 从n开始枚举h, 一共n个数, 所以h <= n
每次判断, 当前是否有h个数>= h
所以每次只需要判断前h个数是否都>= h, 因为所有数都已经从大到到小 排序, 所以只需要判断第h个数是否排好序
然后我们从大大小找到满足要求的数
这样的话, 时间复杂度是O(nlogn)
code
class Solution {
public:
int hIndex(vector<int>& c) {
sort(c.begin(), c.end(), greater<int>());
for (int h = c.size(); h >= 1; h -- )
if (c[h - 1] >= h) return h;
return 0;
}
};
275. H 指数 II
分析
如果用上一题的做法, 也是可以做的, 但是时间复杂度是O(n)的, 我们可以考虑下是否有更优的做法
考虑比O(n)更优的做法, 就只有考虑单调性了, 如果有单调性的话, 那么就可以考虑用二分去做
当h
取到这个位置的时候, 考虑h
更靠右的时候, 因为当前的h
是满足要求的最大的数, 所以比h靠右边的数(>h)不满足要求
那么看看左边, 如果h'
< h
code
class Solution {
public:
int hIndex(vector<int>& c) {
int n = c.size();
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (c[n - mid] >= mid) l = mid; // []内的坐标应该是从大到小数第mid个数
// n - 1是第1个数
// 所以第mid个数, 应该是 n - mid
else r = mid - 1;
}
return r;
}
};
278. 第一个错误的版本
分析
二分模板题
当前如果是错误版本, 那么第1个错误版本必定在当前左边, 所以r = mid
l = mid + 1
code
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int l = 1, r = n;
while (l < r) {
int mid = (long long)l + r >> 1;
if (isBadVersion(mid)) r = mid;
else l = mid + 1;
}
return r;
}
};
279. 完全平方数
分析(完全背包)
把n
分成若干个完全平方数之和, 问最少分成多少项
如果不知道性质的话, 那么可以用完全背包 问题去做
把n
当成背包容量, 可以往里放1, 4, 9, 16, 25
, 每个数的数值当成体积, 价值当成1
然后题目就变成恰好装满背包的情况下,总价值最少是多少
完全背包的时间复杂度是O(nm), 该题目中物品个数的话是
n
\sqrt n
n,
因为大于
n
\sqrt n
n的数, 体积会超过
n
n
n
体积的话是 n n n, 所以用完全背包来做的话, 时间复杂度是 n n \sqrt n n nn
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n + 1);
for (int i = 1; i <= n; i ++ ) {
dp[i] = i;
for (int j = 1; i - j * j >= 0; j ++ )
dp[i] = min(dp[i], dp[i - j * j] + 1);
}
return dp[n];
}
};
分析(数学技巧)
- 拉格朗日4平方和定理: 四平方和定理说明每个正整数均可表示为4个整数的平方和。
- 勒让德三平方数之和: n能表示成3个整数的平方和 当且仅当 n ! = 4 a ( 8 b + 7 ) n != 4^{a}(8b + 7) n!=4a(8b+7)
所以可以试一下, 如果答案位1个数的平方和, 那么
n
=
(
n
)
2
n = (\sqrt n)^2
n=(n)2
1个数不行的话, 两个数, 枚举a, 考虑
b
=
n
−
a
2
b = \sqrt {n - a^2}
b=n−a2是不是整数就行了O(logn)次枚举
答案是 3的话, 只需要判断
n
!
=
4
a
(
8
b
+
7
)
n != 4^{a}(8b + 7)
n!=4a(8b+7)两者的关系
以上都不行的话, 那么答案就是4
这样时间复杂度瓶颈是O( n \sqrt n n)
code
class Solution {
public:
int check(int x){
int r = sqrt(x);
return r * r == x ;
}
int numSquares(int n) {
if (check(n)) return 1;
for (int a = 1; a <= n / a; a ++ )
if (check(n - a * a))
return 2;
while (n % 4 == 0) n /= 4;
if (n % 8 != 7) return 3;
return 4;
}
};
282. 给表达式添加运算符
分析
每个空档一共4种填法, 总共 n − 1 n - 1 n−1个空档, 所以方案数 O ( 4 n − 1 ) O(4^{n - 1}) O(4n−1)
要做的, 就是把
4
n
−
1
4^{n - 1}
4n−1情况全部枚举出来, 枚举出来, 算下表达式的值是多少, 算完之后如果答案=目标值的话
, 把答案存下来, 存答案还需要O(n)的时间, 所以整个时间复杂度是
O
(
n
4
n
)
O(n4^n)
O(n4n)的
可以维护一个代数结构 a + b ∗ _ a + b * \_ a+b∗_
当前等式是
a
+
b
∗
c
a + b * c
a+b∗c, c后面可以跟的运算符+
, -
, *
假设是+
,
a
+
b
∗
c
+
_
a + b * c + \_
a+b∗c+_, 那么可以整理下, 变成
(
a
+
b
∗
c
)
+
1
∗
_
=
a
′
+
b
′
(a + b * c) + 1 * \_ \\ =a' + b'
(a+b∗c)+1∗_=a′+b′
将(a + b * c)看成a’, 符号+后面的数看成1 * b’
所以, 可以发现不管下一个操作符是什么, 我们都可以用同样的代数结构去存
存这个结构就比较方便了, 我们在dfs的时候, 把前面的值存成这个结构就行了, 具体来讲存a, 和存b就可以了
把前面的值存成这个结构, 有什么样的好处呢, 起到压缩的作用; 不管前面的表达式有多么长, 我们都可以将表达式压缩成a, b
比方说, 前面表达式有100位, 压缩成了a, b, 所以代码就快了
code
typedef long long LL;
class Solution {
public:
vector<string> ans;
string path;
vector<string> addOperators(string num, int target) {
path.resize(100);
dfs(num, 0, 0, 0, 1, target);
return ans;
}
void dfs(string& num, int u, int len, LL a, LL b, LL target) {
if (u == num.size()) {
if (a == target) ans.push_back(path.substr(0, len - 1));
} else {
LL c = 0;
for (int i = u; i < num.size(); i ++ ) {
c = c * 10 + num[i] - '0';
path[len ++ ] = num[i];
// +
path[len] = '+';
dfs(num, i + 1, len + 1, a + b * c, 1, target);
if (i + 1 < num.size()) {
// -
path[len] = '-';
dfs(num, i + 1, len + 1, a + b * c, -1, target);
// *
path[len] = '*';
dfs(num, i + 1, len + 1, a, b * c, target);
}
if (num[u] == '0') break;
}
}
}
};