目录
一、左旋字符串
题目要求:实现一个函数,可以左旋字符串中的k个字符。
①一般思维讲解:
如果暴力去求解的话,那么很简单,就是一个一个搬,先把第一个字符存下来,再整体左移覆盖,然后再把存下来的字符放在最后面,如此往复。这个方法暴力且麻烦。
#include<assert.h>
#include<stdio.h>
#include<string.h>
void left_rotate(char arr[],int k)
{
assert(arr);
int i = 0;
int len = strlen(arr);
k = k % len;
for (i = 0; i < k; i++)
{
//旋转一个字符
char tmp = arr[0];
//abcedfabcdef
int j = 0;
for (j = 0; j < len - 1; j++)
{
arr[j] = arr[j + 1];
}
//3
arr[len - 1] = tmp;
}
}
int main()
{
char arr[] = "ABCDEFG";
int k = 0;
scanf("%d", &k);//2 CDEFGAB
left_rotate(arr, k);
printf("%s\n", arr);
return 0;
}
②扩容找字符串解法
这个方法比较好玩,在上面那个移字符的过程中,我们很容易发现移动字符后不就是在最后面又写了一遍字符吗?如果我直接把字符串拷贝一遍放在后面,再进行查找不就行了。是否可行呢?我们分析一下.
可以发现,这种方式还是比较简单的。我们可以试着编译一下:(后面我们就只展现接口函数)
void left_rotate(char arr[], int k)
{
//扩展为两倍,然后直接找
assert(arr);
int len = strlen(arr);
int i = 0;
char str[100] = { 0 };
k = k % len;
strcpy(str, arr);
strcat(str, arr);
for (i = 0; i < len; i++)
{
arr[i] = str[i + k];
}
}
运行上是没问题的。
③逆序求解
只能说可以想到这个方法的是大佬,我是想不出来,具体就是前k个字符逆序,剩余的字符逆序,再整体逆序,就会惊奇地发现完成了左旋。
#include<assert.h>
#include<stdio.h>
#include<string.h>
void reverse(char* left, char* right)
{
assert(left && right);
while (left < right)
{
char tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
void left_rotate(char arr[], int k)
{
int len = strlen(arr);
k = k % len;
reverse(arr,arr+k-1);//左
reverse(arr+k,arr+len-1);//右
reverse(arr, arr + len - 1);//整体
}
运行也是可以的。
总结一下:暴力解法是求解的初步,之后需要对代码进行优化,我们发现了可以增容再查找:
增容的缺点:需要额外创建一个字符串,浪费空间,时间复杂度是O(1),浪费空间节省时间。逆序缺点:时间复杂度是0(N),空间复杂度是0(1)。各有优劣。
需要注意的两个点:
1、assert是良好的编程习惯。
2、k=k%len是为了防止超过旋转字符串长度,增加代码的安全性。
二、最大公约数和最小公倍数之和
输入两个整数,求其最大公约数和最小公倍数之和
①.最大公约数怎么求
首先我们要限制一下范围,两个数的最大公约数最大只可能是较小的那个数。如果不是,只可能比较小的数还要小,不可能比它大,所以比较简单的方式就是从较小的数往下遍历查找。比较简便的方式是辗转相除法,简单来说就是,大的数%小的数,如果不是0,小的数赋值给大的数,%出来的数赋给小的数,那么最大公约数就是最后小的数。
②最小公倍数怎么求
最小公倍数最小就是较大的数,所以从较大的数向上遍历,但是这样比较麻烦,我们根据最大公约数和最小公倍数的乘积等于两个数的乘积这一性质,求出最大公约数就可以得到最小公倍数。
①遍历
int main()
{
//每组输入包含两个正整数n和m
int n, m, Gcd, Icm, i;
while (scanf("%d%d", &n, &m) != EOF)
{
int min = n < m ? n : m;
int max = n > m ? n : m;
for (i = min; i > 0; i--)
{
if ((max % i == 0) && (min % i == 0))
{
Gcd = i;//最大公约数
Icm = m * n / Gcd;//最小公倍数
break;
}
}
printf("%d\n", Gcd + Icm);
}
return 0;
}
②辗转相除法
//辗转相除法
int main()
{
//每组输入包含两个正整数n和m
int n, m, Gcd, Icm, i;
while (scanf("%d%d", &n, &m) != EOF)
{
int min = n < m ? n : m;
int max = n > m ? n : m;
int ret;
//辗转相除法
//大的%小的,不是0,再把小的赋值,不是0的数再赋值
while (ret = max % min)
{
max = min;
min = ret;
}
Gcd = min;
Icm = m * n / Gcd;
printf("%d\n", Icm + Gcd);
}
return 0;
}
三、找单身狗
寻找单身狗:
一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
①便利查找
比较简单的方式就是把这个数组的每个数拿下来去和所有数去匹配,如果只出现了一次,那么这个数就是单身狗。
//暴力便利,每一个数进行查找
int find_single_dog(int arr[],int ret,int len)
{
int count = 0;
for (int i = 0; i < len; i++)
{
if (ret == arr[i])
{
count++;
}
}
if (count == 1)
{
return ret;
}
return -1;
}
int main()
{
//1 2 3 4 5 1 2 3 4 6
int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
//暴力便利,每一个数进行查找
int lenth = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for (i = 0; i < lenth; i++)
{
int ret=find_single_dog(arr,arr[i],lenth);
if (ret == arr[i])
printf("%d ", ret);
}
printf("\n");
return 0;
}
②异或分组查找
这种方式得益于异或,如果把0和一个数组中的数进行异或,如果数组中有的数出现了两次那么这些数异或的结果就是0,如果有的数只出现了一次,那么结果就是这些只出现一次的数异或的结果。
但是这种方法只能找到一个单身狗,如果出现两个,那么结果就是这两个数异或的结果。所以在这里我们需要进行分组,让这两个数到两个组里面,分别从这两个组求出一个单身狗。
这么说可能还是不太清晰,我就做个简图:
总结一下:
1、首先一组数进行异或得到两个单身狗异或得到的结果。
2、得到异或的结果二进制形式下最低哪个位是1,然后是1的分一组,0的分一组。
void find_single_dog(int arr[], int sz, int* pd1, int* pd2)
{
int i = 0;
int ret = 0;
for (i = 0; i < sz; i++)
{
ret ^= arr[i];
}
//计算ret二进制中最右边的第几位是1
//右移和1&
int pos = 0;
for (pos=0; pos < 32; pos++)
{
if (((ret >> pos) & 1) == 1)
{
break;
}
}
for (i = 0; i < sz; i++)
{
if (((arr[i] >> pos) & 1) == 1)
{
*pd1 ^= arr[i];
}
else
{
*pd2 ^= arr[i];
}
}
}
int main()
{
//1 2 3 4 5 1 2 3 4 6
int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
int dog1 = 0;
int dog2 = 0;
find_single_dog(arr, sz, &dog1, &dog2);
printf("%d %d\n", dog1, dog2);
return 0;
}
四、模拟实现atoi函数
atoi这个函数的主要作用是把字符串数字转换为整型数字。
头文件:int atoi (const char * str);
①函数使用:
通过测试,我们发现出了一般的数字字符串比如”123456“这样的,我们还要考虑:
1、空指针
2、空字符串
3、空格
4、+-号问题
5、非数字字符
6、越界问题
7、............
#include<stdlib.h>
#include<ctype.h>
#include<ctype.h>
#include<limits.h>
enum Status
{
VALID, //0
INVALID //1
}sta=INVALID;//默认非法
int my_atoi(const char* str)
{
int flag = 1;
assert(str);//解决空指针
if (*str == '\0')//解决空字符
{
sta = INVALID;
return 0;//歧义 ,非法0
}
//跳过空白字符
while (isspace(*str))
{
str++;
}
if (*str == '+')
{
flag = 1;
str++;
}
else if(*str=='-')
{
flag = -1;
str++;
}
long long ret = 0;
while (*str)
{
if (isdigit(*str))
//越界
{
ret = ret * 10 + flag * (*str - '0');
if (ret > INT_MAX || ret < INT_MIN)
{
//之前的定义为int就不能超过整型最大值,因为超过会发生截断
//所有要设为long
return 0;
}
}
else
{
return (int)ret;
}
str++;
}
if (*str == '\0')
{
sta = VALID;
return (int)ret;
}
}
int main()
{
//int atoi(const char* str)
char arr[20] = "-123456";
int ret=my_atoi(arr);
if (sta == INVALID)
{
printf("非法返回:%d\n", ret);
}
else if (sta == VALID)
{
printf("合法转换:%d\n", ret);
}
//遇到非数字,就停止转换 ,开头遇到空格直接过滤掉,中间遇到空格,停止转换
return 0;
}
函数说明:
1、assert断言解决空指针问题
2、之所以用long long int 而不用int,是因为如果越界了,int类型会发生截断,不可能大于整型最大值,所以要定义为long long int,这是个细节。
3、使用枚举全局变量的目的是为了一些判断,因为如果是空字符串和"0"字符串如果返回是0,那么这是0还是异常的原因是无法判定的。
五、写一个宏可以把一个整数的二进制位的奇数位和偶数位交换
其实宏和函数差不多,如果感觉宏实现起来有难度,可以尝试先用函数的思想,再转化为宏。对于32位整型,提出来偶数位右移和提出来奇数位左移再相加就实现了奇数位和偶数位的交换。
例子列举:
#define SWAP_BIT(n) (((n&0x55555555)<<1)+((n&0xaaaaaaaa)>>1))
int main()
{
int n = 0;
scanf("%d", &n);
int ret=SWAP_BIT(n);
printf("%d\n", ret);
return 0;
}
六、替换空格
实现一个函数,将一个字符串中的每个空格替换为"%20".
这道题目不难实现,问题是要想清楚,简单一点,创建一个新的字符串,遍历旧字符串,如果遇到' '就放进去”%20“,然后再把新的字符串拷贝到旧字符串,这是比较笨且浪费空间的做法。
如果我们不创建新的字符串,那么这道题怎么处置呢?我们要对旧的进行扩容,然后要从后往后搬字符,遇到空格就放'0','2','%',这样是不就解决了?
void replaceSpace(char* str, int length)
{
assert(str);
//一个空格换成三个字符
//统计空格,然后增加空格*2
//从最后往后挪遇到' '就放0 2 %,
//遇到空格,end2挪动三步
//end1再往前
char* cur = str;
int count = 0;
//计算空格数
while (*cur)
{
if (*cur == ' ')
{
count++;
}
cur++;
}
int end1 = length - 1;
int end2 = length + count * 2 - 1;
while (end1 != end2)
{
//We Are Happy.
if (str[end1] != ' ')
str[end2--] = str[end1--];
else
{
end1--;
str[end2--] = '0';
str[end2--] = '2';
str[end2--] = '%';
}
}
}
七、offsetof宏的实现
设计一个宏,实现offsetof的功能
offsetof的功能是计算结构体成员偏移量,我们知道结构体成员地址是连续的,所以能否借助这一特性来写这样一个代码吗?
分析:
#define my_offsetof(type,mumber) (size_t)&(((type*)0)->mumber)
typedef struct Stu
{
char c1;
int i;
char c2;
}Stu;
int main()
{
printf("%d\n", my_offsetof(Stu,c1));
printf("%d\n", my_offsetof(Stu,i));
printf("%d\n", my_offsetof(Stu,c2));
return 0;
}
本期分享就到这里,谢谢大家的观看,也请勘误喔。