算法练习 位运算/递归

一、位运算运用:

        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:

9:0000......1001
0x555555550101......0101
&0000......0001
                ou:
9:0000......1001
0xaaaaaaaa1010......1010
&0000......1000

                  ans:

ou:0000......1000
==>0000......0100
ji:0000......0001
<==0000......0010
ou' ^ ji'0000......0110
#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二进制剩余
000.625
..................
1.2510.25
0.500.5
110
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;    
}

参考:http://t.csdn.cn/v0Ggg 

 

二、递归、查找、排序

        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到BC作为辅助

        把1~N-1从A挪到C,把N从A到B,此时规模变成了1~N-1(B可视为空)

        下一步:

        1~N-1从C到BA作为辅助

#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)

        

        双指针移动更新计数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值