第三章《数组与循环》第8节:数组与循环经典例题

时间:2022-12-30 11:18:40

​利用数组和循环可以解决很多经典问题,比如对数字的查找、排列、筛选等。本小节甄选了其中一些有代表性的问题集中进行讲解,认真学习这些经典例题不仅有助于巩固Java语言的相关知识点,还对提高逻辑思维能力有很大帮助。

3.8.1求整数位数

题目:由用户从控制台上任意输入一个整数,求其位数(例如输入168,运算结果为3)。​

根据Java语言整数之间的除法运算规则可知:任何一个整数被10整除所得的商都比原数字少1位,商再被10整除,又少1位。当商为0时,再被10整除多少次,位数都不变。被10整除多少次使得商成为0,整数就是多少位。例如整数168,第1次整除后商为16,第2次整除后商为1,第3次整除后商为0。总共经过3次被10整除得到商为0,所以168就是3位数。​

根据这个解题原理,可以把程序大致分为下面的几个部分:​

  1. 声明一个整型变量count作为计数器,其初始值设为0,用来统计整数位数。​
  2. 把输入的整数用10整除,如果商不为0,则对商继续用10整除,直到商为0为止。每做一次整除操作,计数器自增1。作经过n次整除,当商为0时,计数器的值就是整数位数。​
  3. 如果用户输入的整数是0,特殊处理一下​

以下是求整数位数程序的完整代码:​

【例03_15 求整数位数】

Exam03_15.java​

import java.util.Scanner;

public class Exam03_15 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num;// 被求位数的整数
int count = 0;// 位数计数器
System.out.println("请输入一个整数");
num = sc.nextInt();
if (num != 0) {
while (num != 0) {// 如果num还没变成0
num = num / 10;// 反复被10整除
count++;// 位数计数器+1
}
} else {// 对数字0特殊处理
count = 1;
}
System.out.println("该数字是一个" + count + "位数");
}
}

3.8.2串数求和

题目:求a+aa+aaa+...+aa...a的值,其中a是1位正整数,加数的个数以字母n表示,n≥1。a与n的值由用户输入。​

为帮助读者理解题目,此处先用一个具体的数值作为举例。假设a的值是2,n的值是5,套用题目中对a和n的解释,a+aa+aaa+...+aa...a就表示2+22+222+2222+22222。如果把这些要相加的数字以竖式的形式表示,则可以得到如图3-5的表示形式:​

第三章《数组与循环》第8节:数组与循环经典例题

图3-5 串数求和竖式表示形式​

如果分别以个位、十位、百位的顺序来看图3-5的竖式,可以看出:竖式中出现了n个2,n-1个20,n-2个200...。也就是说:2+22+222+...+22...2等于2×n+20×(n-1)+200×(n-2)…。进一步观察发现:2×n+20×(n-1)+200×(n-2)…是由n个乘法表达式组成,从前到后,各个乘法表达式遵循的规律是:被乘数都是前一表达式的10倍,而乘数每次减少1。如果表达式中的数字2被替换成1-9中的任意数字,就可求出a+aa+aaa+...+aa...a的值。以下是求a+aa+aaa+...+aa...a值的程序代码:​

【例03_16 串数求和】

Exam03_16.java​

import java.util.Scanner;

public class Exam03_16 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a, n;// 声明a和n
int sum = 0;// 最终求和的结果
System.out.println("请输入a的值");
a = sc.nextInt();
System.out.println("请输入n的值");
n = sc.nextInt();
while (n >= 1) {
sum = sum + a * n;
a = a * 10;// 使a扩大10倍,为下一轮计算做准备
n--;// 使n减去1,为下一轮计算做准备
}
System.out.println("总和为:" + sum);
}
}

3.8.3猴子吃桃

题目:猴子第1天摘下若干个桃子,当即吃了一半后又多吃了1个。第2天又将剩下的桃子吃掉一半,又多吃了1个。以后每天都吃了前1天剩下的一半零1个。到第10天想再吃时,见只剩下1个桃子了。求第1天共摘了多少桃。​

通过分析题目不难看出:每天桃子的数量都有两种,分别是“吃以前”的数量和“吃以后”的数量。题目中已知第10天桃子的数量是“吃以前”的数量,并且要求解的也是第1天“吃以前”桃子的数量,因此解题过程中只讨论每天“吃以前”桃子的数量。以循环方式倒推,以第10天为起点,循环1次可算出第9天桃子的数量,循环2次可算出第8天桃子数量,按此规律,共循环9次可算出最初桃子的数量。假设每天桃子的数量是num,根据猴子吃桃的规律可知,前1天桃子的数量是(num+1)×2,并且num的初始值是1。按照这样的规律就可很快求出第1天猴子摘了多少桃。以下是本题目的程序代码:​

【例03_17 猴子吃桃】

Exam03_17.java​

public class Exam03_17 {
public static void main(String[] args) {
int num = 1;// 最后一天桃子的数量
for (int i = 1; i <= 9; i++) {// 循环9次可推算出第1天桃子的数量
num = (num + 1) * 2;
}
System.out.println("第1天摘了" + num + "个桃子");
}
}

经程序计算,第1天摘了1534个桃子,各位读者可对照这个答案验证程序是否编写正确。​

3.8.4找到最大值

题目:由用户任意输入5个整数,保存到数组中,编写程序找到这五个数中的最大值。​

通过阅读题目可知,本题目的程序可以分为两部分:​

1、用户输入数据并保存到数组中 ​

2、从数组中找到最大值​

输入数据的操作很简单,下面重点分析如何找到所输入数据的最大值。从数组中找到最大值的过程,就如同是武侠小说中擂台比武一样。在众多比武者中,先有一个站上擂台,后续的比武的人轮番登台,打胜者留在台上,败者*。当所有参与比武的人都登台比武一次后,留在擂台上的就是最强者。​

具体实现过程如下:​

  1. 设定一个变量max,其初始值设为数组下标为0的数组元素​
  2. 从数组中下标为1开始到数组最后一个元素为止,用循环的方式依次取出每个数组元素与变量max做比较,如果取出的数组元素比max的值更大,则用该元素的值替换max的值​
  3. 比较结束后,max变量的值即是数组中的最大值​

以下是实现找到最大元素的完整代码:​

【例03_18找到最大值】

Exam03_18.java​

import java.util.Scanner;

public class Exam03_18 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int array[] = new int[5];// 用于保存元素的数组
int max;
for (int i = 0; i < 5; i++) {// 循环输入5个整数
System.out.println("请输入第" + (i + 1) + "个整数");
array[i] = sc.nextInt();
}
max = array[0];// 以数组头一个元素初始化max
for (int i = 1; i < array.length; i++) {// 轮番比较
if (array[i] > max) {// 如当前元素比max的值还大
max = array[i];// 以当前元素替换max值
}
}
System.out.println("最大值是" + max);
}
}


3.8.5数字排列

题目:用1、2、3、4四个数字,能组成多少个每位都互不相同的三位数?都是多少?​

分析题目可知:个位、十位、百位三个位上都有1、2、3、4四种可能。根据嵌套循环的执行特点,可以用3层循环把个位、十位、百位所有排列组合的可能性全部列举一遍。具体做法是:分别用外、中、内3层循环分别列举出百位、十位和个位数字的4种可能性。同时以一个int型变量count作为计数器,每当排列出一种组合的可能性,都判断一下这种组合是否满足三个位上的数值互不相同的条件。如果满足这个条件,则打印数字,并且给计数器加1。当三层循环全部运行结束,计数器的值就是所有满足条件三位数的个数。数字排列的程序代码如下:​

【例03_19数字排列】

Exam03_19.java​

public class Exam03_19 {
public static void main(String[] args) {
int count = 0;// 计数器
for (int x = 1; x <= 4; x++) {// 列举百位的4种可能性
for (int y = 1; y <= 4; y++) {// 列举十位的4种可能性
for (int z = 1; z <= 4; z++) {// 列举个位的4种可能性
if (x != y && y != z && x != z) {// 判断3个位数是否出现重复
count++;// 每排列出一个个十百位都不重复的数字都使计数器+1
System.out.println(x * 100 + y * 10 + z);// 打印符合条件的数字
}
}
}
}
System.out.println("共有" + count + "个三位数");
}
}

这个题目是典型的用循环穷举所有可能性的题目,它的正确答案是排列出24个满足条件的三位数。与之相类似的题目还有很多,例如:用足够多的可自行思考如何编写程序解出该题目。​

3.8.6打印杨辉三角

题目:打印出如下杨辉三角形,总共打印9行​

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

......

为帮助读者解答题目,此处先简单介绍一下杨辉三角。杨辉三角是二项式系数在三角形中的一种几何排列。杨辉三角的特征是:第1行有1个数字,第2行有2个数字...以此类推,第n行有n个数字。每行最左边和最右边的数字都是1,其余数字是其左上方和正上方数字之和。打印杨辉三角的操作可以借助内外两层嵌套循环以及二维数组来实现,具体实现办法是:​

  1. 外层负责循环控制打印行数,内层循环控负责制打印每行的数字​
  2. 每打印一个数字,都把这个数字存放到二维数组的对应位置上​
  3. 根据杨辉三角的形状可知,存储数字的二维数组每一行长度并不相同,因此可以首先把二维数组初始化为n行。每次执行外层循环时,根据每一行存储数字的个数单独初始化二维数组各行的长度。即第1次执行外层循环时,单独初始化二维数组的第1行长度为1。第2次执行外层循环时,初始化二维数组的第2行长度为2,以此类推。​
  4. 我们把要打印的数字称为“当前数字”。在打印过程中,需要判断当前数字的位置,如果当前数字位于本行的首位或末位,直接把数字1存入数组。否则从二维数组中取出当前数字左上方和正上方的数字并求和,这个和就是当前数字的值。把计算出的当前数字存入数组的对应位置,之后打印当前数字。​

以下是打印杨辉三角的完整代码:​

【例03_20打印杨辉三角】

Exam03_20.java​

public class Exam03_20 {
public static void main(String[] args) {
int[][] array = new int[9][];// 创建一个9行的二维数组
for (int i = 0; i < array.length; i++) {
array[i] = new int[i + 1];// 为每个一维数组单独指定长度
for (int j = 0; j <= i; j++) {
if (j == 0 || j == i) {// 每行首位和末位的数字都是1
array[i][j] = 1;
} else {// 计算本行其余数字
array[i][j] = array[i - 1][j - 1] + array[i - 1][j];
}
System.out.print(array[i][j] + "\t");
}
System.out.println();
}
}
}

3.8.7冒泡排序

题目:由用户从控制台任意输入5个整数并存入数组,并用冒泡排序将数组中的数字按从小到大的方式排列。​

由题目可知,本题分两步完成,分别是输入数字存入数组和用冒泡排序对数字完成排列。输入数字并存入数组的操作很简单,下面重点讲述冒泡排序的实现原理。冒泡排序的基本思想是: 让较大的数慢慢往后排,较小的数慢慢往前排。这个排序的具体操作过程为:​

  1. 从数组的头部开始,依次比较相邻的两个元素,如果前一个元素比后一个大,则交换这两个元素。​
  2. 假设数组长度为n,则经过n-1次比较和交换后,数组中最大的元素就被交换到了数组的末尾,第1轮操作完成。​
  3. 把步骤1反复执行n-1次,但每执行1次,比较的次数都比上一轮减少1次。减少比较次数的理由是:第1轮经过n-1次比较和交换,数组最大的元素已经到达数组末尾,因此第2轮比较时,位于数组末尾的最大元素可以不用再次参与比较和交换的操作。同理,第2轮操作完成后,数组中第2大的元素就到达了数组的倒数第2位,因此第3轮操作时,数组中最后2个元素可以不用参与比较和交换,之后的轮次也是如此。​
  4. 整个操作过程可以用两层嵌套循环完成,外层循环负责控制比较多少轮,内层循环负责比较和交换两个相邻元素。​

图3-6展示了冒泡排序的基本算法原理​

第三章《数组与循环》第8节:数组与循环经典例题

图3-6 冒泡排序原理​

图3-6中演示了如何用冒泡排序完成5个数组元素的排序过程。图中每个方框表示一轮操作,每轮操作中深色背景的元素为当前要进行比较和交换的两个相邻元素。​

以下是冒泡排序的完成程序代码:​

【例03_21冒泡排序】

Exam03_21.java​

import java.util.Scanner;

public class Exam03_21 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] data = new int[5];
for (int i = 0; i < data.length; i++) {
System.out.println("请输入第" + (i + 1) + "个整数");
data[i] = sc.nextInt();
}

for (int i = 0; i < data.length - 1; i++) {// n个数字进行n-1轮比较
for (int j = 0; j < data.length - 1 - i; j++) {
if (data[j] > data[j + 1]) {// 比较相邻两个数字
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
System.out.println("元素排序之后是:");
for (int x : data) {
System.out.print(x + " ");
}
}
}

3.8.8猴子分桃

题目:海滩上有一堆桃子,5只猴子来分。第1只猴子把这堆桃子平均分为5份,多了1个,它把多的1个扔入海中,拿走了1份。第2只猴子把剩下的桃子又平均分成5份,又多了1个,它同样把多的1个扔入海中,拿走了1份,第3、4、5只猴子都是这样做的,问海滩上原来最少有多少桃子?​

通过阅读题目可知:假设每只猴子在分桃子时看到的桃子的数量是total,则这只猴子之前那只猴子看到的桃子数量是 total × 5/4 + 1,根据这个规律,以及分桃子的次数就可以倒推出最初海滩上最初有多少桃。但这需要知道第5只猴子分桃时看到的桃子的数量,这个数是多少?暂且不知,但可以肯定的是,第5只猴子至少拿走1只桃子。继续深入分析题目可以得知:第2-5只猴子分桃时看到桃子的数量total一定是4的倍数,因为前一只猴子都是把桃子平均分成5份,留下4份。而第1只猴子看到的桃子数量不一定是4的倍数,因为它是把并不是从4堆平均分好的桃子中取走了自己的一份。​

经过以上分析可以确定解题思路可以分为两大步:​

一、用循环的方式推算每只猴子看到的桃子数。​

二、根据之前的分析结果检验推算的正确性:如果第2-5只猴子看到的桃子数量total是4的倍数,那么推算所得到的桃子个数一定是合理的。因此,第2-5只猴子看到的桃子数量是否是4的倍数是检验推算结果合理性的判断依据。​

如何推算每只猴子看到的桃子数量呢?题目中并没有说第5只猴子看到多少桃,但可以肯定:第5只猴子至少拿走1只桃。假设第5只猴子拿走num个桃子,那么它看到桃子的数量就是num × 5 + 1。再由第5只猴子看到桃子的数量,就可以推算出前面任意一只猴子所看到桃子的数量。所以,在整个推算过程中,是以第5只猴子“拿走”桃子的数量为起点进行推算的。​

程序中可以用变量num来表示第5只猴子拿走桃子的数量。首先设置num的初始值为1,也就是说先假设第5只猴子拿走了1只桃子,以此为起点推算每只猴子看到桃子的数量。每推算一次,都要检验推算结果的合理性。推算过程中一旦检验出第2-5只猴子所看到的桃子数量不是4的倍数,就可以知道最初设定的num值是不正确的,于是立刻停止本轮推算操作,然后用一个比num值大1的数字代替num,重新开始新一轮推算的过程。具体来说就是:最初以num的值为1开始进行推算,边推算边检测,如果发现num为1的情况下,推算出的第2-5只猴子看到桃子的数量不是4的倍数,就把num的值换成2重新开始推算,如果num的值为2仍不能推算出满足条件的结果,则继续把num的值换成3再次重新开始推算,直到以某个num的值为起点推算出第2-5只猴子所看到的桃子数量都是4的倍数,就可以进一步推算得到第1只猴子所看到的桃子数量,这个数量就是海滩上原来有多少桃子,此时就可以彻底结束推算操作。​

实际编码过程中,可以设置一个变量checkedTime作为计数器,每开始新一轮推算时都设置它的初始值为0。在每一轮推算过程中,从第5只猴子开始,每当算出1只猴子所看到桃子数量,就看看这个数量是不是4的倍数。如果是的话,就进行checkedTime++的操作。当某一轮推算过程中,checkedTime达到了4,则说明第2-5这4只猴子所看到的桃子数量都是4的倍数,进而说明这一轮推算已经得到了正确解,推算操作可以彻底结束。​

在实际推算开始之前无法预知实际需要推算多少轮才能得到正解。程序可以用while循环完成反复推算的过程,只要未获得正解,就一轮一轮的推算下去。一旦求得正解,用break语句中止循环即可。具体做法是,设置一个boolean型变量finish,其初始值为false,表示还没有获得正解。设置while循环的条件为“finish==false”,这样只要finish的值为false,虚拟机就会一直执行循环。当某一轮循环中checkedTime的值达到4,就表明已经获得正解,此时设置finish的值为true即可中止循环。以下是猴子分桃程序的完整代码:​

【例03_22猴子分桃】

Exam03_22.java​

public class Exam03_22 {
public static void main(String[] args) {
int num = 1; // 第5只猴子拿走桃子的数量
int total = 0;// 每只猴子看到的桃子的数量
boolean finish = false;
while (finish == false) {// 没有获得合理解就不停止循环
int checkedTime = 0;// 计数器
for (int i = 5; i >= 1; i--) {
if (i == 5) {// 推算第5只猴子看到桃子的数量
total = num * 5 + 1;
} else {// 推算第1-4只猴子看到桃子的数量
total = total * 5 / 4 + 1;
}
if (total % 4 != 0) {// 如果发现某只猴子看到桃子数不是4的倍数
break;// 停止循环开始下一轮推算
} else {
checkedTime++;// 推算出一次合理结果就执行checkedTime++
}
}
if (checkedTime == 4) {// 本轮如果达到连续4次推算说明已获得正解
finish = true;
} else {// 本轮推算未获得正解
num++;// num++为下一轮推算做准备
}
}
System.out.println("第5只猴子拿走" + num + "只桃子");
System.out.println("海滩上总共有" + total + "只桃子");
}
}

本程序的运行结果是:​

第5只猴子拿走255只桃子​
海滩上总共有3121只桃子​

需要提醒各位读者:程序此处得到的正解并非唯一解,而是正解中的最小值。其实如果不控制循环,使之无限运行,可以算出无数个满足条件的桃子数量。

除此文字版教程外,小伙伴们还可以点击这里观看我在本站的视频课程学习Java。