[ 迭代、递归 ]386. 字典序排数

176 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

每日刷题 2021.04.18

题目

  • 给你一个整数 n ,按字典序返回范围 [1, n] 内所有整数。
  • 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法

示例

  • 示例1
输入: n = 13
输出: [1,10,11,12,13,2,3,4,5,6,7,8,9]
  • 示例2
输入: n = 2
输出: [1,2]

提示

  • 1 <= n <= 5 * 10^4

解题思路

  • 调用sort()函数,就可以解决题目。因为sort函数不传入自定义的方法,默认就是按照每个元素为字符串进行比较。即:字典序排序
  • 分析:字典序排序,即:101 < 11的,因为第二位上0是小于1的。
  • 记当前需要返回的区间为[1, n](左闭右闭区间)

迭代

  • 第一种情况:位数: 确定位数,找到当前可以用0填充的最大位数。记初始值为j = 1,那么每次都都需要执行j * 10 ^ m(m从1开始),直到执行到大于n,停下来。执行:下一种情况
    • 同理解释:在j后面加一个0,即:j * 10,如果j * 10 <= n,说明j * 10就是下一个字典序整数
  • 回退的概念:为什么中间使用的是while循环?因为当你的前一个数全部位的0都已经变成了9,就表示这些位都已经遍历过了,就要一直往前找,没有被遍历过的来+1
  • 第二种情况:依次给每一位+1。因为此时的位数已经和n是一样的,那么此时只需要将每一位依次加1处理。
    • 同理解析:如果j % 10 == 9 || j + 1 > n,说明末尾的位数已经搜索完成,退回上一位j / 10 = j,然后继续判断直到j % 10 != 9 && j + 1 <= n为止,j + 1就是下一个字典序整数。

递归

  • 可以想象成一个字典树,每个节点都有9种情况。
使用dfs的方法,字典树
  * 对于每一层的遍历,都需要先输出根节点
  * 对于每一个叶子节点的前一个节点,都需要打印全部子节点
  * 触底返回,打印其他的叶子节点
  * 对于每一个节点来说都是类似的,都是需要打印其全部的子节点
  * 因为开始节点不能为0,因此是多源dfs,需要从多个入口进入进行dfs
  * dfs书写:三步走
    * 1. 确定参数和返回值:i(1 ~ 9) 无
    * 2. 确定临界条件 深度是n的位数 n / 10 (从0开始) => (优化:当前的数是否大于`n`即可)
    * 3. 确定单层的逻辑 将当前的值添加到ans数组中,并遍历其所有的子节点

AC代码

  • 迭代的解法
var lexicalOrder = function(n) {
  let ans = [];
  for(let i = 0, j = 1; i < n; i++) {
    ans.push(j);
    if(j * 10 <= n){
      j = j * 10;
    }else {
      while(j % 10 == 9 || j + 1 > n) {
        j = parseInt(j / 10);
      }
      j++;
    }
  }
  return ans;
};
  • 递归的解法
var lexicalOrder = function(n) {
  let len = parseInt(n / 10) + 1;
  let dept = 1,str = '', ans = [];
  function dfs(cur) {
    // 临界条件 
    if(cur > n) return;
    // 将符合的数值填入到数组中
    ans.push(cur)
    // 遍历其每一个子节点
    for(let j = 0; j <= 9; j++) {
      dfs(cur * 10 + j)
    }
  }

  for(let i = 1; i <= 9; i++) {
    dfs(i);
  }
  return ans;
};

总结

  • 迭代和递归通常都可以相互转换,迭代在空间复杂度上会小于递归,但是递归的做法更容易想清楚。