从零学算法(LCR 135)

实现一个十进制数字报数程序,请按照数字从小到大的顺序返回一个整数数列,该数列从数字 1 开始,到最大的正整数 cnt 位数字结束。
示例 1:
输入:cnt = 2
输出:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]

  • 很容易想到的是,cnt 为几代表从 1 到 10的cnt次-1,所以可以直接循环暴力
  •   public int[] countNumbers(int cnt) {
          int end = (int)Math.pow(10,cnt)-1;
          ans = new int[end];
          for(int i=0;i<end;i++){
              ans[i] = i+1;
          }
          return ans;
      }
    
  • 他人题解:那这题肯定不是为了考察你对循环的使用吧,你用 short/int/long 存储数字,都终归有个存储范围,因此表示大数时考虑使用字符串类型。使用字符串的话,其实你会发现这就是 cnt 位的 0-9 的全排列问题。由于每位都是一样的操作,所以我们基于分治的思想递归生成结果。即遍历 0-9 ,先固定最高位,次高位,…最后固定最后一位,最后一遍遍历到下个数,然后回溯到次低位遍历到下个数…比如 cnt 为 3,先固定第一位 0,然后固定第二位 0,然后第三位的 0,得到 000,继续遍历最后一位,得到 001,002…009,最后一位遍历结束,向上回溯遍历第二位得到 010,011…,初步编写全排列代码如下
  •   StringBuilder res;
      int count = 0, cnt;
      char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
      public String countNumbers(int cnt) {
          this.cnt = cnt;
          res = new StringBuilder(); // 数字字符串集
          num = new char[cnt]; // 定义长度为 cnt 的字符列表
          dfs(0); // 开启全排列递归
          res.deleteCharAt(res.length() - 1); // 删除最后多余的逗号
          return res.toString(); // 转化为字符串并返回
      }
      void dfs(int x) {
          if(x == cnt) { // 终止条件:已固定完所有位
              res.append(String.valueOf(num) + ","); // 拼接 num 并添加至 res 尾部,使用逗号隔开
              return;
          }
          for(char i : loop) { // 遍历 ‘0‘ - ’9‘
              num[x] = i; // 固定第 x 位为 i
              dfs(x + 1); // 开启固定第 x + 1 位
          }
      }
    
  • 这里可以生成我们要的结果,但是会多出比如 000,010,020 这种高位补 0 的字符串,因此第一步我们先去掉高位的 0,我们用一个 start 表示结果字符串 str 的左边界,比如 cnt 为 3,最开始的 000-009,其实我们只要取第 3 位即可,之后的 010-099,其实我们只要取第二位到第三位,start 对应的分别是 str[2:],以及 str[1:],即左边界 start 随着临界值 9,99,999… 不断减少 1,初始为 cnt-1(看代码能看懂),为了不断更新 start,我们就需要用一个变量比如 nine 来表示当前数包含的 9 的数量 ,这就能表示是否来到了临界值。观察后我们会得到如果 cnt-start==nine(cnt-start 你可以看做几位数,比如等于 1 为表示一位数,等于 2 则为表示两位数,nine 就是上面说的,表示增加位数的临界点),start 就需要减 1 了。你可以理解为比如当 nine 为 1,即之后的最大值开始为两位数了,start 每减少 1 就是能表示的数的最多位 +1,所以我们所能表示的就从最开始的一位数成了两位数。
  • 还剩最后一个问题,即题目要求从 1 开始,而我们是从 0 开始,很简单,比如最开始的 000,我们得到了一位数 0,为 0 我们就跳过呗
  • 这里的 nine 的获取别忘了恢复
  •   int[] res;
      // nine:当前数包含的 9 的个数
      int nine = 0, count = 0, start, cnt;
      // num:表示结果字符串的 char 数组
      char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
      public int[] countNumbers(int cnt) {
          this.cnt = cnt;
          res = new int[(int)Math.pow(10, cnt) - 1];
          num = new char[cnt];
          start = cnt - 1;
          dfs(0);
          return res;
      }
      void dfs(int x) {
          if(x == cnt) {
              String s = String.valueOf(num).substring(start);
              if(!s.equals("0")) res[count++] = Integer.parseInt(s);
              if(cnt - start == nine) start--;
              return;
          }
          for(char i : loop) {
              if(i == '9') nine++;
              num[x] = i;
              dfs(x + 1);
          }
          // 回溯后没 9 就给我恢复了,有的话我自会加
          nine--;
      }
    

你可能感兴趣的:(算法学习,#,递归,算法)