Hard

题目描述

有一些红色和蓝色的瓦片围成一个圆形排列。给你一个整数数组 colors 和一个二维整数数组 queries。

瓦片 i 的颜色由 colors[i] 表示:

  • colors[i] == 0 表示瓦片 i 是红色的。
  • colors[i] == 1 表示瓦片 i 是蓝色的。

交替组是圆形中颜色交替的连续瓦片子集(组中除了第一个和最后一个瓦片外,每个瓦片的颜色都与组中相邻瓦片的颜色不同)。

你需要处理两种类型的查询:

  • queries[i] = [1, sizei],确定大小为 sizei 的交替组的数量。
  • queries[i] = [2, indexi, colori],将 colors[indexi] 改为 colori。

返回一个数组 answer,按顺序包含第一种类型查询的结果。

注意:由于 colors 表示一个圆形,第一个和最后一个瓦片被认为是相邻的。

示例 1:

输入:colors = [0,1,1,0,1], queries = [[2,1,0],[1,4]]
输出:[2]

示例 2:

输入:colors = [0,0,1,0,1,1], queries = [[1,3],[2,3,0],[1,5]]
输出:[2,0]

提示:

  • 4 <= colors.length <= 5 * 10^4
  • 0 <= colors[i] <= 1
  • 1 <= queries.length <= 5 * 10^4
  • queries[i][0] == 1 或 queries[i][0] == 2

解题思路

这道题目需要高效处理两种操作:查询特定大小的交替组数量和修改颜色。关键思路如下:

核心观察:

  1. 交替组是连续的颜色交替序列,我们需要找到所有最大交替段
  2. 对于长度为 len 的最大交替段,它包含 max(0, len - k + 1) 个长度为 k 的交替组
  3. 由于数组是环形的,需要特殊处理边界情况

算法步骤:

  1. 使用多重集合(multiset)维护所有最大交替段的长度
  2. 对于查询类型1:遍历所有段长度,计算能贡献多少个指定大小的交替组
  3. 对于查询类型2:更新颜色后,重新计算受影响的交替段

实现细节:

  • 首先找到所有最大交替段并存储其长度
  • 修改颜色时,需要移除旧段、计算新段并更新multiset
  • 环形数组的处理:当所有元素都交替时,整个数组形成一个大的交替组

时间优化: 使用multiset可以快速插入、删除和遍历段长度,每次查询的时间复杂度为 O(段数量),修改操作为 O(log n)。

代码实现

class Solution {
public:
    vector<int> numberOfAlternatingGroups(vector<int>& colors, vector<vector<int>>& queries) {
        int n = colors.size();
        multiset<int> segments;
        
        // Find all maximal alternating segments
        auto findSegments = [&]() {
            segments.clear();
            if (n == 0) return;
            
            // Check if all elements are alternating
            bool allAlternating = true;
            for (int i = 0; i < n; i++) {
                if (colors[i] == colors[(i + 1) % n]) {
                    allAlternating = false;
                    break;
                }
            }
            
            if (allAlternating) {
                segments.insert(n);
                return;
            }
            
            // Find segments
            for (int i = 0; i < n; i++) {
                if (colors[i] == colors[(i + 1) % n]) {
                    int start = (i + 1) % n;
                    int len = 1;
                    int j = start;
                    while (colors[j] != colors[(j + 1) % n] && (j + 1) % n != start) {
                        len++;
                        j = (j + 1) % n;
                    }
                    if (len >= 3) {
                        segments.insert(len);
                    }
                    i = j;
                }
            }
        };
        
        findSegments();
        vector<int> result;
        
        for (auto& query : queries) {
            if (query[0] == 1) {
                int k = query[1];
                int count = 0;
                for (int len : segments) {
                    if (len >= k) {
                        count += len - k + 1;
                    }
                }
                result.push_back(count);
            } else {
                int index = query[1];
                int newColor = query[2];
                if (colors[index] != newColor) {
                    colors[index] = newColor;
                    findSegments();
                }
            }
        }
        
        return result;
    }
};
class Solution:
    def numberOfAlternatingGroups(self, colors: List[int], queries: List[List[int]]) -> List[int]:
        from collections import Counter
        
        n = len(colors)
        segments = Counter()
        
        def find_segments():
            segments.clear()
            if n == 0:
                return
            
            # Check if all elements are alternating
            all_alternating = True
            for i in range(n):
                if colors[i] == colors[(i + 1) % n]:
                    all_alternating = False
                    break
            
            if all_alternating:
                segments[n] += 1
                return
            
            # Find segments
            i = 0
            while i < n:
                if colors[i] == colors[(i + 1) % n]:
                    start = (i + 1) % n
                    length = 1
                    j = start
                    while colors[j] != colors[(j + 1) % n] and (j + 1) % n != start:
                        length += 1
                        j = (j + 1) % n
                    if length >= 3:
                        segments[length] += 1
                    i = j
                else:
                    i += 1
        
        find_segments()
        result = []
        
        for query in queries:
            if query[0] == 1:
                k = query[1]
                count = 0
                for length, freq in segments.items():
                    if length >= k:
                        count += (length - k + 1) * freq
                result.append(count)
            else:
                index = query[1]
                new_color = query[2]
                if colors[index] != new_color:
                    colors[index] = new_color
                    find_segments()
        
        return result
public class Solution {
    public IList<int> NumberOfAlternatingGroups(int[] colors, int[][] queries) {
        int n = colors.Length;
        var segments = new Dictionary<int, int>();
        
        void FindSegments() {
            segments.Clear();
            if (n == 0) return;
            
            // Check if all elements are alternating
            bool allAlternating = true;
            for (int i = 0; i < n; i++) {
                if (colors[i] == colors[(i + 1) % n]) {
                    allAlternating = false;
                    break;
                }
            }
            
            if (allAlternating) {
                segments[n] = segments.GetValueOrDefault(n, 0) + 1;
                return;
            }
            
            // Find segments
            for (int i = 0; i < n; i++) {
                if (colors[i] == colors[(i + 1) % n]) {
                    int start = (i + 1) % n;
                    int len = 1;
                    int j = start;
                    while (colors[j] != colors[(j + 1) % n] && (j + 1) % n != start) {
                        len++;
                        j = (j + 1) % n;
                    }
                    if (len >= 3) {
                        segments[len] = segments.GetValueOrDefault(len, 0) + 1;
                    }
                    i = j;
                }
            }
        }
        
        FindSegments();
        var result = new List<int>();
        
        foreach (var query in queries) {
            if (query[0] == 1) {
                int k = query[1];
                int count = 0;
                foreach (var kvp in segments) {
                    if (kvp.Key >= k) {
                        count += (kvp.Key - k + 1) * kvp.Value;
                    }
                }
                result.Add(count);
            } else {
                int index = query[1];
                int newColor = query[2];
                if (colors[index] != newColor) {
                    colors[index] = newColor;
                    FindSegments();
                }
            }
        }
        
        return result;
    }
}
var numberOfAlternatingGroups = function(colors, queries) {
    const n = colors.length;
    const result = [];
    
    function countAlternatingGroups(size) {
        if (size > n) return 0;
        
        let count = 0;
        for (let i = 0; i < n; i++) {
            let isAlternating = true;
            for (let j = 1; j < size; j++) {
                const curr = (i + j) % n;
                const prev = (i + j - 1) % n;
                if (colors[curr] === colors[prev]) {
                    isAlternating = false;
                    break;
                }
            }
            if (isAlternating) count++;
        }
        return count;
    }
    
    for (const query of queries) {
        if (query[0] === 1) {
            const size = query[1];
            result.push(countAlternatingGroups(size));
        } else {
            const index = query[1];
            const color = query[2];
            colors[index] = color;
        }
    }
    
    return result;
};

复杂度分析

操作时间复杂度空间复杂度
初始化段O(n)O(k)
查询类型1O(k)O(1)
修改类型2O(n)O(1)
总体O(q × n)O(n)

其中 n 是 colors 数组长度,q 是查询数量,k 是不同段长度的数量(最多 O(n))。