给你一个整数 n 和一个在范围 [0, n - 1] 以内的整数 p ,它们表示一个长度为 n 且下标从 0 开始的数组 arr ,数组中除了下标为 p 处是 1 以外,其他所有数都是 0 。
同时给你一个整数数组 banned ,它包含数组中的一些位置。banned 中第 i 个位置表示 arr[banned[i]] = 0 ,题目保证 banned[i] != p 。
你可以对 arr 进行 若干次 操作。一次操作中,你选择大小为 k 的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr 中唯一的 1 不会到达任何 banned 中的位置。换句话说,arr[banned[i]] 始终 保持 0 。
请你返回一个数组 ans ,对于 [0, n - 1] 之间的任意下标 i ,ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,如果无法放到位置 i 处,此数为 -1 。
子数组 指的是一个数组里一段连续 非空 的元素序列。
对于所有的 i ,ans[i] 相互之间独立计算。
将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序 。
1 <= n <= 105
0 <= p <= n - 1
0 <= banned.length <= n - 1
0 <= banned[i] <= n - 1
1 <= k <= n
banned[i] != p
banned 中的值 互不相同
不考虑banned。假定1在i出,翻转[i,i+k),则1到了i+k-1处。翻转[j,j+k-1],翻转之前,i距离子数组的开始i-j,那么翻转后,1的距离子数组的结束i-j,即:j+k-1-(i-j)= k+2*j-i-1=k-i-1+2*j
j的取值范围为:[i-k+1,i],同时[0,n-k]
n=5,k=4
n=5,k=4 | ||
10000 | 00010 | k-i-1=3新位置为:3+0=3 |
01000 | 00100 00001 | k-i-1=2新位置为:2+0=2 2+2=4 |
00100 | 01000 00010 | k-i-1=1新位置为:1+0=1 1+2=3 |
00010 | 10000 00100 | k-i-1=0新位置为:0+0=0 0+2=2 |
00001 | 01000 | k-i-1=-1新位置为: -1+2=1 |
注意:
p已经处理。
- class Solution {
- public:
- vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
- vector<int> vRet(n, -1);
- vRet[p] = 0;
- queue<int> que;
- que.emplace(p);
- set<int> set0, set1;
- for (int i = 0; i < n; i++)
- {
- if (i & 1)
- {
- set1.emplace(i);
- }
- else
- {
- set0.emplace(i);
- }
- }
- for (const auto& b : banned)
- {
- set0.erase(b);
- set1.erase(b);
- }
- set0.erase(p);
- set1.erase(p);
- while (que.size())
- {
- const auto cur = que.front();
- que.pop();
- const int j0 = max(0, cur - k + 1);
- const int j1 = min(cur, n - k);
- const int next0 = k - cur - 1 + 2 * j0;
- const int next1 = k - cur - 1 + 2 * j1;
- auto& setCur = (next0 & 1) ? set1 : set0;
- auto it0 = setCur.lower_bound(next0);
- auto it1 = setCur.upper_bound(next1 + 1);
- for (auto it = it0; it != it1; ++it)
- {
- vRet[*it] = vRet[cur] + 1;
- que.emplace(*it);
- }
- setCur.erase(it0, it1);
- }
- return vRet;
- }
- };
用广度优先实现,入队n次。每次出队时间复杂度O(k),总时间复杂度O(kn)。出队的时间复杂度可以优化。每次出队是连续的奇数或偶数,我们可以用两个std::set分别纪录未处理的奇数和偶数。未处理分两种情况:已经处理,被禁止。每次出队只需要查询两次,时间复杂度O(logn)。故总时间复杂度为O(nlogn)。
如果i是偶数,vDo[i]记录需要处理的偶数中,大于等于i,且最小的数。奇数类似。以n等于8为例,只分析偶数,奇数类似。初始{0,2,4,6}
{0,2,4,6} | 处理0{2,2,4,6} 处理2{0,4,4,6} 处理4{0,2,6,6} 处理6{0,2,4,MAX} |
2,2,4,6 | 处理0{2,4,4,6}处理2{2,4,4,6} 处理4{2,2,6,6}处理6{2,2,6,MAX} |
{2,4,4,6} | 处理0{4,4,6,6},处理2{2,4,6,6}处理4{2,4,6,6}处理6{2,4,4,MAX} |
… | … |
非常类似并集查找,由于i一定小于vDo[i],所以不会有环。可以直接使用封装好的有向图的并集查找。 |
- class Solution {
- public:
- vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
- vector<int> vRet(n, -1);
- vRet[p] = 0;
- queue<int> que;
- que.emplace(p);
- vector<int> vNext(n);
- for (int i = 0; i < n; i++)
- {
- vNext[i] = i;
- }
- const int iMax = 1000 * 1000;
- auto HasDo = [&](int b)
- {
- vNext[b] = (b + 2 < n) ? vNext[b + 2] : iMax;
- };
-
- banned.emplace_back(p);
- for (const auto& b : banned)
- {
- HasDo(b);
- }
- while (que.size())
- {
- const auto cur = que.front();
- que.pop();
- const int j0 = max(0, cur - k + 1);
- const int j1 = min(cur, n - k);
- int next0 = k - cur - 1 + 2 * j0;
- const int next1 = k - cur - 1 + 2 * j1;
-
- for (next0 = GetNext(vNext, next0, iMax); next0 <= next1;)
- {
- vRet[next0] = vRet[cur] + 1;
- HasDo(next0);
- que.emplace(next0);
- next0 = GetNext(vNext, next0, iMax);
- }
-
- }
- return vRet;
- }
- int GetNext(vector<int>& vNext,const int b,const int iMax)
- {
- if ((iMax == b) || (b == vNext[b]))
- {
- return b;
- };
- return vNext[b]= GetNext(vNext, vNext[b],iMax);
- };
- };
- class Solution {
- public:
- vector<int> minReverseOperations(const int n, const int p, vector<int>& banned, int k) {
-
- std::set<int> setOdd, setEven;
- {
- vector<bool> vBanned(n);
- for (const auto& b : banned)
- {
- vBanned[b] = true;
- }
- for (int i = 0; i < n; i++)
- {
- if (p == i)
- {
- continue;
- }
- if (vBanned[i])
- {
- continue;
- }
- if (i & 1)
- {
- setOdd.emplace(i);
- }
- else
- {
- setEven.emplace(i);
- }
- }
- }
-
- std::queue<int> que;
- que.emplace(p);
- vector<int> vDis(n, -1);
- vDis[p] = 0;
- for (int i = 0; que.size(); i++)
- {
- std::queue<int> queNext;
- while (que.size())
- {
- const int iCur = que.front();
- que.pop();
- std::set<int>& setNoDo = ((iCur + k - 1) & 1) ? setOdd : setEven;
- const int iLower = max(iCur - k + 1, k - iCur - 1);
- const int iUpper = min(iCur + k - 1, 2 * n - k - iCur - 1);
- const auto it1 = setNoDo.lower_bound(iLower);
- const auto it2 = setNoDo.upper_bound(iUpper);
- for (auto tmp = it1; tmp != it2; ++tmp)
- {
- queNext.emplace(*tmp);
- vDis[*tmp] = i + 1;
- }
- setNoDo.erase(it1, it2);
- }
- que.swap(queNext);
- }
- return vDis;
- }
- };
class Solution {
public:
vector
std::set
for (int i = 0; i < n; i++)
{
setNeedDo[i % 2].emplace(i);
}
for (const auto& n : banned)
{
setNeedDo[n % 2].erase(n);
}
vector
vRet[p] = 0;
setNeedDo[p % 2].erase(p);
queue
que.emplace(p);
while (que.size())
{
const auto cur = que.front();
que.pop();
const int leftMin = max(cur - (k - 1), 0);
const int rightMin = leftMin + (k - 1);
const int leftMax = min(cur, n - 1 - (k - 1));
const int iPosMin = rightMin - (cur - leftMin);//翻转后的位置,子数组右移一位,翻转后的位置移动2位
const int index = iPosMin % 2;
auto it = setNeedDo[index].lower_bound(iPosMin);
auto ij = setNeedDo[index].upper_bound(iPosMin + 2 * (leftMax - leftMin));
for (auto tmp = it; tmp != ij; ++tmp)
{
vRet[*tmp] = vRet[cur] + 1;
que.emplace(*tmp);
}
setNeedDo[index].erase(it, ij);
}
return vRet;
}
};
如果你觉得复杂,想从简单的算法开始,可以学习我的视频课程。
https://edu.csdn.net/course/detail/38771
我的其它课程
https://edu.csdn.net/lecturer/6176
win7 VS2019 C++17
算法精讲《闻缺陷则喜算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653