现在是临放假回家的节奏,前两天晚上在领导的逼迫下,改bug改到凌晨一点,终于把这次的项目算是推动上线了,话说,明天就要回家了,今天无聊,刚好就抽点时间敲一下前两天高德面试的一道算法题目。
前两天去高德地图面试,面试的最后一道题是一道算法题,当时不给我纸笔,让我硬用手写,用手写我是写不出来的(中途不能调试不能换行呀!),刚刚吃饱饭,正好也有空,就把这道题敲下来了。
先说一下题目,题目是这样的,我们都知道十进制转二进制是用的求二取余法(不知道的自己去百度哈),现在给定一个“超大的String类型的数字”,让转换成2进制。
让我们来看一下,为什么这个数字是String类型的呢,在java中Int类型是32位的,这也就说明了很多时候会数据溢出,当一个数字足够大,比如“289157293842893489023478234”这个数字的时候,Int型就无法表示了,即使是Float或者double型,也是有位数限制的。而我们常用的加减乘除方法都是在数字上进行操作的,换句话说,当这个数字足够大, 大到可能有100位数,一亿亿亿亿亿。。的时候,我们就已经没法用通用的数字计算法计算了。那肿么破?
首先,这个数字,已经破灭了我们强转Int然后做除取余的思路了,那我们就把整个思路细化,小学时候我们怎么算的除法?根号方式算的。也就是322除以3 这个题目怎么算?用小学的方法来看,先取出第一位,也就是3。3除以3结果是1余0。下一步取出321的第二位数字“2”,2除以3,结果0余2。继续往后算,把十位的余数取出来,跟后面的1组合,成22,22除以三,结果的7余1。这样结果就出来了,结果是107,余数是1,这样的话,就把整个除法分解开来了,每次是一到两位数字,这样我们就可以做除法了。以上我们做的,其实就是把一次的除法模拟出来了。具体如下,字写得比较烂。
我们已经模拟出来算法的思路了,剩下的就是 代码了,那这个类的代码如下:
package com.example.test;
public class StringConvert {
private static StringBuilder strRestult;//这个变量是用于存储结果,StringBuilder在频繁变化时候,要比String更高效,而且整个过<span style="white-space:pre"> </span>程不涉及多线程,所以不用考虑线程安全,所以我们整体上都使用的StringBuilder
private static StringBuilder sb;<span style="white-space:pre"> </span>//这个变量用于存储每次除法之后算出来的结果,用于下次计算除法
//计算方法
public StringBuilder Convert(String input){
strRestult = new StringBuilder();//初始化变量
sb = new StringBuilder(input);<span style="white-space:pre"> </span>//初始化变量
//只要模拟除法得出的数字为2或者更大的数,那就说明可以继续模拟除法,要么是两位数或者两位数以上,要么是一位数,但是这个<span style="white-space:pre"> </span>数字必须大于一
while(sb.length()>1||(sb.length()==1&&Integer.parseInt(sb.substring(0,1))>1)){
sb = getTempString(sb);
}<span style="white-space:pre"> </span>//求二取余这个算法最终的一位是算到最后的结果,如果是0的话,就不需要加到首位了,只有1才需要加到首位
if(Integer.parseInt(sb.substring(0,1))==1){
strRestult.insert(0, sb.toString());
}
return strRestult;
}
/**
* 该方法是模拟除法的某一一次除法过程,把这个过程抽取出来成为一个方法,这样使代码更容易理解,避免堆一起成一坨***
* 比如324/2 则先算3/2=2余1 第二步是1*10+2/2=6 余0,第三步骤是4/2=2 StringBuilder为162,用StringBuilder是因为在字符串拼接过程中不需要新开地址空间。
* Stribuder不用重开地址空间,而String是特殊类型,会重新开辟地址空间,影响程序效率。
* @param str
* @return 某次模拟除法的结果,下次将用这个结果去继续模拟除法
*/
private StringBuilder getTempString(StringBuilder str){
int p = 0; //
sb = new StringBuilder();//每次计算,都需要sb初始化,因为需要计算的参数已经当做形参传进来了,现在的sb只需要做记录结<span style="white-space:pre"> </span>果就可以了
for(int i=0;i<str.length();i++){
p = p+Integer.parseInt(str.substring(i, i+1));
if(p%2==0){//除以2整除
sb.append(p/2);
p=0;
}else{//没有整除,有余数,比如324/2,高位数余一,则模拟除法的下一步是12/2
sb.append(p/2);
p=10;
}
}
//考虑到第一位可能为零,比如15,第一位取出来为1/2=0;所以最后要干掉这位数字(如果为0的话)
if(Integer.parseInt(sb.substring(0,1))==0){
sb.deleteCharAt(0);
}
if(p==0){
strRestult.insert(0, 0+"");
}else{
strRestult.insert(0, 1+"");
}
return sb;
}
}
上面就是整体的算法,整体上不是很麻烦,但是要考虑的细节很多,是否需要对外开放public权限,当然,如果这是个工具类的话,完全可以让计算方法改成static方法。整体上计算结果基本是要考虑最后一位的情况下怎么处理,因为2进制的首位数字肯定是1,所以当为0的情况下就不在前面加上了。具体的模拟除法的步骤上要考虑数字的第一位数字,是否大于2,如果不是大于二的数,结果也容易出现0的情况,但是考虑到循环问题,如果每次都判断的话,那就消耗计算,所以采取通用算法,然后处理结果,如果stringbuilder的首位为0,则删除就好,这样就可以不影响计算了。
先这样~