【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.2  题目2

1.3  题目3

1.4  题目4

1.5  题目5

二、两道算法题

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

2.1.1 题目理解

2.1.2 思路

2.2  整数转换

2.2.1 题目理解

2.2.2 思路

2.2.3 报错

2.2.4 优化

结尾


正文

一、五道选择题

1.1  题目1

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

#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

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

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<回车> 输出结果是( )

#
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值为奇数的小写字母转为大写,其他字符不变,然后输出。

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. 修改外层循环条件例如:

for( int i = 0 ; i < MAX1 ; i ++ )
{
    for( int j = 0 ; j < MAX2 ; j ++ )
    {
        if( condition )
        {
            i = MAX1;
            break;
        }
    }
}

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


or( ; symbol != 1 && condition2 ; )
{
    for( ; symbol != 1 && condition3 ; )
    {
        if( condition1 )
        symbol = 1 ;
    }
}

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

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,就是我们要的数字在升序数组中出现的次数。

代码演示:

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 *
 * @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++学得怎么样——

代码演示:

//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是正数符号位),这可能导致无限循环。因此,我们使用无符号整数来避免这个问题。具体来说,将异或结果转换为无符号整数后再进行位统计。

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

unsigned int val = (unsigned int)(A ^ B);

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

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

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

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

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 报错

代码演示:

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

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

还有这种——

//超时了
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”的数量,没有右移操作

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

// 错误代码 —— 会导致无限循环!
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不同的位数

代码演示:

//优化
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)逐位处理机制:

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——

10010 (18) → 1001 (9) → 100 (4) → 10 (2) → 1 (1) → 0

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

// 最坏情况:所有位都是1
val = 0xFFFFFFFF;  // 32个1

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

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

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

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

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

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

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

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

代码演示:

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)

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

代码演示:

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

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

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾莉丝努力练剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值