Minimum Reverse Operations

Minimum Reverse Operations

You are given an integer n and an integer p in the range [0, n - 1]. Representing a 0-indexed array arr of length n where all positions are set to 0's, except position p which is set to 1 .

You are also given an integer array banned containing some positions from the array. For the ith position in banned`arr[banned[i]] = 0`, and banned[i] != p.

You can perform multiple operations on arr. In an operation, you can choose a subarray with size k and reverse the subarray. However, the 1 in arr should never go to any of the positions in banned. In other words, after each operation arr[banned[i]] remains 0 .

Return an array ans where for each i from [0, n - 1]ans[i] is the minimum number of reverse operations needed to bring the 1 to position i in arr, or -1 if it is impossible.

  • A subarray is a contiguous non-empty sequence of elements within an array.
  • The values of ans[i] are independent for all i's.
  • The reverse of an array is an array containing the values in reverse order.

Example 1:

Input: n = 4, p = 0, banned = [1,2], k = 4
Output: [0,-1,-1,1]
Explanation: In this case k = 4 so there is only one possible reverse operation we can perform, which is reversing the whole array. Initially, 1 is placed at position 0 so the amount of operations we need for position 0 is 0. We can never place a 1 on the banned positions, so the answer for positions 1 and 2 is -1. Finally, with one reverse operation we can bring the 1 to index 3, so the answer for position 3 is 1. 

Example 2:

Input: n = 5, p = 0, banned = [2,4], k = 3
Output: [0,-1,-1,-1,-1]
Explanation: In this case the 1 is initially at position 0, so the answer for that position is 0. We can perform reverse operations of size 3. The 1 is currently located at position 0, so we need to reverse the subarray [0, 2] for it to leave that position, but reversing that subarray makes position 2 have a 1, which shouldn't happen. So, we can't move the 1 from position 0, making the result for all the other positions -1. 

 Example 3:

Input: n = 4, p = 2, banned = [0,1,3], k = 1
Output: [-1,-1,0,-1]
Explanation: In this case we can only perform reverse operations of size 1. So the 1 never changes its position.

Constraints:

  • 1n105
  • 0pn1
  • 0banned.lengthn1
  • 0banned[i]n1
  • 1kn
  • banned[i]p
  • all values in banned are unique

 

解题思路

  比赛的时候想到用bfs求最短路,结果发现每个状态的转移次数太多了,要bfs的话时间复杂度差不多是O(nk),肯定超时的。

  可以根据1所在的不同位置把局面定义为对应的状态(即节点),那么一共有n种状态。如果两个状态间能相互转换,那么就在这两个状态之间连一条权值为1的边。因此发现可以从状态p开始bfs,求到其他状态的最小距离。

  可以发现边的数量非常多,假设当前1的所在位置是x,要对长度为k的区间[l,r]进行翻转(lxr),等价于把x变到rx+l。而对于固定长度为k的区间翻转,x最多变换到k个不同的位置,如下图:

  其中区间的左端点l[xk+1,x],对应的右端点r[x,x+k1],当然还要保证不能越界。因此可以发现如果在bfs时直接这样枚举每一条边,那么时间复杂度就达到了O(nk)了。

  可以发现x能变换到的位置的奇偶性都是一样的。假设当前的左端点为l=i,那么右端点就是r=i+k1,对区间[l,r]翻转那么x就会变到rx+l=2ix+k1。当把区间右移一个单位,即l=i+1r=i+k,那么x就会变到rx+l=2(i+1)x+k1,可以发现变化后的位置的奇偶性都是一样的。

  为此,如果1位于x处,那么经过一次区间翻转,x能够变化到的位置就是[xk1,xk+1,xk+3,,x+k1],当然还要保证不能越界。而事实上根据bfs的性质,如果一个节点已经被更新了,那么到这个节点的最小距离就已经确定了,之后不会再被更新。因此在枚举x能够变化到的位置时实际上很多状态都是不会再被更新的了。那么有没有什么做法可以避免枚举到已经不会再更新的状态呢?

  这里提供一个做法,就是开两个平衡树分别存所有奇数的状态和偶数的状态。当要枚举x能变化到的状态时,先根据xk1的奇偶性来判断转移到的状态是奇数还是偶数,然后在对应的平衡树中对区间[xk1,x+k1]内还存在的的状态进行更新,并从平衡树中删除。这样就能保证每次枚举到的状态都是还未被更新的状态。查找区间端点以及删除都是O(logn)的时间复杂度,因此整个bfs的时间复杂度就是O(nlogn)

  AC代码如下:

复制代码
 1 class Solution {
 2 public:
 3     vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
 4         vector<bool> vis(n);
 5         for (auto &x : banned) {
 6             vis[x] = true;
 7         }
 8         set<int> st[2];
 9         for (int i = 0; i < n; i++) {
10             if (!vis[i] && i != p) st[i & 1].insert(i);    // 根据奇偶性把数放到对应的平衡树中 
11         }
12         st[0].insert({-0x3f3f3f3f, 0x3f3f3f3f});    // 插入哨兵,保证在二分时有结果 
13         st[1].insert({-0x3f3f3f3f, 0x3f3f3f3f});
14         vector<int> dist(n, -1);
15         dist[p] = 0; 
16         queue<int> q({p});
17         while (!q.empty()) {
18             int t = q.front();
19             q.pop();
20             int x = k - 1 - t & 1;    // 判断x能转移到的状态的奇偶性 
21             auto l = st[x].lower_bound(2 * max(0, t - k + 1) + k - 1 - t);    // 找到最左边能变化到的位置 
22             auto r = st[x].upper_bound(2 * min(n - 1, t + k - 1) - k + 1 - t);    // 找到最右边能变化到的位置 
23             while (l != r) {
24                 dist[*l] = dist[t] + 1;
25                 q.push(*l);
26                 l = st[x].erase(l);    // 从平衡树中删除已被更新的状态 
27             }
28         }
29         return dist;
30     }
31 };
复制代码

  还可以用并查集来实现上面所说的删除操作。本质上就是找到某个区间内还没有被删除的位置,因此可以开个并查集来维护删除位置的连通性。当要删除一个位置x时,那么就将x所在的集合的代表元素(其实就是x)指向x+2所在集合的代表元素(注意要保证奇偶性相同,因此加2)。每个集合的代表元素的含义就是当前集合中除了代表元素外,每个元素向右找的第一个还没被删除的位置。

  AC代码如下,时间复杂度为O(n)

复制代码
 1 class Solution {
 2 public:
 3     vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
 4         vector<int> fa(n + 2);
 5         for (int i = 0; i < n + 2; i++) {
 6             fa[i] = i;
 7         }
 8         function<int(int)> find = [&](int x) {
 9             return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
10         };
11         for (auto &x : banned) {
12             fa[x] = find(x + 2);
13         }
14         fa[p] = find(p + 2);
15         vector<int> dist(n, -1);
16         dist[p] = 0;
17         queue<int> q({p});
18         while (!q.empty()) {
19             int t = q.front();
20             q.pop();
21             int l = find(2 * max(0, t - k + 1) + k - 1 - t);
22             int r = 2 * min(n - 1, t + k - 1) - k + 1 - t;
23             while (l <= r) {
24                 dist[l] = dist[t] + 1;
25                 q.push(l);
26                 fa[l] = find(l + 2);
27                 l = find(l);
28             }
29         }
30         return dist;
31     }
32 };
复制代码

 

参考资料

  最少翻转操作数 BFS+平衡树【力扣周赛 339】:https://www.bilibili.com/video/BV1va4y1M7Fr/

posted @   onlyblues  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示