算法基础二之二分法与异或运算

二分法:

1)在一个有序数组中,查找某个数是否存在

2)在一个有序数组中,找>=某个数的最左侧的位置

3)在一个有序数组中,找<=某个数最右侧的位置

4)局部最小值问题

 

小技巧:

N/2       N>>1

N*2       N<<1

N*2 + 1     (N<<1) | 1

在一个有序数组中,查找某个数是否存在

package com.zh.class001;

import com.zh.util.SortUtil;

import java.util.Arrays;

public class BSExist {
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = SortUtil.generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
            if (test(arr, value) != exist(arr, value)) {
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }

    // for test
    public static boolean test(int[] sortedArr, int num) {
        for(int cur : sortedArr) {
            if(cur == num) {
                return true;
            }
        }
        return false;
    }

    public static boolean exist(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return false;
        }
        int left = 0;
        int right = arr.length - 1;
        int mid = 0;

        while (left < right) {
            mid = left + ((right - left) >> 1);
            if (arr[mid] == num) {
                return true;
            } else if (arr[mid] > num) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return arr[left] == num;
    }
}

在一个有序数组中,找>=某个数的最左侧的位置

package com.zh.class001;

import com.zh.util.SortUtil;

import java.util.Arrays;

public class BSNearLeft {
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = SortUtil.generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
            if (test(arr, value) != nearleftIndex(arr, value)) {
                SortUtil.printArray(arr);
                System.out.println(value);
                System.out.println(test(arr, value));
                System.out.println(nearleftIndex(arr, value));
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }
    // 在arr上,找满足>=value的最左位置
    public static int nearleftIndex(int[] arr, int value) {
        int left = 0;
        int right = arr.length - 1;
        int index = -1;

        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (arr[mid] >= value) {
                index = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return index;
    }

    // for test
    public static int test(int[] arr, int value) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] >= value) {
                return i;
            }
        }
        return -1;
    }
}

在一个有序数组中,找<=某个数最右侧的位置

package com.zh.class001;

import com.zh.util.SortUtil;

import java.util.Arrays;

public class BSNearRight {
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr = SortUtil.generateRandomArray(maxSize, maxValue);
            Arrays.sort(arr);
            int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
            if (test(arr, value) != nearRightIndex(arr, value)) {
                SortUtil.printArray(arr);
                System.out.println(value);
                System.out.println(test(arr, value));
                System.out.println(nearRightIndex(arr, value));
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }
    // 在arr上,找满足<=value的最右位置
    public static int nearRightIndex(int[] arr, int value) {
        int left = 0;
        int right = arr.length - 1;
        int index = -1;

        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (arr[mid] > value) {
                right = mid - 1;
            } else {
                index = mid;
                left = mid + 1;
            }
        }
        return index;
    }

    // for test
    public static int test(int[] arr, int value) {
        for (int i = arr.length - 1; i >= 0; i--) {
            if (arr[i] <= value) {
                return i;
            }
        }
        return -1;
    }
}

局部最小值问题

条件:无序数组,任意两个相邻的数字都不相等

package com.zh.class001;

/**
 *
 * 找出局部最小
 */
public class BSAwesome {
    public static int getLessIndex(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1; // no exist
        }
        if (arr.length == 1 || arr[0] < arr[1]) {
            return 0;
        }
        if (arr[arr.length - 1] < arr[arr.length - 2]) {
            return arr.length - 1;
        }
        int left = 1;
        int right = arr.length - 2;
        int mid = 0;
        while (left < right) {
            mid = (left + right) / 2;
            if (arr[mid] > arr[mid - 1]) {
                right = mid - 1;
            } else if (arr[mid] > arr[mid + 1]) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return left;
    }
}

异或运算

好记的方法:无进位相加

异或运算的性质:

1)0^N == N    N ^ N == 0

2)异或运算满足交换律和结合律

题目一如何不用额外变量交换两个数

a = a ^ b;
b = a ^ b;
a = a ^ b;
public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = -100;

        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        
        System.out.println("a:" + a);
        System.out.println("b:" + b);
    }
}

a:-100
b:10

public class Test {
    public static void main(String[] args) {

        int[] arr = {3, 1, 100};
        swap(arr, 0, 2);
        System.out.println(Arrays.toString(arr));

    }

    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

}

[100, 1, 3]

注意:交换可以是相同的值,但是必须是不同的内存区域,否则会变成0

public class Test {
    public static void main(String[] args) {

        int[] arr = {3, 1, 100};
        swap(arr, 0, 0);
        System.out.println(Arrays.toString(arr));

    }


    public static void swap(int a, int b) {
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println("a:" + a);
        System.out.println("b:" + b);
    }
}

[0, 1, 100]

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

运用上面的异或性质,相同的数异或后为0.0异或上一个数,不变。可以将所有的数异或,得到的数,就是奇数次出现的数

public class EvenTimesOddTimes {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 1, 2, 3, 4, 1};
        printOddTimesNum1(arr);
    }
    // arr中,只有一种数,出现奇数次
    public static void printOddTimesNum1(int[] arr) {
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        System.out.println(eor);
    }
}

1

题目三 怎么把一个int类型的数,提取出最右侧的1来

 提示:N & ((~N) + 1)

// 怎么把一个int类型的数,提取出最右侧的1来   
 public static int getRightOne(int num) {
        return num & (~num + 1);
    }

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

    // arr中,有两种数,出现奇数次
    public static void printOddTimesNum2(int[] arr) {
        int eor = 0;
        for (int num : arr) {
            eor ^= num;
        }

        // 提取最右侧的1
        int rightOne = eor & (~eor + 1);
        int onlyOne = 0;
        for (int num : arr) {
            if ((num & rightOne) != 0) {
                onlyOne ^= num;
            }
        }
        System.out.println(onlyOne + " " + (eor ^ onlyOne));
    }

推广题目:

一个数组中有一种数出现K次,其他数都出现了M次,M>1,K<M找到,出现了K次的数,要求,额外空间复杂度O(1),时间复杂度O(N)

思路:将每个数转化为二进制,那么int数字可以转化为32大小的数组,因此定义32大小的数组,用来计算每个位置上1的个数和。

如果i位置上1的个数和可以整除M,那么此处出现K次的数,必定不为1,否则为1。

将所有出现K次的数字,为1的位置全部找出来,就可以组成这个数的二级制表示,进而计算出数字。

package com.zh.class001;

import java.util.HashMap;
import java.util.Map;

/**
 * 一个数组中有一种数出现K次,其他数都出现了M次,M>1,K<M找到,
 * 出现了K次的数,要求,额外空间复杂度O(1),时间复杂度O(N)
 *
 */
public class KTimesNum {
    private static final int K = 1;
    private static final int M = 3;
    public static void main(String[] args) {
        int[] arr = {1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 0};
        System.out.println(onlyKTimesNUms(arr, K, M));
        System.out.println(test(arr, K, M));
    }
    public static int onlyKTimesNUms(int[] arr, int k, int m) {
        int[] bitSum = new int[32];

        for (int num : arr) {
            for (int i = 0; i < 32; i++) {
                if (((num >> i) & 1) != 0) {
                    bitSum[i]++;
                }
            }
        }

        int res = 0;
        for (int i = 0; i < 32; i++) {
            if (bitSum[i] % m == 0) {
                continue;
            }
            if (bitSum[i] % m == k) {
                res |= (1 << i);
            } else {
                return -1;
            }
        }

        int zeroCount = 0;
        if (res == 0) {
            for (int num : arr) {
                if (num == 0) {
                    zeroCount++;
                }
            }

            if (zeroCount != k) {
                return -1;
            }
        }
        return res;
    }

    public static int test(int[] arr, int k, int m) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : arr) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }

        for (int key : map.keySet()) {
            if (map.get(key) == k) {
                return key;
            }
        }
        return -1;

    }
}

 

题目五 找出二进制1的个数

    public static int bit1Count(int num) {
        int count = 0;
        while (num != 0) {
            int rightOne = num & (~num + 1);
            count++;
            num ^= rightOne;
        }
        return count;
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值