一、位运算运用:
1、判断奇偶数 x&1 1为奇数,0为偶数
2、获取二进制位是1还是0 x &(1<<n)判断第n位为1还是0
3、交换两个整数变量的值 A=A+B B=A-B A=A-B(A=A^B B=A^B A=A^B)
4、不用判读语句,求整数的绝对值 num * (1 - ((num >>> 31)<<1))
题1:找出唯一成对的数
➢1-1000这1000个数放在含有1001个元素的数组中,只有唯一- 的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
方法一:
根据题意1-1000都至少出现一次,其中一位数出现两次,即将1001个元素之和减去1-1000之和,剩下即为所求的成对数
方法二:
位运算,利用异或的自反性,相同的数的异或值为0,任何数与0异或为它本身。将1-1000这1000个数都与它本身异或一次后,再与剩下的这个数异或,得出的值就是唯一一个重复的数。
#include <bits/stdc++.h> using namespace std; int main() { int a[1005]; for(int i=1;i<=1000;i++){a[i]=i;cout<<a[i]<<" ";} a[1001]=random()%1000+1; cout<<a[1001]<<endl; int x1=0; for(int i=1;i<1001;i++){ x1 = x1^i; } for(int i=1;i<=1001;i++){ x1 = x1^a[i]; } cout<<endl<<x1<<endl; return 0; }
题2:找出落单的那个数
➢一个数组里除了某一个数字之外,其他的数字都出现了两次。请写出程序找出这个只出现一次的数字?
方法一:
利用桶排,用该数作为下标,出现一次即加一,最后输出计数为一的下标
#include <bits/stdc++.h> using namespace std; int main() { int x,max=0,a[1000]={0}; for(int i=1;i<=11;i++){ cin>>x; if(x>max)max=x; a[x]++; } for(int i=0;i<=max;i++){ if(a[i]==1){ cout<<i<<endl; break; } } return 0; }
方法二:
利用异或,对于任何数x ,都有x^x=0 , x^0=x ,同自己求异或为0 ,同0求异或为自己
#include <bits/stdc++.h> using namespace std; int main() { int a[1005]; for(int i=1;i<=11;i++){cin>>a[i];cout<<a[i]<<" ";} int x1=0; for(int i=1;i<=11;i++){ x1 = x1^a[i]; } cout<<endl<<x1<<endl; return 0; }
题3:二进制中1的个数
➢请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
例:9的二进制表示为1001,有2个为1
方法一:
除余
#include <bits/stdc++.h> using namespace std; int sum(int n){ int ans=0; while(n){ ans+=(n%2); n/=2; } return ans; } int main() { int x; cin>>x; cout<<sum(x)<<endl; return 0; }
方法二:
位运算(整数32位)
#include <bits/stdc++.h> using namespace std; int sum(int n){ int ans=0; for(int i=0;i<32;i++){ if((1<<i)&n)ans++; } return ans; } int main() { int n; cin>>n; cout<<sum(n)<<endl; return 0; }
题4:是不是2的整数次方
➢用一条语句判断一个整数是不是2的整数次方
分析:二进制中
4 100
8 1000
16 10000
即二进制中,若数x是2的整数次方,那么二进制表达式内只有一个1(2的0次方为1,2的-1次方为浮点数)
#include <bits/stdc++.h> using namespace std; int main() { int n; cin>>n; if((n-1)&n)cout<<"no"<<endl; else cout<<"yes"<<endl; return 0; }
题5:将整数的奇偶位互换
➢ 例:整数9
1001 ==> 0110
输出6
方法一:
将整数变成二进制的数组,再进行交换
#include <bits/stdc++.h> using namespace std; int main() { int n; cin>>n; int s[32]={0}; int i=0; while(n){ i++; s[i]=n%2; n/=2; } for(int j=i;j>0;j--){ if(j&1){ int t=s[j]; s[j]=s[j+1]; s[j+1]=t; } } int ans=0; if(i&1)i++; for(int j=i;j>0;j--){ ans+=(s[j]*pow(2,j-1)); } cout<<ans<<endl; return 0; }
方法二:
位运算
例 9 1001
ji:
ou:
9: 0 0 0 0 ...... 1 0 0 1 0x55555555 0 1 0 1 ...... 0 1 0 1 & 0 0 0 0 ...... 0 0 0 1
9: 0 0 0 0 ...... 1 0 0 1 0xaaaaaaaa 1 0 1 0 ...... 1 0 1 0 & 0 0 0 0 ...... 1 0 0 0 ans:
ou: 0 0 0 0 ...... 1 0 0 0 ==> 0 0 0 0 ...... 0 1 0 0 ji: 0 0 0 0 ...... 0 0 0 1 <== 0 0 0 0 ...... 0 0 1 0 ou' ^ ji' 0 0 0 0 ...... 0 1 1 0 #include <bits/stdc++.h> using namespace std; int main() { int n; cin>>n; int ou = n & 0xaaaaaaaa; //1010 1010 1010 .... int ji = n & 0x55555555; //0101 0101 0101 .... int ans = (ou>>1)^(ji<<1); cout<<ans<<endl; return 0; }
题6: 0~1 间浮点实数的二进制表示
➢给定一个介于0和1之间的实数,(如0.625) ,类型为double,打印它的二进制表示(0.101,
因为小数点后的二进制分别表示0.5,0.25,0.12......)。
➢如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”
例:
15转二进制,对2取余 1 1 1 1(8 4 2 1)
0.625转二进制,本数乘2取整
X2 二进制 剩余 0 0 0.625 ...... ...... ...... 1.25 1 0.25 0.5 0 0.5 1 1 0 ans: - 0.101 - #include <bits/stdc++.h> using namespace std; int main() { double n,ans=0; cin>>n; int i=0; while(n>0){ n = 2*n; i++; int x=0; if(n>=1){ n-=1; x=1; } ans+=(x*pow(10,-i)); } //判断错误,32位无法表示 if(i>32) cout<<"ERROR"<<endl; else cout<<ans<<endl; return 0; }
精度容易丢失,改进:
#include <bits/stdc++.h> using namespace std; int main() { double n; vector<char> v; v.push_back('0'); v.push_back('.'); cin>>n; while(n>0){ double t =2*n; if(t>=1){ v.push_back('1'); n=t-1; } else{ v.push_back('0'); n=t; } if(v.size()>34){ cout<<"ERROR"<<endl; return 0; } } for(int i=0;i<v.size();i++)cout<<v[i]; cout<<endl; return 0; }
题7:出现k次与出现1次
➢ 数组中只有一个数出现了1次,其他数都出现了k次,请输出只输出只出现了1次的数
方法一:
暴力,计数,桶排
方法二:
2个相同的2进制数做不进位加法,得到的结果为0 10个相同的10进制数做不进位加法,得到的结果为0 k个相同的k进制数做不进位加法,得到的结果为0 ① 转成k进制
② 转成10进制
#include <bits/stdc++.h> using namespace std; //n是待转换的十进制数,m是待转换成的进制数 string intToA(int n,int m){ string ans=""; do{ //使用do{}while()循环类型以防止输入为0的情况 int t=n%m; if(t>=0&&t<=9) ans+=(t+'0'); else ans+=(t+'a'-10); n/=m; }while(n); reverse(ans.begin(),ans.end()); return ans; } int main() { int arr[]={2,2,2,20,7,7,7,3,3,3,6,6,6,1,1,1}; // 数组长度 int N = sizeof(arr)/sizeof(int); // k可以是输入 int k=3,maxLen=0; int ans[100]={0}; for(int i=0;i<N;i++){ string x = intToA(arr[i], k); // k进制 reverse(x.begin(),x.end()); // 翻转相加 if(x.length()>maxLen)maxLen=x.length(); //记录k进制最大长度 for(int j=0;j<x.length();j++){ int temp = (x[j]-'0'); //字符转换到整数 ans[j]+=temp; //每位数相加 ans[j]%=k; //每位数对k取余,即不进位相加 } } //k进制转换为十进制 int sum=0; for(int j=maxLen-1;j>=0;j--){ sum+=(ans[j]*pow(k,j)); } cout<<sum<<endl; return 0; }
样例: 数组:2,2,2,20,7,7,7,3,3,3,6,6,6,1,1,1 k = 3 输出: 20
方法三:
map
#include <bits/stdc++.h> using namespace std; int main() { map <int,int> val; int arr[]={2,2,2,21,7,7,7,3,3,3,6,6,6,1,1,1,10,10,10,0,0,0}; int len =sizeof(arr)/sizeof(int); for(int i=0;i<len;i++){ int t = val[arr[i]]; t++; val[arr[i]]=t; } for(int i=0;i<len;i++){ if(val[arr[i]]==1){ cout<<arr[i]<<endl; } } return 0; }
任意进制转换
一、手工取余法
二、手写函数
//n是待转换的十进制数,m是待转换成的进制数 string intToA(int n,int m){ string ans=""; do{ //使用do{}while()循环类型以防止输入为0的情况 int t=n%m; if(t>=0&&t<=9) ans+=(t+'0'); else ans+=(t+'a'-10); n/=m; }while(n); reverse(ans.begin(),ans.end()); return ans; }
二、递归、查找、排序
1、递归设计
找重复(子问题)
找重复中的变化量-->参数
找参数变化趋势-->设计出口
2、练习策略
循环改递归
经典递归
练习、总结、套路
挑战
(1)、在重复中找变化、在变化中找重复--切蛋糕思维
#include <bits/stdc++.h> using namespace std; //求阶乘 int func_jiecheng(int n){ if(n==1)return 1; else return n*func_jiecheng(n-1); } //打印i-j void func_dayin(int i,int j){ if(i>j) return; cout<<i<<" "; func_dayin(i+1,j); } //数组求和 int func_qiuhe(int arr[],int l,int r){ if(l==r) return arr[l]; return arr[l]+func_qiuhe(arr,l+1,r); } //翻转字符串 /* a b c d --> d c b a (a b c) (d) --> (d)(a b c) (d)(a b) (c) --> (d)(c)(a b) (d)(c)(a) (b) --> (d)(c)(b)(a) */ string func_fanzhuan(string s,int end){ if(end==0) return ""+s[0]; return s[end]+func_fanzhuan(s,end-1); } int main() { // 阶乘 // int n; // cin>>n; // cout<<func_jiecheng(n)<<endl; // 打印i-j // int i,j; // cin>>i>>j; // func_dayin(i,j); // 数组求和 // int arr[]={1,2,3,4,5,56,7,8,9,1,12,1,34}; // int r = sizeof(arr)/sizeof(int); // cout<<func_qiuhe(arr,0,r-1)<<endl; // 翻转字符串 // cout<<func_fanzhuan("abcd",3)<<endl; return 0; }
(2)、多对分支递归--递推公式 or 等价转换
子问题
f( n / 2)+ f ( n / 2 )
f( n / k)+ f ( n / k ) + f ( n / k ) ( k个 )
① 斐波那契数列 f(n) = f(n-1) + f(n-2);
1 1 2 3 5 8 13 21 34
int func(int n){ if(n==1||n==2)return 1; else return func(n-1)+func(n-2); }
② 最大公约数 f(m,n) = f(n, m%n);
// 最大公约数 int gcd(int m,int n){ if(m%n==0)return n; return gcd(n,m%n); }
③ 插入排序改递归
void insort(int *arr, int k){ if(k<2)return; //对前k个元素排序 insort(arr,k-1); //把位置k的元素插入到前面的部分 int x = arr[k]; int index =k-1; while(x<arr[index]){ arr[index+1]=arr[index]; index--; } arr[index+1]=x; }
④ 汉诺塔的规则:
1、有三根相邻的柱子,标号为A,B,C。
2、A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘。
3、现在把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。
算法过程原理:
- n个盘子时:
- 把n-1个圆盘从A经过C移到B上
- 把第n个圆盘从A移到C上
- 把n-1个小圆盘经过A从B移到C上
求解移动步骤:
1~N从A到B,C作为辅助
把1~N-1从A挪到C,把N从A到B,此时规模变成了1~N-1(B可视为空)
下一步:
1~N-1从C到B,A作为辅助
#include <bits/stdc++.h> using namespace std; void han(int N, string from, string to, string help){ if(N==1){ cout<<"move "<<N<<" from "<<from<<" to "<<to<<endl; return; } //把前n-1个盘子移动到辅助空间上 han(N-1,from,help,to); //此时第N个盘子可移到目标空间 cout<<"move "<<N<<" from "<<from<<" to "<<to<<endl; //把N-1从辅助空间上移动到原空间上 han(N-1,help,to,from); } int main(){ han(3,"A","B","C"); return 0; }
⑤ 二分查找递归形式
int barsort(int *arr,int low,int high,int key){ if(low>high) return -1; int mid=low+((high-low)>>1);//(low+high>>>1)防止溢出 int midval = arr[mid]; if(midval<key)return barsort(arr, mid+1, high, key); else if(midval>key)return barsort(arr, low, high-1, key); else return mid; }
⑥ 希尔排序
#include <bits/stdc++.h> using namespace std; void shellsort(int *arr,int len){ //不断缩小增量 for(int interval = len/2 ;interval > 0;interval/=2){ //增量为1的插入排序 for(int i=interval;i<len;i++){ int target = arr[i]; int j=i-interval; while(j>-1&&target<arr[j]){ arr[j+interval]=arr[j]; j-=interval; } arr[j+interval]=target; } } } int main(){ int arr[]={1,2,3,12,4,32,5,7,54,0,9,8765,4,34,76,5,1,4}; int len=sizeof(arr)/sizeof(int); shellsort(arr,len); for(int i=0;i<len;i++){ cout<<arr[i]<<" "; } return 0; }
(3)、例题
① 小白上楼梯
小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式?
int func(int n){ if(n==1)return 1; if(n==2)return 2; if(n==3)return 4; return func(n-1)+func(n-2)+func(n-3); }
② 最长有序子序列(部分有序)
{1,9,2,5,7,3,4,6,8,0}中最长的有序子序列为(3,4,6,8)
双指针移动更新计数