算法很美(一)

一、位运算的奇巧淫技

先来学习一下位运算

&(与),|(或),^(异或),~(非/取反)

>>和<<运算符将二进制进行右移或者左移操作

>>>运算符将用0填充高位;>>运算符用符号位填充高位,没有<<<运算符

对于int型,1<<35与1<<3是相同的(int型只有32位),而左变的操作数是long型时需对右侧操作数模64

一些技巧:
(1)、与:都为1结果为1, 或:有一个为1结果为1, 异或:相同为0、不同为1
(2)、x&1=1 x为奇 ;x&1=0 x为偶 (二进制最低位)

1、唯一成对的数

题目:找出唯一成对的数,1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其他均只出现一次。每个数组元素只能访问一次,设计一个算法,将其找出。要求:不用辅助存储空间。

如:int[] ={1,2,3,4,5,3} ---->3

我的思路:我也是第一次开始练算法,拿到题还是一脸懵逼,于是如上面所示,先想着是就6个数吧,也别多了,不然绕不清楚。题目显示不让用辅助存储空间,意思就是我不可以在开辟另一个数组,于是就更陷入沉思了。怎么想怎么和位运算没啥子联系,只是想到最暴力的方法,(以上面那个小数组为例子)2与1比较,不等,把1扔到外面放入单独一个盒子;3与2比较,不等,把2扔到外面放入单独一个盒子;以此类推,直到3和5比较,也不等,扔出去时候发现之前有个3与之相等,便匹配在一起。再深入想了一下,我头尾相比,来个69比较法(独创秘方,直通幼儿园)?好像变得快了一些,尾部不变,从头开始,1与3比较,不等out;2与3比较,不等out,三下出来了答案。但是题目1000个数字,不可能如此侥幸吧,该怎么处理呢,于是乎还是看了视频讲解。

讲解思路:我们想的是把不重复的数消掉,but--->AA=0,B0=B --->AABCC=B--->消除重复,我们该如何处理呢?看个式子(12k.....k1000)(12...1000),卧槽顿时恍然大悟,在(12...1000)取不重复的排法,那么其中必有一个k值,那么根据异或属性,相同为0、不同为1,3个k异或最后只剩下一个k,输出即可。

public class Solution{

public static void main(String[] args) {
    //首先创建数组,遍历
    int n = 1001;
    int[] arr = new int[n];
    for(int i=0;i<arr.length;i++) {
        arr[i] = i+1;
    }
    //在这里,最后一个数字我们给它随机数k,因为这里只有唯一一个重复
    arr[arr.length-1] = new Random().nextInt(n-1)+1;
    //随机下标
	int index = new Random().nextInt(n);
    //涉及两个方法,需自定义
    swap(arr,index,arr.length-1);
    print(arr);
    int x = 0;
    for(int i=1;i<n-1;i++){
        x = x^i;
    }
    for(int i=0;i<n;i++) {
        x= x^arr[i];
    }
    System.out.println(x)
}

public static void swap(int[] arr,int i,int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
 
public static void print(int[] arr) {
    System.out.println(Arrays.toString(arr))
}    
    
}

2、找出落单的那个数

题目:一个数组里除了某一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现了一次的数字。

如:int[] arr ={1,2,3,4,5,5,4,3,2} --->输出1

我的思路:出现了两次,那我就学以致用吧,这里用^ ,如上(123455432),必然最后就输出1了。

public class Solution2 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,5,4,3,2};
        int x = 0;
        //遍历数组
        for (int i=0;i<arr.length;i++) {
            //任何数与 0 异或等于它本身
            x = x^arr[i];
        }
        System.out.println(x);
    }
}

3、二进制中1的个数

题目:实现一个函数,输入一个整数,输出该数二进制表示中1的个数

如:9的二进制1001,输出2

我的思路:就是数出1的个数,那么就通过位运算来思考,设该数为x,与上(x<<i),i为左移的个数,然后与后得到的result>>i,相当于返回,其返回值为1,我这里设一个count,用count++计数即可。

讲解思路:可能我的算是最原始的思路吧,分享一个很神奇的思路,还是以9为例---1001,我们不断消去它最低位的1,那么消了几次就输出多少。实现也很简单,就只用将1001&1000(9-1)--->1000&0111--->0000停止,由此可见,我们只花了两步消去了所有的1,所以返回count=2;总结起来就是,x&(x-1)就是在不断消去其最低位的1。

public class Solution3 {
    public static void main(String[] args) {
        //这里有需要可以用Scanner方法
        int x = 9;
        int count = 0;
        //不要忘记先转化成二进制
        System.out.println(Integer.toBinaryString(x));
        //int型有32位
        for (int i=0;i<32;i++) {
            if (((x&1<<i)>>i)==1) { //还可以写出 (x&1<<i)==(1<<i)
                count++;
            }
        }
        System.out.println(count);
        /*
        写一下那个神奇代码
        int count = 0;
        while(x!=0) {
        x = (x&(x-1));
        count++;
        }
        System.out.println(count);
        */
    }
}

4、是不是2的整数次方

题目:用一条语句判断一个整数是不是2的整数次方。(这里只考虑正数)

我的思路:2、4、8、16、32、64、128、256、512、1024 ... 没啥思路啊

讲解思路:转化为二进制,停,好,我先试一下 0010、0100、1000、10000,奇怪咯,这2的整数次方,好像嘿嘿嘿二进制就只有1个1啊,用昨天的神奇代码x&(x-1)瞬间实现。再重复一下x&(x-1)的思路,逐步消去最低位的1,这里我们只要一步到位使x转为0,那么x就是2的整数次方。

public class Solution4 {
    public static void main(String[] args) {
        Is(1024);
        Is(1000);
    }

    private static void Is(int x) {
        //这里我们转为二进制可以观察一下
        System.out.println(x+"二进制数为:"+Integer.toBinaryString(x));
            if (((x&(x-1))==0)) {
                System.out.println(x+"是2的整数次方");
            }else {
                System.out.println(x+"不是2的整数次方");
            }
    }
}

5、0~1间浮点实数的二进制表示

题目:给定一个介于0~1间的实数,如:0.625,类型为double,打印它的二进制表现形式(0.101),因为小数点后的二进制分别表示为(0.5,0.25,0.125 ...)。如果该数字无法精确地用32位以内的二进制表现,则打印ERROR。

我的思路:赶紧打开IDEA看看double的包装类Double的方法,结果当然大失所望,只存在Double.toHexString() 的方法,即转为16进制,没有转2进制 。那肯定需要我们自己来写一个方法,在想方法时,我还想过要不把这个16进制转为2进制?脑子里仍没有具体思路,看看讲解。

讲解思路:整数转二进制——÷2,取余留商 ; 小数转二进制——×2,每次循环扣掉整数部分,然后继续乘2。例如上述所说0.625×2--->1.25 去掉整数部分--->0.25×2--->0.5×2--->1.0 去掉整数部分--->0.0不在循环-->每次去掉部分得到二进制数0.101。看来是大一学的都还给老师了,这都忘记了emmm,开始撸代码。

public class Solution5 {
    public static void main(String[] args) {
        double x = 0.625;
        //这里需要用到StringBuilder的append方法
        StringBuilder sb = new StringBuilder("0.");
        while (x>0) {
            double d = x*2;
            if (d>=1) {
                sb.append("1");
                x = d-1;
            }else {
                sb.append("0");
                x = d;
            }
            if (sb.length()>34) {
                System.out.println("ERROR");
                return;
            }
        }
        //连接打印成二进制数
        System.out.println(sb.toString());
    }
}

6、出现k次与出现一次

题目: 数组中只有一个数出现了1次,其他的数都出现了K次,请输出只出现了一次的数。

我的思路:本章节的压轴题还是不一样啊,先仔细回想一下前面的知识点,说白了无非就是运用进制和位运算。还是先假设int[] nums={1,2,2,3,3}显然当k=2则输出1。那么接下来就是怎么运用本章所学的知识来解了,首先想的肯定是异或,乍一看,诶运用在上述nums数组,直接输出1,真香。但是,若arr={1,2,2,2,3,3,3}这咋解决,异或也没啥用吧。那就先考虑集合,一个个数数总是可以吧,毕竟活人不能被尿憋死。

import java.util.*;

public class Solution6 {
    public static int getTheOne(int[] nums) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i=0;i<nums.length;i++) {
            if (map.containsKey(nums[i])) {
                int num = map.get(nums[i]);
                num++;
                map.put(nums[i],num);
            }else {
                map.put(nums[i],1);
            }
        }
        Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            int key = iterator.next();
            if (map.get(key)==1) {
                return key;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] nums = {1,2,2,2,3,3,3};
        System.out.println(getTheOne(nums));
        //输出1
    }

}

讲解思路:2个相同的2进制数做不进位加法,结果为0;10个相同的10进制做不进位加法,结果为0。--->k个相同的k进制数做不进位加法,结果为0。这个非常人所能发现也。。。然后Java在这里很友好的提供了一个手工取余法,即任意进制转换法---Integer.toString(i,radix);最后通过不进位加法得到我们所剩的唯一一个k进制,还需要把它转换为10进制。我给个中肯的评价,思想很高级,实现很麻烦,但很适合奇葩进制哈哈。在这里贴出代码,仅供思考与学习吧。暂且我还没有捋顺。

public class Solution7 {
    public static void main(String[] args) {
        int[] nums = {2,2,2,9,7,7,7,8,8,8,3,3,3,0,0,0};
        int len = nums.length;
        char[][] kRadix = new char[len][];
        int k = 3;
        int maxLen = 0;
        //转成k进制字符数组
        //对每个数字
        for (int i=0;i<len;i++) {
            //求每个数字的三进制字符串并翻转,然后转为字符数组。
            kRadix[i] = new StringBuilder(Integer.toString(nums[i],k)).reverse().toString().toCharArray();
            if (kRadix[i].length>maxLen) {
                maxLen = kRadix[i].length;
            }
            }
        int[] resNum = new int[maxLen];
        for (int i=0;i<len;i++) {
            //不进位加法
            for (int j=0;j<maxLen;j++) {
                if (j>=kRadix[i].length) {
                    resNum[j] += 0;
                }
                else {
                    resNum[j] += (kRadix[i][j]-'0');
                }
            }
        }
        int res = 0;
        for (int i=0;i<maxLen;i++) {
            res +=(resNum[i]%k)*(int)(Math.pow(k,i));
        }
        System.out.println(res);
        //输出9
    }
}


第一章【完】

原文地址:https://www.cnblogs.com/wangzheming35/p/11892558.html