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


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

❄专栏传送门:《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.1.1 题目理解](#2.1.1 题目理解)

[2.1.2 思路](#2.1.2 思路)

[2.2 寻找数组的中心下标](#2.2 寻找数组的中心下标)

[2.2.1 尝试暴力写代码](#2.2.1 尝试暴力写代码)

[2.2.2 加深题目理解](#2.2.2 加深题目理解)

[2.2.3 优化思路](#2.2.3 优化思路)

[2.2.4 优化方案实现](#2.2.4 优化方案实现)

结尾


正文

一、五道选择题

1.1 题目1

**题干:**以下对C语言函数的有关描述中,正确的有( )【多选】

A. 在C语言中,一个函数一般由两个部分组成,它们是函数首部和函数体

B. 函数的实参和形参可以是相同的名字

C. 在main()中定义的变量都可以在其它被调函数中直接使用

D. 在C程序中,函数调用不能出现在表达式语句中

解析:正确选项是AB。

A. 正确。函数首部包括函数返回类型、函数名和参数列表,函数体包括一对花括号内的语句序列。

B. 正确。实参和形参的作用域不同,实参是调用函数时的实际值,形参是函数定义中的变量,它们可以同名,但代表不同的实体。

C. 错误。在main函数中定义的变量是局部变量,只能在main函数内部使用,其他函数不能直接访问。除非这些变量是全局变量或通过参数传递。

D. 错误。函数调用可以出现在表达式语句中,例如:int result = add(a,b); 或 printf("Hello"); 都是表达式语句中包含函数调用。

1.2 题目2

**题干:**在C语言中,以下正确的说法是( )

A. 实参和与其对应的形参各占用独立的存储单元

B. 实参和与其对应的形参共占用一个存储单元

C. 只有当实参和与其对应的形参同名时才共占用存储单元

D. 形参是虚拟的,不占用存储单元

解析:正确选项是A选项。

A. 正确。实参和形参各占用独立的存储单元。

B. 错误。它们不共享存储单元,而是各自独立。

C. 错误。是否同名不影响存储单元的占用,即使同名也是不同的存储单元(因为作用域不同);

D. 错误。形参是函数内的局部变量,会占用存储单元(通常在栈上)。
在C语言中,函数参数的传递是值传递(pass by value)。这意味着:

(1)当函数被调用时,实参的值会被复制到形参中;

(2)因此,实参和形参占用不同的存储单元 (即独立的内存空间);

(3)对形参的修改不会影响实参(除非通过指针间接修改)。

1.3 题目3

**题干:**在上下文及头文件均正常的情况下,下列代码的输出是( )(注:print 已经声明过了)

cpp 复制代码
int main()
{
    char str[] = "Geneius";
    print(str);
    return 0;
}
print(char *s)
{
    if(*s)
    {
        print(++s);
        printf("%c", *s);
    }
}

A. suiene B. neius C. run-time error D. suieneG

解析:正确选项是A选项。

代码理解:

1、main函数定义了一个字符串 str = "Geneius",然后调用 print(str); 。

2、printf 函数是一个递归函数------

(1)如果当前字符 *s 不是空字符(即字符串未结束),则递归调用 print(++s)(注意:这里先递增 s,然后传入递归)。

(2)递归返回后,打印当前字符 *s(注意:此时 s 已经递增过一次,所以指向的是下一个字符)。

执行过程:
原始字符串: "Geneius"(注意:字符串以空字符 '\0' 结尾)。

递归调用过程(每次调用 print(++s),所以指针不断后移):

第一次调用:s指向"Geneius" -> 递归调用 print(++s)(现在s指向"eneius");

第二次调用:s指向"eneius" -> 递归调用 print(++s)(现在s指向"neius");

第三次调用:s指向"neius" -> 递归调用 print(++s)(现在s指向"eius");

第四次调用:s指向"eius" -> 递归调用 print(++s)(现在s指向"ius");

第五次调用:s指向"ius" -> 递归调用 print(++s)(现在s指向"us");

第六次调用:s指向"us" -> 递归调用 print(++s)(现在s指向"s");

第七次调用:s指向"s" -> 递归调用 print(++s)(现在s指向空字符'\0');

第八次调用:s指向'\0',条件if(*s)为假,递归终止并返回。

调用完然后开始回溯(从最深层的递归返回):

第七次调用:s原本指向"s",但调用时是print(++s),所以递归返回后,s现在指向空字符(即'\0'之后?实际上已经越界了)。但注意:在递归调用中,s的值已经被改变(因为++s是前置递增,修改了s本身)。所以每次递归返回后,s指向的是原来字符串的下一个字符(甚至最后是空字符之后)。

具体回溯时打印的字符:

从最深层的递归(第八次调用)返回第七次调用:此时s指向空字符(即'\0')之后?但实际第七次调用时,传入的s是递增后的(指向空字符),然后递归返回后,s仍然指向空字符(但字符串已经结束)。所以打印的是空字符(不显示),不过这里的代码实际上是有问题的。

关键错误:
在递归调用中,使用了 print(++s),这修改了指针s的值。当递归返回时,s已经指向下一个字符(而不是原来的字符)。因此------

(1)第一次递归调用后,s指向"eneius"(即第二个字符);

(2)当递归返回时,打印的是当前s指向的字符(即第二个字符'e'),而不是第一个字符'G'。

类似地,整个回溯过程打印的是字符串从第二个字符开始逆序 (但最后一个是空字符之后,未定义) ,实际上,字符串"Geneius"的长度为7 (加上空字符共8个字节)。递归调用直到遇到空字符(第八次调用时,s指向空字符,递归停止) 。然后回溯------

第七次调用:s原本指向"s"(最后一个字符),但调用print(++s)后,s指向空字符。然后递归返回后,打印*s(即空字符,不显示)。

第六次调用:s原本指向"us"(即字符'u'),调用print(++s)后,s指向"s"。递归返回后,打印*s(即's')。

第五次调用:s原本指向"ius"(即字符'l'),调用print(++s)后,s指向"us"。递归返回后,打印*s(即'u')。

第四次调用:s原本指向"eius"(即字符'e'),调用print(++s)后,s指向"lus"。递归返回后,打印*s(即'l')。

第三次调用:s原本指向"neius"(即字符'n'),调用print(++s)后,s指向"eius"。递归返回后,打印*s(即'e')。

第二次调用:s原本指向"eneius"(即字符'e'),调用print(++s)后,s指向"neius"。递归返回后,打印*s(即'n')。

第一次调用:s原本指向"Geneius"(即字符'G'),调用print(++s)后,s指向"eneius"。递归返回后,打印*s(即'e')。

所以打印的字符序列是(从最深回溯):' '(空字符,不显示)、's'、'u'、'i'、'e'、'n'、'e'。

即最终输出为:"suien"(但缺少第一个字符'G',并且似乎多打了一个'e')。

实际上,输出应该是:"suiene"(但注意第一个字符'e'是第二次调用打印的,而第一次调用打印的是'e'(第二个字符))。

观察字符串"Genelus":
索引0: 'G'
索引1: 'e'
索引2: 'n'
索引3: 'e'
索引4: 'i'
索引5: 'u'
索引6: 's'
索引7: '\0'

回溯打印:

递归深度7(s指向索引7): 打印*s(即'\0',不显示);

递归深度6(s指向索引6): 打印*s(即's');

递归深度5(s指向索引5): 打印*s(即'u');

递归深度4(s指向索引4): 打印*s(即'i');

递归深度3(s指向索引3): 打印*s(即'e');

递归深度2(s指向索引2): 打印*s(即'n');

递归深度1(s指向索引1): 打印*s(即'e')。

所以输出是:suiene(即"suiene")。

**注意:**原字符串是"Geneius",逆序应为"suieneG",但这里由于递归偏移,打印的是从第二个字符开始逆序(即"eneius"的逆序)为"suiene",而没有第一个'G',所以输出是"suiene"。

选项D是"suieneG"(多了一个G),实际上,代码不会打印第一个字符'G',因为第一次调用就递增了指针;另外,最后递归到空字符时,尝试打印空字符(不显示),所以没有额外输出。

我们来看这四个选项------

选项A正确;

选项B "neius" 是正序"Geneius"的一部分(错误);

选项C "run-time error":可能因为最后打印空字符(但不会错误,只是不显示),或者指针越界(但通常不会立即错误);

选项D "suieneG" 包含第一个字符'G',但实际没有。

所以正确答案就是A. suiene

1.4 题目4

**题干:**对于函数 void f(int x); ,下面调用正确的是( )

A. int y=f(9); B. f(9); C. f(f(9)); D. x=f();

解析:正确选项是B选项。

于函数 void f(int x); ,这是一个返回类型为 void 的函数,即它不返回任何值。因此:

A. 错误,因为 f(9) 没有返回值,不能赋值给 int y。

B. 正确,直接调用函数,不需要使用返回值。

C. 错误,因为 f(9) 没有返回值,不能作为 f 的参数。

D. 错误,因为 f 需要一个 int 类型的参数,这里没有提供参数;而且 f 没有返回值,不能赋值给 x。

1.5 题目5

**题干:**给定 fun 函数如下,那么 fun(10) 的输出结果是( )

cpp 复制代码
int fun(int x)
{
    return (x==1) ? 1 : (x + fun(x-1));
}

A. 0 B. 10 C. 55 D. 3628800

解析:正确选项是C选项。

这道题很简单,函数 fun(int x) 是一个递归函数,其逻辑如下:

1)如果 x == 1,返回 1;

2)否则,返回 x + fun(x-1)。

因此,fun(10) 的计算过程如下所示:

cpp 复制代码
fun(10) = 10 + fun(9)
fun(9) = 9 + fun(8)
fun(8) = 8 + fun(7)
fun(7) = 7 + fun(6)
fun(6) = 6 + fun(5)
fun(5) = 5 + fun(4)
fun(4) = 4 + fun(3)
fun(3) = 3 + fun(2)
fun(2) = 2 + fun(1)
fun(1) = 1

我们代入计算之后得到------

cpp 复制代码
fun(2) = 2 + 1 = 3
fun(3) = 3 + 3 = 6
fun(4) = 4 + 6 = 10
fun(5) = 5 + 10 = 15
fun(6) = 6 + 15 = 21
fun(7) = 7 + 21 = 28
fun(8) = 8 + 28 = 36
fun(9) = 9 + 36 = 45
fun(10) = 10 + 45 = 55

所以,fun(10) 的结果是 55,正确答案选择C选项

选择题答案如下:

1.1 AB

1.2 A

1.3 A

1.4 B

1.5 C

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

二、两道算法题

2.1 图片整理

牛客网链接:HJ34 图片整理

题目描述:

2.1.1 题目理解

我们根据题目要求,需要对输入的字符串按照ASCII码值进行排序

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

2.1.2 思路

(1)使用scanf读取输入字符串;

(2)使用qsort函数进行排序,需要提供一个;

(3)比较函数compare比较函数直接通过字符的ASCII码值相减来确定顺序;

(4)最后使用printf输出排序后的字符串。

代码演示:

cpp 复制代码
//C语言
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int compare(const void* a, const void* b)
{
    return (*(char*)a - *(char*)b);
}

int main()
{
    char s[1001];
    scanf("%s", s);
    int len = strlen(s);
    qsort(s, len, sizeof(char), compare);
    printf("%s", s);

    return 0;
}

时间复杂度O(1)

空间复杂度O(1)

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

代码演示:

cpp 复制代码
//C++复盘
#include <iostream>
#include"string"
#include"algorithm"
using namespace std;

int main()
{
    string s;
    cin >> s;
    sort(s.begin(), s.end());
    cout << s << endl;
    return 0;
}

时间复杂度: O(logn)****,空间复杂度: O(1)****。

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

2.2 寻找数组的中心下标

链接:724. 寻找数组的中心下标

力扣题解链接:前缀和优化法解决【寻找数组的中心下标】问题

题目描述:

2.2.1 尝试暴力写代码

我们先尝试用传统暴力方法:(循环)直接解题------

大致思路:

cpp 复制代码
// 时间复杂度 O(n²)
for (int i = 0; i < n; i++) 
{
    int leftSum = 0, rightSum = 0;
    
    // 计算左侧和:O(i)
    for (int j = 0; j < i; j++) 
    {
        leftSum += nums[j];
    }
    
    // 计算右侧和:O(n-i-1)
    for (int j = i + 1; j < n; j++) 
    {
        rightSum += nums[j];
    }
    
    if (leftSum == rightSum) 
        return i;
}

有了第一种思路,博主展示一下用【暴力方法】解题的代码:

cpp 复制代码
//C语言实现------迭代暴力方法
int pivotIndex(int* nums, int numsSize)
{
    for (int i = 0; i < numsSize; i++)
    {
        int leftsum = 0, rightsum = 0;
        for (int j = 0; j < i; j++)
        {
            leftsum += nums[j];
        }
        for (int j = i + 1; j < numsSize; j++)
        {
            rightsum += nums[j];
        }
        if (leftsum == rightsum)
        {
            return i;
        }
    }
    return -1;
}

时间复杂度O(n^2)

空间复杂度O(1)

我们不推荐这种方法,时间复杂度不好,还能再优化一下。

2.2.2 加深题目理解

我们可以用前缀和优化法处理诸如【寻找数组的中心下标】这类"寻找平衡点"问题; 这种方法避免了每次计算左右两侧和的重复计算,通过数学关系直接判断,效率很高。

2.2.3 优化思路

(1)**计算总和:**首先遍历整个数组,计算所有元素的总和;

(2)遍历寻找中心下标: 再次遍历数组,维护一个左侧和的变量;

(3)**检查条件:**对于每个位置i,检查 左侧和 == 总和 - 左侧和 - 当前元素

等价于检查 2 * 左侧和 == 总和 - 当前元素;

(4)**返回结果:**如果找到满足条件的位置,立即返回;如果遍历完都没找到,返回-1。

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

2.2.4 优化方案实现

代码演示:

cpp 复制代码
int pivotIndex(int* nums, int numsSize) {
    int total = 0;
    // 计算数组所有元素的总和
    for (int i = 0; i < numsSize; i++)
    {
        total += nums[i];
    }

    int leftSum = 0;
    for (int i = 0; i < numsSize; i++)
    {
        // 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素
        if (leftSum == total - leftSum - nums[i])
        {
            return i;
        }
        leftSum += nums[i];
    }

    return -1;
}

时间复杂度O(1)

空间复杂度O(1)

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

代码演示:

cpp 复制代码
//C++实现
#include<vector>
using namespace std;

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int total = 0;
        // 计算数组所有元素的总和
        for (int num : nums)
        {
            total += num;
        }

        int leftSum = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            // 右侧元素之和 = 总和 - 左侧元素之和 - 当前元素
            if (leftSum == total - leftSum - nums[i])
            {
                return i;
            }
            leftSum += nums[i];
        }

        return -1;
    }
};

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

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


结尾

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

往期回顾:

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

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

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

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

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

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

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

相关推荐
孟婆来包棒棒糖~6 分钟前
Maven快速入门
java·spring boot·spring·maven·intellij-idea
2501_924889552 小时前
商超高峰客流统计误差↓75%!陌讯多模态融合算法在智慧零售的实战解析
大数据·人工智能·算法·计算机视觉·零售
壹Y.3 小时前
非线性规划学习笔记
学习·数学建模
jingfeng5143 小时前
C++模板进阶
java·c++·算法
杨杨杨大侠3 小时前
附录 1:[特殊字符] Maven Central 发布完整指南:从零到成功部署
java·spring boot·maven
ahauedu3 小时前
AI资深 Java 研发专家系统解析Java 中常见的 Queue实现类
java·开发语言·中间件
地平线开发者3 小时前
征程 6X | 常用工具介绍
算法·自动驾驶
项目題供诗3 小时前
React学习(十二)
javascript·学习·react.js
小厂永远得不到的男人3 小时前
基于 Spring Validation 实现全局参数校验异常处理
java·后端·架构