@每日一题分享
今天分享的题目是Lec. 768 最多能完成排序的块 ||
题目如下:嫌麻烦的小伙伴可以先看看题目,然后在去lec做题
题目
思路
题目分析
题目要求我们获得最多的能过完成排序的块,实际上问题可以转换为能够符合题中整体排序要求的前提下,找到数组中每一个部门最短的排序子数组,可能这么说有点不太清晰,请看用例 [2,1,3,4,4];
我们首先确定,整体的排序数组为[1,2,3,4,4] (这点如何确定,后续会提到);另外,最短无序子数组中有提到,如何确定最短的需要排序的子数组:最短的排序的子数组的边界一定是需要调换位置的,因此,只需要找到两个最需要调换的边界即可。在上个例子中,[2,1] 即为两个两个边界,那么这个既能够构成最短的子数组。
那么,如何确定整体的排序顺序呢? 最暴力的方法就是通过排序;另外,利用滑动窗口的机制也能够找到最短的子数组,如上个例子中
显然,[2,1,3,4,4]和[1,2,3,4,4]中的[2,1]和[1,2]能够满足完成排序的块,并且是最短的,因为在他们都处于上边提到的边界。
代码
int maxChunksToSorted(vector<int>& arr)
{
vector<int> expected = arr; // 复制数组,通过排序获得整体有序的数组,然后在与原数组进行比对
sort(expected.begin(), expected.end());
unordered_map<int, int> count; // 记录每个元素出现的个数
int occurrence = 0; // 记录两个数组到 i 位置时,有多少种元素差异
int res = 0;
for (int i = 0; i < arr.size(); ++i)
{
// 利用滑动窗口的机制
int x = arr[i], y = expected[i];
++count[x];
if (count[x] == 1) ++occurrence;
if (count[x] == 0) --occurrence;
--count[y];
if (count[y] == -1) ++occurrence;
if (count[y] == 0) --occurrence;
// 如果当前没有元素差异,说明已经处于边界
if (occurrence == 0) ++res;
}
return res;
}
进阶分析
上述提到的方法调用了一次系统排序API,时间复杂度为
O
(
N
⋅
l
o
g
N
)
O(N \cdot \rm{log}N)
O(N⋅logN)
最多能完成排序的块中可以了解到,一个块能够满足整体有序的条件一定是 当前包括 i 位置以前的最大值一定小于 i 位置以后的最小值;
换成表达式即为 preMAX[i] <= sufMIN[i+1]
代码:
int maxChunksToSorted(vector<int>& arr)
{
int n = arr.size();
vector<int> preMAX(n,0);
vector<int> sufMIN(n,0);
preMAX[0] = arr[0];
sufMIN[n-1] = arr[n-1];
for (int i = 1; i < n; ++i) {
preMAX[i] = max(preMAX[i-1],arr[i]);
sufMIN[n-i-1] = min(sufMIN[n-i],arr[n-i-1]);
}
int ans = 0;
for (int i = 0; i < n-1; ++i) {
if (preMAX[i] <= sufMIN[i+1]) { // 一定不能同时包括 i 元素, 因为自称一块是需要包括当前元素的。
++ans;
}
}
return ans+1; // 上边的遍历中,没有考虑最后一段,需要加上最后一段
}
上述均为学习过程中的思考,简单记录一下