位移操作

  1. 基础:
  • << : 左移运算符,num << 1,相当于num乘以2
  • >> : 右移运算符,num >> 1,相当于num除以2
  • >>> : 无符号右移,忽略符号位,空位都以0补齐
  • 原码,反码,补码。正数的原码,反码,补码都一样,负数的反码是对除了符号位(最高位)对原码取反,补码是对反码+1
  • 负数的带符号移位时,最高位——符号位,不变
  1. 判断2的次方数
    如果一个数是2的次方数的话,根据上面分析,那么它的二进数必然是最高位为1,其它都为0,那么如果此时我们减1的话,则最高位会降一位,其余为0的位现在都为变为1,那么我们把两数相与,就会得到0,用这个性质也能来解题,而且只需一行代码就可以搞定,如下所示
class Solution {
public:
    public boolean isPowerOfTwo(int n) {
        if(n > 0)
        {
            if((n & (n - 1)) == 0)
                return true;
        }
        
        return false;
    }
};
  1. 判断3的次方数
    高中学过的换底公式为logab = logcb / logca,那么如果n是3的倍数,则log3n一定是整数,我们利用换底公式可以写为log3n = log10n / log103,注意这里一定要用10为底数,不能用自然数或者2为底数,否则当n=243时会出错,原因请看这个帖子。现在问题就变成了判断log10n / log103是否为整数,在c++中判断数字a是否为整数,我们可以用 a - int(a) == 0 来判断
class Solution {
public:
     public boolean isPowerOfFour(int num) {
        return (num>0 && (isIntegerForDouble(Math.log(num)/ Math.log(3))) );
    }
    
    public static boolean isIntegerForDouble(double obj) {  
        double eps = 1e-10;  // 精度范围  
        return obj-Math.floor(obj) < eps;  
    } 
};
  1. 判断4的次方数
    同上,log的换底公式
  2. 按位翻转
    思路:每次取最右位的数字,然后判断是1还是0
public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        int result = 0;
        
        
        for(int i=0;i<32;i++){
            if((n&1) == 1){
                result = (result <<1) +1;
            }else{
                result = (result <<1);
            }
            n=n>>1;
        }
        
        return result;
    }
}
  1. 找到数组里落单的一个数字——数组里除了一个数字出现1次之外,其他都出现了2次
    采用异或的方法。
class Solution {
    public int singleNumber(int[] nums) {
        if(nums == null || nums.length == 0)
            return -1;
        
        int result = 0;
        
        for(int cur: nums){
            result ^= cur;
        }
        
        return result;
    }
}
  1. 找到s和t字符串之间,唯一不同的那个元素char——思路同6
class Solution {
    public char findTheDifference(String s, String t) {
        char[] a = s.toCharArray();
        char[] b = t.toCharArray();
        
        
        int result = 0;
        for(char cur: a){
            result ^= cur;
        }
        
         for(char cur: b){
            result ^= cur;
        }
        
        return (char) result;
    }
}
  1. 给出3*n + 1 个非负整数,除其中一个数字之外其他每个数字均出现三次,找到这个数字
    思路: 利用位操作 Bit Operation 来解此题。建立一个32位的数字,来统计输入数组各元素之和。
    需要每次对各元素同一bit位进行加和,如果这个数字出现过3次,那么%3=0;如果只出现过1次,%3!=0,而且余多少,改数字的这bit位上就是多少。
class Solution {
    public int singleNumber(int[] nums) {
        int result = 0;
        
        for(int i =0;i<32; i++){
            int sum = 0;
            
            for(int cur: nums){
                sum += (cur >> i) & 1;
            }
            result |= (sum % 3) << i;
        }
        
        return result;
    }
}
  1. 求子集,列出所有结果——这个不是bit operation,是数组的移位
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        if(nums == null || nums.length ==0)
            return [];
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        res.add(new ArrayList<Integer>());
        
        for(int cur: nums){
            int size = res.size();
            
            for(int i=0; i<size; i ++){
                List<Integer> cur_list = new ArrayList<>(res.get(i));
                cur_list.add(cur);
                res.add(cur_list);
            }
        }
        
        return res;
    }
}
  1. 求重复的DNA序列
    All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACGAATTCCG". When studying DNA, it is sometimes useful to identify repeated sequences within the DNA.

Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.
For example,
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",
Return:
["AAAAACCCCC", "CCCCCAAAAA"]
思路:A: 0100 0001  C: 0100 0011  G: 0100 0111  T: 0101 0100

由于我们的目的是利用位来区分字符,当然是越少位越好,通过观察发现,每个字符的后三位都不相同,故而我们可以用末尾三位来区分这四个字符。而题目要求是10个字符长度的串,每个字符用三位来区分,10个字符需要30位,在32位机上也OK。为了提取出后30位,我们还需要用个mask,取值为0x7ffffff,用此mask可取出后27位,再向左平移三位即可。算法的思想是,当取出第十个字符时,将其存在哈希表里,和该字符串出现频率映射,之后每向左移三位替换一个字符,查找新字符串在哈希表里出现次数,如果之前刚好出现过一次,则将当前字符串存入返回值的数组并将其出现次数加一,如果从未出现过,则将其映射到1。为了能更清楚的阐述整个过程,我们用题目中给的例子来分析整个过程:

首先我们取出前九个字符AAAAACCCC,根据上面的分析,我们用三位来表示一个字符,所以这九个字符可以用二进制表示为001001001001011011011,然后我们继续遍历字符串,下一个进来的是C,则当前字符为AAAAACCCCC,二进制表示为001001001001011011011011,然后我们将其存入哈希表中,用二进制的好处是可以用一个int变量来表示任意十个字符序列,比起直接存入字符串大大的节省了内存空间,然后再读入下一个字符C,则此时字符串为AAAACCCCCA,我们还是存入其二进制的表示形式,以此类推,当某个序列之前已经出现过了,我们将其存入结果res中即可

class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        List<String> res = new ArrayList<String>();
        
        if(s==null || s.length() < 10)
            return res;
        
        char[] chars = s.toCharArray();
        Set<Integer> combinesIntegers = new HashSet<Integer>();
        
        int mask = 0x7ffffff; // 掩码:能够提取低27位(也就是后面9个字符)
        int i =0; // 数组遍历的角标
        int cur = 0; // 当前窗口为10的字符串,用一个数字表示的结果
        
        while(i<9){
            cur = (cur << 3) | (chars[i++] & 7); // ACGT,每个字母用3个数字表示。所以用7(0b111即可作为当前字母的提取掩码)
            // i++;
        }
        
        while(i < s.length()){
            cur = ((cur & mask) << 3) | (chars[i++] & 7);
            
            if(combinesIntegers.contains(cur)){ // 如果哈希表里已有,说明之前这个四个字母的组合已经出现过,即可加入返回队列里
                String target = s.substring(i-10,i);
                if(res.contains(target) == false){ // 判断,返回队列不要有重复数据
                    res.add(target);
                }
            }else{
                combinesIntegers.add(cur);
            }
            
            // i++;
        }
        
        return res;
    }
}
  1. 数字范围相与
    Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive.

For example, given the range [5, 7], you should return 4.
只要写代码找到m和n的左边公共的部分即可

class Solution {
    public int rangeBitwiseAnd(int m, int n) {
        if(m == n)
            return m;
        
        int mask = 0xffffffff;
        while((m & mask) != (n & mask)){
            mask = mask << 1;
        }
        return m & mask;
    }
}
  1. 给出2*n + 2个的数字,除其中两个数字之外其他每个数字均出现两次,找到这两个数字。
    分析:
  • 异或:两个相同数字异或,结果为0;0与任意数异或,结果是其本身
  • a & (-a): 可以得到这个数字里某一位为1的值。 is a trick to extract the lowest set bit of i
  • a & 1: 获取数字最低位
  • 解题:(1) 从前往后遍历,所有数字异或的结果=那两个特别数字异或的结果xor
    (2) 将nums分为两大阵营:将xor转化为只保留某一位为1、其他为0的数字, 然后与各个nums数字与操作。=1 PK =0,自然分为两大阵营。
class Solution {
    public int[] singleNumber(int[] nums) {
        int xor = 0; // 用于保存异或结果
        int group0=0; // 用于保存第一组的异或结果
        int group1=0; // 用户保存第二组的异或结果
        
        for(int cur: nums){
            xor ^= cur;
        }
        xor = xor &(-xor); // 这个公式,可以求出xor里某一位=1的数字
        
        for(int cur: nums){
            if( (xor & cur) == 0){
                group0 ^= cur;
            }
            else{
                group1 ^= cur;
            }
        }
        
        int[] returns = new int[2];
        returns[0] = group0;
        returns[1] = group1;
        
        return returns;
    }
}
  1. 单词长度的最大积——让我们求两个没有相同字母的单词的长度之积的最大值
    给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。
    示例 1:
    输入: ["abcw","baz","foo","bar","xtfn","abcdef"]
    输出: 16
    解释: 这两个单词为 "abcw", "xtfn"。

示例 2:
输入: ["a","ab","abc","d","cd","bcd","abcd"]
输出: 4
解释: 这两个单词为 "ab", "cd"。

因为题目中说都是小写字母,那么只有26位,一个整型数int有32位,我们可以用后26位来对应26个字母,若为1,说明该对应位置的字母出现过;进而我们也可以用一个int数字表示一个英文单词。
这样判断两个单词没有共同字母的条件是:这两个int数 相与结果=0

class Solution {
    public int maxProduct(String[] words) {
        if(words == null || words.length <= 1 ){
            return 0;
        }
        
        int max_product = 0;
        
        List<Integer> mask = new ArrayList<Integer>(); // 因为都是小写字母,总数最多26个。所以可以用一个32位的Int,用其二进制表示法 表示一个字母,而且bit中为1的只有一个。因此一个单词,也同样可以用一个二进制数表示。
        
        for(int i =0 ; i< words.length;i++){
            // 计算 当前单词的mask值
            int cur_mask = 0;
            for(char cur: words[i].toCharArray()){
                cur_mask |= (1 << (cur-'a'));
            }
            mask.add(cur_mask);
            
            // 遍历i之前的那些数组
            for(int j =0; j <i; j++){
                if((mask.get(i) & mask.get(j)) == 0){
                    max_product = Math.max(max_product, words[i].length() * words[j].length());
                }
            }
            
        }
        
        return max_product;
        
    }
}
  1. Sum of Two Integers——不用+、-, 但要求计算出两个数字之和
  • 两个数字 如果忽略进位 求和可用异或计算
  • 两个数字的进位 可用与计算
class Solution {
    public int getSum(int a, int b) {
        int xor = a ^ b;
        int carray = (a & b) <<1 ;
        
        
        if(carray != 0 ){
            return getSum(xor, carray);
        }
        return xor;
    }
}
  1. 将数字转换为16进制——给定一个整数,写一个函数将其转换为16进制,不能用系统自带的进制转换函数
    mask固定
class Solution {
    public String toHex(int num) {
        if(num == 0) {
            return "0";
        }
        String ans = "";
        int len = 0;
        while(num != 0 && len < 8) {
            int bit = num & 15;
            if(bit < 10) {
                ans = (char)('0' + bit) + ans;
            }
            else {
                ans = (char)('a' + bit - 10) + ans;
            }
            num >>= 4;
            len++;
        }
        return ans;
    }
}
  1. 整数替换
    If n is even, replace n with n/2.
    If n is odd, you can replace n with either n + 1 or n - 1.
    思路:
    利用bit位的操作。如果这个数偶数,那么末位的bit位一定是0。如果一个数是奇数,那么末位的bit位一定是1。对于偶数,操作是直接除以2。
    对于奇数的操作:
    如果倒数第二位是0,那么n-1的操作比n+1的操作能消掉更多的1。
    1001 + 1 = 1010
    1001 - 1 = 1000
    如果倒数第二位是1,那么n+1的操作能比n-1的操作消掉更多的1。
    1011 + 1 = 1100
    1111 + 1 = 10000
public int integerReplacement(int n) {
    // 处理大数据的时候tricky part, 用Long来代替int数据
    long N = n;
    int count = 0;
    while(N != 1) {
        if(N % 2 == 0) {
            N = N >> 1;
        }
        else {
            // corner case;
            if(N == 3) {
                count += 2;
                break;
            }
            N = (N & 2) == 2 ? N + 1 : N - 1;
        }
        count ++;
    }
    return count;
}
  1. x&(-x):
    如果x是奇数,则返回1
    如果x是偶数,则返回 最右bit=1的数字,该数也是:能整除这个偶数的最大的2的幂(即: y = x & -x , 则 x % y = 0, 且 y = 2 ^ k). 例如x=12时,12&(-12)=0100=4

i & (~i + 1) is a trick to extract the lowest set bit of i.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容