【C语言16天强化训练】从基础入门到进阶:Day 4


🔥个人主页艾莉丝努力练剑

❄专栏传送门:《C语言》《数据结构与算法》C语言刷题12天IO强训LeetCode代码强化刷题洛谷刷题C/C++基础知识知识强化补充C/C++干货分享&学习过程记录

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平

前言:距离我们学完C语言已经过去一段时间了,在学习了初阶的数据结构之后,博主还要更新的内容就是【C语言16天强化训练】,之前博主更新过一个【C语言刷题12天IO强训】的专栏,那个只是从入门到进阶的IO模式真题的训练。【C语言16天强化训练】既有IO型,也有接口型。和前面一样,今天依然是训练五道选择题和两道编程算法题,希望大家能够有所收获!



目录

正文

一、五道选择题

[1.1 题目1](#1.1 题目1)

[1.2 题目2](#1.2 题目2)

[1.3 题目3](#1.3 题目3)

[1.4 题目4](#1.4 题目4)

[1.5 题目5](#1.5 题目5)

二、两道算法题

[2.1 错误的集合](#2.1 错误的集合)

题目理解:

[2.2 密码检查](#2.2 密码检查)

题目理解:

结尾


正文

一、五道选择题

1.1 题目1

**题干:**设变量已正确定义,以下不能统计出一行中输入字符个数(不包含回车符)的程序段是()

A. n=0;while(ch=getchar()!='\n')n++; B. n=0;while(getchar()!='\n')n++;

C. for(n=0;getchar()!='\n';n++); D. n=0;for(ch=getchar();ch!='\n';n++);

解析:

其他选项都没有问题,我们看D选项这里,在for循环的初始化部分,ch=getchar()只执行一次(读取第一个字符),然后循环条件检查 ch!='\n' 。如果第一个字符不是换行符,则进入循环,但循环体内没有语句(只有空语句;),且迭代部分n++执行,但没有再次读取字符。因此,ch始终是第一个字符,如果第一个字符不是换行符,循环将无限执行,因为ch永远不改变,导致无限循环和错误计数。所以,这个选项不能正确统计字符个数。

因此,不能统计一行中输入字符个数(不包含回车符)的选项是D。

1.2 题目2

**题干:**运行以下程序后,如果从键盘上输入 65 14 <回车> ,则输出结果为( )

cpp 复制代码
int main()
{
    int m, n;
    printf("Enter m,n;");
    scanf("%d%d", &m,&n);
    while (m!=n) //1
    {
        while(m>n) m=m-n; //2
        while(n>m) n=n-m; //3
    }
    printf("m=%d\n",m);
    return 0;
}

A. 3 B. 2 C. 1 D. 0

解析:

我们输入m=65, n=14,<回车>之后------
第一次外层循环(m != n,65 != 14)
进入内层循环1:m>n(65>14),执行m=m-n多次:
第一次:m=65-14=51
第二次:m=51-14=37
第三次:m=37-14=23
第四次:m=23-14=9(现在m=9, n=14,m<n,退出内层循环1)
进入内层循环2:n>m(14>9),执行n=n-m:
第一次:n=14-9=5(现在n=5, m=9,n<m,退出内层循环2)
此时m=9, n=5(不相等),继续外层循环。
第二次外层循环(m != n,9 != 5)
内层循环1:m>n(9>5),执行m=m-n:
第一次:m=9-5=4(现在m=4, n=5,m<n,退出)
内层循环2:n>m(5>4),执行n=n-m:
第一次:n=5-4=1(现在n=1, m=4,n<m,退出)
此时m=4, n=1(不相等),继续外层循环。
第三次外层循环(m != n,4 != 1)
内层循环1:m>n(4>1),执行m=m-n多次:
第一次:m=4-1=3
第二次:m=3-1=2
第三次:m=2-1=1(现在m=1, n=1,m==n,退出内层循环1)
此时m=1, n=1,相等,退出外层循环。

最终输出m=1。

因此,输出结果为1,即选项C

1.3 题目3

**题干:**若运行以下程序时,从键盘输入 ADescriptor<回车> ,则下面程序的运行结果是( )

cpp 复制代码
#include <stdio.h>
int main()
{
    char c;
    int v0=0,v1=0,v2=0;
    do
    {
        switch(c=getchar())
        {
            case'a':case'A':
            case'e':case'E':
            case'i':case'I':
            case'o':case'O':
            case'u':case'U':v1 += 1;
            default:v0+= 1;v2+=1;
        }
    }while(c!='\n');
    printf("v0=%d,v1=%d,v2=%d\n",v0,v1,v2);
    return 0;
}

A. v0=7,v1=4,v2=7 B. v0=8,v1=4,V2=8 C. v0=11,v1=4,v2=11 D. v0=12,v1=4,v2=12

解析:

总共处理了12个字符(包括换行符)------

元音有:' A ',' e ',' i ',' o '(共4个),所以 v1 = 4。

每个字符都会执行default,所以 v0 和 v2 都是12。

所以正确答案就是D

1.4 题目4

**题干:**如下函数是求两个int数字最大公约数的,指出其中存在的问题【多选】( )

cpp 复制代码
int gcd(char x,char y)
{
    int min = x < y ? x : y;
    for (min = 0; min > 0; min--)
    if (x % min = 0 && y % min = 0)
        return min;
}

A. 参数类型不对 B. 循环变量min初值不对 C. 判断等于的符号不对 D. 返回类型不对

解析:

A选项------参数类型不对: 函数目的是求两个整数的最大公约数,但参数类型为char(字符类型),而不是int(整数类型)。虽然char可以视为小整数,但通常最大公约数函数应使用int类型,以处理更大的数字和通用整数。因此,参数类型不正确的确是其存在的问题。

B选项------循环变量min初值不对: 在循环中,min被初始化为 0(for(min = 0; ...)),但循环条件为min > 0,因此循环根本不会执行(因为初始值0不满足min>0)。正确的做法是使用之前计算的 min(即 x 和 y 中的较小值)作为初始值,然后递减。但这里覆盖了之前的值,导致逻辑错误。

C选项------判断等于的符号不对: 在条件判断中,if (x % min = 0 && y % min = 0) 使用了赋值运算符 = 而不是相等比较运算符 == 。这会导致编译错误(因为赋值操作不允许在条件中这样使用)或逻辑错误(总是将0赋值给表达式)。正确的应该是 ==。

D选项------返回类型不对: 函数返回类型是 int,这本身是合适的,因为最大公约数是整数。但问题在于函数可能没有返回任何值(如果循环没有执行,则没有返回值),这会导致未定义行为。不过,返回类型 int 本身并不是错误,但函数设计有缺陷。

因此,所有选项A、B、C都是正确的问题描述。D选项(返回类型不对)可能不是主要问题,但函数确实存在返回类型与逻辑不匹配的风险(可能无返回值)。严格来说,D也是问题之一(因为函数可能无法返回正确结果)。

但根据题干**"指出其中存在的问题【多选】"**,A、B、C是明显错误,D也有问题。

**综上所述,**正确答案就是ABCD。

1.5 题目5

**题干:**执行下面的程序段,语句3的执行次数为( )

cpp 复制代码
for(i = 0; i <= n-1; i++) // (1)
    for(j = n; j > i; j--) // (2)
        state; // (3)

A. n(n+2)/2 B. (n-1)(n+2)/2 C. n(n+1)/2 D. (n-1)(n+2)

解析:

我们先来分析一下这个嵌套循环------

**外层循环:**i 从 0 到 n-1(共 n 次迭代)。

**内层循环:**j 从 n 向下到 i+1(包括),因此内层循环次数为 n - i(因为 j 从 n 到 i+1,步长为1,次数为 n - i)。

语句(3)的执行次数是内层循环的总次数,即对每个 i,内层循环执行 n - i 次。

总次数的计算过程如下所示------

正确选项是C. n(n+1)/2

选择题答案如下:

1.1 D

1.2 C

1.3 D

1.4 ABCD

1.5 C

校对一下,大家都做对了吗?

二、两道算法题

2.1 错误的集合

前面我们都是牛客网的题目,今天终于有一道力扣题目了!

力扣题目链接:645. 错误的集合​​​​​​

力扣题解链接:解决【错误的集合】问题

题目描述:

题目理解:

我们需要去找出重复的数字和丢失的数字,可以用计数数组。

这道题是接口型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

代码演示:

cpp 复制代码
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* findErrorNums(int* nums, int numsSize, int* returnSize) {
    // 创建计数数组,初始化为0
    int* count = (int*)calloc(numsSize + 1, sizeof(int));
    int* result = (int*)malloc(2 * sizeof(int));
    *returnSize = 2;
    
    // 统计每个数字出现的次数
    for (int i = 0; i < numsSize; i++) {
        count[nums[i]]++;
    }
    
    // 找出重复的数字和丢失的数字
    for (int i = 1; i <= numsSize; i++) {
        if (count[i] == 2) {
            result[0] = i; // 重复的数字
        }
        if (count[i] == 0) {
            result[1] = i; // 丢失的数字
        }
    }
    
    free(count);
    return result;
}

时间复杂度O(n)

空间复杂度O(n)

我们可以改进一下,优化复杂度------

cpp 复制代码
int* findErrorNums(int* nums, int numsSize, int* returnSize) {
    int* result = (int*)malloc(2 * sizeof(int));
    *returnSize = 2;
    
    long long n = numsSize;
    long long sum = n * (n + 1) / 2;
    long long sum_sq = n * (n + 1) * (2 * n + 1) / 6;
    long long actual_sum = 0;
    long long actual_sum_sq = 0;
    
    for (int i = 0; i < numsSize; i++) {
        actual_sum += nums[i];
        actual_sum_sq += (long long)nums[i] * nums[i];
    }
    
    // sum - actual_sum = missing - duplicate
    // sum_sq - actual_sum_sq = missing^2 - duplicate^2
    long long diff = sum - actual_sum;        // missing - duplicate
    long long diff_sq = sum_sq - actual_sum_sq; // missing^2 - duplicate^2
    
    // missing + duplicate = diff_sq / diff
    long long sum_md = diff_sq / diff;
    
    result[1] = (diff + sum_md) / 2; // missing number
    result[0] = sum_md - result[1];  // duplicate number
    
    return result;
}

时间复杂度O(n)

空间复杂度O(1)

我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

用计数数组实现的代码演示:

cpp 复制代码
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int n = nums.size();
        vector<int> count(n + 1, 0);
        vector<int> result(2);
        
        // 统计每个数字出现的次数
        for (int num : nums) {
            count[num]++;
        }
        
        // 找出重复的数字和丢失的数字
        for (int i = 1; i <= n; i++) {
            if (count[i] == 2) {
                result[0] = i; // 重复的数字
            }
            if (count[i] == 0) {
                result[1] = i; // 丢失的数字
            }
        }
        
        return result;
    }
};

时间复杂度:O(n),空间复杂度:O(n)

我们还可以对空间复杂度进行一下优化------

cpp 复制代码
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int n = nums.size();
        long long sum = (long long)n * (n + 1) / 2;
        long long sum_sq = (long long)n * (n + 1) * (2 * n + 1) / 6;
        long long actual_sum = 0;
        long long actual_sum_sq = 0;
        
        for (int num : nums) {
            actual_sum += num;
            actual_sum_sq += (long long)num * num;
        }
        
        long long diff = sum - actual_sum;        // missing - duplicate
        long long diff_sq = sum_sq - actual_sum_sq; // missing² - duplicate²
        
        long long sum_md = diff_sq / diff;        // missing + duplicate
        
        int missing = (diff + sum_md) / 2;
        int duplicate = sum_md - missing;
        
        return {duplicate, missing};
    }
};

时间复杂度:O(n),空间复杂度:O(1)

我们目前要写出来C++的写法是非常考验前面C++的学习情况,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待**!**

2.2 密码检查

题目链接:REAL585 密码检查

题目描述:

题目理解:

我们将编写一个C程序来逐个检查每个密码,并输出"YES"或"NO"。

由题意可知,密码必须满足以下条件:

1、密码只能由大写字母、小写字母和数字构成;

2、密码不能以数字开头;

3、密码至少包含大写字母、小写字母和数字中的两种类型;

4、密码长度至少为8。

这道题是IO型的,下面是C语言的模版(如果是IO型就可以不用管它们了)------

代码演示:

cpp 复制代码
#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main() 
{
    int n;
    scanf("%d", &n);
    while (getchar() != '\n'); // 清空输入缓冲区

    for (int i = 0; i < n; i++) {
        char password[101];
        if (fgets(password, sizeof(password), stdin) == NULL) {
            printf("NO\n");
            continue;
        }

        // 移除换行符和可能的回车符
        int len = strlen(password);
        while (len > 0 && (password[len - 1] == '\n' || password[len - 1] == '\r')) {
            password[len - 1] = '\0';
            len--;
        }

        // 检查是否为空字符串
        if (len == 0) {
            printf("NO\n");
            continue;
        }

        int has_upper = 0, has_lower = 0, has_digit = 0;
        int valid = 1;

        // 条件4: 长度至少为8
        if (len < 8) {
            valid = 0;
        }

        // 条件2: 不能以数字开头
        if (isdigit(password[0])) {
            valid = 0;
        }

        // 检查每个字符
        for (int j = 0; j < len; j++) {
            unsigned char c = password[j]; // 使用unsigned char避免符号扩展问题
            if (isupper(c)) {
                has_upper = 1;
            } else if (islower(c)) {
                has_lower = 1;
            } else if (isdigit(c)) {
                has_digit = 1;
            } else {
                valid = 0;
                break;
            }
        }

        // 条件3: 至少包含两种类型
        if (valid) {
            int type_count = has_upper + has_lower + has_digit;
            if (type_count < 2) {
                valid = 0;
            }
        }

        printf("%s\n", valid ? "YES" : "NO");
    }

    return 0;
}

时间复杂度O(n*m)

空间复杂度O(m)

这个程序存在一定的问题,测试用例只能通过两个。博主会把最终代码实现呈现在下面------

时间复杂度O(n*m)

空间复杂度O(m)

我们学习了C++之后也可以尝试用C++来实现一下,看看自己前段时间C++学得怎么样------

代码演示:

cpp 复制代码
#include <iostream>
#include <string>
#include <cctype>
using namespace std;

bool isValidPassword(const string& password) {
    // 检查长度
    if (password.length() < 8) 
    {
        return false;
    }

    // 检查不能以数字开头
    if (isdigit(password[0])) 
    {
        return false;
    }

    bool hasUpper = false;
    bool hasLower = false;
    bool hasDigit = false;

    for (char c : password) 
    {
        // 检查字符类型
        if (isupper(c)) 
        {
            hasUpper = true;
        } 
        else if (islower(c)) 
        {
            hasLower = true;
        } else if (isdigit(c)) 
        {
            hasDigit = true;
        } 
        else 
        {
            // 包含非法字符
            return false;
        }
    }

    // 检查至少包含两种类型
    int typeCount = (hasUpper ? 1 : 0) + (hasLower ? 1 : 0) + (hasDigit ? 1 : 0);
    return typeCount >= 2;
}

int main() 
{
    int n;
    cin >> n;
    cin.ignore(); // 消耗换行符

    for (int i = 0; i < n; i++) 
    {
        string password;
        getline(cin, password);

        if (isValidPassword(password)) 
        {
            cout << "YES" << endl;
        } 
        else 
        {
            cout << "NO" << endl;
        }
    }
    return 0;
}

时间复杂度:O(n*m),空间复杂度:O(m)

我们目前要写出来C++的写法是非常考验前面C++的学习情况,大家可以尝试去写一写,优先掌握C语言的写法,博主还没有介绍C++的算法题,之后会涉及的,敬请期待**!**


结尾

本文内容到这里就全部结束了,希望大家练习一下这几道题目,这些基础题最好完全掌握!

往期回顾:

【C语言16天强化训练】从基础入门到进阶:Day 3

【C语言16天强化训练】从基础入门到进阶:Day 2

【C语言16天强化训练】从基础入门到进阶:Day 1

**结语:**感谢大家的阅读,记得给博主"一键四连",感谢友友们的支持和鼓励!