Hard

题目描述

给你一个整数 n 和一个整数 p,表示一个长度为 n 的数组 arr,其中除了位置 p 被设置为 1 之外,所有元素都被设置为 0。同时给你一个整数数组 banned,包含被限制的位置。

arr 上执行以下操作:

  • 如果唯一的 1 不在 banned 位置上,则反转大小为 k 的子数组。

返回一个长度为 n 的整数数组 answer,其中第 i 个结果是将唯一的 1 移动到数组中位置 i 所需的最少操作次数,如果不可能则返回 -1。

示例 1:

输入:n = 4, p = 0, banned = [1,2], k = 4
输出:[0,-1,-1,1]
解释:
- 最初 1 位于位置 0,所以位置 0 需要 0 次操作。
- 我们永远不能将 1 放在被禁止的位置上,所以位置 1 和 2 的答案是 -1。
- 执行大小为 4 的操作来反转整个数组。
- 一次操作后,1 位于位置 3,所以位置 3 的答案是 1。

示例 2:

输入:n = 5, p = 0, banned = [2,4], k = 3
输出:[0,-1,-1,-1,-1]
解释:
- 最初 1 位于位置 0,所以位置 0 需要 0 次操作。
- 我们不能对子数组位置 [0, 2] 执行操作,因为位置 2 在 banned 中。
- 因为 1 不能设置在位置 2,所以通过更多操作也不可能将 1 设置在其他位置。

示例 3:

输入:n = 4, p = 2, banned = [0,1,3], k = 1
输出:[-1,-1,0,-1]
解释:执行大小为 1 的操作,1 永远不会改变位置。

约束:

  • 1 <= n <= 10^5
  • 0 <= p <= n - 1
  • 0 <= banned.length <= n - 1
  • 0 <= banned[i] <= n - 1
  • 1 <= k <= n
  • banned[i] != p
  • banned 中的所有值都是唯一的

解题思路

这是一个图论问题,可以用 BFS 求最短路径。关键在于理解反转操作的本质:

核心思想:

  1. 当前位置为 pos,反转长度为 k 的子数组后,1 会到达新位置
  2. 对于位置 pos,可能的反转区间是 [l, l+k-1],其中 l 的范围需要满足:
    • l <= pos <= l+k-1(pos 在反转区间内)
    • 0 <= l <= n-k(区间合法)
  3. 反转后,位置 pos 会变成 l + (l+k-1) - pos = 2*l + k - 1 - pos

算法步骤:

  1. 使用 BFS,从起始位置 p 开始
  2. 对每个当前位置,计算所有可能的目标位置
  3. 跳过被禁止的位置和已访问的位置
  4. 使用 set 优化,避免重复计算已访问位置

优化要点:

  • 使用两个 set 分别存储奇偶位置的未访问节点,因为反转操作具有奇偶性质
  • 每次访问后立即从 set 中移除,避免重复访问

代码实现

class Solution {
public:
    vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {
        vector<int> answer(n, -1);
        unordered_set<int> ban(banned.begin(), banned.end());
        
        // 分别存储奇偶位置的未访问节点
        set<int> odd, even;
        for (int i = 0; i < n; i++) {
            if (ban.find(i) == ban.end()) {
                if (i % 2 == 0) even.insert(i);
                else odd.insert(i);
            }
        }
        
        queue<int> q;
        q.push(p);
        answer[p] = 0;
        if (p % 2 == 0) even.erase(p);
        else odd.erase(p);
        
        while (!q.empty()) {
            int pos = q.front();
            q.pop();
            
            // 计算可能的反转区间起始位置范围
            int minL = max(0, max(pos - k + 1, pos + k - n));
            int maxL = min(n - k, pos);
            
            // 计算目标位置范围
            int minTarget = 2 * minL + k - 1 - pos;
            int maxTarget = 2 * maxL + k - 1 - pos;
            
            // 选择对应的set(奇偶性相同)
            set<int>& candidates = (minTarget % 2 == 0) ? even : odd;
            
            // 找到范围内的所有未访问位置
            auto it = candidates.lower_bound(minTarget);
            vector<int> toRemove;
            
            while (it != candidates.end() && *it <= maxTarget) {
                int target = *it;
                answer[target] = answer[pos] + 1;
                q.push(target);
                toRemove.push_back(target);
                it++;
            }
            
            // 移除已访问的位置
            for (int target : toRemove) {
                candidates.erase(target);
            }
        }
        
        return answer;
    }
};
class Solution:
    def minReverseOperations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:
        answer = [-1] * n
        ban = set(banned)
        
        # 分别存储奇偶位置的未访问节点
        odd = set()
        even = set()
        for i in range(n):
            if i not in ban:
                if i % 2 == 0:
                    even.add(i)
                else:
                    odd.add(i)
        
        from collections import deque
        q = deque([p])
        answer[p] = 0
        if p % 2 == 0:
            even.discard(p)
        else:
            odd.discard(p)
        
        while q:
            pos = q.popleft()
            
            # 计算可能的反转区间起始位置范围
            min_l = max(0, max(pos - k + 1, pos + k - n))
            max_l = min(n - k, pos)
            
            # 计算目标位置范围
            min_target = 2 * min_l + k - 1 - pos
            max_target = 2 * max_l + k - 1 - pos
            
            # 选择对应的set(奇偶性相同)
            candidates = even if min_target % 2 == 0 else odd
            
            # 找到范围内的所有未访问位置
            to_remove = []
            for target in list(candidates):
                if min_target <= target <= max_target:
                    answer[target] = answer[pos] + 1
                    q.append(target)
                    to_remove.append(target)
            
            # 移除已访问的位置
            for target in to_remove:
                candidates.discard(target)
        
        return answer
public class Solution {
    public int[] MinReverseOperations(int n, int p, int[] banned, int k) {
        int[] answer = new int[n];
        Array.Fill(answer, -1);
        HashSet<int> ban = new HashSet<int>(banned);
        
        // 分别存储奇偶位置的未访问节点
        SortedSet<int> odd = new SortedSet<int>();
        SortedSet<int> even = new SortedSet<int>();
        for (int i = 0; i < n; i++) {
            if (!ban.Contains(i)) {
                if (i % 2 == 0) even.Add(i);
                else odd.Add(i);
            }
        }
        
        Queue<int> q = new Queue<int>();
        q.Enqueue(p);
        answer[p] = 0;
        if (p % 2 == 0) even.Remove(p);
        else odd.Remove(p);
        
        while (q.Count > 0) {
            int pos = q.Dequeue();
            
            // 计算可能的反转区间起始位置范围
            int minL = Math.Max(0, Math.Max(pos - k + 1, pos + k - n));
            int maxL = Math.Min(n - k, pos);
            
            // 计算目标位置范围
            int minTarget = 2 * minL + k - 1 - pos;
            int maxTarget = 2 * maxL + k - 1 - pos;
            
            // 选择对应的set(奇偶性相同)
            SortedSet<int> candidates = (minTarget % 2 == 0) ? even : odd;
            
            // 找到范围内的所有未访问位置
            List<int> toRemove = new List<int>();
            var view = candidates.GetViewBetween(minTarget, maxTarget);
            
            foreach (int target in view.ToList()) {
                answer[target] = answer[pos] + 1;
                q.Enqueue(target);
                toRemove.Add(target);
            }
            
            // 移除已访问的位置
            foreach (int target in toRemove) {
                candidates.Remove(target);
            }
        }
        
        return answer;
    }
}
var minReverseOperations = function(n, p, banned, k) {
    const result = new Array(n).fill(-1);
    const bannedSet = new Set(banned);
    const queue = [p];
    const visited = new Set([p]);
    result[p] = 0;
    
    let operations = 0;
    
    while (queue.length > 0) {
        const size = queue.length;
        
        for (let i = 0; i < size; i++) {
            const pos = queue.shift();
            
            // Find all possible positions after one operation
            for (let start = Math.max(0, pos - k + 1); start <= Math.min(n - k, pos); start++) {
                const end = start + k - 1;
                const newPos = start + end - pos;
                
                if (newPos >= 0 && newPos < n && !visited.has(newPos) && !bannedSet.has(newPos)) {
                    visited.add(newPos);
                    result[newPos] = operations + 1;
                    queue.push(newPos);
                }
            }
        }
        operations++;
    }
    
    return result;
};

复杂度分析

复杂度类型复杂度值说明
时间复杂度O(n log n)BFS 访问每个节点一次,set 操作为 O(log n)
空间复杂度O(n)存储 set 和队列所需的空间