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


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

❄专栏传送门:《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

**题干:**如下程序的功能是( )

cpp 复制代码
#include <stdio.h>
int main()
{
    char ch[80] = "123abcdEFG*&";
    int j;
    puts(ch);
    for(j = 0; ch[j] != '\0'; j++)
        if(ch[j] >= 'A' && ch[j] <= 'Z')
            ch[j] = ch[j] + 'e' - 'E';
    puts(ch);
    return 0;
}

A. 测字符数组ch的长度 B. 将数字字符串ch转换成十进制数

C. 将字符数组ch中的小写字母转换成大写 D. 将字符数组ch中的大写字母转换成小写

解析: 循环遍历字符数组,遇到大写字母('A' ~ 'Z')时,通过ch[j] = ch[j] + 'e' - 'E';将其转换为小写(ASCII中 'e' - 'E' = 32,正好是大写转小写的差值)。

由此可知,答案是D。

1.2 题目2

**题干:**对于代码段,下面描述正确的是( )

cpp 复制代码
t=0;
while(printf("*"))
{
    t++;
    if (t<3)
        break;
}

A. 其中循环控制表达式与0等价 B. 其中循环控制表达式与'0'等价

C. 其中循环控制表达式是不合法的 D. 以上说法都不对

解析:printf("*")始终返回1(成功输出字符数),循环条件恒为真,但内部 t<3 时 break 会终止循环,因此控制表达式既不与0等价(非零为真),也不与'0'等价(ASCII值48,非零),也是合法的,故答案选D

1.3 题目3

**题干:**以下程序运行时,若输入 1abcedf2df<回车> 输出结果是( )

cpp 复制代码
#
include <stdio.h>
int main()
{
    char ch;
    while ((ch = getchar()) != '\n')
    {
    if (ch % 2 != 0 && (ch >= 'a' && ch <= 'z'))
        ch = ch - 'a' + 'A';
    putchar(ch);
    }
    printf("\n");
    return 0;
}

A. 1abcedf2df B. 1ABCEDF2DF C. 1AbCEdf2df D. 1aBceDF2DF

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

题目所给的这段代码功能

将输入字符串中ASCII值为奇数的小写字母转为大写,其他字符不变,然后输出。

cpp 复制代码
if (ch % 2 != 0 && (ch >= 'a' && ch <= 'z'))
    ch = ch - 'a' + 'A';

(1)ch % 2 != 0 判断字符的ASCII值是否为奇数(即二进制最低位为1)。

(2)ch >= 'a' && ch <= 'z' 判断是否是小写字母。

满足条件时,将小写字母转换为大写(通过ASCII码差值计算:'a' - 'A' = 32,

但这里用的是 ch = ch - 'a' + 'A'; ,等价于 ch - 32)。

功能 :将输入字符串中ASCII值为奇数的小写字母转换为大写,其他字符不变。
例如:

(1)输入 "abcde"(a=97奇, b=98偶, c=99奇, d=100偶, e=101奇);

(2)输出 "AbCdE"(仅奇数字母被转大写)。

1.4 题目4

**题干:**下列条件语句中,功能与其他语句不同的是( )

A. if(a) printf("%d\n",x); else printf("%d\n",y);

B. if(a==0) printf("%d\n",y); else printf("%d\n",x);

C. if (a!=0) printf("%d\n",x); else printf("%d\n",y);

D. if(a==0) printf("%d\n",x); else printf("%d\n",y);

**解析:**答案是D选项

A、B、C均为a非零时输出x,a为零时输出y;而D是a为零时输出x,a非零时输出y,功能相反。

1.5 题目5

**题干:**我们知道C语言的 break 语句只能跳出离它最近的一层循环,可是有时候我们需要跳出多层循环,下列跳出多层循环的做法正确的是( )【多选】

A. 将程序写成函数用return结束函数,便可跳出循环

B. 修改外层循环条件例如:

cpp 复制代码
for( int i = 0 ; i < MAX1 ; i ++ )
{
    for( int j = 0 ; j < MAX2 ; j ++ )
    {
        if( condition )
        {
            i = MAX1;
            break;
        }
    }
}

C. 在外层循环设置判断条件例如:

cpp 复制代码
or( ; symbol != 1 && condition2 ; )
{
    for( ; symbol != 1 && condition3 ; )
    {
        if( condition1 )
        symbol = 1 ;
    }
}

D. 在外层循环后面加入break例如:

cpp 复制代码
for( ; condition2 ; )
{
    for( ; condition3 ; )
    {
        if( condition1 )
        symbol = 1 ;
    } 
    if(symbol == 1 )
        break ;
}

**解析:**答案是ABCD。

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

A选项: return直接结束函数,跳出所有循环。

**B选项:**内层break跳出内层循环,同时修改外层循环变量i迫使外层循环结束。

C选项: 通过符号变量symbol控制外层循环条件,条件不满足时终止循环。

**D选项:**内层修改symbol后,外层循环通过break终止。
所有选项均能实现跳出多层循环,答案已经有了。

选择题答案如下:

1.1 D

1.2 D

1.3 A

1.4 D

1.5 ABCD

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

二、两道算法题

2.1 数字在升序数组中出现的次数

牛客链接:JZ53 数字在升序数组中出现的次数

题目描述:

2.1.1 题目理解

这道题既然说了长度为n的非降序数组(升序数组)、非负数整数k(0或正整数),而且要我们统计k(常见的叫法是val,这里叫什么都无所谓)出现的次数,也就是指定的一个数在这个长度为n的非降序数组中一共出现了几次,很简单,题目提示了这道题考查的是二分法。

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

我们不管三七二十一,先判断有效长度,nunsLen等于0,直接return 0

2.1.2 思路

我们的思路就是,两次二分查找,左边界查一遍,比k小,left就mid+1,比k大,right就mid-1;右边界加上nums[mid]刚好等于我们要找的k这个范围,同样------比k小或者等于k,left就mid+1,比k大,right就mid-1。因为是接口型,我们不用自己输入输出,直接return我们的结果------左边界和右边界二分查找完,我们定义一个新变量,返回就返回右边界-左边界+1,就是我们要的数字在升序数组中出现的次数。

代码演示:

cpp 复制代码
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 *
 * @param nums int整型一维数组
 * @param numsLen int nums数组长度
 * @param k int整型
 * @return int整型
 */
int GetNumberOfK(int* nums, int numsLen, int k) {
    // write code here
    if (numsLen == 0) {
        return 0;
    }
    //进行两次二分查找 时间复杂度:O(logn)
    //左边界
    int left = 0;
    int right = numsLen - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < k) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    int left_bound = left;
    //右边界
    left = 0;
    right = numsLen - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= k) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    int right_bound = right;
    return right_bound - left_bound + 1;
}

这里博主用了两次二分查找,空间上也只是调用了常数次的变量,所以复杂度良好------

时间复杂度O(logn)

空间复杂度O(1)

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

代码演示:

cpp 复制代码
//C++的实现
class Solution {
public:
    int GetNumberOfK(vector<int>& nums, int k) {
        if (nums.empty()) return 0;

        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < k) {
                left = mid + 1;
            }
            else {
                right = mid - 1;
            }
        }
        int left_nums = left;

        left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= k) {
                left = mid + 1;
            }
            else {
                right = mid - 1;
            }
        }
        int right_nums = right;

        return right_nums - left_nums + 1;
    }
};

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

2.2 整数转换

力扣链接:面试题 05.06. 整数转换

力扣题解链接:用异或、右移操作轻松搞定【整数转换】问题

题目描述:

2.2.1 题目理解

像整数转换这种题目,我们要联系前面学过的异或、按位与等知识点。要解决这个问题,我们需要计算将整数A转换为整数B所需改变的位数。这实际上就是计算A和B的二进制表示中有多少位是不同的。最直接的方法就是使用异或操作:异或操作的结果中,每一位为1表示该位在A和B中不同,为0则表示相同。因此,我们只需要计算A异或B的结果中1的个数即可。

2.2.2 思路

我们的思路主要就是围绕三点:"1"的统计、异或的操作、负数的处理------

(1)异或操作:首先对A和B进行异或操作,得到一个整数C,其中每一个为1的位表示A和B在该位上不同。

(2)统计1的个数:然后统计C中1的个数,这个数就是需要改变的位数。

(3)处理负数:由于整数可能为负数,而C语言中负数的右移操作是算术右移(会填充符号位,1是负数符号位、0是正数符号位),这可能导致无限循环。因此,我们使用无符号整数来避免这个问题。具体来说,将异或结果转换为无符号整数后再进行位统计。

还有一点很重要,我们得使用无符号整数------

cpp 复制代码
unsigned int val = (unsigned int)(A ^ B);

使用无符号数很重要,因为:

1)对于有符号数 ,右移是算术右移 (用符号位填充左边);

2)对于无符号数 ,右移是逻辑右移 (用0填充左边)。

如果是负数使用算术右移,可能会导致无限循环!

cpp 复制代码
int signed_val = -1;      // 二进制: 11111111 11111111 11111111 11111111
unsigned int unsigned_val = (unsigned int)-1; // 同上

signed_val >>= 1;    // 结果还是 -1 (算术右移,符号位填充)
unsigned_val >>= 1;  // 结果变成 2147483647 (逻辑右移,0填充)

这样我们就把负数给处理好了。

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

2.2.3 报错

代码演示:

我们有了思路,直接开始写代码,有两种写法------

cpp 复制代码
int convertInteger(int A, int B) {
    int count = 0;
    unsigned int val = (unsigned int)(A ^ B);
    while (val != 0)
    {
        count += val & 1;
    }
    return count;
}

还有这种------

cpp 复制代码
//超时了
int convertInteger(int A, int B) {
    int count = 0;
    unsigned int val = (unsigned int)(A ^ B);
    while (val != 0)
    {
        if (val & 1)
        {
            count++;
        }
    }
    return count;
}

时间复杂度O(1)

空间复杂度O(1)

可是一调试,坏了:【超出时间限制】------

2.2.4 优化

我们仔细想一下,思路是没问题的,怎么样给它优化一下呢?

之前写轮转数组那道题的时候博主就展示了三种写法,最后是用数组逆置解决的,一开始也是提示【超出时间限制】,之前是因为力扣的通过比较严格:有几十万个测试用例,才通不过的,这道题目我们看到**【超出时间限制】一定要条件反射------是不是无限循环****了?**

我们发现,原本我们只是单纯地统计"1"的数量,没有右移操作

如果没有右移操作,代码会变成:

cpp 复制代码
// 错误代码 ------ 会导致无限循环!
while (val != 0) {
    count += val & 1;
    // 缺少 val >>= 1; 
}

后果就是------

(1)无限循环 :val 的值永远不会改变;

(2)永远检查同一个位 :每次都只检查最低位;

(3)程序卡死 :无法退出循环,程序卡死。

那么我们加上右移操作,很有可能就能解决问题了!

val >>= 1; 是右移赋值操作,相当于 val = val >> 1;。

**val >>= 1;**的作用是:

1、逐位处理:每次右移一位,让下一个位成为最低位;

2、遍历所有位:通过循环右移,可以检查数值的所有二进制位;

3、配合 &1:每次检查当前的最低位是否为1;

4、确保正确性:使用无符号数避免算术右移的问题。

这样就能准确统计出异或结果中1的个数,即A和B不同的位数

代码演示:

cpp 复制代码
//优化
int convertInteger(int A, int B) {
    int count = 0;
    unsigned int val = (unsigned int)(A ^ B);
    while (val != 0)
    {
        count += val & 1;
        val >>= 1;//避免无限循环
    }
    return count;
}

时间复杂度O(1)

空间复杂度O(1)

我们再调试一下,发现代码顺利通过了!

我们发现,增加val >>= 1;这个式子似乎让原本【超出时间限制】的问题被优化了?!

是的。****增加val >>= 1;这个操作是避免无限循环的关键优化!

**为什么****增加val >>= 1;**能做到优化?

(1)逐位处理机制:

cpp 复制代码
xor_val = 18;  // 二进制: 10010

循环1: 检查 10010 的最低位 (0) → 右移 → 1001
循环2: 检查 1001 的最低位 (1) → 右移 → 100  
循环3: 检查 100 的最低位 (0) → 右移 → 10
循环4: 检查 10 的最低位 (0) → 右移 → 1
循环5: 检查 1 的最低位 (1) → 右移 → 0 → 退出循环

(2)确保循环终止: 每次右移都让数值变小(或者至少位数减少),最终会变成0------

cpp 复制代码
10010 (18) → 1001 (9) → 100 (4) → 10 (2) → 1 (1) → 0

(3)优化时间复杂度: 对于32位整数,最多也只需要32次循环****------

cpp 复制代码
// 最坏情况:所有位都是1
val = 0xFFFFFFFF;  // 32个1

// 需要32次右移才能变成0
// 每次循环都是O(1)操作,总时间复杂度O(32)=O(1)

既然是常数,那就是时间复杂度就是O(1)

由此可见,右移操作有以下几个好处------

(1)避免死循环:没有右移就会卡死;

(2)确保正确性:能遍历所有位;

(3)控制时间复杂度:固定32次循环;

(4)通过测试用例:否则会"超出时间限制"。

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

代码演示:

cpp 复制代码
class Solution {
public:
    int convertInteger(int A, int B) {
        unsigned int xor_val = (unsigned int)(A ^ B);
        int count = 0;
        while (xor_val != 0) {
            count += xor_val & 1;
            xor_val >>= 1;
        }
        return count;
    }
};

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

我们可以进阶一下,使用内置函数能不能解决?甚至更加简单!

代码演示:

cpp 复制代码
class Solution {
public:
    int convertInteger(int A, int B) {
        return __builtin_popcount(A ^ B);
    }
};

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

是不是直接薄纱?哈哈哈,C++有时候就是这样,所以我们学习了C++再去刷题,会很"有趣"。

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


结尾

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

往期回顾:

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

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

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

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

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

相关推荐
小眼睛FPGA4 分钟前
【盘古100Pro+开发板实验例程】FPGA学习 | gamma 变化 | 图像实验指导手册
科技·学习·ai·fpga开发·fpga
励志不掉头发的内向程序员15 分钟前
STL库——string(类模拟实现)
开发语言·c++
subuq29 分钟前
Web3.0 时代的电商系统:区块链如何解决信任与溯源问题?
大数据·网络·学习
郝学胜-神的一滴43 分钟前
使用C++11改进工厂方法模式:支持运行时配置的增强实现
开发语言·c++·程序人生·设计模式
袁培宇1 小时前
python学习打卡day40
人工智能·python·学习
爱和冰阔落1 小时前
从关机小游戏学 C 语言:分支循环 + 关键字(break/continue)实战
c语言·开发语言
Korloa1 小时前
表达式(CSP-J 2021-Expr)题目详解
c语言·开发语言·数据结构·c++·算法·蓝桥杯·个人开发
手握风云-1 小时前
回溯剪枝的 “减法艺术”:化解超时危机的 “救命稻草”(一)
算法·机器学习·剪枝
yodala2 小时前
C++中的内存管理(二)
开发语言·c++
屁股割了还要学2 小时前
【数据结构入门】排序算法:插入排序
c语言·开发语言·数据结构·算法·青少年编程·排序算法