Leetcode 第 366 场周赛题解
- Leetcode 第 366 场周赛题解
- 题目1:2894. 分类求和并作差
- 思路
- 代码
- 复杂度分析
- 题目2:2895. 最小处理时间
- 思路
- 代码
- 复杂度分析
- 题目3:2896. 执行操作使两个字符串相等
- 思路
- 代码
- 复杂度分析
- 题目4:2897. 对数组执行操作使平方和最大
- 思路
- 代码
- 复杂度分析
Leetcode 第 366 场周赛题解
题目1:2894. 分类求和并作差
思路
模拟
代码
/** @lc app=leetcode.cn id=2894 lang=cpp** [2894] 分类求和并作差*/// @lc code=start
// class Solution
// {
// public:
// int differenceOfSums(int n, int m)
// {
// int num1 = 0, num2 = 0;
// for (int i = 1; i <= n; i++)
// {
// if (i % m == 0)
// num2 += i;
// else
// num1 += i;
// }
// return num1 - num2;
// }
// };class Solution
{
public:int differenceOfSums(int n, int m){// total = num1 + num2// ans = num1 - num2 = total - 2 * num2int num = 0;for (int i = 1; i <= n; i++)if (i % m == 0)num += i;return (1 + n) * n / 2 - 2 * num;}
};
// @lc code=end
one-line code:
return n * (n + 1) / 2 - n / m * (n / m + 1) * m;
复杂度分析
时间复杂度:O(n)。
空间复杂度:O(1)。
题目2:2895. 最小处理时间
思路
贪心。
直觉上来说,最早空闲时间越大的处理器,处理 tasks 越小的任务,那么完成时间越早。
我们可以把 processorTime 从小到大排序,tasks 从大到小排序,那么答案就是 processorTime[i] + tasks[4 * i] 的最大值。
代码
/** @lc app=leetcode.cn id=2895 lang=cpp** [2895] 最小处理时间*/// @lc code=start
class Solution
{
public:int minProcessingTime(vector<int> &processorTime, vector<int> &tasks){int n = processorTime.size();int min_time = INT_MIN;sort(processorTime.begin(), processorTime.end());sort(tasks.begin(), tasks.end(), greater<int>());for (int i = 0; i < n; i++){int process_max_time = processorTime[i] + tasks[4 * i];min_time = max(min_time, process_max_time);}return min_time;}
};
// @lc code=end
复杂度分析
时间复杂度:O(nlogn),其中 n 为数组 processorTime 的长度。
空间复杂度:O(1)。
题目3:2896. 执行操作使两个字符串相等
思路
两种操作都不会改变字符串中 1 和 0 的个数的奇偶性。
特判:
- s1 == s2,无需操作,返回0。
- s1中1的个数的奇偶性不等于s2中1的个数的奇偶性,无法让二者相等,返回-1。
遍历两个字符串,将字符不同的索引保存在数组 pos 中。
若字符不同的索引数为 pos.size(),那么有 pos.size()/2 对字符需要进行翻转。我们先只考虑操作一,将每两个下标都进行操作一翻转,那么总的操作代价为 x∗pos.size()/2。
然后遍历数组 pos,查看两个相邻下标进行操作二的翻转是否比进行操作一的翻转所需要的代价还小。如果还小,则把答案更新为两个相邻下标进行操作二翻转时的代价。
当一个下标和相邻的下标进行操作二翻转时,它只和前面的下标或者后面的下标组成一对进行翻转,但不能和前面的下标和后面的下标同时进行翻转。
在下面的代码中,cur 表示的是当前下标和前面下标不做操作二时的最优解,pre 表示前一个下标和前前一个下标不做操作二翻转时的最优解, pre+pos[i]−pos[i−1]−x 表示前一个下标和前前一个下标不做操作二翻转但和当前下标做操作二翻转时的最优解。
代码
/** @lc app=leetcode.cn id=2896 lang=cpp** [2896] 执行操作使两个字符串相等*/// @lc code=start
class Solution
{
public:int minOperations(string s1, string s2, int x){// 特判if (s1 == s2)return 0;if (count(s1.begin(), s1.end(), '1') % 2 != count(s2.begin(), s2.end(), '1') % 2)return -1;int n = s1.size();vector<int> pos;for (int i = 0; i < n; i++)if (s1[i] != s2[i])pos.push_back(i);int cur = pos.size() / 2 * x, pre = cur;for (int i = 1; i < pos.size(); i++){int next = min(cur, pre + pos[i] - pos[i - 1] - x);pre = cur;cur = next;}return cur;}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是字符串 s1 的长度。
空间复杂度:O(n),其中 n 是字符串 s1 的长度。
题目4:2897. 对数组执行操作使平方和最大
思路
位运算+贪心
考虑两个数 a 和 b 的二进制表示,讨论二进制第 i 位在 a 和 b 中是否为 1 的情况:
- 若 a 和 b 的第 i 位都是 0,那么 a AND b 和 a OR b 的第 i 位也都是 0。
- 若 a 和 b 的第 i 位恰有一个 1,那么 a AND b 的第 i 位是 0,a OR b 的第 i 位是 1。
- 若 a 和 b 的第 i 位都是 1,那么 a AND b 和 a OR b 的第 i 位也都是 1。
所以操作等价于:把一个数的 0 和另一个数的同一个比特位上的 1 交换。
令 f(a) 表示 a 的二进制表示中有几个 1。
我们发现:f(a) + f(b) = f(a AND b) + f(a OR b)。只不过所有的 1 首先都被 a OR b “抢”了,剩下的 1 才会留给 a AND b。也就是说,每个二进制位中,1 的总数不变。我们可以通过任意次操作,把 1 都集中在某个数里。
设交换前两个数是 x 和 y(x > y),把小的数上的 1 给大的数,假设交换后 x 增加了 d,那么 y 也减少了 d。
交换前:x2+y2
交换后:(x+d)2+(y-d)2=x2+y2+2d(x-y)+2d2>x2+y2
这说明应该通过交换,让一个数越大越好。
相当于把 1 都聚集在一个数中,比分散到不同的数更好。
因此做法就是统计每个二进制位里有多少 1,然后每次用这些 1 拼出尽可能大的数即可。
代码
/** @lc app=leetcode.cn id=2897 lang=cpp** [2897] 对数组执行操作使平方和最大*/// @lc code=start
class Solution
{
private:const int MOD = 1e9 + 7;public:int maxSum(vector<int> &nums, int k){vector<int> count(32, 0);for (const int num : nums){for (int i = 0; i < 32; i++)count[i] += (num >> i) & 01;}long long ans = 0;for (int i = 0; i < k; i++){int x = 0;for (int i = 0; i < 32; i++)if (count[i] > 0){x += 1 << i;count[i]--;}ans = (ans + (long long)x * x) % MOD;}return ans;}
};
// @lc code=end
复杂度分析
时间复杂度:O(nlogU),其中 n 为数组 nums 的长度,U 为数组 nums 的最大值。
空间复杂度:O(logU),其中 U 为数组 nums 的最大值。