今天来讲一下数字分离(又叫数位分离)问题,那么什么是数字分离呢?
顾名思义,数字分离就是将一个正整数的每一个数字单独分离出来,并进行打印或者储存操作或者是进行其他运算。
数字分离在编程练习题中非常常见,很多题目必须分离数字后才有办法进行后续操作。
比如:789分离成 7 8 9 或者 9 8 7
数字分离的方法有很多种,我们先来说一下基本的思想:
下面我们以789这个数字为例,看看我们可以通过下面的哪几种方式分离出每个数字:
请注意演示的代码并不一定是最佳的,只是一种参考思路,用什么语言取决于你自己的习惯,万变不离其宗。
一、数学运算法
第一种思想:
分离步骤:
编号 | 运算 | 结果 |
---|---|---|
1 | 789/100 | 7 |
2 | 789%100 | 89 |
3 | 89/10 | 8 |
4 | 89%10 | 9 |
5 | 9/1 | 9 |
6 | 9%1 | 0 |
仔细观察我们可以发现,在已知是n位数的情况下,我们可以通过这种方式很快地分离出所有数字,
用文字来描述上述过程就是:
先记k=10的n-1次方
1.将要分离的数字除以k
2.将要分离的数字对k取余
3.让k = k / 10
记取余的结果为要分离的数,重复上述1 - 3的过程,直到取余的结果为0。则每次的除法运算的结果(第一步操作的结果)即为要分离的数字。
下面我们用代码简单实现一下这个过程:
import java.util.Scanner;
public class Depart1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
depart1(n);
scanner.close();
}
public static void depart1(int n) {
int m = 0;// 表示n是几位数
int temp = n;// 临时变量
int beiChu;// 被除数
int yu;// 余数
int k;// 储存k
// 循环判断数的位数
while (temp != 0) {
temp = temp / 10;
m++;
}
for (beiChu = n, k = (int) Math.pow(10, m-1); beiChu != 0; k /= 10) {
System.out.println(beiChu / k);
yu = beiChu % k;
beiChu = yu;
}
}
}
运算结果:
显然这个过程是比较繁琐的,开辟了很多变量空间,在判断位数的过程中又进行了循环除法运算。但是它的优点是正序输出了所有的数字,并且在已知是n位数的情况下可以比较快地正序输出想要的结果。
第二种思想:
分离步骤:
编号 | 运算 | 结果 |
---|---|---|
1 | 789%10 | 9 |
2 | 789/10 | 78 |
3 | 78%10 | 8 |
4 | 78/10 | 7 |
5 | 7%10 | 7 |
6 | 7/10 | 0 |
用文字来描述上述过程就是:
1.将要分离的数字对10取余
2.将要分离的数字除以10
记除以10的结果为要分离的数,重复上述1 - 2的过程,直到除以10的结果为0。则每次的取余运算的结果(第一步操作的结果)即为要分离的数字。
下面我们用代码来演示一下这个过程:
import java.util.Scanner;
public class Depart2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
depart2(n);
scanner.close();
}
public static void depart2(int n) {
int beiChu;//被除数
int yu;//余数
for(beiChu = n;beiChu != 0;) {
yu = beiChu % 10;
System.out.println(yu);
beiChu = beiChu / 10;
}
}
}
运算结果:
我们可以发现,这种方法相比第一种方法,不用进行几位数的判断,并且每次除运算和取余运算要除的数一直都是恒定的10,这对我们输入任意的正整数的分离更具有普遍性,更具推广性,代码也比较简洁,我最初学编程使用的数字分离就是采用这种方式进行。
但是这种方法的不足之处是输出来的数字是逆序的,当我们要正序使用的时候,可能就需要对储存的结构(如数组)进行反转操作或者逆序输出操作,在某些情况下就增加了操作。
第三种思想:
那么有没有一种方法,能够结合前面谈到的两种思想,既能做好正序输出,又能做到代码简约呢?
答案是有的,我们还可以结合递归的思想进行操作。
关于递归,从自然语言描述的角度,我觉得可以这样去理解:
举个生活中例子:
假如我要去北京旅游,这是我想要达到的目的。
要去北京旅游,那么就要先去买去北京的机票。要买去北京的机票,那么就要先去官网订票。要去官网订票,那么就要先打开电脑的浏览器。要先打开电脑的浏览器,那么就要先启动电脑。要启动电脑,那么就先要插上电脑的电源。
好,现在电脑插上电源了。我就可以启动电脑。启动完电脑了,就可以打开浏览器。打开浏览器了,就可以输入网址到官网订票。订完票了,我就买到去北京的票了。买到了票,我就可以去旅游了。到这里,我的目的就达成了!
前提的前提的前提的前提。
要完成一件事,就必须先完成另外一件事,要完成另外的那件事,就必须再去先做另外的一件事,以此类推,直到最基本的那件事解决了,那么一步一步返回,那么一件一件事就依次倒着返回,直到最开始的事完成,那么这件事就解决了。
递归的思想和这个类似,但是请你注意,递归还要求以下几点:
1.完成每件事的基本操作都是一样的,递归函数可以描述完成每一件事完整的过程,包括最开始要做的的那件事。
2.要做这些事一定是有穷的,一定是做到某件事为止,到最底层触发了某个条件,完成指定的操作,就返回上一层调用处。然后继续完成上一层调用处剩下的步骤,然后再返回继续完成再上一层调用处剩下的步骤,直到最开始的调用处完成所有的步骤,得到结果,问题求解。
关于递归,从数学的角度,我觉得可以这样去理解:
递归可以结合复合函数来理解。
曾经的你一定遇到过这样的数学问题:
问题:已知f(x)=2x, x=3 ,求f(f(f(x))的值。
想一想,求解这个问题的过程和我们用自然语言描述的过程是不是有着异曲同工之处?
我们来看看求解过程:
f(x)=2x=2*3=6;
f(f(x))=2*f(x)=2*6=12;
f(f(f(x))=2*f(f(x))=2*12=24;
用公式描述整个求解过程即:
f(f(f(x))=2*f(f(x))=2*2*f(x)=2*2*2x=2*2*2*3=24。
现在,你对递归是不是有了新的认识?
那么下面请看用递归思想实现的正序输出数字分离代码:
import java.util.Scanner;
public class Depart3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
depart3(n);
scanner.close();
}
public static void depart3(int n) {
//函数出口,被除数为0,说明除到底了
if(n == 0) {
return;
}
depart3(n/10);//先一步一步除以10,直到除到底
//789 78 7 0
System.out.println(n % 10);//依次返回时,分离出尾数 7%10 78%10 789%10
}
}
运行结果是:
那么下面再请看用递归思想实现的逆序输出数字分离代码:
import java.util.Scanner;
public class Depart3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
depart3(n);
scanner.close();
}
public static void depart3(int n) {
//函数出口,被除数为0,说明除到底了
if(n == 0) {
return;
}
System.out.println(n % 10);//先分离出尾数 789%10 78%10 7%10
depart3(n/10);//再一步一步除以10,直到除到底
//789 78 7 0
}
}
运行结果是:
用递归的思想实现的数字分离很明显有代码十分简洁,并且只要改变代码的顺序就可以实现按正序或逆序分离所有的数字。
当然任何方法都存在一定的缺陷,它也不例外。
这种方法的缺点是:对机器内存的要求比较高,它的运行需要较多次数的函数调用,如果调用层数比较深,需要增加额外的堆栈处理(还有可能出现堆栈溢出的情况),比如参数传递需要压栈等操作,会对执行效率有一定影响。但是对于我们现代的机器来说,这一点有时是可以忽略不计的,加上我们要分离的数一般比较小,调用的次数也有限,在不考虑内存和时间的限制下,使用这种方式进行数字分离还是可取的!
以上的三种思想的数字分离方式是通过数学运算的方法来实现的,不知道大家发现没有,对于数学运算的方法(除10取余,辗转相除)的方法实际上对于输入的数字大小是有一定的限制的(在不同的语言环境下的取值范围可能有所不同),以JAVA语言运行环境为例:
byte的取值范围为-128~127,占用1个字节(-2的7次方到2的7次方-1)
short的取值范围为-32768~32767,占用2个字节(-2的15次方到2的15次方-1)
int的取值范围为(-2147483648~2147483647),占用4个字节(-2的31次方到2的31次方-1)
long的取值范围为(-9223372036854774808~9223372036854774807),占用8个字节(-2的63次方到2的63次方-1)
这样在某些情况下就有可能造成数据溢出,当我们要输入一个很长的天文数字时,就不能用上面这几种方法来分离数字了,那这个时候怎么办呢?我们接着往下看!
二、字符串接收分离法
既然用数学运算法对输入的数的大小有限制,那我们换一种思路,改用字符串接收数字。
大家知道,字符串本身的长度理论上应该是不受限制的(但是由于操作系统的一些特性(比如最大连续内存)和编译运行的环境和某些硬件因素,可能也有一定的限制),不管怎么样,对于储存我们要输入的数字来说应该是绰绰有余了。
下面我们通过字符串接收方法来实现以下数字分离,其实方法很简单粗暴,我们直接接收,然后遍历每个子字符就可以了。(单纯打印的话,直接输出结果即可。要计算或者储存具体的数的话要通过计算与’0’字符的ASCII码差值后储存)。
示例代码:
import java.util.Scanner;
public class Depart4 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
depart4(str);
scanner.close();
}
public static void depart4(String str) {
for(int i = 0;i
对于以上所有算法,将 输出语句 改为 赋值储存语句 即可实现数字分离后储存下来进行后续操作。
对于数字分离的算法,暂时先讲到这里,当然还有很多其他的方法可以完成目的,等以后看到或者想到了再进行补充。
由于笔者学识有限,若有不足之处或者错误的地方,还请留言批评指正,我们一起共同进步。
-pakqoo 写于 2019年08月31日 23:16
挥舞着键盘和本子,发誓要把世界写个明明白白!