每日算法刷题Day44 7.8:leetcode前缀和4道题,用时1h40min
距离和
1.套路
图解
![[距离和.png]]
class Solution {
public:vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {sort(nums.begin(), nums.end()); // 排序int n = nums.size(), m = queries.size();vector<long long> res(m);vector<long long> s(n + 1);s[0] = 0;for (int i = 0; i < n; ++i)s[i + 1] = s[i] + nums[i];for (int i = 0; i < m; ++i) {int q = queries[i];long long j = lower_bound(nums.begin(), nums.end(), q) -nums.begin(); // 找到第一个大于等于q的索引jlong long left = q * j - s[j]; // [0,j),绿色面积long long right = s[n] - s[j] - q * (n - j); // [j,n),蓝色面积res[i] = left + right;}return res;}
};
2.题目描述
1.给你一个 非递减 有序整数数组 nums
。
请你建立并返回一个整数数组 result
,它跟 nums
长度相同,且result[i]
等于 nums[i]
与数组中所有其他元素差的绝对值之和(条件)。
换句话说, result[i]
等于 sum(|nums[i]-nums[j]|)
,其中 0 <= j < nums.length
且 j != i
(下标从 0 开始)。
2.给你一个下标从 0 开始的整数数组 nums
。现有一个长度等于 nums.length
的数组 arr
。对于满足 nums[j] == nums[i]
且 j != i
的所有 j
,arr[i]
等于所有 |i - j|
之和(条件)。如果不存在这样的 j
,则令 arr[i]
等于 0
。
返回数组 arr
(答案) 。
3.给你一个正整数数组 nums
。
同时给你一个长度为 m
的整数数组 queries
。第 i
个查询中,你需要将 nums
中所有元素变成 queries[i]
。你可以执行以下操作 任意 次:
- 将数组里一个元素 增大 或者 减小
1
。
请你返回一个长度为m
的数组answer
(答案) ,其中answer[i]
是将nums
中所有元素变成queries[i]
的 最少 操作次数。
注意,每次查询后,数组变回最开始的值。
3.学习经验
1.首先是一个排序的数组,为了计算所有值到某一个定值q
的距离和,可以二分lower_bound
找到第一个大于等于q的值的索引j
,从而划分成蓝色部分和绿色部分,再利用前缀和整个部分求和
1. 1685.有序数组中差绝对值之和(中等)
1685. 有序数组中差绝对值之和 - 力扣(LeetCode)
思想
1.给你一个 非递减 有序整数数组 nums
。
请你建立并返回一个整数数组 result
,它跟 nums
长度相同,且result[i]
等于 nums[i]
与数组中所有其他元素差的绝对值之和。
换句话说, result[i]
等于 sum(|nums[i]-nums[j]|)
,其中 0 <= j < nums.length
且 j != i
(下标从 0 开始)。
2.跟套路一样,只不过已经排好序了
代码
class Solution {
public:vector<int> getSumAbsoluteDifferences(vector<int>& nums) {int n = nums.size();vector<int> res(n);vector<long long> s(n + 1);s[0] = 0;for (int i = 0; i < n; ++i)s[i + 1] = s[i] + nums[i];for (int i = 0; i < n; ++i) {int j =lower_bound(nums.begin(), nums.end(), nums[i]) - nums.begin();// [0,j)long long left = nums[i] * j - s[j];// [j,n)long long right = s[n] - s[j] - (n - j) * nums[i];res[i] = left + right;}return res;}
};
2. 2615.等值距离和(中等,学习)
1685. 有序数组中差绝对值之和 - 力扣(LeetCode)
思想
1.给你一个下标从 0 开始的整数数组 nums
。现有一个长度等于 nums.length
的数组 arr
。对于满足 nums[j] == nums[i]
且 j != i
的所有 j
,arr[i]
等于所有 |i - j|
之和。如果不存在这样的 j
,则令 arr[i]
等于 0
。
返回数组 arr
。
2.因为题目是求j != i
的所有 j
,arr[i]
等于所有 |i - j|
之和,所以可以联想到图示的nums
数组的值应该是下标,基线q就是是i
,因为这些下标共性是nums[下标]
相同,所以得先按nums[下标]
分组,得到下标数组,这个用map来实现
3.还可以考虑去用增量的形式实现,如下图所示:
![[等值距离和.jpg]]
代码
相同元素分组+前缀和
class Solution {
public:vector<long long> distance(vector<int>& nums) {int n = nums.size();vector<long long> res(n);map<int, vector<long long>> mp; // 分组,相同的nums[i]的下标for (int i = 0; i < n; ++i) {mp[nums[i]].emplace_back(i);}for (auto& t : mp) {auto idxs = t.second;int m = idxs.size();vector<long long> s(m + 1);for (int i = 0; i < m; ++i)s[i + 1] = s[i] + idxs[i];for (int i = 0; i < m; ++i) {// q为idxs[i]int j = lower_bound(idxs.begin(), idxs.end(), idxs[i]) -idxs.begin();long long left = idxs[i] * j - s[j];long long right = s[m] - s[j] - idxs[i] * (m - j);res[idxs[i]] = left + right;}}return res;}
};
相同元素分组+考虑增量
class Solution {
public:vector<long long> distance(vector<int>& nums) {int n = nums.size();vector<long long> res(n);map<int, vector<long long>> mp; // 分组,相同的nums[i]的下标for (int i = 0; i < n; ++i) {mp[nums[i]].emplace_back(i);}for (auto& t : mp) {auto idxs = t.second; // idxs值为原先下标int m = idxs.size();long long s = 0; // 先算idxs[0]到其他位置的距离for (int i = 1; i < m; ++i)s += idxs[i] - idxs[0];res[idxs[0]] = s;for (int i = 1; i < m; ++i) {long long t =s + (2 * i - m) *(idxs[i] - idxs[i - 1]); // 考虑增量后idxs[i]位置的sres[idxs[i]] = t;s = t;}}return res;}
};
3.2602.使数组元素全部相等的最少操作次数(中等,学习)
2602. 使数组元素全部相等的最少操作次数 - 力扣(LeetCode)
思想
1.给你一个正整数数组 nums
。
同时给你一个长度为 m
的整数数组 queries
。第 i
个查询中,你需要将 nums
中所有元素变成 queries[i]
。你可以执行以下操作 任意 次:
- 将数组里一个元素 增大 或者 减小
1
。
请你返回一个长度为m
的数组answer
,其中answer[i]
是将nums
中所有元素变成queries[i]
的 最少 操作次数。
注意,每次查询后,数组变回最开始的值。
2.如套路所示
代码
排序+二分+前缀和:
class Solution {
public:vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {sort(nums.begin(), nums.end()); // 排序int n = nums.size(), m = queries.size();vector<long long> res(m);vector<long long> s(n + 1);s[0] = 0;for (int i = 0; i < n; ++i)s[i + 1] = s[i] + nums[i];for (int i = 0; i < m; ++i) {int q = queries[i];long long j = lower_bound(nums.begin(), nums.end(), q) -nums.begin(); // 找到第一个大于等于q的索引jlong long left = q * j - s[j]; // [0,j),绿色面积long long right = s[n] - s[j] - q * (n - j); // [j,n),蓝色面积res[i] = left + right;}return res;}
};
暴力方法:
class Solution {
public:vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {int n=nums.size(),m=queries.size();vector<long long> res;for(int i=0;i<m;++i){long long cnt=0;for(int j=0;j<n;++j) cnt+=abs(nums[j]-queries[i]);res.emplace_back(cnt);}return res;}
};
前缀异或和(异或先不考虑)
1.套路
从集合论到位运算,常见位运算技巧分类总结!
还是前缀和优化,只不过前缀和生成和计算的时候可以用异或优化,但是目前先不考虑这层优化,先写出来
2.题目描述
1.给你一个字符串 s
,请你对 s
的子串进行检测。
每次检测,待检子串都可以表示为 queries[i] = [left, right, k]
。我们可以 重新排列 子串 s[left], ..., s[right]
,并从中选择 最多 k
项替换成任何小写英文字母。
如果在上述检测过程中,子串可以变成回文形式的字符串,那么检测结果为 true
,否则结果为 false
(条件)。
返回答案数组 answer[]
,其中 answer[i]
是第 i
个待检子串 queries[i]
的检测结果(答案)。
注意:在替换时,子串中的每个字母都必须作为 独立的 项进行计数,也就是说,如果 s[left..right] = "aaa"
且 k = 2
,我们只能替换其中的两个字母。(另外,任何检测都不会修改原始字符串 s
,可以认为每次检测都是独立的)
3.学习经验
1. 1177.构建回文串检测(中等,学习)
思想
1.给你一个字符串 s
,请你对 s
的子串进行检测。
每次检测,待检子串都可以表示为 queries[i] = [left, right, k]
。我们可以 重新排列 子串 s[left], ..., s[right]
,并从中选择 最多 k
项替换成任何小写英文字母。
如果在上述检测过程中,子串可以变成回文形式的字符串,那么检测结果为 true
,否则结果为 false
。
返回答案数组 answer[]
,其中 answer[i]
是第 i
个待检子串 queries[i]
的检测结果。
注意:在替换时,子串中的每个字母都必须作为 独立的 项进行计数,也就是说,如果 s[left..right] = "aaa"
且 k = 2
,我们只能替换其中的两个字母。(另外,任何检测都不会修改原始字符串 s
,可以认为每次检测都是独立的)
2.已经分析出对于每个查询,要计算出[left,right]
出现次数为奇数次的字母的种类个数m
,且m/2<=k
返回True
(重要分析)
3.现在考虑如何分析出优化,上面要得到[left,right]
出现次数为奇数次的字母的种类个数m
,可以用前缀和计算每种字母出现的次数,相比之前的前缀和数组,多了一个长度为26的第二维度,表示不同的字母,可以用固定大小的数组容器array<int,26>
表示
4.异或的优化先暂时不考虑
代码
暴力超时:
class Solution {
public:vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {int n = queries.size();vector<bool> res(n);for (int i = 0; i < n; ++i) {int left = queries[i][0], right = queries[i][1], k = queries[i][2];map<char, long long> mps; // 字符-次数for (int j = left; j <= right; ++j)++mps[s[j]];long long cnt = 0;for (auto& mp : mps) {if (mp.second % 2)++cnt;}if (cnt / 2 <= k)res[i] = true;elseres[i] = false;}return res;}
};
前缀和优化:
class Solution {
public:vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {int n = s.size(), m = queries.size();vector<bool> res(m);vector<array<int, 26>> sum(n + 1);for (int i = 0; i < n; ++i) {sum[i + 1] = sum[i];++sum[i + 1][s[i] - 'a'];}for (int i = 0; i < m; ++i) {int left = queries[i][0], right = queries[i][1], k = queries[i][2];long long cnt = 0;for (int j = 0; j < 26; ++j) {cnt += (sum[right + 1][j] - sum[left][j]) % 2; // 奇数+1,偶数+0}if (cnt / 2 <= k)res[i] = true;elseres[i] = false;}return res;}
};