
1. 循环到底在练什么从一道题看透Java循环的底层逻辑很多人学Java循环的时候第一反应是“for、while、do-while语法我都懂”但一到写题就卡壳。这其实不是语法问题而是没有建立起“循环思维”。所谓循环思维说白了就是把一个重复动作抽象成“起点、终点、步长、重复体”四个要素。你拿到任何一道循环练习题先别急着写代码先问自己四个问题从哪个数开始到哪个数结束每次变化多少每次重复做什么把这四个问题回答清楚代码基本就出来了。我见过太多初学者拿到“打印九九乘法表”这种题直接开始敲for结果写到一半发现内层循环的边界条件搞不清楚。问题出在哪出在他没有先把“行”和“列”的关系用自然语言描述出来。九九乘法表一共九行第i行有i个表达式每个表达式是j × i 结果其中j从1到i。你看用自然语言把逻辑理清楚之后代码就是翻译工作而不是“猜”工作。这篇内容适合谁看如果你正在学Java基础循环这块总是似懂非懂做题靠背答案换个题型就不会了那这篇就是写给你的。如果你已经工作了一两年想回头把基础打扎实或者准备面试时被问到循环相关的算法题这篇也能帮你重新梳理一遍。我会从最基础的累加求和开始一路讲到嵌套循环、循环控制、常见陷阱每道题都附上完整代码和详细解析你可以直接拿去练。提示循环练习题的价值不在于“做出来”而在于“为什么这样写”。每道题我都会先讲思路再给代码最后分析容易踩的坑。你最好先自己写一遍再看我的解法。2. 基础循环练习题从累加到阶乘把单层循环吃透2.1 累加求和最基础但最容易写错的入门题题目计算1到100所有整数的和。这道题几乎是所有Java教材的第一道循环题。思路很简单定义一个变量sum初始化为0然后用for循环从1遍历到100每次把当前数加到sum里。代码长这样int sum 0; for (int i 1; i 100; i) { sum i; } System.out.println(1到100的和是 sum);输出结果是5050。这道题太简单了对吧但我要说的是很多人在这里会犯一个低级错误把sum定义在循环内部。如果你写成for (int i 1; i 100; i) { int sum 0; sum i; }那每次循环sum都会被重新赋值为0最后的结果就是100而不是5050。这个错误的本质是没理解“累加器变量必须定义在循环外部”。你可以把sum想象成一个存钱罐循环就是每次往里面扔硬币存钱罐必须放在外面不能每次扔之前都换一个新的。还有一个变体计算1到100之间所有偶数的和。这时候你有两种写法。第一种是在循环体内加判断int sum 0; for (int i 1; i 100; i) { if (i % 2 0) { sum i; } }第二种是直接调整步长int sum 0; for (int i 2; i 100; i 2) { sum i; }两种写法结果都是2550。第一种更通用第二种效率略高。实际开发中如果步长固定用第二种更简洁如果条件复杂用第一种更灵活。2.2 求阶乘注意数据类型的溢出陷阱题目计算10的阶乘10!。阶乘的定义是n! 1 × 2 × 3 × ... × n。思路和累加几乎一样只是把加法换成乘法初始值从0换成1。代码long result 1; for (int i 1; i 10; i) { result * i; } System.out.println(10的阶乘是 result);结果是3628800。这里我特意用了long而不是int为什么因为int的最大值是2147483647而13的阶乘就已经超过这个范围了。你可以试试把上面的代码改成int result 1然后计算13的阶乘看看会发生什么。你会得到一个负数这就是整数溢出。溢出的原理是当计算结果超过数据类型的最大值时会从最小值重新开始“绕回来”就像时钟从12点走到1点一样。注意做阶乘、累乘这类题目时一定要预估结果的范围。如果结果可能超过int的范围就用long如果long也不够就要用BigInteger。这是很多初学者容易忽略的点。2.3 统计位数用循环拆解数字的每一位题目给定一个整数统计它是几位数。比如12345是5位数。这道题的核心思路是每次把数字除以10位数加1直到数字变成0。代码int num 12345; int count 0; while (num ! 0) { num / 10; count; } System.out.println(这是一个 count 位数);注意这里用了while而不是for因为循环次数不确定取决于数字的大小。while适合“不知道循环多少次只知道循环条件”的场景。这道题还有一个变体把数字的每一位打印出来。思路类似只是每次取余数int num 12345; while (num ! 0) { int digit num % 10; System.out.println(digit); num / 10; }这样打印出来是5、4、3、2、1顺序是反的。如果要正序打印可以先把每一位存到数组里再反向遍历。这个变体经常出现在面试题里考察你对循环和数组的综合运用。3. 嵌套循环从打印图形到九九乘法表3.1 打印直角三角形理解外层控制行、内层控制列题目打印一个5行的直角三角形每行星号数量递增。* ** *** **** *****思路外层循环控制行数从1到5内层循环控制每行的星号数量从1到当前行号。代码for (int i 1; i 5; i) { for (int j 1; j i; j) { System.out.print(*); } System.out.println(); }这里的关键是System.out.print和System.out.println的区别。print不换行println换行。内层循环用print把星号打在同一行内层循环结束后用println换行。很多初学者会把内层的print写成println结果星号竖着排成一列。如果你理解了这道题可以试试打印倒直角三角形、等腰三角形、菱形。倒直角三角形只需要把内层循环的条件改成j 6 - i。等腰三角形稍微复杂一点需要先打印空格再打印星号空格数量是5 - i星号数量是2 * i - 1。菱形则是两个等腰三角形拼在一起。这些题目看起来花哨但本质都是“外层控制行内层控制列”的嵌套逻辑。3.2 九九乘法表嵌套循环的经典综合题题目打印九九乘法表。1×11 1×22 2×24 1×33 2×36 3×39 ...思路外层循环i从1到9代表行号内层循环j从1到i代表每行的表达式。每个表达式是j × i j*i。代码for (int i 1; i 9; i) { for (int j 1; j i; j) { System.out.print(j × i (j * i) \t); } System.out.println(); }这里用\t制表符对齐输出让结果更整齐。如果你用空格列会对不齐。这道题还有一个变体打印完整的乘法表不限制j i也就是一个9×9的矩阵。只需要把内层循环改成j 9即可。提示嵌套循环的总执行次数是外层次数乘以内层次数。九九乘法表的外层执行9次内层平均执行5次总共执行45次。理解这一点对后面分析算法效率很重要。3.3 打印素数嵌套循环加标志位的组合应用题目打印1到100之间的所有素数。素数的定义是只能被1和自身整除的数。判断一个数n是否为素数需要检查从2到n-1之间有没有能整除它的数。代码for (int i 2; i 100; i) { boolean isPrime true; for (int j 2; j i; j) { if (i % j 0) { isPrime false; break; } } if (isPrime) { System.out.print(i ); } }这里引入了两个新东西boolean标志位和break。标志位isPrime初始为true一旦发现能整除的数就设为false并跳出内层循环。外层循环结束后检查标志位如果还是true就打印。这个模式在循环练习题里非常常见你一定要掌握。优化点内层循环其实不需要检查到n-1只需要检查到sqrt(n)即可。因为如果n有一个大于sqrt(n)的因子那必然有一个小于sqrt(n)的对应因子。比如100的因子对是(2,50)、(4,25)、(5,20)、(10,10)你只需要检查到10就能判断100不是素数。这个优化能把内层循环的次数从n降到sqrt(n)效率提升非常明显。4. 循环控制与常见陷阱break、continue和死循环4.1 break和continue的区别一个跳出一个跳过break的作用是立即终止当前循环继续执行循环后面的代码。continue的作用是跳过本次循环剩余的代码直接进入下一次循环。用一道题来区分题目打印1到10之间的所有数但遇到5就停止。for (int i 1; i 10; i) { if (i 5) { break; } System.out.print(i ); } // 输出1 2 3 4题目打印1到10之间的所有数但跳过5。for (int i 1; i 10; i) { if (i 5) { continue; } System.out.print(i ); } // 输出1 2 3 4 6 7 8 9 10你看break是“不玩了”continue是“这次不算下次继续”。在嵌套循环中break和continue默认只作用于当前所在的那一层循环。如果你想跳出外层循环需要给外层循环加标签outer: for (int i 1; i 3; i) { for (int j 1; j 3; j) { if (i 2 j 2) { break outer; } System.out.println(i , j); } }标签语法在实际开发中很少用但面试题里偶尔会出现了解一下就行。4.2 死循环while(true)的正确打开方式死循环是指循环条件永远为true循环体会一直执行下去。最常见的死循环写法是while(true)。很多人觉得死循环是bug其实不然死循环在特定场景下是故意设计的。比如游戏的主循环、服务器的监听循环都需要一直运行。关键是要在循环体内设置退出条件while (true) { // 做一些事情 if (退出条件) { break; } }初学者最容易犯的死循环错误是忘记更新循环变量。比如int i 1; while (i 10) { System.out.println(i); // 忘记写 i }这段代码会无限打印1。因为i永远是1条件永远成立。避免这种错误的方法是在写while循环时先把循环变量的更新语句写好再写循环体。4.3 do-while至少执行一次的循环do-while和while的区别是do-while先执行一次循环体再判断条件。也就是说do-while的循环体至少执行一次。典型场景是菜单选择Scanner scanner new Scanner(System.in); int choice; do { System.out.println(1. 开始游戏); System.out.println(2. 退出); System.out.print(请选择); choice scanner.nextInt(); } while (choice ! 2);不管用户输入什么菜单至少显示一次。如果用while就需要先手动显示一次菜单代码会重复。do-while的适用场景就是“先做一次再决定要不要继续”。5. 循环练习题速查表与避坑指南5.1 常见题型与解题思路对照表题型推荐循环核心思路易错点累加求和for累加器定义在循环外累加器定义在循环内求阶乘for初始值为1注意溢出用int导致溢出统计位数while每次除以10忘记更新循环变量打印图形嵌套for外层行内层列print和println混用九九乘法表嵌套for内层边界是i内层边界写成9判断素数嵌套for标志位break忘记重置标志位菜单选择do-while至少执行一次用while导致代码重复5.2 循环练习中的五个高频坑第一个坑循环变量在循环外定义。比如int i; for (i 0; i 10; i)这种写法虽然合法但i的作用域会扩大到循环外容易造成变量污染。推荐在for的初始化部分定义变量把作用域限制在循环内。第二个坑浮点数作为循环变量。比如for (double d 0.1; d 1.0; d 0.1)由于浮点数精度问题循环次数可能不是10次。如果必须用浮点数建议用整数循环变量再除以倍数。第三个坑在循环体内修改循环变量。比如for (int i 0; i 10; i) { i; }这样循环只执行5次。除非你明确知道自己在做什么否则不要在循环体内修改循环变量。第四个坑嵌套循环中混淆内外层变量。比如打印九九乘法表时把j i写成i j结果就完全不对了。写嵌套循环时给变量起有意义的名字比如row和col比i和j更不容易搞混。第五个坑忘记break导致多余计算。比如判断素数时发现一个因子就可以跳出内层循环了如果不写break内层循环会继续执行浪费性能。虽然对100以内的数影响不大但养成好习惯很重要。提示做循环练习题时建议先在纸上画出循环的执行过程特别是嵌套循环画出每一轮内外层变量的值。这个过程看起来笨但能帮你真正理解循环的执行流程。5.3 从练习题到实际应用的过渡建议循环练习题做完了怎么过渡到实际开发我的建议是找一些小项目来练手。比如写一个控制台版的猜数字游戏用while循环让玩家一直猜直到猜对写一个简单的学生成绩统计程序用for循环遍历数组求平均分写一个打印日历的程序用嵌套循环处理月份和日期。这些项目不大但能把循环和条件判断、数组、方法结合起来比单纯做练习题更接近真实开发。另外LeetCode上有很多循环相关的简单题比如“两数之和”的暴力解法、“罗马数字转整数”的遍历解法都可以用循环来实现。做这些题的时候不要只追求通过要思考有没有更优的循环写法。比如能不能用增强for循环代替普通for循环能不能用while代替for让代码更清晰这些思考会让你对循环的理解更上一层楼。我个人在实际教学中的体会是循环这块最大的分水岭不是语法而是“把问题拆解成重复步骤”的能力。有些人天生擅长这种拆解有些人需要大量练习才能建立感觉。如果你属于后者不要着急把上面这些题反复写三遍每遍都尝试不同的写法慢慢你就会发现看到一道新题时脑子里会自动浮现出循环的框架。这个感觉一旦建立起来后面学数组、集合、算法都会轻松很多。