重要思想总结
求二进制序列中1的个数:
m&(m-1)会把m的二进制序列中最右边的1去掉,每进行一次m&(m-1)得到一个新的m,都会消除掉m二进制序列中最右边的1,故m二进制序列中1的个数即消除1的次数,即m和m-1相与的次数。
#include<stdio.h>
int main()
{
int m = 15;
int count = 0;
while (m)
{
m = m & (m - 1);
count++;
}
printf("%d\n", m);
return 0;
}
拓展: 如果m = 2^n,那么m和(m-1)相与一次,结果就为0。
检测num中某一位是0还是1:
我们将一个数的二进制序列和1相与,如果相与后的结果位1,则说明该数最低位为1,如果相与后的结果为0,说明该数的最低位为0。
所以如果我们想要检测num中第i位是0还是1,我们只需将其向右移动i位,即移动到最低位,然后和1相与,如果结果为0,则第i个比特位为0,如果结果为1,则第i个比特位为1。
注:
二进制序列的位数从0开始算,即最低位为第0位,最高位为第31位。
不创建临时变量交换值:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d\n", a,b);
a = a^b;
b = a^b;
a = a^b;
printf("交换后:a = %d b = %d\n", a,b);
return 0;
}
判断数值的位数
获取数值的每一位:
数值拆分的规律如下:
个位 :数值 % 10
十位 :数值 / 10 % 10
百位 :数值 / 10 / 10 % 10
千位 :数值 / 10 / 10 / 10 % 10;
…
判断数值的位数:
#include<stdio.h>
int main()
{
int count = 1;//至少为1位数
int temp = 0;
scanf("%d", &temp);
while (temp /= 10)
{
count++;
}
printf("%d\n", count);
}
获取数值的每一位数:
#include<stdio.h>
//获取数值的每一位数
int main()
{
int num = 0;
scanf("%d", &num);
int m = 0;
while (num)
{
//依次获取个位数,十位数,百位数....
m = num % 10;
num /= 10;
}
return 0;
}
把一个整数的二进制位的奇数位和偶数位交换
所谓奇数位和偶数位交换就是指所有奇数位左移一位,所有偶数位右移一位,那我们只需要获取这个数的偶数位,奇数位全部置为0,然后将偶数位右移一位,即实现了所有偶数位右移一位,同时获取奇数位,偶数位全部置位0,然后所有奇数位再左移一位,即实现了所有奇数位左移一位,把所有偶数位右移一位后的结果加上所有奇数位左移一位后的结果即为所有偶数位右移一位且所有奇数位左移一位后的结果,即偶数位和奇数位交换的结果。
那我们要如何获取这个数的偶数位,奇数位全部置为0,并且同时获取奇数位,偶数位全部置位0呢?
我们只需将这个整数与101010101010101010101010101010相与,即与0xaaaaaaaa相与,就可以获取所有的偶数位同时奇数位全部置为0,同理,我们只需要将这个整数与01010101010101010101010101010101相与,即与0x55555555相与即可获取所有的奇数位并且将偶数位置为0。
用一个宏来实现这个功能就是
#define SWAP(x)((((x)&0xaaaaaaaa) >> 1) + (((x)&0x55555555) << 1))
函数同理。
将个位数,十位数,百位数…组成一个完整的数
核心思想:个位数乘以十的零次方 + 十位数乘以十的1次方 + 百位数乘以十的2次方…
#include<stdio.h>
#include<math.h>;
int main()
{
int n = 0;//表示几位数
scanf("%d", &n);
int k = 0;//用于核心语句的幂次
int re = 0;//记录结果
int i = 0;//记录每一位数
while (n)
{
//依次输入个位数,十位数,百位数
scanf("%d", &i);
//该句是核心代码
re += i * pow(10, k++);
n--;
}
printf("%d\n", re);
return 0;
}
找素数
判断1~n之间有多少个素数,并输出所有素数,素数:如果除了1和它本身以外,不能被其他正整数整除,就叫素数。对于一个整数y,无需判断在2 ~ y-1之间能否被其它整数整除。只需判断2 ~ 根号y之间是否能够被其它整数整除:
#include <math.h>
int main()
{
int y = 0;
int count = 0;
for (y = 101; y <= 200; y+=2)
{
//因为偶数一定不是素数,所以直接判断奇数即可
int n = 0;
int flag = 1;//假设y是素数
for (n = 2; n <= sqrt(y); n++)
{
if (y % n == 0)
{
flag = 0;//y不是素数
break;
}
}
if (flag == 1)
{
printf("%d ", y);
count++;
}
}
printf("\ncount = %d\n", count);
return 0;
}
将秒转化为时分秒
#include <stdio.h>
int main()
{
int h = 0;//小时数
int m = 0;//分钟数
int s = 0;//秒数
int seconds = 0;
//输入
scanf("%d", &seconds);
//计算
h = seconds / 3600;
m = (seconds % 3600) / 60;
s = (seconds % 3600) % 60;
//输出
printf("%d %d %d", h, m, s);
return 0;
}
求最大公约数
方法一:
#include <stdio.h>
int main()
{
int m = 0;
int n = 0;
scanf("%d %d", &m, &n);
//求最大公约数
int ret = 0;
//1. 求m和n 较小值,假设就是最大公约数
if (m > n)
ret = n;
else
ret = m;
while (1)
{
if (m % ret == 0 && n % ret == 0)
{
break;
}
ret--;
}
//打印
printf("%d\n", ret);
return 0;
}
方法二:辗转相除法
int main()
{
int m = 0;
int n = 0;
scanf("%d %d", &m, &n);//18 24
//求最大公约数
int ret = 0;
while (ret=m%n)
{
m = n;
n = ret;
}
printf("%d\n", n);
return 0;
}
求最小公倍数
方法一:暴力求解法
#include<stdio.h>
int minbei(int a, int b)
{
int re = 0;
if (a == b)
{
re = a;
}
else
{
re = a > b ? a : b;
while (!(re % a == 0 && re % b == 0))
{
re++;
}
}
return re;;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("%d\n", minbei(a, b));
return 0;
}
方法二:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
int i = 1;
//如果a*i%b==0,那么a*i一定是最小公倍数
while((a * i % b)!= 0)
{
i++;
}
printf("%d\n",a*i);
return 0;
}
闰年的判断
# include <stdio.h>
//1. 能被4整除,并且不能被100整除是闰年
//2. 能被400整除是闰年
int main()
{
int y = 0;
int count = 0;
for (y = 1000; y <= 2000; y++)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400==0))
{
count++;
printf("%d ", y);
}
}
//输出个数
printf("\ncount = %d\n", count);
return 0;
}
倒置字符串
比如输入为:I like Beijing.
逆置后输出为:Beijing. like I
思想:先逆置每个单词,然后逆置整个字符串,就实现了逆置一句话中的单词
#include<stdio.h>
#include<string.h>
#include<assert.h>
//逆置函数
void reverse(char* left, char* right)
{
//断言,确保指针不为空再进行操作
assert(left && right);
while (left < right)
{
char temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}
int main()
{
char arr[100] = { 0 };
//输入字符串
gets(arr);
int sz = strlen(arr);//获取字符串的长度
//逆置字符串
char* start = arr;
char* end = start;
//1.先逆置每个单词
while (1)
{
while (*end != ' ' && *end != '\0')
{
end++;
}
reverse(start, end - 1);
//当*end == '\0',说明刚刚逆置的是最后一个单词,所以跳出该循环
if (*end == '\0')
{
break;
}
else
{
start = end + 1;
end = start;
}
}
//2.逆置整个字符串
start = arr;
end = arr + sz - 1;
reverse(start, end);
//输出字符串
printf("%s", arr);
return 0;
}
给元素排序且删除掉重复的元素
核心思想:我们假设一共有n个元素,且元素的值k满足 k>=0 && k<=n,那我们可以定义一个数组arr[n+1],也就是k的取值在下标范围之内,那么如果k的值为2,那我们就将2赋值给arr[2],如果k的值为80,那么我们就将80赋值给arr[80],这样就既保证了元素有序,也保证了元素不会重复。
但是要切记,使用这个思想的前提是k的取值在下标范围之内
假设0 ≤ n ≤ 100,0 ≤ k ≤ n代码如下:
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;//表示我们要排序的元素个数
int temp = 0;//输入几,就把它存放到下标为几的元素中
int arr[101] = { 0 };
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
scanf("%d", &temp);
arr[temp] = temp;
}
for (i = 1; i <= n; i++)
{
if (arr[i] != 0)
{
printf("%d ", arr[i]);
}
}
return 0;
}
删除指定元素
方法一:
对于一个数组我们想删除一个指定元素,只需要定义两个变量i和j,i不断向后遍历来寻找要删除的元素,j表示当前可以存放元素的位置,只要i所指向的元素不是要删除的元素,那么我们就可以将i所指向的元素赋值给j所指向的数组单元,然后i和j都自增1,如果i指向的元素是待删除元素,那么不进行赋值操作,j此时指向待删除元素,j不变,i自增1,如果后面的元素不是待删除元素,那么将i指向的元素赋值给j指向的位置,即实现了删除指定元素。
如图所示,假设我们要删除元素9,从上往下依次为执行过程:
#include <stdio.h>
int main()
{
int arr[50] = { 0 };
int n = 0;//记录有效序列的长度
scanf("%d", &n);
int i = 0;
int j = 0;
//输入要删除的值
int del = 0;
scanf("%d", &del);
//输入数据
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < n; i++)
{
if (arr[i] != del)
{
arr[j++] = arr[i];
}
}
//遍历输出
for (i = 0; i < j; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
方法二:
如果我们只是需要输出数组中的元素,那么可以直接跳过待删除元素进行输出,也就是依次遍历输出数组中的每个元素,当遇到指定元素时跳过不输出,继续输出后面的元素。这样做的缺点是表面上删除了指定元素,实际上并没有删除。
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d",&n);
int arr[50] = {0};
int i = 0;
int key = 0;//要删除的数字
scanf("%d",&key);//获取要删除的元素
//将元素存储到数组中
for(i = 0;i<n;i++)
{
scanf("%d",&arr[i]);
}
//跳过输出指定数字即删除指定数字
for(i = 0;i<n;i++)
{
if(arr[i] != key)
{
printf("%d ",arr[i]);
}
}
return 0;
}
左旋字符串
实现一个函数,可以左旋字符串中的k个字符。
常规思路:旋转k个字符串即旋转k次,每次旋转1个字符
void left_move(char* str, int k)
{
int i = 0;
//旋转k次
for (i = 0; i < k; i++)
{
//每次旋转一个字符
char tmp = *str;
int len = strlen(str);
int j = 0;
for (j = 0; j < len - 1; j++)
{
*(str + j) = *(str + j + 1);
}
*(str + len - 1) = tmp;
}
}
思路2:
左旋k个字符,我们只需要分三步走即可:
第一步,逆置第1个字符到第k个字符之间的字符
第二部,逆置第k+1个字符到最后1个字符之间的字符
第三步,逆置第1个字符至最后一个字符,即整体逆置一遍
#include<stdio.h>
#include<string.h>
void reverse(char* left, char* right)
{
while (left < right)
{
char temp = *right;
*right = *left;
*left = temp;
left++;
right--;
}
}
void leftMove(char* str, int k)
{
int len = strlen(str);
k %= len;
reverse(str, str + k - 1);
reverse(str+k, str + len-1);
reverse(str, str + len-1);
}
int main()
{
char str[] = "ABCD";
int k = 0;
scanf("%d", &k);
leftMove(str, k);
printf("%s\n", str);
return 0;
}
其它
如果元素取值与数组下标有关系,可将元素存在数组中,方便存取。
比如1月31 天,2月28/29天,3月31天…。月份的天数和月份1,2,3,4…相关,即与数组下标相关,故我可以将月份的天数存储在数组中:
int arr[12] = {31,28,31,30,31,30,31,30,30,31,30,31}
那么1月份的天数就是arr[0],2月份的天数就是arr[1]…