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 '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:
- all values in banned are unique
解题思路
比赛的时候想到用bfs求最短路,结果发现每个状态的转移次数太多了,要bfs的话时间复杂度差不多是,肯定超时的。
可以根据所在的不同位置把局面定义为对应的状态(即节点),那么一共有种状态。如果两个状态间能相互转换,那么就在这两个状态之间连一条权值为的边。因此发现可以从状态开始bfs,求到其他状态的最小距离。
可以发现边的数量非常多,假设当前的所在位置是,要对长度为的区间进行翻转(),等价于把变到。而对于固定长度为的区间翻转,最多变换到个不同的位置,如下图:
其中区间的左端点,对应的右端点,当然还要保证不能越界。因此可以发现如果在bfs时直接这样枚举每一条边,那么时间复杂度就达到了了。
可以发现能变换到的位置的奇偶性都是一样的。假设当前的左端点为,那么右端点就是,对区间翻转那么就会变到。当把区间右移一个单位,即,,那么就会变到,可以发现变化后的位置的奇偶性都是一样的。
为此,如果位于处,那么经过一次区间翻转,能够变化到的位置就是,当然还要保证不能越界。而事实上根据bfs的性质,如果一个节点已经被更新了,那么到这个节点的最小距离就已经确定了,之后不会再被更新。因此在枚举能够变化到的位置时实际上很多状态都是不会再被更新的了。那么有没有什么做法可以避免枚举到已经不会再更新的状态呢?
这里提供一个做法,就是开两个平衡树分别存所有奇数的状态和偶数的状态。当要枚举能变化到的状态时,先根据的奇偶性来判断转移到的状态是奇数还是偶数,然后在对应的平衡树中对区间内还存在的的状态进行更新,并从平衡树中删除。这样就能保证每次枚举到的状态都是还未被更新的状态。查找区间端点以及删除都是的时间复杂度,因此整个bfs的时间复杂度就是。
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 };
还可以用并查集来实现上面所说的删除操作。本质上就是找到某个区间内还没有被删除的位置,因此可以开个并查集来维护删除位置的连通性。当要删除一个位置时,那么就将所在的集合的代表元素(其实就是)指向所在集合的代表元素(注意要保证奇偶性相同,因此加)。每个集合的代表元素的含义就是当前集合中除了代表元素外,每个元素向右找的第一个还没被删除的位置。
AC代码如下,时间复杂度为:
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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17320848.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效