“拨”出数位上的数字 - 多种思路实现反向输出一个四位数

“反向输出一个X位数”是C语言乃至其它编程语言入门的一道经典例题。虽然难度不大,但其实现思路是多样的(共同的核心是如何拨出数位上的数字,并重新组合)。

对于初学者的学习而言,编程思路和代码实现自然是多多益善。并且,该编程问题容易作为一个思路模块出现在更大更复杂的编程项目中。因而,本文以“反向输出一个4位数为例”,整理了容易接触到的一些如何“拨出各个数位上的数字”的几种方法,而各个实现方法的利弊交给诸位读者自行理解与权衡,同时在最后添加了一道相对复杂的变式拓展习题。

希望对诸位读者学习C语言有所帮助。若诸位读者有更好的实现办法,非常欢迎各位指出讨论!


一、引例 -- 反向输出一个四位数

1. 题干描述

牛客网OJ题链接:BC19 反向输出一个四位数https://www.nowcoder.com/practice/1f7c1d67446e4361bf4af67c08e0b8b0?tpId=107&&tqId=33300&rp=1&ru=/ta/beginner-programmers&qru=/ta/beginner-programmers/question-ranking 

2. 思路简述

总体思路非常简单:反向输出一个四位数,可以理解为将该四位数的个、十、百、千四个数位上的数字取出,并以相反的顺序重新组合。

如 1234,取出个位数4,十位数3,百位数2,千位数1,反向按千,百,十,个的顺序重组,即为4321.


二、实现思路 -- 拨出数字

思路一:用取模运算分别拨出数位

代码

//方法一
#include<stdio.h>

int main(){
    int num = 0;
    int ge,shi,bai,qian;    //分别接受各个数位上的数字

    scanf("%d",&num);    //输入一个四位数

    ge = num/1%10;
    shi = num/10%10;
    bai = num/100%10;
    qian = num/1000%10;

    printf("%d%d%d%d",ge,shi,bai,qian);
    
    return 0;
}

说明

拨出数位上的数字的常用方法:num/数位%10

例如,要取千位上的数字,则num/1000%10;要取百位上的数字,则num/100%10。以此类推,可以分别取出所有数位上的数字。

该方法的好处是,在取出数字后可以最灵活简便地进行组合。毕竟单个数字已经拿到手了,具体怎么排列(如不要反向输出,而要输出3124,4231等),完全可以任意。并且可以拿数位上的数进行任意的运算,以达到题干的某些要求。

例如经典例题水仙花数,水仙花数指的是一个三位数,个位³+十位³+百位³ = 这个数本身。那么完全可以用上述办法实现:

//示例-水仙花数

ge = num/1%10;
shi = num/10%10;
bai = num/100%10;

//达到如下条件的数称之为水仙花数:
ge*ge*ge + shi*shi*shi + bai*bai*bai == num

是一种非常容易想到的思路。

当然,这种方法不适合数量级比较大的情况。要是不止是四位数,更有五位数、六位数,显然这种方法就太麻烦了。

思路二:循环取数

代码

//方法二

int main(){
    int num = 0;
    scanf("%d",&num);
    while(num){
        printf("%d",num % 10);
        num /= 10;
    }

    return 0;
}

说明

这个思路与上一个比起来,看着令人舒服地多。它依然用到取模运算,但与思路一略有差别。它的实现流程是:

1. 模运算从低位到高位取出各位上的数字,并打印。

2. 除运算降低数位。

如此循环往复。以1234为例:

1234 % 10  得到个位上数字 4,由于它先被取出来打印,所以它最后也排在最前面。

1234 / 10  得到数位比之前低一位的新数 123  (其实也不难理解,我的目的就是把数位上的数取下来,既然个位数字4已经被我拿到了,那还留着4干什么?除运算把它扔掉了!)

这个思路还是比较巧妙的。虽然它的时间复杂度为O(N),但它确实比思路一更适合数位更多的情况。三位数、五位数、七位数的逆向它都能实现,而不需要再开辟新的变量。

思路三:scanf -- "%1d"

代码

//方法三
#include<stdio.h>

int main(){
    int a,b,c,d;
    scanf("%1d%1d%1d%1d",&a,&b,&c,&d);
    printf("%d%d%d%d",d,c,b,a);

    return 0;
}

说明

采取控制宽域输入的办法,直接取数,简单粗暴。该程序的实现原理如下:

"%*d"  可以理解为scanf从左向右一次性读入输入数的宽度(或从缓存区一次性取走的数字的个数),"%1d"即从左到右一次读入1个数字。


当输入1234 时,该程序的执行流程是这样的:

1给a        a = 1

2给b        b = 2

3给c        c = 3

4给d        d = 4

输出 d c b a,即 4321

当然,"%2d"则表示从左到右一次性读入两个。下图中,12给a,34给b

思路四:数组

代码

//方法四
#include<stdio.h>

int main()
{
    char ch[10];
    scanf("%s", ch);
    for (int i = 3; i >=0; i--)
    {
        printf("%c", ch[i]);
    }
    return 0;
}

说明

数组的办法也非常容易想到。采取数组的倒序遍历,可以直观地将四位数逆向输出,不需要特别地去“取数”。


三、变式拓展题 -- 编写函数,实现统计和输出一个正整数中各位数字中零的个数,及各位数字中最大者。

1. 题干描述

编写函数,实现统计和输出一个正整数中各位数字中零的个数,及各位数字中最大者。

例如:1080其零的个数是2,各位数字中最大者是8。主函数负责正整数的输入。

2. 解题思路

代码

#include<stdio.h>

void fun(int n, int* zeroCount, int* maxDigit){
	*zeroCount = 0;    //计数器:计0的个数
	*maxDigit = -1;    //初始化,-1正常情况下不可能取到,故以此为标记
	
    while(n!=0){
		int d = n % 10;    //同上述思路二,循环取数

		if( !d ){
			(*zeroCount)++;    //如果取出的这个数为0,计数器++
		}

		if(d > *maxDigit){
			*maxDigit = d;    //如果取出的这个数比预设的最大数更大,则取代
		}

		n = n/10;
	}
}

//主函数
int main(){
	int n;
    int zc, md;    //分表代表fun中的zeroCount和maxDigit
                   //由于输出操作在主函数中实现,因而将要输出的数作为实参传入fun

	printf("Please input an integer:");    //输入一个任意的正整数
	scanf("%d",&n);

	fun(n, &zc, &md);    //函数调用
	printf("%d has %d zero(s), the max digit is %d",n,zc,md);
    
    return 0;
}

说明

1. 用函数的传址调用,主函数中定义zc,md用以输出。

2. 在fun函数中需要直接控制改变main函数中的值。

3. 该题是引例思路的一个小型的综合运用。本题中fun函数内,while循环即思路二循环取数的化用,while循环中的第一句语句与最后一句语句完全就是思路二的模板。计数器和取最大值的语句也并不难想到,将它们实现嵌套在其内,配合变量即可包装成一个可行的函数。

4. 从本题中还能得出,循环取数隐含了一种“遍历各个数位”的思想。该题虽然没有明说要“取数”,但依然和引例的思路二大同小异:要遍历一遍每个数位。

5. 要知道为什么选取思路二。还记得我们上面说过,思路二对整数的长度要求不限吗?正是这个优点。若采用其它的思路,不是说完全无法实现,而是确实会麻烦一些。以下展示了一个思路不清晰写出的“惨案”。

反例

大致浏览即可,勿学!(代码风格是C++)

//复杂化了!!!不建议!!!
#include<iostream>
#include<math.h>
using namespace std;

int digitX(int x){
	int dgt = 0;
	while(x){
		x /= 10;
		dgt++;
	}
	return dgt;	
}//求数的位数

void stcs(int x){
	int a[81] = {0};
	int i;
	int cnt = 0;

	for(i=0; i < digitX(x); i++){
		a[i] = x/(int)pow(10,i)%10;
		if(a[i]==0){
			cnt++;
		}
	}

	cout << "共有 " << cnt << " 个0" << endl;

	int maxI=i;

	for(i=0; i <= digitX(x); i++){
		if(a[i] > a[maxI]){
			int tmp;
			tmp = a[i];
			a[i] = a[maxI];
			a[maxI] = tmp;
		}
	}

	cout<<"最大的数字是 "<<a[maxI]<<endl;//输出最大的数
}

int main(){
	//1、编写函数,实现统计和输出一个正整数中各位数字中零的个数,及各位数字中最大者。
	int num,digit;
	cout << "请输入一个正整数:";
	cin >> num;
	digit = digitX(num);
	cout << "这个数共有 " << digit << " 位" << endl;
	stcs(num);

    return 0;
}

这片代码实现并不建议大家借鉴。它认为,需要在干活前先知道这个整数有几位。剩下的代码实现也是非常繁琐,咋一看也许不知所云,甚至将输出的语句也写在了函数体内而非main函数内,这也是不太好的风格。反例的目的正是希望大家不要模仿,仅作鞭尸作用。因而大家不必细看,知道这样做并不好就可以了。

(大家不用担心代码的主人见了会不高兴,因为这其实就是我初学时写的。由于当初学得并不扎实,吭哧吭哧费了好大力,写完后也是非常高兴,因为它最终确实可以跑起来完成题目。提交了作业后似乎很长时间没有管过,但事实上它实在是太烂了,故在此鞭尸示众。后来进行了修改,才有了新的正常的代码。

这个故事告诉我们,有时回头看看以前写的东西也是很重要的!改改曾经的的代码说不定也有收获呢(手动狗头))


四、总结

本文通过牛客网引例“如何反向输出一个四位数”,介绍了“拨”出一个整数各个数位的四种思路。并以思路二循环取数为基础,展示了一道拓展例题。总体难度不大,各种方法有利有弊,需要诸位根据实际情况自行取舍使用。

如有其它方法,欢迎各位一同学习讨论!

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值