本文涉及知识点
C++贪心
数论:质数、最大公约数、菲蜀定理
C++图论
LeetCode2607. 使子数组元素和相等
给你一个下标从 0 开始的整数数组 arr 和一个整数 k 。数组 arr 是一个循环数组。换句话说,数组中的最后一个元素的下一个元素是数组中的第一个元素,数组中第一个元素的前一个元素是数组中的最后一个元素。
你可以执行下述运算任意次:
选中 arr 中任意一个元素,并使其值加上 1 或减去 1 。
执行运算使每个长度为 k 的 子数组 的元素总和都相等,返回所需要的最少运算次数。
子数组 是数组的一个连续部分。
示例 1:
输入:arr = [1,4,1,3], k = 2
输出:1
解释:在下标为 1 的元素那里执行一次运算,使其等于 3 。
执行运算后,数组变为 [1,3,1,3] 。
- 0 处起始的子数组为 [1, 3] ,元素总和为 4
- 1 处起始的子数组为 [3, 1] ,元素总和为 4
- 2 处起始的子数组为 [1, 3] ,元素总和为 4
- 3 处起始的子数组为 [3, 1] ,元素总和为 4
示例 2:
输入:arr = [2,5,5,7], k = 3
输出:5
解释:在下标为 0 的元素那里执行三次运算,使其等于 5 。在下标为 3 的元素那里执行两次运算,使其等于 5 。
执行运算后,数组变为 [5,5,5,5] 。
- 0 处起始的子数组为 [5, 5, 5] ,元素总和为 15
- 1 处起始的子数组为 [5, 5, 5] ,元素总和为 15
- 2 处起始的子数组为 [5, 5, 5] ,元素总和为 15
- 3 处起始的子数组为 [5, 5, 5] ,元素总和为 15
提示:
1 <= k <= arr.length <= 105
1 <= arr[i] <= 109
同余 中位数贪心
错误想法
nums[i…i+k-1]之和等于nums[i+1…i+k],则nums[i]等于nums[i+k] 。
i1<k <i2,i1%k等于i2%k ,i2+k >= n。nums[i2…i1-1]之和等于nums[i2+1…i1]
→
\rightarrow
→ nums[i2] == nums[i1]。
故本题
⟺
\iff
⟺ 同余的下标全部相等。根据中位数贪心,同余的数全部等于中的数。
并集查找
nums[i]和nums[(i+k)%nums.size()]相等,用并集查找处理间接相等。
代码
核心代码
class CUnionFind
{
public:
CUnionFind(int iSize) :m_vNodeToRegion(iSize)
{
for (int i = 0; i < iSize; i++)
{
m_vNodeToRegion[i] = i;
}
m_iConnetRegionCount = iSize;
}
CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
{
for (int i = 0; i < vNeiBo.size(); i++) {
for (const auto& n : vNeiBo[i]) {
Union(i, n);
}
}
}
int GetConnectRegionIndex(int iNode)
{
int& iConnectNO = m_vNodeToRegion[iNode];
if (iNode == iConnectNO)
{
return iNode;
}
return iConnectNO = GetConnectRegionIndex(iConnectNO);
}
void Union(int iNode1, int iNode2)
{
const int iConnectNO1 = GetConnectRegionIndex(iNode1);
const int iConnectNO2 = GetConnectRegionIndex(iNode2);
if (iConnectNO1 == iConnectNO2)
{
return;
}
m_iConnetRegionCount--;
if (iConnectNO1 > iConnectNO2)
{
UnionConnect(iConnectNO1, iConnectNO2);
}
else
{
UnionConnect(iConnectNO2, iConnectNO1);
}
}
bool IsConnect(int iNode1, int iNode2)
{
return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
}
int GetConnetRegionCount()const
{
return m_iConnetRegionCount;
}
vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
{
const int iNodeSize = m_vNodeToRegion.size();
vector<int> vRet(iNodeSize);
for (int i = 0; i < iNodeSize; i++)
{
vRet[GetConnectRegionIndex(i)]++;
}
return vRet;
}
std::unordered_map<int, vector<int>> GetNodeOfRegion()
{
std::unordered_map<int, vector<int>> ret;
const int iNodeSize = m_vNodeToRegion.size();
for (int i = 0; i < iNodeSize; i++)
{
ret[GetConnectRegionIndex(i)].emplace_back(i);
}
return ret;
}
private:
void UnionConnect(int iFrom, int iTo)
{
m_vNodeToRegion[iFrom] = iTo;
}
vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
int m_iConnetRegionCount;
};
class Solution {
public:
long long makeSubKSumEqual(vector<int>& arr, int k) {
CUnionFind uf(arr.size());
for (int i = 0; i < arr.size(); i++) {
uf.Union(i, (i + k) % arr.size());
}
auto m = uf.GetNodeOfRegion();
long long ans = 0;
for (auto& [tmp,indexs] : m) {
vector<int> v;
for (const auto& i : indexs) {
v.emplace_back(arr[i]);
}
const int i = v.size() / 2;
nth_element(v.begin(), v.begin() + i, v.end());
for (const auto& n : v) {
ans += abs(n - v[i]);
}
}
return ans;
}
};
单元测试
vector<int> arr;
int k;
TEST_METHOD(TestMethod11)
{
arr = { 1, 4, 1, 3 }, k = 2;
auto res = Solution().makeSubKSumEqual(arr, k);
AssertEx(1LL, res);
}
TEST_METHOD(TestMethod12)
{
arr = { 2,5,5,7 }, k = 3;
auto res = Solution().makeSubKSumEqual(arr, k);
AssertEx(5LL, res);
}
TEST_METHOD(TestMethod13)
{
arr = { 10,3,8 }, k = 2;
auto res = Solution().makeSubKSumEqual(arr, k);
AssertEx(7LL, res);
}
TEST_METHOD(TestMethod14)
{
arr = { 10,9,1,10,5 }, k = 3;
auto res = Solution().makeSubKSumEqual(arr, k);
AssertEx(14LL, res);
}
裴蜀定理
所有长度为k的子数组相等
→
\rightarrow
→ a[i] = a[i+kx]
n = nums.size()
循环数组
→
\rightarrow
→ a[i] = a[i+ny]
两者结合:a[i] = a[i+kx+ny]
根据裴蜀定理 kx+ny一定是gcd(k,n)的倍数。且存在正数解(x1,y1)使得kx+ny=gcd(k,n) 。我们对x1,y1同时乘以z,则结果是gck(k,n)的z倍。
故:kx+ny 只能是gck(k,n)的倍数,且可以是gcd(k,n)的任何倍数。
即: a[i]= a[i + z *gcd(k,n)],
⟺
\iff
⟺ 周期是gcd(k,n)
代码
class Solution {
public:
long long makeSubKSumEqual(vector<int>& arr, int k) {
const int cycle = gcd(arr.size(), k);
vector<vector<int>> v1(k);
for (int i = 0; i < arr.size(); i++) {
v1[i % cycle].emplace_back(arr[i]);
}
long long ans = 0;
for (auto& v : v1) {
const int i = v.size() / 2;
nth_element(v.begin(), v.begin() + i, v.end());
for (const auto& n : v) {
ans += abs(n - v[i]);
}
}
return ans;
}
};
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。