Link:
题目要求
给你一个整数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]相互之间独立计算。
- 将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序 。
核心算法和原理
- BFS的想法:把数组看成一堆节点,从一个节点开始,可以翻到的节点是一个固定的范围
- 翻到的那个节点的ans即为 所有能翻到这个点的节点中ans最小的 + 1
- 由于出发点唯一,所以转换成了「从p开始到所有节点的最短路」,路权为1所以直接用BFS解决
- 判断任意节点i翻转后可达的范围:分别考虑左右边界,画图确定左边界和右边界的可能取值(max(lb1, lb2), min(rb1, rb2)),是一个step = 2的序列
- banned数组中的元素看作不可达即可
- 分别用 有序集合 和 并查集的源码见下
代码
BFS + 有序集合(std::set)
class Solution { public: vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) { /* * indices: 不包含banned的 未访问到的偶数节点和奇数节点集合 * 维护两个有序集合的原因:为了排除banned节点 并 使用二分查找优化在set中查找下边界,因为每次翻转后可到达的点要么奇要么偶 */ unordered_set<int> banSet{banned.begin(), banned.end()}; set<int> indices[2]; for (int i = 0; i < n; i++) { if (i != p && !banSet.contains(i)) { indices[i & 1 == 1].insert(i); } } // 插入一个不可能访问到的哨兵结点,这样下面set的迭代过程无需判断it != st.end() indices[0].insert(n); indices[1].insert(n); vector<int> ans(n, -1); ans[p] = 0; queue<int> q; q.push(p); while (!q.empty()) { int u = q.front(); // cout << u << endl; q.pop(); // 考虑越界问题后,算出来的「u节点翻转后可达的左右边界」 int lb = max(u - k + 1, k - u - 1); int rb = min(u + k - 1, 2 * n - k - u - 1); auto& st = indices[lb & 1 == 1]; // essential: it = st.erase(it)可以迭代it至下一个元素 for (auto it = st.lower_bound(lb); *it <= rb; it = st.erase(it)) { ans[*it] = ans[u] + 1; q.push(*it); } } return ans; } };
BFS + 并查集
class UnionSet { private: vector<int> fa; public: UnionSet(int n) : fa(n) { iota(fa.begin(), fa.end(), 0); } int find(int x) { if (fa[x] != x) { fa[x] = find(fa[x]); } return fa[x]; } void merge(int from, int to) { fa[find(from)] = fa[to]; } }; class Solution { public: vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) { UnionSet indices(n + 2); indices.merge(p, p + 2); for (int i: banned) { // 此之谓 删除操作 // 之后每次通过find寻找i时,自动找到>= i的 未被删除元素 indices.merge(i, i + 2); } vector<int> ans(n, -1); ans[p] = 0; queue<int> q; q.push(p); while (!q.empty()) { int u = q.front(); // cout << u << endl; q.pop(); // 考虑越界问题后,算出来的「u节点翻转后可达的左右边界」 int lb = max(u - k + 1, k - u - 1); int rb = min(u + k - 1, 2 * n - k - u - 1); for (int j = indices.find(lb); j <= rb; j = indices.find(j + 2)) { // 因为[lb, rb]中符合条件的最终都会merge到rb + 2 indices.merge(j, rb + 2); ans[j] = ans[u] + 1; q.push(j); } } return ans; } };
