Hard

题目描述

给定一个长度为 n 的二进制字符串 s,其中:

  • ‘1’ 表示活跃区段
  • ‘0’ 表示非活跃区段

你最多可以执行一次交换来最大化 s 中活跃区段的数量。在一次交换中,你需要:

  1. 将被 ‘0’ 包围的连续 ‘1’ 块转换为全部 ‘0’
  2. 然后,将被 ‘1’ 包围的连续 ‘0’ 块转换为全部 ‘1’

另外,给定一个二维数组 queries,其中 queries[i] = [li, ri] 表示子字符串 s[li…ri]。

对于每个查询,确定在对子字符串 s[li…ri] 执行最优交换后,s 中活跃区段的最大可能数量。

返回数组 answer,其中 answer[i] 是 queries[i] 的结果。

注意:

  • 对于每个查询,将 s[li…ri] 视为在两端增强了 ‘1’,形成 t = ‘1’ + s[li…ri] + ‘1’。增强的 ‘1’ 不计入最终计数。
  • 查询彼此独立。

示例 1:

输入:s = "01", queries = [[0,1]]
输出:[1]

示例 2:

输入:s = "0100", queries = [[0,3],[0,2],[1,3],[2,3]]
输出:[4,3,1,1]

提示:

  • 1 <= n == s.length <= 10^5
  • 1 <= queries.length <= 10^5
  • s[i] 是 ‘0’ 或 ‘1’
  • queries[i] = [li, ri]
  • 0 <= li <= ri < n

解题思路

解题思路

这道题的核心是理解交换操作的本质:将被0包围的1块转换为0,然后将被1包围的0块转换为1。

关键观察:

  1. 交换操作实际上是将一个0块及其相邻的两个1块合并成一个更大的1块
  2. 需要预处理字符串,将连续的0和1分成段,并为每段分配ID
  3. 对于每个0段,计算如果将其转换为1后能获得的最大收益

算法步骤:

  1. 段分割:将字符串分成连续的0段和1段,记录每段的起始位置、结束位置和长度
  2. 收益计算:对于每个0段i,计算 ans[i] = len[i-1] + len[i] + len[i+1],即将0段转换为1段后的总长度
  3. 查询处理:对于每个查询范围[l,r],找到完全包含在该范围内的0段,计算最大收益
  4. 边界处理:需要特别处理查询范围的首尾段,确保0段完全被包含

优化技巧:

  • 使用线段树或简单遍历进行范围最大值查询
  • 预计算每个位置对应的段ID,便于快速定位
  • 特殊处理边界情况,如没有可交换的0段时返回原始1的数量

时间复杂度主要由查询处理决定,每个查询需要O(段数)的时间来找到最优解。

代码实现

class Solution {
public:
    vector<int> maxActiveSectionsAfterTrade(string s, vector<vector<int>>& queries) {
        int n = s.length();
        
        // 分段:将连续的0和1分成段
        vector<int> segStart, segEnd, segLen;
        vector<char> segType;
        vector<int> posToSeg(n);
        
        int i = 0;
        while (i < n) {
            int start = i;
            char type = s[i];
            while (i < n && s[i] == type) {
                posToSeg[i] = segStart.size();
                i++;
            }
            segStart.push_back(start);
            segEnd.push_back(i - 1);
            segLen.push_back(i - start);
            segType.push_back(type);
        }
        
        int segCount = segStart.size();
        vector<int> ans(segCount, 0);
        
        // 计算每个0段的收益
        for (int i = 0; i < segCount; i++) {
            if (segType[i] == '0') {
                int gain = segLen[i];
                if (i > 0 && segType[i-1] == '1') gain += segLen[i-1];
                if (i < segCount-1 && segType[i+1] == '1') gain += segLen[i+1];
                ans[i] = gain;
            }
        }
        
        vector<int> result;
        
        for (auto& query : queries) {
            int l = query[0], r = query[1];
            int leftSeg = posToSeg[l];
            int rightSeg = posToSeg[r];
            
            int maxGain = 0;
            int originalOnes = 0;
            
            // 计算原始1的数量
            for (int i = l; i <= r; i++) {
                if (s[i] == '1') originalOnes++;
            }
            
            // 检查每个0段是否完全包含在查询范围内
            for (int i = leftSeg; i <= rightSeg; i++) {
                if (segType[i] == '0') {
                    // 检查0段是否完全在查询范围内
                    if (segStart[i] >= l && segEnd[i] <= r) {
                        // 检查相邻的1段是否也在范围内
                        bool canTrade = true;
                        int gain = segLen[i];
                        
                        if (i > 0 && segType[i-1] == '1') {
                            if (segStart[i-1] >= l) gain += segLen[i-1];
                            else canTrade = false;
                        }
                        
                        if (i < segCount-1 && segType[i+1] == '1') {
                            if (segEnd[i+1] <= r) gain += segLen[i+1];
                            else canTrade = false;
                        }
                        
                        if (canTrade) {
                            maxGain = max(maxGain, gain);
                        }
                    }
                }
            }
            
            result.push_back(max(originalOnes, maxGain));
        }
        
        return result;
    }
};
class Solution:
    def maxActiveSectionsAfterTrade(self, s: str, queries: List[List[int]]) -> List[int]:
        n = len(s)
        
        # 分段:将连续的0和1分成段
        segments = []
        pos_to_seg = [0] * n
        
        i = 0
        while i < n:
            start = i
            seg_type = s[i]
            while i < n and s[i] == seg_type:
                pos_to_seg[i] = len(segments)
                i += 1
            segments.append({
                'type': seg_type,
                'start': start,
                'end': i - 1,
                'length': i - start
            })
        
        seg_count = len(segments)
        
        result = []
        
        for l, r in queries:
            left_seg = pos_to_seg[l]
            right_seg = pos_to_seg[r]
            
            max_gain = 0
            original_ones = sum(1 for i in range(l, r + 1) if s[i] == '1')
            
            # 检查每个0段是否可以进行交换
            for i in range(left_seg, right_seg + 1):
                seg = segments[i]
                if seg['type'] == '0':
                    # 检查0段是否完全在查询范围内
                    if seg['start'] >= l and seg['end'] <= r:
                        gain = seg['length']
                        can_trade = True
                        
                        # 检查左相邻的1段
                        if i > 0 and segments[i-1]['type'] == '1':
                            if segments[i-1]['start'] >= l:
                                gain += segments[i-1]['length']
                            else:
                                can_trade = False
                        
                        # 检查右相邻的1段
                        if i < seg_count - 1 and segments[i+1]['type'] == '1':
                            if segments[i+1]['end'] <= r:
                                gain += segments[i+1]['length']
                            else:
                                can_trade = False
                        
                        if can_trade:
                            max_gain = max(max_gain, gain)
            
            result.append(max(original_ones, max_gain))
        
        return result
public class IList<int> MaxActiveSectionsAfterTrade(string s, int[][] queries) {
    int n = s.Length;
    
    // 分段:将连续的0和1分成段
    var segments = new List<(char type, int start, int end, int length)>();
    var posToSeg = new int[n];
    
    int i = 0;
    while (i < n) {
        int start = i;
        char segType = s[i];
        while (i < n && s[i] == segType) {
            posToSeg[i] = segments.Count;
            i++;
        }
        segments.Add((segType, start, i - 1, i - start));
    }
    
    int segCount = segments.Count;
    var result = new List<int>();
    
    foreach (var query in queries) {
        int l = query[0], r = query[1];
        int leftSeg = posToSeg[l];
        int rightSeg = posToSeg[r];
        
        int maxGain = 0;
        int originalOnes = 0;
        
        // 计算原始1的数量
        for (int j = l; j <= r; j++) {
            if (s[j] == '1') originalOnes++;
        }
        
        // 检查每个0段是否可以进行交换
        for (int j = leftSeg; j <= rightSeg; j++) {
            var seg = segments[j];
            if (seg.type == '0') {
                // 检查0段是否完全在查询范围内
                if (seg.start >= l && seg.end <= r) {
                    int gain = seg.length;
                    bool canTrade = true;
                    
                    // 检查左相邻的1段
                    if (j > 0 && segments[j-1].type == '1') {
                        if (segments[j-1].start >= l) {
                            gain += segments[j-1].length;
                        } else {
                            canTrade = false;
                        }
                    }
                    
                    // 检查右相邻的1段
                    if (j < segCount - 1 && segments[j+1].type == '1') {
                        if (segments[j+1].end <= r) {
                            gain += segments[j+1].length;
                        } else {
                            canTrade = false;
                        }
                    }
                    
                    if (canTrade) {
                        maxGain = Math.Max(maxGain, gain);
                    }
                }
            }
        }
        
        result.Add(Math.Max(originalOnes, maxGain));
    }
    
    return result;
}
var maxActiveSectionsAfterTrade = function(s, queries) {
    const n = s.length;
    
    // 分段:将连续的0和1分成段
    const segments = [];
    const posToSeg = new Array(n);
    
    let i = 0;
    while (i < n) {
        const start = i;
        const segType = s[i];
        while (i < n && s[i]

复杂度分析

复杂度类型分析
时间复杂度O(n + q*k),其中 n 是字符串长度,q 是查询数量,k 是平均段数
空间复杂度O(n + k),用于存储段信息和位置映射