记一次算法面试题

207 阅读6分钟

有序数列{1, 2, 3, 4, 5, 6, … n} 从1到n的有序数列,0这个数字总共出现了多少次?
输入:整数n(1 ≤ n ≤ 1,000,000,000)
输出:0在数列中出现的次数

以下是个人的思路,如果有更好的方案,欢迎交流。

通用方案

如下代码只是普通方案,通过遍历的方式判断0存在,自增count来计算0出现的次数。

这里的作用有两个:

  1. 输出包含0的数值,以便观察规律;
  2. 验证后续新算法的正确性。
    @Test
    public void test() {
     int n = 10106;
     int count = 0;
     for (int i = 1; i <= n; i++) {
         String value = String.valueOf(i);
         if (value.contains("0")) {
             //System.out.print(value + " ");
             char[] array = value.toCharArray();
             for (int j = 0; j < array.length; j++) {
                 if (array[j] == '0') {
                     count++;
                 }
             }
         }
     }
     System.out.print(count);
    }

部分数值

10 20 30 40 50 60 70 80 90 100
101 102 103 104 105 106 107 108 109
110 120 130 140 150 160 170 180 190 200
201 202 203 204 205 206 207 208 209
210 220 230 240 250 260 270 280 290 300
301 302 303 304 305 306 307 308 309
310 320 330 340 350 360 370 380 390 400
401 402 403 404 405 406 407 408 409
410 420 430 440 450 460 470 480 490 500
501 502 503 504 505 506 507 508 509
510 520 530 540 550 560 570 580 590 600
601 602 603 604 605 606 607 608 609
610 620 630 640 650 660 670 680 690 700
701 702 703 704 705 706 707 708 709
710 720 730 740 750 760 770 780 790 800
801 802 803 804 805 806 807 808 809
810 820 830 840 850 860 870 880 890 900
901 902 903 904 905 906 907 908 909
910 920 930 940 950 960 970 980 990 1000
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
1101 1102 1103 1104 1105 1106 1107 1108 1109
1110 1120 1130 1140 1150 1160 1170 1180 1190 1200

通过观察,可以按照个位,十位,百位....分别为0的方式进行计算。
例如
n=54,则个位为0的数为5个(n/10),十位不可能出现0,
n=543,则个位为0的数为54个(n/10),十位为0的数为5×10个(n/10/10)
n=6543,则个位为0的数为654个(n/10),十位为0的数为65×10个(n/10/10),百位为0的数为6×100个
....
以此类推

    public long countZero(long n) {
        long temp = n;//临时存储原数据
        long count = 0;//统计0出现次数
        int i = 0;//标记个位,十位,百位....
        while (n / 10 > 0) {
            count += n/ 10 * Math.pow(10, i);
            i++;
            n= n / 10;
        }

        return count;
    }

然而,还需要考虑特殊情况的数值,即中间位可能为0的情况,比如1000,1001,1016,10106....等等。
个位计算方式不变,依然是n/10,随着每次循环,n都降1位。
在n % 10 == 0 的情况下,
n=1000,十位:(100/10-1)×10+1,百位:1
n=1001,十位:(100/10-1)×10+1+1,百位:1+1
n=1002,十位:(100/10-1)×10+2+1,百位:2+1
n=1016,十位:(101/10-1)×10+6+1,百位:(10/10-1)*100+16+1

因此得到如下结果

public long  countZero(int n) {
        long temp = n;//临时存储原数据
        long count = 0;//统计0出现次数
        int i = 0;//标记个位,十位,百位....
        while (n / 10 > 0) {
            if (n % 10 == 0 && i != 0) {
                count += ((n / 10) - 1) * Math.pow(10, i) + 1 + temp % Math.pow(10, i);
            } else {
                count += n / 10 * Math.pow(10, i);
            }

            i++;
            n = n / 10;
        }
        return count;
    }

看似简单,整个过程还是有点小漫长,尤其对于中间位为0的规律查找,同时也对代码的调试、调整了一段时间。