zoukankan      html  css  js  c++  java
  • 二分搜索基础

    一、二分搜索模板

    简单模板代码:

        public static int binarySearch(int[] array, int target) {
            if (array == null || array.length == 0) {
                return -1;
            }
    
            int start = 0;
            int end = array.length - 1;
            int middle;
            // 相邻 start = end - 1 或者 start = end
            while (start + 1 < end) { // (1)
                middle = start + (end - start) / 2; // (2)
                if (array[middle] == target) {
                    return middle;
                } else if (array[middle] < target) {
                    //target在右边
                    start = middle;
                } else {
                    end = middle;
                }
            }
            if (array[start] == target) {  // (3)
                return start;
            } else if (array[end] == target) { // (4)
                return end;
            } else {
                return -1;
            }
        }

    说明:

    (1) (start+end)/2 在start+end的值较大的时候有可能会超出范围, 因此这里使用start+(end-start)/2

    (2) 为什么使用start+1<end? 为了防止死循环:

      循环终止条件为:start+1>=end, 即相邻或者相交的情况

    (3) (4) : 在相邻或者相交的情况下都终止, 再判断可以简单防止死循环

    二、二分查找区间

    问题描述: http://www.lintcode.com/en/problem/search-for-a-range/

    即给定一个有序的数组和target value, 寻找target value的起始和结束位置, 不存在就返回(-1,-1);

    思路: 二分搜索的扩展.

    (1) 分二次查找

    (2) 结束条件都是相邻或者相交

    (3) 找左边界就是start->end

    (4) 找右边界就是end->start

    public class BinarySearch {
    
        public static void main(String[] args) {
            int[] array = new int[]{1, 2, 3, 4, 4 ,4, 4, 5};
            System.out.println(Arrays.toString(binarySearch(array, 4)));
        }
    
        public static int[] binarySearch(int[] array, int target) {
            if (array == null || array.length == 0) {
                return new int[]{-1, -1};
            }
    
            int start = 0;
            int end = array.length - 1;
            // left是左边的, right是右边的
            int middle, left, right;
    
            // 相邻 start = end - 1 或者 start = end
            while (start + 1 < end) {
                middle = start + (end - start) / 2;
                if (array[middle] == target) {
                    end = middle;
                } else if (array[middle] < target) {
                    //target在右边
                    start = middle;
                } else {
                    end = middle;
                }
            }
    
            if (array[start] == target) {
                left = start;
            } else if (array[end] == target) {
                left = end;
            } else {
                return new int[]{-1, -1};
            }
    
            start = 0;
            end = array.length - 1;
            middle = 0;
    
            while (start + 1 < end) {
                middle = start + (end - start) / 2;
                if (array[middle] == target) {
                    start = middle;
                } else if (array[middle] < target) {
                    //target在右边
                    start = middle;
                } else {
                    end = middle;
                }
            }
    
            if (array[end] == target) {
                right = end;
            } else if (array[start] == target) {
                right = start;
            } else {
                return new int[]{-1, -1};
            }
            return new int[]{left, right};
        }
    
    }

    三、二分查找插入位置

    问题描述:

    http://www.lintcode.com/en/problem/search-insert-position/

    (1) 给一个有序的数组

    (2) 寻找插入位置

    二分查找的精髓: 每次去掉一半, 最后剩下的情况就是start=end或者start+1=end, 而无论哪种都可以当做start+1=end考虑;

       public static int searchInsert(int[] A, int target) {
            if (A == null || A.length == 0) {
                return 0;
            }
            int start = 0, end = A.length - 1;
    
            while (start + 1 < end) {
                int mid = start + (end - start) / 2;
                if (A[mid] == target) {
                    return mid;
                } else if (A[mid] < target) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
    
            if (A[start] >= target) {
                return start;
            } else if (A[end] >= target) {
                return end;
            } else {
                return end + 1;
            }
        }

    四、循环数组二分查找

    问题描述:

    http://www.lintcode.com/en/problem/search-in-rotated-sorted-array/

    Suppose a sorted array is rotated at some pivot unknown to you beforehand.

    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

    You are given a target value to search. If found in the array return its index, otherwise return -1.

    You may assume no duplicate exists in the array.

    循环数组的二分查询,画图2个象限,简单的分类讨论感觉用代码实现也很麻烦.

    这里说一下思路:

    (1) 首先判断mid在第一部分和第二部分

    (2) 将问题细化, 这里只考虑第一部分:

      如果A[start] < A[end], 则是一般情况

      否则 start, mid ,end将数组分为了3个区间, A[mid]<=target<=A[end]则说明target在mid和end中间, 否则是start,mid中.

    (3) 第二部分类似

    (4) 问题解决

        public static int search(int[] A, int target) {
            if (A == null || A.length == 0) {
                return -1;
            }
            int start = 0, end = A.length - 1;
            int mid;
            while (start + 1 < end) {
                mid = start + (end - start) / 2;
                if (A[mid] == target)
                    return mid;
                // 分为2大类: 在哪边?
                if (A[mid] > A[start]) {
                    if (A[end] >= A[start]) {
                        //..
                        if (A[mid] > target) {
                            end = mid;
                        } else {
                            start = mid;
                        }
                    } else {
                        if (target >= A[start] && target <= A[mid]) {
                            end = mid;
                        } else {
                            start = mid;
                        }
                    }
                } else {
                    if (A[start] <= A[end]) {
                        if (A[mid] > target) {
                            end = mid;
                        } else {
                            start = mid;
                        }
                    } else {
                        if (target >= A[mid] && target <=A[end]){
                            start = mid;
                        }else {
                            end = mid;
                        }
                    }
                }
    
    
            }
            if (A[start] == target) {
                return start;
            } else if (A[end] == target) {
                return end;
            } else {
                return -1;
            }
        }

    五、矩阵二分搜索

    问题描述, 非常简单, 一个矩阵m*n 可以用数组表示:

      (1) 没一行都有序

      (2) 第n+1行比第n行的都大

    例如:

    [
        [1, 3, 5, 7],
        [10, 11, 16, 20],
        [23, 30, 34, 50]
    ]

    非常简单, 看成是一个数组就可以..

        public boolean searchMatrix(int[][] matrix, int target) {
            if (matrix == null || matrix.length == 0) {
                return false;
            }
            if (matrix[0] == null || matrix[0].length == 0) {
                return false;
            }
    
            int row = matrix.length, column = matrix[0].length;
            int start = 0, end = row * column - 1;
    
            while (start + 1 < end) {
                int mid = start + (end - start) / 2;
                int number = matrix[mid / column][mid % column];
                if (number == target) {
                    return true;
                } else if (number < target) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
    
            if (matrix[start / column][start % column] == target) {
                return true;
            } else if (matrix[end / column][end % column] == target) {
                return true;
            }
    
            return false;
        }

    下面考虑更为复杂的情况:

    (1) 每一行都是有序的

    (2) 每一列都是有序的要怎么做?

    这里说明一下思路:

    从左下方开始判断, 如果target < matirx[i][j], 说明第i列的数都没用, i++

    如果target > matirx[i][j], 说明第j列的数都没有用, j++. 

    六、合并有序数组

    合并有序数组非常简单, 但是假设这样的情况: A中有足够的位置去合并, 不能创建新的数组要怎么办 ?

    A = [1, 2, 3, empty, empty], B = [4, 5]

    After merge, A will be filled as [1, 2, 3, 4, 5]

    思路非常简单, 看empty在哪边? 这里从后面开始扩展就可以~

    代码如下: 这里也是创建了新数组C, 然后把A拷贝进去模拟A中有足够的位置:

        public static void main(String[] args) {
            merge(
                    new int[]{1, 3, 4},
                    new int[]{2, 3, 5, 8, 9}
            );
        }
    
        public static void merge(int[] A, int B[]) {
            int[] C = new int[A.length + B.length];
            System.arraycopy(A, 0, C, 0, A.length);
            int a = A.length - 1;
            int b = B.length - 1;
            int i = a + b + 1;
            while (i >= 0 && a >= 0 && b >= 0) {
                if (A[a] > B[b]) {
                    C[i] = A[a];
                    a--;
                } else {
                    C[i] = B[b];
                    b--;
                }
                i--;
            }
            if (b < 0) {
                //a 剩下
                System.arraycopy(A, 0, C, 0, a + 1);
            } else if (a < 0) {
                System.arraycopy(B, 0, C, 0, b + 1);
            }
    
            System.out.println(Arrays.toString(C));
        }

    七、2个有序数组寻找kth问题

    (1) 2个有序数组

    (2) 寻找2个数组加起来第k个

    (3) 要求是算法复杂度是log(m+n)

    思路是什么:要log的时间复杂度,每次必须抛弃一半:

    只要每次比较 A[k/2]和B[k/2]就可以, 如果A[k/2]<B[k/2]就可以抛弃A中的k/2个元素...

    代码如下(没有经过充分测试):

      public static void main(String[] args) {
            int[] A = {1, 2, 3, 4, 5, 6};
            int[] B = {2, 3, 4, 5};
            System.out.println(findMedianSortedArrays(A, B));
        }
    
        public static double findMedianSortedArrays(int A[], int B[]) {
            int len = A.length + B.length;
            if (len % 2 == 1) {
                return findKth(A, 0, B, 0, len / 2 + 1);
            }
            return (
                    findKth(A, 0, B, 0, len / 2) + findKth(A, 0, B, 0, len / 2 + 1)
            ) / 2.0;
        }
    
        /**
         * @param A       数组A
         * @param A_start 数组A起始位置
         * @param B       数组B
         * @param B_start 数组B起始位置
         * @param k       寻找第K个数
         * @return
         */
        public static int findKth(int[] A, int A_start, int[] B, int B_start, int k) {
    
            if (A_start > A.length - 1) {
                return B[B_start + k - 1];
            }
            if (B_start > B.length - 1) {
                return A[A_start + k - 1];
            }
            //
    
            if (k == 1) {
                return Math.min(A[A_start], B[B_start]);
            }
            // 要考虑k/2超出边界的情况
            int a_new_start = A_start + k / 2 - 1;
            int b_new_start = B_start + k / 2 - 1;
            int a_length, b_length;
            if (a_new_start > A.length - 1) {
                a_new_start = A.length - 1;
            }
            a_length = a_new_start - A_start + 1;
            if (b_new_start > B.length - 1) {
                b_new_start = B.length - 1;
            }
            b_length = b_new_start - B_start + 1;
    
            if (A[a_new_start] < B[b_new_start]) {
                //A这边小一点, 全面的全部被抛弃
                return findKth(A, a_new_start + 1, B, B_start, k - a_length);
            } else {
                return findKth(A, A_start, B, b_new_start + 1, k - b_length);
            }
        }
  • 相关阅读:
    Java --关于线程池的理解Executors.newFixedThreadPool(int n)
    有参数返回的Lambda
    vuejs经验交流之-开发环境和生产环境的启动
    vuejs经验交流之-安装(NPM)
    vuejs经验交流之-form表单校验
    Vue.js简介
    vuejs经验交流之-components(组件)
    vuejs经验交流之-图片上传
    vuejs经验交流之-watch 深度监测
    setTimeout(), clearTimeout()
  • 原文地址:https://www.cnblogs.com/carl10086/p/6287300.html
Copyright © 2011-2022 走看看