6012. 统计各位数字之和为偶数的整数个数
思路 预处理
时间复杂度 查询 O ( 1 ) \mathrel{O}(1) O(1),预处理 O ( n ) \mathrel{O}(n) O(n)
空间复杂度 O ( n ) \mathrel{O}(n) O(n)
在查询之前预处理所有数字的答案,这样查询时即可 O ( 1 ) \mathrel{O}(1) O(1) 的得到答案。
借助 std::call_once
可使得预处理逻辑仅执行一次。
// cnt 存储答案
int cnt[1001] = {0};
// 预处理函数
void init() {
for (int i = 1; i <= 1000; i++) {
int sum = 0;
for (int j = i; j; j /= 10) {
sum += j % 10;
}
cnt[i] = cnt[i-1] + ((sum%2) ? 0 : 1);
}
}
// std::call_once 函数所需的标记变量
std::once_flag flag;
class Solution {
public:
int countEven(int num) {
// 尝试调用预处理函数
std::call_once(flag, init);
// 返回答案
return cnt[num];
}
};
6013. 合并零之间的节点
思路 链表的基操啦
时间复杂度 O ( n ) \mathrel{O}(n) O(n)
空间复杂度 O ( 1 ) \mathrel{O}(1) O(1)
我觉得这道题主要在考察链表的遍历和删除。
设有 ListNode *cur
初始时指向头节点。使用 cur
从头至尾遍历链表。
设有 ListNode *zero
指向已遍历的节点中最靠后的零节点。初始时指向头节点。
按照上述变量的定义,在遍历过程中:
- 合并逻辑变为:
zero->val += cur->val
。 - 删除逻辑变为:每当 cur 指向一个零节点,则将
zero->next = cur; zero = cur
。即丢掉上一个零节点与当前零节点之间的节点,并更新zero
。
不难发现,遍历结束后,最后一个节点的值仍为零,需删除。
设有 ListNode *truncate
指向已遍历的节点中的倒数第二个零节点。初始时指向头结点。当遍历结束后,执行 tuncate->next = nullptr
即可解决上述问题。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeNodes(ListNode* head) {
ListNode *truncate = head;
for (ListNode *zero = head, *cur = head->next; cur != nullptr;cur = cur->next) {
if (cur->val == 0) {
// zero 要跳了。
// 在跳之前,更新 truncate。
// 这样 truncate 就指向了倒数第二个零节点。
truncate = zero;
// 删除节点
zero->next = cur;
// zero 跳到 cur
zero = cur;
} else {
// 合并
zero->val += cur->val;
}
}
// 丢弃最后一个零节点
truncate->next = nullptr;
return head;
}
};
6014. 构造限制重复的字符串
思路 贪心
时间复杂度 O ( n ∗ c ) \mathrel{O}(n*c) O(n∗c)。 n n n 为字符串长度, c c c 为字符种类数。
空间复杂度 O ( n + c ) \mathrel{O}(n+c) O(n+c)
设有字符串 a n w anw anw 存储答案,初始为空。
因为要是字典序最大,所以总是选择尽可能大的字符追加至 a n w anw anw 尾部。
一次选择字符的过程如下:
- 如果, s s s 中的最大字符与 a n w anw anw 尾部字符不同,或添加后不会超出重复限制,则将该字符从 s s s 删除,并追加至 a n w anw anw。
- 否则,将一个次大字符从 s s s 中删除,并追加至 a n w anw anw。
最多重复 n n n 次挑选过程,即可构造出最大的 a n w anw anw。
class Solution {
public:
string repeatLimitedString(string s, int repeatLimit) {
// 统计每种字符出现的次数
int cnt[26] = {0};
for (auto c : s) {
cnt[c-'a']++;
}
// anw 存储答案
string anw;
// rep anw尾部连续重复字符的个数
int rep = 0;
// pre anw 尾部字符的编号,方便处理anw为空的情形。
char pre = -1;
// n 次挑选过程
for (int op = 0; op < s.size(); op++) {
// 从大的开始挑选
for (int i = 25; i >= 0; i--) {
// 检查第 i 种字符是否符合条件
if (cnt[i] <= 0) continue;
if (i == pre && rep >= repeatLimit) continue;
// 更新数据
anw += char(i+'a');
if (i == pre) rep++;
else rep = 1;
pre = i;
cnt[i]--;
// 一次挑选过程只能挑一个字符
break;
}
}
return anw;
}
};
6015. 统计可以被 K 整除的下标对数目
思路 最大公约数
时间复杂度 O ( n V ) \mathrel{O}(n\sqrt V) O(nV)
空间复杂度 O ( V ) \mathrel{O}(V) O(V)
如果三个正整数 x x x, y y y, k k k 满足 x ∗ y x*y x∗y 被 k k k 整除。
则 k k k 的所有质因子必然包含在 x ∗ y x*y x∗y 的质因子集合中。比如,
- k = 60 = 2 ∗ 2 ∗ 3 ∗ 5 k = 60 = 2*2*3*5 k=60=2∗2∗3∗5
- x = 6 = 2 ∗ 3 x = 6 = 2*3 x=6=2∗3
- y = 10 = 2 ∗ 5 y = 10 = 2*5 y=10=2∗5
显然 6 ∗ 10 6*10 6∗10 能被 60 60 60 整除。
根据上述结论,我们可枚举
n
u
m
s
i
nums_i
numsi,找出那些不在
n
u
m
s
i
nums_i
numsi 中的
k
k
k 的质因子,设这些质因子的乘积为
p
i
p_i
pi,则有
p
i
=
k
g
c
d
(
n
u
m
s
i
,
k
)
p_i = \frac{k}{gcd(nums_i, k)}
pi=gcd(numsi,k)k
然后,统计出
n
u
m
s
[
0..
i
−
1
]
nums[0..i-1]
nums[0..i−1] 中有多少个数字被
p
i
p_i
pi 整除,该数量记为
c
i
c_i
ci。则答案为:
a
n
w
=
∑
i
=
0
n
−
1
c
i
anw = \sum_{i=0}^{n-1}{c_i}
anw=i=0∑n−1ci
可以配合注释一起理解~
class Solution {
public:
// cnt[i] 表示 nums 中能被 i 整除的数字的个数
int cnt[100001] = {0};
long long coutPairs(vector<int>& nums, int k) {
// anw 记录答案
int64_t anw = 0;
// 开始枚举数字 nums[i]
for(int i = 0; i < nums.size(); i++) {
// 计算 p
int p = k / __gcd(nums[i], k);
// 累加上 nums[0..i-1] 区间内,能被 p 整除的数字的数量。
anw += cnt[p];
// 更新 cnt。将 nums[i] 统计进去,注意 nums[i] 能被多个数整除。
for (int j = 1, s = sqrt(nums[i]); j <= s; j++) {
if (nums[i]%j == 0) {
// j 能整除 nums[i],则 nums[i]/j 也能整除 nums[i]。
// 但须注意 j == nums[i]/j 的情形,避免重复统计。
cnt[j]++;
if (nums[i]/j != j) {
cnt[nums[i]/j]++;
}
}
}
}
return anw;
}
};