算法26-----位运算

目录:

  技巧:

应用技巧1:x&(x-1)【消除x(二进制)最后一位1】

        • 判断是否为2的幂

应用技巧2:<<左移【除2】

        • 判断是否为2的幂
        • 判断是否为4的幂

应用技巧3:>>右移【×2】结合&1【判断最后一位是否为1】

应用技巧4:子集【根据二进制来打印子集】

应用技巧5:异或【两个相同的数异或结果为0】

题目:

      1. 如何不用额外空间交换两个变量
      2. 不用任何比较判断找出两个数中较大的数
      3. 只用位运算不用算术运算实现整数的加减乘除运算
      4. 整数的二进制表达中有多少个1
      5. 在其他数都出现偶数次的数组中找到出现奇数次的数
      6. 在其他数都出现K次的数组中找到只出现一次的数
      7. 请设置一种加密过程,完成对明文text的加密和解密工作。
      8. 数的幂【 举例:10^1101 = 10^0001*10^0100*10^1000】
      9. 吃糖果
      10. 分硬币

技巧:

1、应用技巧1:与&【消除x(二进制)最后一位1】

def checkPowerof2(x):
    return x>0 and x&(x-1)==0

应用技巧二:判断n是否为4的幂:

def isFour(n):
    m=1
    while m<n:
        m=m<<2
    return m==n

 

 3、应用技巧3:>>右移【×2】结合&1 

题目:

  You are given an array A1,A2...AN. You have to tell how many pairs (i, j) exist such that 1 ≤ i < j ≤ N and Ai XOR Aj is odd.

  

思路:判断列表数据中最后一位是0的个数和1的个数,两者相乘即为结果。

代码:

T = int(input())
for i in range(T):
    N = int(input())
    res = 0
    arr = list(map(int,input().split()))
    odd , even = 0 , 0
    for i in range(N):
        if arr[i]&1:
            odd += 1
        else:
            even += 1
    print(odd * even)

 

4、应用技巧4:子集

// Non Recursion
class Solution {
    /**
     * @param S: A set of numbers.
     * @return: A list of lists. All valid subsets.
     */
    public List<ArrayList<Integer>> subsets(int[] nums) {
        List<ArrayList<Integer>> result = new ArrayList<List<Integer>>();
        int n = nums.length;
        Arrays.sort(nums);
        
        // 1 << n is 2^n
        // each subset equals to an binary integer between 0 .. 2^n - 1
        // 0 -> 000 -> []
        // 1 -> 001 -> [1]
        // 2 -> 010 -> [2]
        // ..
        // 7 -> 111 -> [1,2,3]
        for (int i = 0; i < (1 << n); i++) {
            List<Integer> subset = new ArrayList<Integer>();
            for (int j = 0; j < n; j++) {
                // check whether the jth digit in i's binary representation is 1
                if ((i & (1 << j)) != 0) {
                    subset.add(nums[j]);
                }
            }
            result.add(subset);
        }
        
        return result;
    }
}

 

5、应用技巧5:异或

 

java代码:

public class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int i = 0; i < nums.length; i++){
            ones = (ones ^ nums[i]) & ~twos;
            twos = (twos ^ nums[i]) & ~ones;
        }
        return ones;
    }
}

public class Solution {
    public int[] singleNumber(int[] nums) {
        //用于记录,区分“两个”数组
        int diff = 0;
        for(int i = 0; i < nums.length; i ++) {
            diff ^= nums[i];
        }
        //取最后一位1
        //先介绍一下原码,反码和补码
        //原码,就是其二进制表示(注意,有一位符号位)
        //反码,正数的反码就是原码,负数的反码是符号位不变,其余位取反
        //补码,正数的补码就是原码,负数的补码是反码+1
        //在机器中都是采用补码形式存
        //diff & (-diff)就是取diff的最后一位1的位置
        diff &= -diff;
        
        int[] rets = {0, 0}; 
        for(int i = 0; i < nums.length; i ++) {
            //分属两个“不同”的数组
            if ((nums[i] & diff) == 0) {
                rets[0] ^= nums[i];
            }
            else {
                rets[1] ^= nums[i];
            }
        }
        return rets;
    }
}

 题目:

1、如何不用额外空间交换两个变量:

2、不用任何比较判断找出两个数中较大的数

法一:得到a-b的值的符号,若a-b是负数,则返回b,否则a。【存在溢出风险,a、b正负不同时,相减会溢出】

法二:

 

c = a-b,sign(c),sign(a),sign(b):表示a,b,c的正负

sign(a) == sign(b):则不会溢出,sign(c)>0则返回a,否则返回b

sign(a) != sign(b):返回sign(a)、sign(b)大的一个。

3、只用位运算不用算术运算实现整数的加减乘除运算

给定两个32位整数a,b,可正,可负,可0,不能使用算术运算符,分别实现a和b的加减乘除运算。

加法思路:

两个运算【异或】以及【&加左移<<】。

不考虑进位:a^b

仅考虑进位:(a&b)<<1

将上面两个不断相加直到没有进位产生为最终结果。

代码:

def add(a,b):
    sum = a
    while b!=0:
        sum = a^b
        b = (a&b)<<1
        a = sum
    return sum
a = 19
b = 12
add(a,b)

 减法思路:a-b 实现a+(-b)即可。b取反加1(补码)即为-b

#加法
def add(a,b):
    sum = a
    while b!=0:
        sum = a^b
        b = (a&b)<<1
        a = sum
    return sum

#减法
#取反加1得-b
def negNum(b):
    return add(~b,1)
#a+(-b)
def minus(a,b):
    return add(a,negNum(b))
    
a = 19
b = 12
minus(a,b)

乘法运算:

代码:

除法思路:

代码:

4、整数的二进制表达中有多少个1

给定一个32位整数n,可为0,可为正,也可为负,返回该整数二进制表达中1的个数。

简单思路:右移判断1的个数(循环32次)

循环次数更少的思路:1的个数次数

 法3:循环次数和法2一样:

 

法四:平行算法:

 

5、在其他数都出现偶数次的数组中找到出现奇数次的数

给定一个整型数组arr,其中只有一个数出现了奇数次,其他的数都出现了偶数次i,打印这个数。

进阶问题:有两个数出现了奇数次,其他的数都出现了偶数次,打印这两个数。

进阶思路:先将数组所有数字异或,最后结果就是两个出现一次的数字相互异或的结果,再将这两个数分别分在两个数组中进行异或。

 

https://www.cnblogs.com/Lee-yl/p/9123793.html

6、在其他数都出现K次的数组中找到只出现一次的数

 给定一个整型数组arr和一个大于1的整数k。已知arr中只有1个数出现了1次,其他的数都出现了k次,请返回只出现了1次的数。

思路:时间O(N),空间O(1)

定律:k个相同的k进制无进位相加的结果为0.
例如: 两个相同的2进制 100110101
100110101
无进位相加 结果 0000000

解法:把数组中的每个元素看成k进制 , 所有元素相加后得到的k进制结果就是 那个只出现一次的数,然后转成相应的十进制,得解。

详细解释:

一个重要结论:

解该题:

代码:

 

def onceNum(arr,k):
    eO = [0] * 32
    for i in range(len(arr)):
        setExclusiveOr(eO,arr[i],k)
    res = getNumFromKSysNum(eO,k)
    return res
#获得所有的K进制无进位加和
def setExclusiveOr(eO,value,k):
    curKSysNum = getKSysNumFromNum(value,k)
    for i in range(len(eO)):
        eO[i] = (eO[i] + curKSysNum[i]) % k
#获得K进制,res返回的是k进制
def getKSysNumFromNum(value,k):
    res = [0] * 32
    index = 0
    while value != 0:
        res[index] = value % k
        index += 1
        value = value // k
    return res

#将结果的那个K进制数转成十进制数输出
def getNumFromKSysNum(eO,k): res = 0 for i in range(len(eO) - 1,-1,-1): res = res * k + eO[i] return res arr = [1,1,1,2,2,2,4] k = 3 onceNum(arr,k)

 

 

7、请设置一种加密过程,完成对明文text的加密和解密工作。

 

8、数的幂

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

思路:

写出指数的二进制表达,例如13表达为二进制1101。 通过&1和>>1来逐位读取1101,为1时将该位代表的乘数累乘到最终结果。

 举例:10^1101 = 10^0001*10^0100*10^1000

代码:

    def Power(self, base, exponent):
        # write code here
        #return math.pow(base,exponent)
        
        if base == 0 :
            return 0
        if exponent == 0:
            return 1
        elif exponent < 0:
            n = -exponent
        else:
            n = exponent
        res = 1
        cur = base
        while n:
            if (n&1) == 1:
                res *= cur
            cur *= cur
            n = n >> 1
        return res if exponent > 0 else (1/res)

 

9、吃糖果

思路:所以的数二进制求和sum,然后判断sum中有几个1。

  【1 ,1,2,3,3】--->【1,1,10,11,11】---> sum(1,1,10,11,11) --->1010【两个1,答案为2】

  代码:

10、今年腾讯的一道笔试题:拼凑硬币

题目描述:小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^k的硬币,所有小Q拥有的硬币就是1,1,2,2,4,4,8,8.....小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少种方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案)

输入:

输入包括一个整数n(1<=n<=10^18),表示小Q需要支付多少钱,注意n的范围

输出:

输出一个整数,表示小Q可以拼凑出n元钱的方案数

样例输入:6

样例输出:3 (例如4+2, 4+1+1, 2+2+1+1)

一个很秒的思路:https://blog.csdn.net/xiaoquantouer/article/details/78076764

将硬币分为两份:1,2,4,8,16,... 和1,2,4,8,16,...

组成两个数值为a, b的两个数,他们的和是a+b=n;

a在第一份中只有可能有一种组合方式(采用二进制的思想,任何一个数都可以由若干个2^i的数相加的和组成),而b = n-a也只会有一种组合形式。

将a和b使用二进制表示,举个例子n=11,有a=101, b=110这种组合,即a = 1+0+4=5,b=0+2+4=6。但是注意到会有重复的情况,比如111+100和101+110本质上是同一种组合方法,所以需要将二个数做二进制异或然后去重。

  代码:

#include <iostream>
#include<set>
using namespace std;
 
int main()
{
    int n;
    while(cin>>n){
        set<int> countset;
        for(int i=1;i<=n/2;i++){
            int result =i^(n-i);
            countset.insert(result);
        }
        cout<<countset.size()<<endl;
    }
    return 0;
}

 

 

  

 

 

 

posted on 2018-09-04 20:40  吱吱了了  阅读(430)  评论(0编辑  收藏  举报

导航