作者推荐
利用广度优先或模拟解决米诺骨牌
本文涉及的基础知识点
二分查找算法合集
题目
给你一个下标从 0 开始的 正整数 数组 nums 和一个 正整数 limit 。
在一次操作中,你可以选择任意两个下标 i 和 j,如果 满足 |nums[i] - nums[j]| <= limit ,则交换 nums[i] 和 nums[j] 。
返回执行任意次操作后能得到的 字典序最小的数组 。
如果在数组 a 和数组 b 第一个不同的位置上,数组 a 中的对应字符比数组 b 中的对应字符的字典序更小,则认为数组 a 就比数组 b 字典序更小。例如,数组 [2,10,3] 比数组 [10,2,3] 字典序更小,下标 0 处是两个数组第一个不同的位置,且 2 < 10 。
示例 1:
输入:nums = [1,5,3,9,8], limit = 2
输出:[1,3,5,8,9]
解释:执行 2 次操作:
- 交换 nums[1] 和 nums[2] 。数组变为 [1,3,5,9,8] 。
- 交换 nums[3] 和 nums[4] 。数组变为 [1,3,5,8,9] 。
即便执行更多次操作,也无法得到字典序更小的数组。
注意,执行不同的操作也可能会得到相同的结果。
示例 2:
输入:nums = [1,7,6,18,2,1], limit = 3
输出:[1,6,7,18,1,2]
解释:执行 3 次操作: - 交换 nums[1] 和 nums[2] 。数组变为 [1,6,7,18,2,1] 。
- 交换 nums[0] 和 nums[4] 。数组变为 [2,6,7,18,1,1] 。
- 交换 nums[0] 和 nums[5] 。数组变为 [1,6,7,18,1,2] 。
即便执行更多次操作,也无法得到字典序更小的数组。
示例 3:
输入:nums = [1,7,28,19,10], limit = 3
输出:[1,7,28,19,10]
解释:[1,7,28,19,10] 是字典序最小的数组,因为不管怎么选择下标都无法执行操作。
参数范围:
1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= limit <= 109
分析
时间复杂度
O(nlogn),枚举每个元素,每个枚举都需要二分查找。
代码
分析
setValue是nums按降序排序,如果一个数x1能替换比它大的数,那么一定存在一个比它大的数x2,且x2-x1 <= limit。setNot记录所有不存在x2的x1。
求一个数x能不替换成的最小数y:
在setValue中,y在x的右边。
setNot中 y在setNot.upper_bound(n)的左边
核心代码
class Solution {
public:
vector<int> lexicographicallySmallestArray(vector<int>& nums, int limit) {
std::multiset<int,std::greater<>> setValue(nums.begin(), nums.end());
std::set<int, std::greater<>> setNot;
for ( auto it = setValue.begin(); it != setValue.end(); ++it )
{
auto itNext = std::next(it);
if ((setValue.end() != itNext) && (*it - *itNext > limit))
{
setNot.emplace(*itNext);
}
}
for ( auto& n : nums)
{
auto it = setNot.upper_bound(n);
int iEnd = (setNot.end() == it) ? -1 : *it;
auto it2 = setValue.lower_bound(iEnd);
if( setValue.begin() != it2 )
{
n = *std::prev(it2);
setValue.erase(setValue.find(n));
continue;
}
setValue.erase(setValue.find(n));
auto ij = setValue.lower_bound(n);
if ((setValue.end() != ij) && (setValue.begin() != ij))
{
if (*std::prev(ij) - *ij > limit)
{
setNot.emplace(*ij);
}
}
}
return nums;
}
};
测试用例
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
Assert(v1[i], v2[i]);
}
}
int main()
{
vector nums, res;
int limit;
{
nums = { 1, 5, 3, 9, 8 };
limit = 2;
Solution slu;
res = slu.lexicographicallySmallestArray(nums, limit);
Assert(res, vector{1, 3, 5, 8, 9});
}
{
nums = { 1, 7, 6, 18, 2, 1 };
limit = 3;
Solution slu;
res = slu.lexicographicallySmallestArray(nums, limit);
Assert(res, vector{1, 6, 7, 18, 1, 2});
}
{
nums = { 1, 7, 28, 19, 10 };
limit = 3;
Solution slu;
res = slu.lexicographicallySmallestArray(nums, limit);
Assert(res, vector{1, 7, 28, 19, 10});
}
//CConsole::Out(res);
}
并集查找
周赛时,没想到并集查找,用自己想的办法,每个细节都需要斟酌、尝试,非常花时间。建议尽量用现有算法。
分析
规则一:如果a,b能交换,b,c能交换,则a,b,c可以交换。
a b c |
b a c |
c a b |
c b a |
规则二:如果a b c能互换,c d 能互换,则a 和d 能互换。
a b c d |
**c b a ** d |
d b a c |
d b c a |
规则一,a b ,b c => 可以调整成任何顺序
规则二: a b,b c ,c d=>可以调整成任何顺序
规则三:增加a e可以互换
a ??? e |
e ??? a |
根据规则二,???a 可以换成任意顺序 |
总结
利用图论知识,a b能互换则a b连通,利用并集查找解决问题。同一个并集查找升序排序。
代码
class CUnionFind
{
public:
CUnionFind(int iSize) :m_vNodeToRegion(iSize)
{
for (int i = 0; i < iSize; i++)
{
m_vNodeToRegion[i] = i;
}
m_iConnetRegionCount = iSize;
}
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 GetNodeCountOfRegion()//各联通区域的节点数量
{
const int iNodeSize = m_vNodeToRegion.size();
vector vRet(iNodeSize);
for (int i = 0; i < iNodeSize; i++)
{
vRet[GetConnectRegionIndex(i)]++;
}
return vRet;
}
std::unordered_map<int, vector> GetNodeOfRegion()
{
std::unordered_map<int, vector> 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 m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
int m_iConnetRegionCount;
};
class Solution {
public:
vector lexicographicallySmallestArray(vector& nums, int limit) {
m_c = nums.size();
auto vSort = nums;
sort(vSort.begin(), vSort.end());
CUnionFind uf(m_c);
for (int i = m_c - 1; i > 0; i–)
{
if (vSort[i] - vSort[i - 1] <= limit)
{
uf.Union(i, i - 1);
}
}
unordered_map<int, int> mValueToRegion;
unordered_map<int, vector> mReginToValues;
for (int i = 0; i < m_c; i++)
{
const int iRegion = uf.GetConnectRegionIndex(i);
mValueToRegion[vSort[i]] = iRegion;
mReginToValues[iRegion].emplace_back(vSort[i]);
}
for ( auto& [region, v] : mReginToValues)
{
std::sort(v.begin(), v.end(), std::greater<>());
}
for (int i = 0; i < m_c; i++)
{
const int iRegion = mValueToRegion[nums[i]];
nums[i] = mReginToValues[iRegion].back();
mReginToValues[iRegion].pop_back();
}
return nums;
}
int m_c;
};
优化
各连通区域是连续的,所以直接处理更简单。mValueToRegion[i] 记录第i个连通区域的数。比如:limit是4
14 12 8 3 1 ,可以分成{14 12 8} {3 1}.
代码
class Solution {
public:
vector lexicographicallySmallestArray(vector& nums, int limit) {
m_c = nums.size();
auto vSort = nums;
sort(vSort.begin(), vSort.end());
vector<vector> vGroupNum;
unordered_map<int, int> mValueToRegion;
for (int i = 0 ; i < m_c ; i++ )
{
if ((0 == i) || (vSort[i] - vSort[i - 1] > limit))
{
vGroupNum.emplace_back();
}
vGroupNum.back().emplace_back(vSort[i]);
mValueToRegion[vSort[i]] = vGroupNum.size() - 1;
}
for (auto& v : vGroupNum)
{
std::sort(v.begin(), v.end(), std::greater<>());
}
for (int i = 0; i < m_c; i++)
{
const int iRegion = mValueToRegion[nums[i]];
nums[i] = vGroupNum[iRegion].back();
vGroupNum[iRegion].pop_back();
}
return nums;
}
int m_c;
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快
速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境:
VS2022 C++17