zoukankan      html  css  js  c++  java
  • Bit(位操作)

    1.1一个整型数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这一个只出现一次的数字。

    思路:所有数字异或,最后结果就是只出现一次的数。

        public int singleNumber(int[] nums) {
            int length = nums.length;
            if(length == 1){
                return nums[0];
            }
            int bitResult = 0;
            for(int i = 0; i < length; ++i){
                bitResult ^= nums[i];
            }
            return bitResult;
        }
    View Code

    1.2一个整型数组里除了一个数字之外,其他的数字都出现了三次。请写程序找出这一个只出现一次的数字。

    1.3 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

    思路:位运算中异或的性质:两个相同数字异或=0一个数和0异或还是它本身

    只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
    依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字
    //num1,num2分别为长度为1的数组。传出参数
    //将num1[0],num2[0]设置为返回结果
    public class Solution {
        public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
            int length = array.length;
            if(length == 2){
                num1[0] = array[0];
                num2[0] = array[1];
                return;
            }
            int bitResult = 0;
            for(int i = 0; i < length; ++i){
                bitResult ^= array[i];
            }
            //此时bitResult是两个只出现一次的数字相异或的结果
            int index = findFirst1(bitResult);//z知道第一个1出现的位置
            for(int i = 0; i < length; ++i){
                if(isBit1(array[i], index)){
                    num1[0] ^= array[i];
                }else{
                    num2[0] ^= array[i];
                }
            }
        }
        private int findFirst1(int bitResult){
            int index = 0;
            while(((bitResult & 1) == 0) && index < 32){
                bitResult >>= 1;
                index++;
            }
            return index;
        }
        private boolean isBit1(int target, int index){
            return ((target >> index) & 1) == 1;
        }
    }
    View Code

    另一种解法:

    public class Solution {
        public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
            ArrayList<Integer> list = new ArrayList<Integer>();
            for (int i = 0; i < array.length; i++)
            {
                if (!list.contains(array[i])) {
                    list.add(array[i]);
                } else {
                    list.remove(new Integer(array[i]));
                }
            }
            if (list.size()>1){
                num1[0] = list.get(0);
                num2[0] = list.get(1);
            }
        }
    }
    View Code

    1.给定两个32位的整数M与N,以及表示比特位置的i与j。编写一个方法,将M插入N,使得M从N的第j位开始,到第i位结束。假定从j位到i位足以容纳M,也即若M=10011,那么j和i之间至少可容纳5个位。例如,不可能出现j=3和i=2的情况,因为第3位和第2位之间放不下M。

    示例输入:N=10000000000,M=10011,i=2,j=6,输出:N=10001001100

    思路:问题的解决分为三个步骤:(1)将N中从j到i之间的位清零:利用掩码来清零,除j到i之间的位为0外,这个掩码的其余位均为1。先创建掩码的左半部分,然后是右半部分,最终得到整个掩码。(2)对M执行移位操作,与j和i之间的位对齐(3)合并M与N

    public class bitInsert {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println(updateBits(4, 1, 1, 1));
        }
        
        public static int updateBits(int n, int m, int i, int j) {
            //创建掩码,用来清除n中i到j的位  例:i=2, j=4  掩码应该为11100011
            int allones = ~0;//等同于一连串的1
            //在位置j之前的位均为1,其余为0 11111111左移5位 left=11100000
            int left = allones << (j + 1);
            //在位置i之后的位均为1 right=00000100-00000001=00000011
            int right = ((1 << i) - 1);
            //除i到j的位为0,其余位均为1 mask= 11100011
            int mask = left | right;
            //清除位置j到i的位,然后将m放进去
            int n_cleared = n & mask;//清除j到i的位
            int m_shifted = m << i;//将m移至相应的位置
            return n_cleared | m_shifted;//对两者执行位或操作
        }
    
    }
    View Code

    2.给定一个介于0和1之间的实数(如0.72),类型为double,打印它的二进制表示。如果该数字无法准确地用32位以内的二进制表示,则打印“ERROR”。

    思路:为了打印小数部分,可以将这个数乘以2, 检查2n是否大于或等于1。实质上等同于“移动”小数部分,也即:r=2(10进制)*n。若r>=1,可知n的小数点后面正好有一个1。不断重复上述步骤,可以检查每个数位。

        public static String printBinary(double num) {
            //确保是介于0和1之间的实数
            if (num >= 1 || num <= 0) {
                return "ERROR";
            }
            StringBuilder binary = new StringBuilder();
            binary.append(".");
            while (num > 0) {
                //设定长度上限:32个字符
                if (binary.length() >= 32) {
                    return "ERROR";
                }
                
                double r = num * 2;
                if (r >= 1) {
                    binary.append("1");
                    num = r - 1;
                } else {
                    binary.append("0");
                    num = r;
                }
            }
            return binary.toString();
        }
    View Code

    3.给定一个正整数,找出与其二进制表示中1的个数相同、且大小最接近的那两个数 (一个略大,一个略小)。

    解法一:蛮力法:在n的二进制表示中,数出1的个数,然后增加或减小,直至找到1的个数相同的数字。

        public static int[] getCloseNumber(int x) {
             int[] res = new int[2];//保存结果的数组
             int i = 1;
             int num = oneNumber(x);
             while (res[0] == 0 || res[1] == 0){
                 if (oneNumber(x - i) == num && res[0] == 0) {
                     res[0] = x - i;
                 }
                 if (oneNumber(x + i) == num && res[1] == 0) {
                     res[1] = x + i;
                 }
                 i++;
             }
             return res;
         }
         //获取x的二进制表示中1的个数
         public static int oneNumber(int x) {
             String str = Integer.toBinaryString(x);
             int res = 0;
             for (int i = 0; i < str.length(); i++) {
                 if (str.charAt(i) == '1'){
                     res++;
                 }
             }
             return res;
         }
    View Code

    解法二:位操作法

    解法三:算术解法

    4.解释代码((n & (n-1)) ==0)的具体含义。

    含义:检查n是否为2的某次方(或者检查n是否为0)。

    5.编写一个函数,确定需要改变几个位,才能将整数A转成整数B。

    思路:使用异或(XOR)操作找出两个数之间有哪些位不用,也即异或操作的结果C中有几个1。

    解法一:不断对C执行移位操作,然后检查最低有效位。

        public static int bitSwapRequired(int a, int b) {
            int count = 0;
            for (int c = a ^ b; c != 0; c = c >> 1) {
                count += c & 1;
            }
            return count;
        }
    View Code

    解法二:不断翻转最低有效位,计算要多少次C会变成0。操作C=C&(C-1)会清除C的最低有效位。

        public static int bitSwapRequired(int a, int b) {
            int count = 0;
            for (int c = a ^ b; c != 0; c = c & (c - 1)) {
                count++;
            }
            return count;
        }
    View Code

    6.编写程序,交换某个整数的奇数位和偶数位,使用指令越少越好(也就是说,位0与位1交换,位2与位3交换,以此类推)。

    思路:先操作奇数位,再操作偶数位。可以用10101010(即0xAA)作为掩码,提取奇数位,并将它们右移1位,移到偶数位的位置。对于偶数位,可以施以相同的操作。最后,将两次操作的结果合并成一个值。

        public int swapOddEvenBits(int x) {
            return ( ((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1) );
        }
    View Code

    上述代码实现的是32位整数。如果要处理64位整数,需要修改掩码。

    7.数组A包含0到n的所有整数,但其中缺了一个。在这个问题中,只用一次操作无法取得数组A里某个整数的完整内容。此外,数组A的元素皆以二进制表示,唯一可用的访问操作是“从A[i]取出第j位数据”,该操作的时间复杂度是常数。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

    思路:类似的问题:给定一列0到n的数,其中只缺一个数字,把这个数找出来。直接将这列数相加,然后与0到n的总和(即n*(n-1)/2)进行比较,两者差值就是那个缺失的数。

    题目中说了所有的整型数都是以二进制数表示的,而且可以以O(1)的时间来访问任意一位,这也很明显的提示了我们用位操作。

    先来看一个n=13的例子,那么0到13的二进制数表示如下:

    0000       00100        0100       01100
    00001        00101        01001        01101
    00010        0011       01010
    00011        00111        01011

    然后观察上面数字的最低有效位Least Significant Bit (LSB),然后统计1和0的个数,可以得出一个规律:当n是奇数时,0和1的个数相等,当n是偶数时,0比1多一个。

    那么当移除一个数后,就有下面四种情况:

    N为偶数,0比1多一个

    N为奇数,0和1个数相等

    移除0

    0和1个数相等

    0比1个数少

    移除1

    0比1个数多

    0比1个数多

    从上表可以看出来比较LSB的1和0的个数就可以知道移除的是1还是0,只要0的个数小于等于1,就是移除0,若0的个数大于1,就是移除1.一旦知道最低有效位移除的值,那么把所有不符合要求的都去掉,然后再用相同方法来判断倒数第二低有效位,以此类推直到所有的数字都被移除了,具体过程参见如下,当n=13,且移除了一个数:

    00000        00100        0100       01100
    0000       0010       01001        01101
    00010        00110        01010
    ------           00111        01011

    那么我们来统计最低有效位的0和1的个数,发现0比1个数多,由此我们知道移除的数的最低有效位是1,那么我们把所有最低有效位是0的数都去掉:

    00000        00100        01000        01100
    00001        00101        01001        01101
    00010        00110        01010
    ------           00111        01011

    我们再来统计倒数第二低有效位的0和1的个数,我们发现0比1个数多,那么移除数的倒数第二低有效位还是1,同样把其他的去掉:

    00000        00100        01000        01100
    00001        00101        01001        01101
    00010        00110        01010
    ------           00111        01011

    我们再来统计倒数第三低有效位的0和1的个数,我们发现0比1个数相等,那么移除数的倒数第三低有效位是0,同样把其他的去掉,那么就只剩下一个了:

    01011

    那么倒数第四低有效位的0比1个数小,移除的是0,把这个不是0的删掉,则数组为空了,我们就可以停止了,把所有移除的组合起来就得到0011,也就是最后的答案。时间复杂度为O(N)。

        public static int findMissing(ArrayList<BitInteger> array) {
            //bit 0位对应于LSB(最低有效位)。以此为起点,逐步向较高的位推进
            return findMissing(array, 0);
        }
        public static int findMissing(ArrayList<BitInteger> input, int column) {
            //终止条件与错误条件
            if (column >= BitInteger.INTEGER_SIZE) {
                return 0;
            }
            //记录符合条件的倒数第column位是0或1的数的数组
            ArrayList<BitInteger> oneBits = new ArrayList<BitInteger>(input.size()/2);
            ArrayList<BitInteger> zeroBits = new ArrayList<BitInteger>(input.size()/2);
            for (BitInteger t : input) {
                //数t的第column位等于0
                if (t.fetch(column) == 0) {
                    zeroBits.add(t);
                } else {
                    oneBits.add(t);
                }
            }
            if (zeroBits.size() <= oneBits.size()) {
                int v = findMissing(zeroBits, column + 1);//递归的方式计算出v的其他位
                return (v << 1) | 0;//插入0
            } else {
                int v = findMissing(oneBits, column + 1);
                return (v << 1) | 1;//插入1
            }
        }
    View Code

    8.有个单色屏幕存储在一个一维字节数组中,使得8个连续像素可以存放在一个字节里。屏幕宽度为w,且w可被8整除(即一个字节不会分布在两行上),屏幕高度可由数组长度及屏幕宽度推算得出。请实现一个函数drawHorizontalLine(byte[] screen, int width, int x1, int x2, int y),绘制从点(x1, y)到点(x2, y)的水平线。

    思路:首先计算x1和x2之间包含几个完整字节,使用screen[byte_pos]=0XFF,一次就能设定一整个字节。这条线起点和终点剩余部分的位,使用掩码设定。

    分析:

    绘制水平线本质上是找到水平线上的起点和终点,然后连接,起点和终点都已经确认,这个应该没什么难度。

    由于是一维数组,首先明确在屏幕中:x代表水平方向,也就是实际意义上的竖轴(列),y代表垂直方向,代表了矩阵中实际意义的横轴(行) 。

    假设给定的像素位置为(x,y),那么它实际在一维数组的位置p=x+w*y,所以起点(x1,y)在是screen[x1+w*y]中的字节,(x2,y)对应screen[x2+w*y]字节,找到两个字节后调用drawLine函数绘制 。

    书上解法:

    绘线的本质实际上将线上的所有像素的颜色都设置为同一个颜色。那么暴力破解解法就是遍历从(x1,y)到(x2,y)上所有经过的像素设置颜色,时间复杂度为O(x2-x1)。

    注意:给定的x是像素的横坐标而不是字节,一个像素占据一个bit

    关键: 

    1 绘线的本质实际上将线上的所有像素的颜色都设置为同一个颜色
    2
    确定比特x1对应的字节x1Byte = x1 / 8,以及起始比特位x1Bit = x1 % 8
    确定比特x2对应的字节x2Byte = x2 / 8, 以及结束比特位x2Bit = x2 % 8
    如果x1Bit不为0,说明x1Byte不是完整的字节,令起始字节startByte = x1Byte + 1;否则,令startByte = x1Byte
    如果x2Bit不为7,说明x2Byte不是完整的字节,令结束字节endByte = x2Byte - 1;否则,令endByte = x2Byte
    将startByte~endByte中字节全部设置为0xff(即白色)
    设置左边不完整字节(左边起始字节)掩码,用于后续“与“”运算,必须将不完整字节前面x1Bit位排除,即前面x1Bit位为0,后面8-x1Bit位为1, 所以左边掩码leftMask = 0xff >> x1Bit
    设置右边不完整字节(右边最后字节)掩码,必须将x2Bit位包含,即前面x2Biit(包含x2Bit位本身)位为1,后面8-x2Bit位为0, 所以右边掩码rightMask = ~ ( 0xff >> (x2Bit + 1) )
    还需要判断x1与x2对应的字节是否在同一个字节中,因为如果在同一个字节中,需要对左边掩码和右边掩码进行与操作才能得到真实的掩码
         mask = leftMask & rightMask
         对应字节在screen中位置pos = w * y/8 + x1Byte
         对应的字节为 screen[ pos ] |= mask
         [易错,容易漏掉] 
    如果不在同一个字节,则对左边不完整字节,掩码处理
         posLeft = w * y/8 + x1Byte
         screen[ posLeft ] |= leftMask
    右边不完整字节取掩码
         posRight = w * y/8 + x2Byte
         screen[ posRight ] |= rightMask
    结束
    3 screen.at(pos) |= leftMask;//注意是“或“”运算,通过1来设置颜色,其余保持字节原先的颜色
    如果用与,那么掩码中0的部分会使得像素颜色发生变化,而我们真正需要的是掩码中的1,
    掩码中的0应该不起作用,所以用或运算。

        public static void drawLine(byte[] screen, int width, int x1, int x2, int y) {
            int start_offset = x1 % 8;//起始比特位
            int first_full_byte = x1 / 8;//比特(bit)x1对应的字节(byte)
            if (start_offset != 0) {//比特x1对应的字节不完整
                first_full_byte++;//起始字节加1
            }
            
            int end_offset = x2 % 8;//终止比特位
            int last_full_byte = x2 / 8;//比特(bit)x2对应的字节(byte)
            if (end_offset != 7) {//比特x2对应的字节不完整
                last_full_byte--;//终止字节减1
            }
            
            // 设置完整的字节
            for (int b = first_full_byte; b <= last_full_byte; b++) {
                screen[(width / 8) * y + b] = (byte) 0xFF;
            }
            //创建用于线条起点和终点的掩码
            //将不完整字节前面start_offset Bit位排除,即前面start_offset Bit位为0,后面8-start_offset Bit位为1
            byte start_mask = (byte) (0xFF >> start_offset);
            //将end_offset Bit位包含,即前面end_offset Bit(包含end_offset Bit位本身)位为1,后面8-end_offset Bit位为0
            byte end_mask = (byte) ~(0xFF >> (end_offset + 1));
            
            // 设定线条的起点和终点
            if ((x1 / 8) == (x2 / 8)) { //x1和x2位于同一字节
                byte mask = (byte) (start_mask & end_mask);//对左边掩码和右边掩码进行与操作才能得到真实的掩码 
                screen[(width / 8) * y + (x1 / 8)] |= mask;
            } else {
                if (start_offset != 0) {
                    int byte_number = (width / 8) * y + first_full_byte - 1;
                    screen[byte_number] |= start_mask;
                }
                if (end_offset != 7) {
                    int byte_number = (width / 8) * y + last_full_byte + 1;
                    screen[byte_number] |= end_mask;
                } 
            }
        }
    View Code
  • 相关阅读:
    Flask之模型字段解析与OA建模实训
    CentOS7下部署Django项目详细操作步骤
    多线程爬虫之生产者和消费者模式
    Flask的函数视图与类视图
    经典算法题之约瑟夫环与全排列
    selenium之滑块验证码破解代码详解
    基于CentOS7的MySQL数据库主从备份
    CentOS7下部署Flask项目部署
    selenium的学习和使用
    缓冲区溢出
  • 原文地址:https://www.cnblogs.com/struggleli/p/7891437.html
Copyright © 2011-2022 走看看