算法:位运算应用总结

权限

例如,在一个系统中,用户一般有查询(Select)、新增(Insert)、修改(Update)、删除(Delete)四种权限,四种权限有多种组合方式,也就是有16中不同的权限状态(2的4次方)。

一般情况下会想到用四个boolean类型变量来保存:

public class Permission {

	// 是否允许查询
	private boolean allowSelect;

	// 是否允许新增
	private boolean allowInsert;

	// 是否允许删除
	private boolean allowDelete;

	// 是否允许更新
	private boolean allowUpdate;

	// 省略Getter和Setter
}

上面用四个boolean类型变量来保存每种权限状态。

下面是另外一种方式,使用位掩码的话,用一个二进制数即可,每一位来表示一种权限,0表示无权限,1表示有权限。

public class NewPermission {
	// 是否允许查询,二进制第1位,0表示否,1表示是
	public static final int ALLOW_SELECT = 1 << 0; // 0001

	// 是否允许新增,二进制第2位,0表示否,1表示是
	public static final int ALLOW_INSERT = 1 << 1; // 0010

	// 是否允许修改,二进制第3位,0表示否,1表示是
	public static final int ALLOW_UPDATE = 1 << 2; // 0100

	// 是否允许删除,二进制第4位,0表示否,1表示是
	public static final int ALLOW_DELETE = 1 << 3; // 1000

	// 存储目前的权限状态
	private int flag;

	/**
	 *  重新设置权限
	 */
	public void setPermission(int permission) {
		flag = permission;
	}

	/**
	 *  添加一项或多项权限
	 */
	public void enable(int permission) {
		flag |= permission;
	}

	/**
	 *  删除一项或多项权限
	 */
	public void disable(int permission) {
		flag &= ~permission;
	}

	/**
	 *  是否拥某些权限
	 */
	public boolean isAllow(int permission) {
		return (flag & permission) == permission;
	}

	/**
	 *  是否禁用了某些权限
	 */
	public boolean isNotAllow(int permission) {
		return (flag & permission) == 0;
	}

	/**
	 *  是否仅仅拥有某些权限
	 */
	public boolean isOnlyAllow(int permission) {
		return flag == permission;
	}
}

如果不用任何数就交换两个变量的值

 	a = a ^ b;
    b = a ^ b;
    a = a ^ b;

^也叫做无进位加法,它有三个定律:

  • 定律一:0 ^ N = N
  • 定律二:N ^ N=0
  • 定律三:其加法满足结合律

上面就是结合律的应用

主要,上面的这种交换方法仅仅a和b分别指向不同的内存是才能交换,如果是同一个内存,那么就是第二条定律,会变为0

把int最右侧的1提取出来

在这里插入图片描述

t = a & (-a);

出现奇数次的数

一个数组中有一种数出现了奇数次,其他数出现了偶数次,怎么找出并打印这种数

// arr中,只有一种数,出现奇数次
void printOddTimesNum1(std::vector<int> &vec){
    int val = 0;
    for (int i : vec) {
        val ^= i;
    }
    std::cout << val;
}



int main(){
    std::vector<int> arr1  { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 };
    printOddTimesNum1(arr1);
}

第二定律 + 第三定律

一个数组中有两种数出现了奇数次,其他数出现了偶数次,怎么找出并打印这种数

关键在于:怎么把两种奇数次的数分离

(1)遍历一次数字,得到 eor = a ^ b
(2)因为eor一定不为0,所以eor二进制表示中一定有一个1。我们拿出这个1(随便哪个1都可以,这里规定最右侧的1),假设第三位为1
(3)二进制中有1,那么说明a、b这个位上一定不相同。同样的,对于所有的arr,一定也可以分为第三位为1的,和第三位为0的数。
(4)我们申请一个新的eor1,把那些第三位为1的数拉到eor1,这样就可以把a拉出来了。,也就是eor1 = a
(5)然后我们就可以拿到eor2=b了

// arr中,有两种数,出现奇数次
void printOddTimesNum2(std::vector<int> &arr){
    int eor = 0;
    for (int i = 0; i < arr.size(); ++i) {
        eor ^= arr[i];
    }

    // a 和 b是两种数
    // eor != 0
    // eor最右侧的1,提取出来
    // eor :     00110010110111000
    // rightOne :00000000000001000
    int rightOne = eor & (-eor); // 提取出最右的1

    int onlyOne = 0 ;
    for (int i = 0; i < arr.size(); ++i) {
        //  arr[1] =  111100011110000
        // rightOne=  000000000010000
        if((rightOne & arr[i]) == 0){
            onlyOne ^= arr[i];
        }
    }

    std::cout << onlyOne <<"\t" << (eor ^ onlyOne) <<"\n";
}

int main(){
    std::vector<int> arr1  { 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 };
    printOddTimesNum2(arr1);
}

一个数组中有两种数出现了M次,其他数出现了M次,并且M > 1, K > M,请怎么找出出现了K次的数,要求:额外空间复杂度O(1),时间复杂度O(N)

一个整形,是32位。对于一个数,比如8,那么它可以转换为一个32位的二进制数组

(1)因此,准备一个固定长度为32位的数组int []t
(2)然后将每一个数转为32位,并加到t中的某个位置上。
(3)对int []t每一个%M,然后转为num,就是答案

在这里插入图片描述

// 请保证arr中,只有一种数出现了K次,其他数都出现了M次
int onlyKTimes(std::vector<int> arr, int k, int m){
    // t[0] 0位置的1出现了几个
    // t[i] i位置的1出现了几个
    std::vector<int> t(32);
    for(int num : arr){
        for (int i = 0; i <= 31; ++i) {
            if (((num >> i) & 1) != 0){
                t[i]++;
            }
        }
    }

    int ans = 0;
    // 如果这个出现了K次的数,就是0
    // 那么下面代码中的 : ans |= (1 << i);
    // 就不会发生
    // 那么ans就会一直维持0,最后返回0,也是对的!
    for(int i = 0; i < 32; i++){
        if(t[i] % m != 0){  //
            ans |= (1 << i);
        }
    }
    return ans;
}

下面是对数器代码:


#include <random>     // mt19937 and uniform_int_distribution
#include <algorithm>  // generate
#include <vector>     // vector
#include <iterator>   // begin, end, and ostream_iterator
#include <functional> // bind
#include <iostream>   // cout
#include <chrono>
#include <map>
#include <set>


// 请保证arr中,只有一种数出现了K次,其他数都出现了M次
int onlyKTimes(std::vector<int> arr, int k, int m){
    // t[0] 0位置的1出现了几个
    // t[i] i位置的1出现了几个
    std::vector<int> t(32);
    for(int num : arr){
        for (int i = 0; i <= 31; ++i) {
            if (((num >> i) & 1) != 0){
                t[i]++;
            }
        }
    }

    int ans = 0;
    // 如果这个出现了K次的数,就是0
    // 那么下面代码中的 : ans |= (1 << i);
    // 就不会发生
    // 那么ans就会一直维持0,最后返回0,也是对的!
    for(int i = 0; i < 32; i++){
        if(t[i] % m != 0){  //
            ans |= (1 << i);
        }
    }
    return ans;
}



static int test(std::vector<int> arr, int k, int m){
    std::map<int, int> mp;
    for(int num : arr){
        mp[num]++;
    }


    for(auto it : mp){
        if(it.second == k){
            return it.first;
        }
    }

    return -1;
}




std::default_random_engine e;
int RandomNumber(int minValue, int maxValue){
    std::uniform_int_distribution<int> distV(minValue, maxValue);
    return distV(e);
}


// 请保证arr中,只有一种数出现了K次,其他数都出现了M次
std::vector<int> generateRandomArray(int maxkinds, int minValue, int maxValue, int k, int m) {
    int ktimeNum = RandomNumber(minValue, maxValue);
    int numKinds = RandomNumber(2, 2 + maxkinds);
    int size = k + (numKinds - 1) * m;
    std::vector<int> v(size);
    int index = 0;
    for (; index < k; ++index) {
        v[index] = ktimeNum;
    }
    numKinds--;
    std::set<int> set;
    set.insert(ktimeNum);
    while (numKinds != 0){
        // 要新的数
        int curNum = 0;
        do{
            curNum = RandomNumber(minValue, maxValue);
        }while (set.count(curNum));
        set.insert(curNum);
        numKinds--;
        for(int i = 0; i < m; i++){
            v[index++] = curNum;
        }
    }

    // 打乱v
    std::shuffle(v.begin(), v.end(), e);
    return v;
}
int main(){
   // e.seed(time(NULL));


    using std::chrono::high_resolution_clock;
    using std::chrono::duration_cast;
    using std::chrono::nanoseconds;

    auto start = high_resolution_clock::now();

    int kinds = 10;
    int range = 200;
    int testTime = 10000;
    int max = 9;
    std::uniform_int_distribution<int> distV(1, 9);
    for (int i = 0; i < testTime; ++i) {
        int a = distV(e);
        int b = distV(e);
        int k = std::min(a, b);
        int m = std::max(a, b);
        if(k == m){
            m++;
        }

        std::vector<int> random_data = generateRandomArray(kinds, -range, range, k, m);
        int ans1 = test(random_data, k, m);
        int ans2 = onlyKTimes(random_data, k, m);
        if(ans1 != ans2){
            std::cout << "出错了\r\n";
            std::cout << "["<< k << ", "<< m << "] arr: ";
            std::sort(random_data.begin(), random_data.end());
            copy(begin(random_data), end(random_data),
                 std::ostream_iterator<int>(std::cout, "\t"));
            std::cout <<std::endl;

            break;
        }
    }


    auto end = high_resolution_clock::now();

    std::cout << "测试结束: "<< duration_cast<nanoseconds>(end - start).count() << "ns\n";
    return 0;
}

往右找最接近n的奇数

i | n
#include <stdio.h>
int main(){
    for(int i = -10; i <  10; i++){
        printf("%d, %d\t\t\t", i, i | 1);
        if(i % 10 == 0){
            printf("\n");
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值