二进制码流验证

48 阅读4分钟

二进制码流验证

问题背景

工程师小A正在对一个二进制码流(一个由 01 组成的序列)进行验证。验证的目标是找到在特定规则下,可以形成的最长的连续目标数字(01)的序列。

验证规则

  1. 目标值 (target) : 首先会给定一个目标值,它要么是 0,要么是 1
  2. 操作: 为了获得更长的连续 target 序列,你最多可以反转码流中的一位。反转意味着将 0 变为 1,或将 1 变为 0
  3. "最多"的含义: 你可以选择反转一位,也可以选择不进行任何反转。决策的依据是哪种方式能得到最长的连续 target 序列。

目标

给定目标值 target 和二进制码流 bits,计算并返回通过上述规则可以获得的最大连续 target 的长度。


输入格式

  • target: 第一个参数,一个整数,表示要寻找的目标值。

    • 值仅为 01
  • bits: 第二个参数,一个整数数组(或列表),表示二进制码流。

    • 1 <= bits.length <= 10000
    • 数组中每个元素 bits[i] 的值仅为 01

输出格式

  • 一个整数,代表在最多反转一位的情况下,能够得到的最大连续 target 的长度。

样例说明

样例 1

  • 输入:

    • target = 1
    • bits = [0, 1, 1, 0, 1, 0, 1, 0, 0]
  • 输出: 4

  • 解释:

    1. 目标: 获取最长的连续 1 的序列。

    2. 分析码流: [0, 1, 1, 0, 1, 0, 1, 0, 0]

    3. 寻找最佳反转点:

      • 观察到 [1, 1][1] 这两段连续的 1 被一个 0 分隔开:... 1, 1, **0**, 1 ...
      • 如果我们把这个分隔它们的 0(位于索引3)反转为 1,码流变为 [0, 1, 1, **1**, 1, 0, 1, 0, 0]
      • 这样,我们就得到了一个长度为 4 的连续 1 序列 [1, 1, 1, 1]
    4. 对比其他情况:

      • 如果不反转,最长的连续 1 序列是 [1, 1],长度为 2。
      • 如果反转其他位置的 0,例如将索引0的0变为1,得到 [**1**, 1, 1, 0, 1, ...],最长连续 1 的长度为 3。
    5. 结论: 最佳策略是反转索引为 3 的 0,得到的最长长度为 4。

样例 2

  • 输入:

    • target = 0
    • bits = [0, 0, 0, 0, 0, 0, 0, 0]
  • 输出: 8

  • 解释:

    1. 目标: 获取最长的连续 0 的序列。

    2. 分析码流: 整个码流 [0, 0, 0, 0, 0, 0, 0, 0] 已经是一个长度为 8 的连续 0 序列。

    3. 决策:

      • 不进行任何反转,我们已经拥有了长度为 8 的连续 0
      • 如果反转任何一位(将一个 0 变为 1),都会破坏这个连续序列,导致最大长度变小。
    4. 结论: 最佳策略是不进行反转,最大长度就是原始码流的长度 8。

/**
 * 核心是使用滑动窗口算法来找到包含至多一个“非目标”比特的最长子数组。
 */
public class BitFlipMaximizer {

    /**
     * 主方法,计算在最多翻转一位的情况下,可获得的最大连续 target 的个数.
     *
     * @param target 目标值 (0 或 1)
     * @param bits   二进制码流数组
     * @return 最大连续 target 的个数
     */
    public int findMaxConsecutive(int target, int[] bits) {
        
        // --- 滑动窗口所需的状态变量 ---

        int left = 0;          // 滑动窗口的左边界指针
        int maxLen = 0;        // 用于记录找到的最大长度
        int gapCount = 0;      // 记录当前窗口内“非目标”比特(间隙)的数量

        // --- 遍历数组,移动滑动窗口的右边界 ---

        // right 指针负责向右移动,以扩大窗口
        for (int right = 0; right < bits.length; right++) {

            // --- 步骤 1: 扩大窗口并更新状态 ---

            // 如果新进入窗口的比特不是我们的目标值,那么它就是一个“间隙”
            if (bits[right] != target) {
                gapCount++;
            }

            // --- 步骤 2: 检查窗口有效性,必要时收缩窗口 ---

            // 一个有效的窗口最多只能包含一个“间隙”(因为我们最多只能翻转一位)。
            // 如果窗口内的间隙超过一个,就需要从左边收缩窗口,直到窗口再次变得有效。
            while (gapCount > 1) {
                // 检查即将被移出窗口的左边界比特 (bits[left])
                // 如果它是一个“间隙”,那么在我们移出它之后,窗口内的间隙数就会减少。
                if (bits[left] != target) {
                    gapCount--;
                }
                // 将左边界向右移动一位,完成收缩。
                left++;
            }

            // --- 步骤 3: 更新最大长度 ---

            // 经过上面的收缩步骤,此时的窗口 [left, right] 是一个有效的窗口。
            // 计算当前有效窗口的长度。
            int currentWindowLength = right - left + 1;
            // 用当前窗口的长度更新我们记录到的最大长度。
            maxLen = Math.max(maxLen, currentWindowLength);
        }

        // 遍历结束后,maxLen 中存储的就是最终结果。
        return maxLen;
    }
}