1. 循环结构基础:从入门到实战
作为一名C语言开发者,我经常在洛谷上刷题来保持手感。循环结构是编程中最基础也最重要的概念之一,掌握好循环结构能帮助我们解决各种实际问题。今天我就来分享洛谷循环结构题单的21道题目解析和代码实现,希望能帮助正在学习C语言的你。
循环结构主要分为for循环、while循环和do-while循环三种形式。在解决实际问题时,我们需要根据具体场景选择合适的循环结构。比如当循环次数已知时,for循环通常是最佳选择;而当循环次数不确定时,while循环可能更合适。
2. 基础题目解析与实现
2.1 P5718 找最小值
这道题目要求我们输入多个整数,找出其中的最小值并输出。这是最基础的循环结构应用之一。
核心思路:
- 首先读取输入的整数个数n
- 然后循环读取n个整数
- 初始化最小值为第一个数
- 遍历数组,比较每个元素与当前最小值
- 如果发现更小的数,则更新最小值
c复制#include<stdio.h>
int main() {
int n = 0, i = 0;
int arr[100];
scanf("%d\n", &n);
for (i = 0;i < n;i++) {
scanf("%d", &arr[i]);
}
int min = arr[0],j=0;
for (j = 1;j < n;j++) {
if (min > arr[j]) {
min = arr[j];
}
}
printf("%d", min);
return 0;
}
注意事项:
- 最小值初始化为第一个元素,而不是0或其他任意值
- 比较时使用>而不是>=,避免不必要的赋值操作
- 数组大小要足够大,防止溢出
2.2 P5719 分类平均
这道题目要求将输入的整数分为奇数和偶数两组,分别计算两组的平均值。
解题要点:
- 使用%运算符判断奇偶性
- 维护两个累加变量和两个计数器
- 注意整数除法会导致精度丢失,需要转换为浮点数
c复制#include<stdio.h>
int main(){
int n=0,k=0;
scanf("%d %d",&n,&k);
double A=0,B=0,a=0,b=0;
for(int i=1;i<=n;i++){
if(i%k==0){
A+=i;
a++;
}else{
B+=i;
b++;
}
}
printf("%.1f %.1f",A/a,B/b);
return 0;
}
经验分享:
- 浮点数输出使用%.1f控制小数点后位数
- 可以将条件i%k==0改为i%k,因为非零值在C中视为真
- 变量命名要有意义,如A表示能被k整除的数和,a表示其个数
2.3 P5720 一尺之棰
这道题目模拟"一尺之棰,日取其半"的过程,计算多少次后长度小于指定值。
关键点:
- 使用while循环,条件为当前长度大于等于阈值
- 每次循环长度减半
- 计数器记录循环次数
c复制#include<stdio.h>
int main(){
int a=0,A=0;
scanf("%d",&a);
while(a){
a/=2;
A++;
}
printf("%d",A);
return 0;
}
优化建议:
- 可以使用位运算a>>=1代替a/=2,效率更高
- 注意处理a为0的特殊情况
- 循环条件可以改为while(a>=1)更明确
3. 进阶题目解析与实现
3.1 P5721 数字直角三角形
这道题目要求按规则打印由数字组成的直角三角形图案。
解题思路:
- 使用双层for循环嵌套
- 外层控制行数,内层控制列数
- 使用计数器变量记录当前数字
- 使用%02d格式化输出,保证两位数显示
c复制#include<stdio.h>
int main(){
int n=0;
scanf("%d",&n);
int A=1;
for(int i=0;i<n;i++){
for(int j=0;j<n-i;j++){
printf("%02d",A);
A++;
}printf("\n");
}
return 0;
}
注意事项:
- 注意换行符的位置
- %02d确保数字不足两位时前面补零
- 可以优化为单层循环,利用数学关系计算每行起始数字
3.2 P1009 阶乘之和
计算S=1!+2!+3!+⋯+n!的结果,需要考虑大数问题。
优化方案:
- 使用迭代法计算阶乘,避免重复计算
- 使用long long类型防止溢出
- 内层循环计算阶乘,外层循环累加
c复制#include<stdio.h>
int main() {
int n;
long long sum = 0, fact = 1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
fact *= i;
sum += fact;
}
printf("%lld",sum);
return 0;
}
经验分享:
- 对于n>20的情况,需要使用大数处理库
- 可以预先计算并存储阶乘结果,空间换时间
- 输出使用%lld格式说明符
3.3 P1980 计数问题
统计1到n的所有整数中,数字k出现的总次数。
算法思路:
- 外层循环遍历1到n
- 内层循环分解每个数字的各位
- 使用计数器统计k出现的次数
c复制#include<stdio.h>
int main(){
int n=0,x=0,A=0;
scanf("%d %d",&n,&x);
for(int i=1;i<=n;i++){
int b=i;
while(b){
if(b%10==x){
A++;
}
b/=10;
}
}
printf("%d",A);
return 0;
}
优化建议:
- 对于大n,可以使用数学方法直接计算,效率更高
- 可以处理x为0的特殊情况
- 变量命名可以更有意义,如count代替A
4. 数学相关题目解析
4.1 P1035 级数求和
计算S=1+1/2+1/3+⋯+1/n,求最小的n使得S >= k。
注意事项:
- 使用浮点数累加,避免整数除法
- while循环条件为sum < k
- 注意循环变量的初始化和更新
c复制#include<stdio.h>
int main(){
int k=0,a=1;
double s=1;
scanf("%d",&k);
while(s<=k){
a++;
s+=1.0/a;
}
printf("%d",a);
return 0;
}
经验分享:
- 1.0/a确保浮点数除法
- 可以使用调和数性质估算初始a值,减少循环次数
- 对于大k,需要考虑浮点数精度问题
4.2 P1720 斐波那契数列
根据斐波那契数列的公式,计算第n项的值。
实现方法:
- 使用数学公式直接计算
- 需要包含math.h头文件
- 注意浮点数精度和输出格式
c复制#include <math.h>
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
double a = 1, b = 1;
int i;
for (i = 1; i <= n; i++)
{
a *= 0.5 + sqrt(5) / 2;
b *= 0.5 - sqrt(5) / 2;
}
printf("%.2f", (a - b) / sqrt(5));
}
注意事项:
- 公式中的sqrt(5)可以预先计算存储
- 对于大n,需要考虑浮点数精度限制
- 输出使用%.2f保留两位小数
5. 综合应用题目
5.1 P5723 质数口袋
找出所有连续的质数,使其和不超过n,输出这些质数的个数和具体值。
算法实现:
- 编写质数判断函数
- 循环遍历自然数,判断是否为质数
- 累加质数,直到和超过n
- 输出结果
c复制#include<stdio.h>
#include<stdbool.h>
bool isPrime(int num) {
if(num <= 1) return false;
for(int i=2; i*i<=num; i++) {
if(num % i == 0) return false;
}
return true;
}
int main() {
int L, sum = 0, count = 0;
scanf("%d", &L);
for(int i=2; ; i++) {
if(isPrime(i)) {
if(sum + i > L) break;
sum += i;
count++;
printf("%d\n", i);
}
}
printf("%d", count);
return 0;
}
优化建议:
- 使用筛法预先计算质数,提高效率
- 可以限制i的上限为L-sum,减少不必要的判断
- 添加输入验证,确保L为正整数
5.2 P1217 回文质数
找出区间[a, b]内的所有回文质数(既是质数又是回文数)。
解题策略:
- 先判断回文数,再判断质数(回文数判断更快)
- 优化质数判断算法
- 处理特殊数字情况(如偶数位回文数)
c复制#include<stdio.h>
#include<stdbool.h>
bool isPrime(int n) {
if(n <= 1) return false;
if(n == 2) return true;
if(n % 2 == 0) return false;
for(int i=3; i*i<=n; i+=2) {
if(n % i == 0) return false;
}
return true;
}
bool isPalindrome(int n) {
if(n < 10) return true;
int original = n, reversed = 0;
while(n > 0) {
reversed = reversed * 10 + n % 10;
n /= 10;
}
return original == reversed;
}
int main() {
int a, b;
scanf("%d %d", &a, &b);
for(int i=a; i<=b; i++) {
if(isPalindrome(i) && isPrime(i)) {
printf("%d\n", i);
}
}
return 0;
}
性能优化:
- 可以预先生成回文数,再判断是否为质数
- 对于大区间,可以使用筛法优化
- 跳过偶数回文数(除了2)
6. 实际应用题目
6.1 P1089 储蓄计划
模拟津津12个月的收支情况,计算年底的存款。
实现要点:
- 循环处理12个月的数据
- 每月初增加300元
- 扣除当月支出,检查是否足够
- 将整百部分存入妈妈那里
c复制#include<stdio.h>
int main() {
int expenses[12], saved = 0, cash = 0;
for(int month=0; month<12; month++) {
scanf("%d", &expenses[month]);
cash += 300;
if(cash < expenses[month]) {
printf("-%d", month+1);
return 0;
}
cash -= expenses[month];
saved += cash / 100 * 100;
cash %= 100;
}
printf("%d", (int)(saved * 1.2) + cash);
return 0;
}
注意事项:
- 注意月份输出是1-based
- 利息计算要加上现金余额
- 可以使用更清晰的变量名,如monthly_saved
6.2 P1420 最长连号
找出整数序列中连续递增1的子序列的最长长度。
算法思路:
- 遍历数组,比较相邻元素
- 维护当前连号长度和最大连号长度
- 当不满足递增1时,重置当前长度
c复制#include<stdio.h>
int main() {
int n, a[10000];
scanf("%d", &n);
for(int i=0; i<n; i++) {
scanf("%d", &a[i]);
}
int max_len = 1, current_len = 1;
for(int i=1; i<n; i++) {
if(a[i] == a[i-1]+1) {
current_len++;
if(current_len > max_len) {
max_len = current_len;
}
} else {
current_len = 1;
}
}
printf("%d", max_len);
return 0;
}
优化建议:
- 可以边读入边处理,节省空间
- 添加输入验证,确保n不超过数组大小
- 可以提前终止循环,如果剩余元素不足以超过当前max_len
7. 总结与经验分享
通过这21道题目的练习,我们可以全面掌握C语言循环结构的各种应用场景。在实际编程中,选择合适的循环结构、优化循环条件、处理好边界情况是关键。
一些通用建议:
- 对于固定次数的循环,优先使用for循环
- 循环条件要明确,避免死循环
- 注意循环变量的初始化和更新
- 处理大数时要考虑数据类型的选择
- 嵌套循环时,注意内外层循环的分工
在解决实际问题时,我通常会先分析问题需求,设计算法流程,然后编写代码,最后进行测试和优化。这种系统化的方法能帮助我高效地解决问题。