【大厂机试题解法笔记】恢复数字序列

题目

对于一个连续正整数组成的序列,可以将其拼接成一个字符串,再将字符串里的部分字符打乱顺序。如序列8 9 10 11 12,拼接成的字符串为89101112,打乱一部分字符后得到90811211,原来的正整数10就被拆成了0和1。

现给定一个按如上规则得到的打乱字符的字符串,请将其还原成连续正整数序列,并输出序列中最小的数字。

输入描述

输入一行,为打乱字符的字符串和正整数序列的长度,两者间用空格分隔,字符串长度不超过200,正整数不超过1000,保证输入可以还原成唯一序列。

输出描述

输出一个数字,为序列中最小的数字。

用例

输入 输出
19801211 5 8

思考

给出了有序序列的长度,因此可从整数 1 开始选取连续的给定长度的数字序列和打乱序列进行匹配,如果是组成成分完全相同的序列就终止选取序列循环,返回枚举序列的起点数字,即序列中最小数字。这个在循环中枚举起点选取固定长度序列操作就是滑动窗口思想的应用,窗口的大小就是给定的序列长度。问题是怎么比较选取有序序列和打乱的目标序列?我开始想到的做法竟然是把序列拆分成单个数字字符数组再转成单个数字组成的数组从小到大排序后再连接成字符串和同样这样处理的目标字符串进行比较,完全相同就找到目标序列。如[9, 10, 11]->[9,1,0,1,1]->[0,1,1,1,9]->"01119"。这样反复拆分字符串又排序,做法肯定不好。实际上比较两个字符串的成分是否相同应该统计组成它们的0-9数字频率是否相同,如果相同则它们的有序序列一定相同。定义一个数组,索引 0~9 对应的值为索引表示的数字出现的频率,预先计算目标字符串的频率数组 matchFreqs,每次移动窗口左边界时开始更新选取的数字序列频率数组 freqs,并比较 matchFreqs 和 freqs 频率是否完全匹配,匹配则找到结果,然后返回窗口左边界值。

算法过程

  1. 统计目标字符串数字频率:用长度为 10 的数组记录 0-9 各数字出现次数。
  2. 确定起始数字范围 :最大起始值设为1000-n(受题目条件限制)。
  3. 遍历检查可能起始值 :对每个起始数i,生成i到i+n-1的数字串,统计频率并与目标对比。
  4. 输出匹配结果 :找到频率完全一致的起始值i,输出即为最小数字。

时间复杂度:O (m + k×n),m 为字符串长度,k 为遍历次数(最多 1000)。

参考代码

javascript 复制代码
function solution() {
  const arr = readline().split(' ');
  const n = parseInt(arr[1]);
  const matchStr = arr[0];
  const matchFreqs = Array(10).fill(0);
  for (let num of matchStr) {
    matchFreqs[Number(num)]++;
  }

  const check = function(i, j) {
    let s = '';
    for (let k = i; k < j; k++) {
      s += k;
    }
    s = s.toString();
    const freqs = Array(10).fill(0);
    for (let c of s) {
      freqs[Number(c)]++;
    }

    for (let i = 0; i <= 9; i++) {
      if (freqs[i] !== matchFreqs[i]) {
        return false;
      }
    }

    return true;
  };

  for (let i = 1; i <= 1000-n; i++) {
    let j = i + n;
    if (check(i, j)) {
      console.log(i);
      return;
    }
  }

}

const cases = [
  `19801211 5`,
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution();
});
相关推荐
CHANG_THE_WORLD15 小时前
并发编程指南 同步操作与强制排序
开发语言·c++·算法
gaoshou4516 小时前
代码随想录训练营第三十一天|LeetCode56.合并区间、LeetCode738.单调递增的数字
数据结构·算法
自信的小螺丝钉16 小时前
Leetcode 240. 搜索二维矩阵 II 矩阵 / 二分
算法·leetcode·矩阵
闪电麦坤9516 小时前
数据结构:深度优先搜索 (Depth-First Search, DFS)
数据结构·深度优先
悠哉悠哉愿意16 小时前
【机器学习学习笔记】线性回归实现与应用
笔记·学习·机器学习
大筒木老辈子16 小时前
Linux笔记---计算机网络概述
linux·笔记·计算机网络
KING BOB!!!17 小时前
Leetcode高频 SQL 50 题(基础版)题目记录
sql·mysql·算法·leetcode
我是渣哥18 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
THMAIL18 小时前
机器学习从入门到精通 - 机器学习调参终极手册:网格搜索、贝叶斯优化实战
人工智能·python·算法·机器学习·支持向量机·数据挖掘·逻辑回归
lytk9918 小时前
矩阵中寻找好子矩阵
线性代数·算法·矩阵