找出最长的神奇数列

1.题目:

小F是一个好学的中学生,今天他学习了数列的概念。他在纸上写下了一个由 01 组成的正整数序列,长度为 n。这个序列中的 10 交替出现,且至少由 3 个连续的 01 组成的部分数列称为「神奇数列」。例如,10101 是一个神奇数列,而 1011 不是。现在,小F想知道在这个序列中,最长的「神奇数列」是哪一个。你能帮他找到吗?

如果有多个神奇数列,那么输出最先出现的一个。


测试样例

样例1:

输入:inp = "0101011101"
输出:'010101'

样例2:

输入:inp = "1110101010000"
输出:'10101010'

样例3:

输入:inp = "1010101010101010"
输出:'1010101010101010'

2.分析:

1.神奇数列的定义,是从左到右读取,如果左边的数字和右边数字不同,继续读取。

2.一组数可能有多组神奇数列,如’101001010’ 包含 ‘1010‘ 和 ‘01010‘,应返回最长的

3.可以先读取满足条件的数据,存放在数组里,比较多组数组,找出最长的

3.解决:

3.1 二维数组

我们可以用一个二维数组存放满足规则的数组

判断左右的数是否不一致,因为题目里只有0和1,可以转为数字之后,通过异或(XOR)操作去判断。左右不一致的数字,异或操作返回结果为1。实现了isReverseNum函数。

1^0 // 1
0^0 // 0
1^0 // 0
0^1 // 1
// 找出二维数组里,长度最长的一项
function getLongestList<T>(list: Array<T[]>): T[] {
  let result: T[] = [];
  list.forEach((item) => {
    if (item.length > result.length) {
      result = item;
    }
  });
  return result;
}

// 判断是否为相反的数(1和0相反,通过异或操作判断)
const isReverseNum = (prev: number, next: number) => {
  return (prev ^ next) === 1;
};

// 获取相反的数 1->0 0->1
const getReverseNum = (char: string | number): number =>
  Number(char) === 1 ? 0 : 1;

const searchNumList = (char: string) => {
  let index = 0;
  const result: Array<number[]> = []; // 存储二维数组
  let storeIndex = 0; // 当前存储的下标
  let prevNum: number = getReverseNum(char[0]);

  while (index < char.length) {
    const currentNum = Number(char[index]);
    // 满足条件,加入到二维数组
    if (isReverseNum(prevNum, currentNum)) {
      result[storeIndex] = [...(result[storeIndex] || []), currentNum];
      prevNum = currentNum;
      index++;
    } 
    // 不满足条件,下标自增
    else {
      prevNum = getReverseNum(currentNum);
      index++;
      storeIndex++;
    }
  }

  return result;
};

function solution(inp: string): string {
  const list = searchNumList(inp);
  const result = getLongestList(list);
  if (result.length < 3) return "";
  return result.join("");
}

function main() {
  // Add your test cases here
  console.log(solution("0101011101") === "010101");
  console.log(solution("1110101010000") === "10101010");
  console.log(solution("1010101010101010") === "1010101010101010");
}

main();

这样的解法可以满足条件,但是二维数组比较占据空间,还可以用其他的解法。

3.2 一维数组

二维数组每次有数据,都直接存入,最后再比较。其实可以优化,在存入前去判断,用替换代替直接存入,优化:

由于条件判断不满足,以及while语句结束,可能currentList都有更新,所以调用updateResult去判断是否存入。

// 判断是否为相反的数(1和0相反,通过异或操作判断)
const isReverseNum = (prev: number, next: number) => {
  return prev + next === 1;
};

// 获取相反的数 1->0 0->1
const getReverseNum = (char: string | number): number => 1 - Number(char);

// 更新结果数组
const updateResult = (currentList: number[], result: number[]): void => {
  if (currentList.length > result.length && currentList.length > 3) {
    result.splice(0); // 清空原数组
    for (let i = 0; i < currentList.length; i++) {
      result[i] = currentList[i]; // 复制新数组
    }
  }
};

const searchNumList = (char: string) => {
  let result: number[] = []; // 存储符合条件的数组
  const currentList: number[] = []; // 符合本次检索的数组
  let index = 0;

  while (index < char.length) {
    const currentNum = Number(char[index]); // 本次检索的数字
    const prevNum =
      index === 0 ? getReverseNum(currentNum) : Number(char[index - 1]); // 本次需要取反的数字

    // 满足条件,放入数组
    if (isReverseNum(prevNum, currentNum)) {
      currentList.push(currentNum);
      index++;
    }
    // 不满足条件,判断是否要存入result,如果比result的数量多,超过三个,存入,清空当前检索的数组
    else {
      updateResult(currentList, result);
      currentList.length = 0;
      index++;
    }
  }

  updateResult(currentList, result);

  return result;
};

function solution(inp: string): string {
  const list = searchNumList(inp);
  return list.join("");
}

function main() {
  // Add your test cases here
  //   console.log(solution("0101011101") === "010101");
  //   console.log(solution("1110101010000") === "10101010");
  console.log(solution("1010101010101010") === "1010101010101010");
}

main();

3.3 滑动窗口

这道题目实际上是要提取满足条件的最长子字符串。可以用滑动窗口的思路去解决。

思路:

维护左右两个指针。

当右指针没有到达字符串尾部,能继续循环。

如果满足匹配条件,右指针不断增加

如果不满足,左指针移动到右指针位置,继续查找

function findMagicSequence(inp: string): string {
    let left = 0;
    let right = 0;
    let maxLength = 0;
    let maxSeq = "";

    while (right < inp.length) {
      // 扩大右边界直到不满足交替条件或者达到字符串末尾
      while (right < inp.length - 1 && inp[right] !== inp[right + 1]) {
        right++;
      }

      // 计算当前窗口的长度
      const windowLength = right - left + 1;

      // 如果窗口长度大于等于3,更新最长的神奇数列
      if (windowLength >= 3 && windowLength > maxLength) {
        maxLength = windowLength;
        maxSeq = inp.substring(left, right + 1);
      }

      // 移动左边界到下一个位置
      left = right + 1;
      right = left;
    }

    return maxSeq;
  }

  function main() {
    console.log(findMagicSequence("0101011101")); // '010101'
    console.log(findMagicSequence("1110101010000")); // '10101010'
    console.log(findMagicSequence("1010101010101010")); // '1010101010101010'
  }

  main();