二分搜索基础

一、二分搜索模板

简单模板代码:

    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);
        }
    }

 

posted @ 2017-02-06 20:44  carl_ysz  阅读(350)  评论(0编辑  收藏  举报