且听疯吟

leetcodeweekly contes 361

2023-09-06

leetcode weekly contes 361

T4 算是比较难的题目了,需要仔细思考的题目,关键在题目给定的某些提示。

7020. 统计对称整数的数目

给你两个正整数 lowhigh

对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字之和相等,则认为这个数字是一个对称整数。

返回在 [low, high] 范围内的 对称整数的数目

示例 1:

输入:low = 1, high = 100
输出:9
解释:在 1 到 100 范围内共有 9 个对称整数:11、22、33、44、55、66、77、88 和 99 。

示例 2:

输入:low = 1200, high = 1230
输出:4
解释:在 1200 到 1230 范围内共有 4 个对称整数:1203、1212、1221 和 1230 。

提示:

  • 1 <= low <= high <= 104

地址

https://leetcode.cn/contest/weekly-contest-361/problems/count-symmetric-integers/

题意

直接模拟

思路

  1. 直接检测 $[low, high]$ 之间所有的元素,是否为对称数,满足对称数需要满足两个条件:
    • 数位长度为偶数;
    • 数位的前一半与后一半数字之和相等
  2. 复杂度分析:
  • 时间复杂度:$O(n \log n)$, 其中 $n$ 表示给定的元素。
  • 空间复杂度:$O(\log n)$。

代码

[sol1-C++]
class Solution {
public:
int countSymmetricIntegers(int low, int high) {
int ans = 0;
for (int i = low; i <= high; i++) {
string s = to_string(i);
int n = s.size();
if (n % 2 == 0) {
int sum1 = 0, sum2 = 0;
for (int i = 0; i < n / 2; i++) {
sum1 += s[i] - '0';
}
for (int i = n / 2; i < n; i++) {
sum2 += s[i] - '0';
}
if (sum1 == sum2) {
ans++;
}
}
}
return ans;
}
};

8040. 生成特殊数字的最少操作

给你一个下标从 0 开始的字符串 num ,表示一个非负整数。

在一次操作中,您可以选择 num 的任意一位数字并将其删除。请注意,如果你删除 num 中的所有数字,则 num 变为 0

返回最少需要多少次操作可以使 num 变成特殊数字。

如果整数 x 能被 25 整除,则该整数 x 被认为是特殊数字。

示例 1:

输入:num = "2245047"
输出:2
解释:删除数字 num[5] 和 num[6] ,得到数字 "22450" ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 2 位数字。

示例 2:

输入:num = "2908305"
输出:3
解释:删除 num[3]、num[4] 和 num[6] ,得到数字 "2900" ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 3 位数字。

示例 3:

输入:num = "10"
输出:1
解释:删除 num[0] ,得到数字 "0" ,可以被 25 整除。
可以证明要使数字变成特殊数字,最少需要删除 1 位数字。

提示

  • 1 <= num.length <= 100
  • num 仅由数字 '0''9' 组成
  • num 不含任何前导零

地址

https://leetcode.cn/contest/weekly-contest-361/problems/minimum-operations-to-make-a-special-number/

题意

数学

思路

  1. 题目本身不是特别难,但是需要一些数学技巧,首先我们之间能被 $25$ 整除的元素有以下特征:
    • 要么为元素 $0$;
    • 要么数字最后两位为 $00,25,50,75$;
  2. 我们依次尝试上述两种情况,要么将数字变为 $0$, 要么变为指定的数字;
    • 变为 $0$ 时,如果 $num$ 中本身含有 $0$ ,此时需要删除 $n-1$ 次,否则删除 $n$ 次;
    • 剩余的则是将字符串进行匹配,从尾部到头部开始匹配直到匹配目标字符串为止,需要将数字的最后两位变为指定的模式;
  3. 复杂度分析:
  • 时间复杂度:$O(n)$,其中 $n$ 为给定的字符串的长度。
  • 空间复杂度:$O(1)$。

代码

class Solution {
public:
int minimumOperations(string num) {
int n = num.size();
vector<string> arr = {"00", "25", "50", "75"};
int ans = n;
auto calc = [&](string s) {
int count = 0;
for (int i = s.size() - 1, j = n - 1; i >= 0; i--) {
while (j >= 0 && num[j] != s[i]) {
j--;
count++;
}
if (j < 0) {
return -1;
}
if (num[j] == s[i]) {
j--;
}
}
return count;
};

for (int i = 0; i < 4; i++) {
int cnt = calc(arr[i]);
if (cnt >= 0) {
ans = min(ans, cnt);
}
}

/* zero */
for (int i = 0; i < n; i++) {
if (num[i] == '0') {
ans = min(ans, n - 1);
}
}
return ans;
}
};

6952. 统计趣味子数组的数目

给你一个下标从 0 开始的整数数组 nums ,以及整数 modulo 和整数 k

请你找出并统计数组中 趣味子数组 的数目。

如果 子数组 nums[l..r] 满足下述条件,则称其为 趣味子数组

  • 在范围 [l, r] 内,设 cnt 为满足 nums[i] % modulo == k 的索引 i 的数量。并且 cnt % modulo == k

以整数形式表示并返回趣味子数组的数目。

注意:子数组是数组中的一个连续非空的元素序列。

示例 1:

输入:nums = [3,2,4], modulo = 2, k = 1
输出:3
解释:在这个示例中,趣味子数组分别是:
子数组 nums[0..0] ,也就是 [3] 。
- 在范围 [0, 0] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
子数组 nums[0..1] ,也就是 [3,2] 。
- 在范围 [0, 1] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
子数组 nums[0..2] ,也就是 [3,2,4] 。
- 在范围 [0, 2] 内,只存在 1 个下标 i = 0 满足 nums[i] % modulo == k 。
- 因此 cnt = 1 ,且 cnt % modulo == k 。
可以证明不存在其他趣味子数组。因此,答案为 3 。

示例 2:

输入:nums = [3,1,9,6], modulo = 3, k = 0
输出:2
解释:在这个示例中,趣味子数组分别是:
子数组 nums[0..3] ,也就是 [3,1,9,6] 。
- 在范围 [0, 3] 内,只存在 3 个下标 i = 0, 2, 3 满足 nums[i] % modulo == k 。
- 因此 cnt = 3 ,且 cnt % modulo == k 。
子数组 nums[1..1] ,也就是 [1] 。
- 在范围 [1, 1] 内,不存在下标满足 nums[i] % modulo == k 。
- 因此 cnt = 0 ,且 cnt % modulo == k 。
可以证明不存在其他趣味子数组,因此答案为 2 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= modulo <= 109
  • 0 <= k < modulo

地址

https://leetcode.cn/contest/weekly-contest-361/problems/count-of-interesting-subarrays/

题意

前缀和

思路

  1. 典型的前缀和的思想,假设当前索引 $i$ 中前 $i$ 个元素中含有满足要求的索引数量为 $x$ 个,此时我们需要找到索引 $j$ 使得索引 $[j,i]$ 之间符合题目要求的索引数量对 $mod$ 取模为 $k$, 此时我们自然而然想到前缀和设 $sum[i]$ 表示前 $i$ 个元素中符合题目要求的数量,此时需要满足 $(sum[i] - sum[j]) \bmod modulo = k$,则此时需要满足如下要求:

    • 设 $sum[i] \bmod modulo = x$, 此时如要使得 $(sum[i] - sum[j]) \bmod modulo = k$,则此时 $sum[j] \bmod modulo = (x + modulo - k) \bmod modulo$;

    根据以上分析我们利用哈希依次记录当前前缀和为 $x$ 的出现次数为 $cnt[x]$,则后续的计算过程就是非常简单的题目了。

  2. 复杂度分析:

  • 时间复杂度:$O(n)$,其中 $n$ 表示数组的长度;
  • 空间复杂度:$O(n)$,其中 $n$ 表示数组的长度;

代码

class Solution {
public:
long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {
int n = nums.size();
vector<int> sum(n + 1);
for (int i = 0; i < n; i++) {
if (nums[i] % modulo == k) {
sum[i + 1] = sum[i] + 1;
} else {
sum[i + 1] = sum[i];
}
}
unordered_map<int, int> cnt;
long long res = 0;
cnt[0] = 1;
for (int i = 0; i < n; i++) {
int x = sum[i + 1];
int y = x % modulo;
res += cnt[(y + modulo - k) % modulo];
cnt[sum[i + 1] % modulo]++;
}
return res;
}
};

100018. 边权重均等查询

现有一棵由 n 个节点组成的无向树,节点按从 0n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ui, vi, wi] 表示树中存在一条位于节点 ui 和节点 vi 之间、权重为 wi 的边。

另给你一个长度为 m 的二维整数数组 queries ,其中 queries[i] = [ai, bi] 。对于每条查询,请你找出使从 aibi 路径上每条边的权重相等所需的 最小操作次数 。在一次操作中,你可以选择树上的任意一条边,并将其权重更改为任意值。

注意:

  • 查询之间 相互独立 的,这意味着每条新的查询时,树都会回到 初始状态
  • aibi的路径是一个由 不同 节点组成的序列,从节点 ai 开始,到节点 bi 结束,且序列中相邻的两个节点在树中共享一条边。

返回一个长度为 m 的数组 answer ,其中 answer[i] 是第 i 条查询的答案。

示例 1:

img

输入:n = 7, edges = [[0,1,1],[1,2,1],[2,3,1],[3,4,2],[4,5,2],[5,6,2]], queries = [[0,3],[3,6],[2,6],[0,6]]
输出:[0,0,1,3]
解释:第 1 条查询,从节点 0 到节点 3 的路径中的所有边的权重都是 1 。因此,答案为 0 。
第 2 条查询,从节点 3 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 0 。
第 3 条查询,将边 [2,3] 的权重变更为 2 。在这次操作之后,从节点 2 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 1 。
第 4 条查询,将边 [0,1]、[1,2]、[2,3] 的权重变更为 2 。在这次操作之后,从节点 0 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 3 。
对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。

示例 2:

img

输入:n = 8, edges = [[1,2,6],[1,3,4],[2,4,6],[2,5,3],[3,6,6],[3,0,8],[7,0,2]], queries = [[4,6],[0,4],[6,5],[7,4]]
输出:[1,2,2,3]
解释:第 1 条查询,将边 [1,3] 的权重变更为 6 。在这次操作之后,从节点 4 到节点 6 的路径中的所有边的权重都是 6 。因此,答案为 1 。
第 2 条查询,将边 [0,3]、[3,1] 的权重变更为 6 。在这次操作之后,从节点 0 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 2 。
第 3 条查询,将边 [1,3]、[5,2] 的权重变更为 6 。在这次操作之后,从节点 6 到节点 5 的路径中的所有边的权重都是 6 。因此,答案为 2 。
第 4 条查询,将边 [0,7]、[0,3]、[1,3] 的权重变更为 6 。在这次操作之后,从节点 7 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 3 。
对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。

提示:

  • 1 <= n <= 104
  • edges.length == n - 1
  • edges[i].length == 3
  • 0 <= ui, vi < n
  • 1 <= wi <= 26
  • 生成的输入满足 edges 表示一棵有效的树
  • 1 <= queries.length == m <= 2 * 104
  • queries[i].length == 2
  • 0 <= ai, bi < n

地址

https://leetcode.cn/contest/weekly-contest-361/problems/minimum-edge-weight-equilibrium-queries-in-a-tree/

题意

倍增lca

思路

  1. 没有想到是竟然又出了倍增的题目,确实是个不错的题目,如果非常熟悉倍增的话,则本题不算特别难,题目有几个关键点值得分析:
  • 每条边的权重 $w_i$ 的取值范围为 $[1,26]$,这是一个非常关键的题目,有了这个提示我们想到了可以统计每条路径上不同边的权重统计数目,这样用一个长度为 $26$ 的数组即可实现;
  • 为什么需要倍增,题目给定的树的节点范围为 $[1,10^4]$,查询的长度范围为 $10^4$,这就说明了我们必须要选择 $O(n)$ 或者 $O(\log n)$ 的解法,我们仔细分析来看对于查询为 $10^4$ 数量级,$O(n)$ 的解法无论如何都不现实。结合上面的分析,此时我们就需要 $O(n\log n)$ 的解法,自然而然想到了倍增 $LCA$ 的解法,每次倍增时保存两个节点之间路径中不同边的权重统计;
  • 每次查询不同的边时 $(x,y)$,此时我们通过倍增查询 $lca(x,y)$,此时我们分别统计 $x \rightarrow lca(x,y)$ 中的边的权重统计数目,同时也统计 $y \rightarrow lca(x,y)$ 的边权重统计数目,二者相加即可得到 $x \rightarrow y$ 之间的所有边权重的统计数目。
  1. 根据 $1$ 的思路分析以后我们采用倍增的模板即可,可以参考 LCA,题目就变得比较简单,非常好的模板题目。
  2. 复杂度分析:
  • 时间复杂度:$O(U \times m \log n)$,其中 $m$ 表示查询数组的长度,$n$ 表示节点的数目, $U$ 表示权重的最大值。
  • 空间复杂度:$O(Un)$,其中 $m$ 表示查询数组的长度,$n$ 表示节点的数目, $U$ 表示权重的最大值。

代码

class Solution {
public:
using pii = pair<int, int>;

void bfs(vector<int> &depth, vector<vector<pii>> &graph) {
depth[0] = 0;
queue<int> qu;
qu.emplace(0);
while (!qu.empty()) {
int curr = qu.front();
qu.pop();
for (auto [x, w] : graph[curr]) {
if (depth[x] >= 0) continue;
depth[x] = depth[curr] + 1;
qu.emplace(x);
}
}
}

void add(vector<int> &arr1, vector<int> &arr2) {
for (int i = 0; i < arr1.size(); i++) {
arr1[i] += arr2[i];
}
}

vector<int> minOperationsQueries(int n, vector<vector<int>>& edges, vector<vector<int>>& queries) {
int m = edges.size();
vector<vector<pii>> graph(n);
/* 建立图的关系 */
for (auto e : edges) {
int u = e[0], v = e[1], w = e[2];
graph[u].emplace_back(v, w);
graph[v].emplace_back(u, w);
}

vector<int> res;
vector<int> depth(n, -1);
int dp[n][21][27];
int fa[n][21];
memset(dp, 0, sizeof(dp));
memset(fa, 0, sizeof(fa));

/* 求每个节点的深度, bfs 或者 dfs 均可*/
bfs(depth, graph);

/* 倍增初始化 */
for (auto e : edges) {
int u = e[0], v = e[1], w = e[2];
/* 深度较小的父节点 */
if (depth[u] < depth[v]) {
swap(u, v);
}
fa[u][0] = v;
dp[u][0][w]++;
}

/* 建立倍增关系 */
for (int j = 1; j <= 20; j++) {
for (int i = 0; i < n; i++) {
int x = fa[i][j - 1];
int y = fa[x][j - 1];
fa[i][j] = y;
/* 每次记录两个节点之间的节点数目关系 */
for (int k = 0; k <= 26; k++) {
dp[i][j][k] = dp[i][j - 1][k] + dp[x][j - 1][k];
}
}
}

/* 对每个查询进行 lca 倍增查询 */
for (auto vec : queries) {
int x = vec[0];
int y = vec[1];
if (depth[x] > depth[y]) {
swap(x, y);
}

/* 倍增对齐操作 */
int diff = depth[y] - depth[x];
vector<int> cnt(27);
for (int j = 0; diff; ++j, diff >>= 1) {
if (diff & 1) {
for (int k = 0; k <= 26; k++) {
cnt[k] += dp[y][j][k];
}
y = fa[y][j];
}
}

if (x != y) {
/* 倍增查询 */
for (int j = 20; j >= 0 && y != x; --j) {
if (fa[x][j] != fa[y][j]) {
for(int k = 0; k <= 26; k++) {
cnt[k] += dp[x][j][k] + dp[y][j][k];
}
x = fa[x][j];
y = fa[y][j];
}
}
for (int k = 0; k <= 26 && x >= 0 && y >= 0; k++) {
cnt[k] += dp[x][0][k] + dp[y][0][k];
}
}
int total = accumulate(cnt.begin(), cnt.end(), 0);
int maxVal = *max_element(cnt.begin(), cnt.end());
res.emplace_back(total - maxVal);
}
return res;
}
};

欢迎关注和打赏,感谢支持!

扫描二维码,分享此文章