编程导航算法通关村第十三关 | 数字与数学基础

90 阅读7分钟

数字统计专题

符号统计

力扣(LeetCode)

只需要看有多少个负数,就能够判断最后乘积的符号了

public int arraySign(int[] nums) {                           
    int prod = 1;                       
    for(int i = 0; i < nums.length; ++i) {     
        if(nums[i] == 0) {               
            return 0;//一切归零
        }else if(nums[i] < 0) {
            //交替就够了,很好的处理技巧
            prod = -prod;
        }
    }
    return prod;       
}

阶乘零的个数

. - 力扣(LeetCode)

这个题如果硬算,一定会超时,其实我们可以统计有多少个 0,实际上是统计 2 和 5 一起出现多少对,不过因为 2 出现的次数一定大于 5 出现的次数,因此我们只需要检查 5 出现的次数就好了,那么在统计过程中,我们只需要统计 5、10、15、 25、 ... 5^n 这样 5 的整数倍项就好了,最后累加起来,就是多少个 0。

代码为:

public int trailingZeroes(int n) {
    int cnt = 0;
    for (long num = 5; n / num > 0; num *= 5) {
        cnt += n / num;
    }
    return cnt;
}

溢出问题

整数反转

. - 力扣(LeetCode)

首先想一下,怎么去反转一个整数。用栈?或者把整数变成字符串再反转字符串?都可以但都不好。我们只要一边左移一边处理末尾数字就可以了。以12345为例,先拿到5,再拿到4,之后是3,2,1,然后就可以反向拼接出一个数字了。那如何获得末尾数字呢?好办,循环取模运算即可。

例如:

1.将12345 % 10 得到5,之后将12345 / 10=1234
2.将1234 % 10 得到4,再将1234 / 10=123
3.将123 % 10 得到3,再将123 / 10=12
4.将12 % 10 得到2,再将12 / 10=1
5.将1 % 10 得到1,再将1 / 10=0

画成图就是:

这样的话,是不是将循环的判断条件设为x>0就可以了呢?不行!因为忽略了负数的问题,应该是while(x!=0)。去掉符号,剩下的数字,无论正数还是负数,按照上面不断的/10这样的操作,最后都会变成0,所以判断终止条件就是!=0。有了取模和除法操作,就可以轻松解决第一个问题,如何反转。

接下来看如何解决溢出的问题。我们知道32位最大整数是MAX=2147483647,如果一个整数num>MAX,那么应该有以下规律:

nums/10 > MAX/10=214748364,也就是如果底数第二位大于4了,不管最后一位是什么都已经溢出了,如下:

所以我们要从到最大数的1/10时,就要开始判断,也即:

  • 如果 num>214748364 那后面就不用再判断了,肯定溢出了。
  • 如果num= 214748364,这对应到上图中第三、第四、第五排的数字,需要要跟最大数的末尾数字比较,如果这个数字比7还大,说明溢出了。
  • 如果num<214748364,则没问题,继续处理。

这个结论对于负数也是一样的,所以实现代码就是:

public int reverse(int x) {
    int res = 0;
    while(x!=0) {
        //获得末尾数字
        int tmp = x%10;
        //判断是否大于最大32位整数,也可以使用Integer.MAX_VALUE/10来代替214748364
        if (res>214748364 || (res==214748364 && tmp>7)) {
            return 0;
        }
        //判断是否小于最小的32位整数
        if (res<-214748364 || (res==-214748364 && tmp<-8)) {
            return 0;
        }
        res = res*10 + tmp;
        x /= 10;
    }
    return res;
}

回文数

. - 力扣(LeetCode)

映入脑海的第一个想法是将数字转换为字符串,并检查字符串是否为回文。但是,这需要额外的非常量空间来创建问题描述中所不允许的字符串。

第二个想法是将数字本身反转,然后将反转后的数字与原始数字进行比较,如果它们是相同的,那么这个数字就是回文。 但是,如果反转后的数字大于int.MAX,我们将遇到整数溢出问题。

按照第二个想法,为了避免数字反转可能导致的溢出问题,为什么不考虑只反转 int 数字的一半?毕竟,如果该数字是回文,其后半部分反转后应该与原始数字的前半部分相同。

例如,输入 1221,我们可以将数字 “1221” 的后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相同,我们得知数字 1221 是回文。

这个反转思路与链表的反转是一样的,不过要简单多了。

这里还不能忘的问题就是,反转之后数字可能会溢出,因此必须做防范,方法我们上面说了,这里我们可以进一步简化一下:

public boolean isPalindrome(int x) {
    // 特殊情况:
    // 如上所述,当 x < 0 时,x 不是回文数。
    // 同样地,如果数字的最后一位是 0,为了使该数字为回文,
    // 则其第一位数字也应该是 0
    // 只有 0 满足这一属性
    if (x < 0 || (x % 10 == 0 && x != 0)) {
        return false;
    }
    
    int revertedNumber = 0;
    while (x > revertedNumber) {
        revertedNumber = revertedNumber * 10 + x % 10;
        x /= 10;
    }
    
    // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
    // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
    // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
    return x == revertedNumber || x == revertedNumber / 10;
}

进制专题

七进制数

. - 力扣(LeetCode)

向遍历每次的余数,依次是 2、0、2,因此十进制数 100 转成七进制数是202 。如果num<0,则先对 num 取绝对值,然后再转换即可。使用代码同样可以实现该过程,需要注意的是如果单纯按照整数来处理会非常麻烦,既然题目说以字符串形式返回,那我们干脆直接用字符串类,代码如下:

public String convertToBase7(int num) {
    StringBuilder sb = new StringBuilder();
    //先拿到正负号
    boolean sign = num < 0;
    //这样预处理一下,后面都按照正数处理num就行了
    if(sign)
        num *= -1;
    //循环取余和整除    
    do{
        sb.append(num%7 + "");
        num/=7;
    }while(num > 0);
    //添加符号
    if(sign)
        sb.append("-");
    //上面的结果是逐个在末尾加的,需要反过来   
    return sb.reverse().toString();
}

进制转换

给定一个十进制数M,以及需要转换的进制数N,将十进制数M转化为N进制数。M是32位整数,2<=N<=16。

这个题目的思路不复杂,但是想写正确却很不容易,甚至越写越糊涂。本题有好几个需要处理的问题:

  • 1.超过进制最大范围之后如何准确映射到其他进制,特别是ABCDEF这种情况。简单的方式是大量采用if 判断,但是这样会出现写了一坨,最后写不下去。
  • 2.需要对结果进行一次转置。
  • 3.需要判断负号。

下面这个是我总结出的最精简,最容易理解的实现方案。注意采取三个措施来方便处理:

  • 1.定义大小为16的数组F,保存的是2到16的各个进制的值对应的标记,这样赋值时只计算下标,不必考虑不同进制的转换关系了。
  • 2.使用StringBuffer完成数组转置等功能,如果不记得这个方法,工作量直接飙升。
  • 3.通过一个flag来判断正数还是负数,最后才处理。
// 要考虑到 余数 > 9 的情况,2<=N<=16.
public static final String[] F = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
//将十进制数M转化为N进制数
public String convert (int M, int N) {
    Boolean flag=false;
    if(M<0){
        flag=true;
        M*=-1;
    }
    StringBuffer sb=new StringBuffer();
    int temp;
    while(M!=0){
        temp=M%N;
        //技巧一:通过数组F[]解决了大量繁琐的不同进制之间映射的问题
        sb.append(F[temp]);
        M=M/N;
    }
    //技巧二:使用StringBuffer的reverse()方法,让原本麻烦的转置瞬间美好
    sb.reverse();
    //技巧三:最后处理正负,不要从一开始就揉在一起。
    return (flag? "-":"")+sb.toString();
}