二进制码流验证
问题背景
工程师小A正在对一个二进制码流(一个由 0 和 1 组成的序列)进行验证。验证的目标是找到在特定规则下,可以形成的最长的连续目标数字(0 或 1)的序列。
验证规则
- 目标值 (
target) : 首先会给定一个目标值,它要么是0,要么是1。 - 操作: 为了获得更长的连续
target序列,你最多可以反转码流中的一位。反转意味着将0变为1,或将1变为0。 - "最多"的含义: 你可以选择反转一位,也可以选择不进行任何反转。决策的依据是哪种方式能得到最长的连续
target序列。
目标
给定目标值 target 和二进制码流 bits,计算并返回通过上述规则可以获得的最大连续 target 的长度。
输入格式
-
target: 第一个参数,一个整数,表示要寻找的目标值。- 值仅为
0或1。
- 值仅为
-
bits: 第二个参数,一个整数数组(或列表),表示二进制码流。1 <= bits.length <= 10000- 数组中每个元素
bits[i]的值仅为0或1。
输出格式
- 一个整数,代表在最多反转一位的情况下,能够得到的最大连续
target的长度。
样例说明
样例 1
-
输入:
target = 1bits = [0, 1, 1, 0, 1, 0, 1, 0, 0]
-
输出:
4 -
解释:
-
目标: 获取最长的连续
1的序列。 -
分析码流:
[0, 1, 1, 0, 1, 0, 1, 0, 0] -
寻找最佳反转点:
- 观察到
[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]。
- 观察到
-
对比其他情况:
- 如果不反转,最长的连续
1序列是[1, 1],长度为 2。 - 如果反转其他位置的
0,例如将索引0的0变为1,得到[**1**, 1, 1, 0, 1, ...],最长连续1的长度为 3。
- 如果不反转,最长的连续
-
结论: 最佳策略是反转索引为 3 的
0,得到的最长长度为 4。
-
样例 2
-
输入:
target = 0bits = [0, 0, 0, 0, 0, 0, 0, 0]
-
输出:
8 -
解释:
-
目标: 获取最长的连续
0的序列。 -
分析码流: 整个码流
[0, 0, 0, 0, 0, 0, 0, 0]已经是一个长度为 8 的连续0序列。 -
决策:
- 不进行任何反转,我们已经拥有了长度为 8 的连续
0。 - 如果反转任何一位(将一个
0变为1),都会破坏这个连续序列,导致最大长度变小。
- 不进行任何反转,我们已经拥有了长度为 8 的连续
-
结论: 最佳策略是不进行反转,最大长度就是原始码流的长度 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;
}
}