1. 程式人生 > >leetcode刷題 二分查詢

leetcode刷題 二分查詢

作為本科非科班出身的CSer也經歷了看到leetcode不知從何刷起的感覺,現在準備重新刷一下leetcode,寫部落格主要是記錄下自己的思路,儘量保持每天兩道,這篇主要總結二分查詢所遇到的問題以及變種。

打個廣告微博 @辣醬一直就這樣  歡迎同學私信討論

先說一下二分查詢的模版

1. start + 1 < end //在相鄰的時候退出避免死迴圈
2. start + (end - start) / 2 //找中間值,裝逼寫法避免mid比Integer.max_value還大導致溢位
3. A[mid] ==, <, >//判斷要根據找target是第一次出現還是最後一次出現來決定把mid 給start 還是end
4. A[start] A[end] ? target//最後相鄰退出同樣要考慮是找target第一次出現還是最後一次出現.
二分查詢基本題型,leetcode上面好像沒有,我找了lintcode上面一道

這道題就是最基本的二分查詢,target有重複,需要找到target第一次出現時所在陣列的下標,很簡單套模版

class Solution {
    /**
     * @param nums: The integer array.
     * @param target: Target to find.
     * @return: The first position of target. Position starts from 0.
     */
    public int binarySearch(int[] nums, int target) {
        //write your code here
        if(nums.length == 0){
            return -1;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid ;
        while(start + 1 < end){
            mid = start + (end - start) / 2;
            if(nums[mid] == target){
                end = mid;//注意是找第一個出現的位置,我們就認為找到一個他就是最右邊的往左找看有沒有相同的
            }else if(nums[mid] < target){
                start = mid;
            }else{
                end = mid ;
            }
        }
        if(nums[start] == target){
            return start;
        }
        if(nums[end] ==target){
            return end;
        }
        return -1;
    }
}
接著提高一點leetcode 34Search for a Range https://leetcode.com/problems/search-for-a-range/description/

這道題就是讓你找到target在陣列中的起始和結束位置,也不難,思路就是順著上一題,先二分一次找到target第一次出現的位置,再二分一次找到target最後一次出現的位置

public class Solution {
    public int[] searchRange(int[] nums, int target) {
        int []res = {-1 , -1} ;
        if(nums == null || nums.length == 0){
            return res ; 
        }
        //先找左邊的位置;
        int start = 0 ;
        int end = nums.length - 1 ;
        int mid ;
        while(start + 1 < end){
            mid = start + (end - start) / 2;
            if(nums[mid] == target){
                end = mid ;//左邊可能還有要往左邊找
            }else if(nums[mid] < target){
                start = mid ;
            }else{
                end = mid ;
            }
        }
        if(nums[start] == target)//先判斷左邊的
        {
            res[0] = start ;
        }else if(nums[end] == target){
            res[0] = end ;
        }
        //右邊同左邊的全都反過來
        start = 0 ;
        end = nums.length - 1 ;
        while(start + 1 < end){
            mid = start + (end - start) / 2;
            if(nums[mid] == target){
                start = mid ;
            }else if(nums[mid] < target){
                start = mid ;
            }else if(nums[mid] > target){
                end = mid ; 
            }   
        }
        if(nums[end] == target){
            res[1] = end ;
        }else if(nums[start] == target){
            res[1] = start ; 
        }
        
        return res ; 
    }
}

這道題就是給了一個數組把target插入到裡面讓陣列還是有序排列,如果數組裡面有這個值就返回陣列下標,如果沒有的就找到最後一個比他小的數插入到這個數後面,如果沒有比target還小的數就直接插入到陣列最前面,還是用二分查詢,套上面的模板,最後處理end start 有些繁瑣,注意就好了

public class Solution {
    public int searchInsert(int[] nums, int target) {
        if(nums == null || nums.length == 0 || nums[0] > target){
            return 0 ;
            }
        int start = 0 ;
        int end = nums.length - 1 ;
        int mid ;
        while(start + 1 < end){
            mid = start + (end - start) / 2 ;
            if(nums[mid] == target){
                return mid ;
            }else if (nums[mid] > target){
                end = mid ;
            }else if (nums[mid] < target){
                start = mid ;
            }
        }
        if(nums[end] == target){
            return end ;
        }else if(nums[end] < target){
            return end + 1 ;
        }else if(nums[start] == target){
            return start ;
        }
        return start + 1 ;
    }
}
接下是74Search a 2D Matrix 搜尋矩陣中是否有目標值 , 矩陣的每一行和每一列都是按順序排列,每一行的第一個數都比上一行的最後一個數大,思路還是用二分查詢,一次先找到所在的行,再一次二分找到所在的列
public class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return false ;
        }
        int row = matrix.length ;
        int colum = matrix[0].length ;
        int mid ;
        //先找行
        int start = 0 ;
        int end = row - 1 ;
        while(start + 1 < end){
            mid = start + (end - start) / 2;
            if(matrix[mid][0] == target){
                return true ;
            }else if(matrix[mid][0] > target) {
                end = mid ;
            }else {
                start = mid ;
            }
        }
        //處理剩下的start 和 end 
        if(matrix[end][0]  <= target){
            row = end ;
        }else if (matrix[start][0] <= target){
            row = start ;
        }else{
            return false ;
        }
        
        //再找列
        start = 0;
        end = colum - 1 ;
        while(start + 1 < end){
            mid = start + (end - start) / 2;
            if(matrix[row][mid] == target){
                return true ; 
            }else if(matrix[row][mid] < target) {
                start = mid ;
            }else{
                end = mid ;
            }
        }
//         只剩兩個地方沒處理,只需要判斷是否等於就好
        if(matrix[row][start] == target){
            return true ;
        }else if(matrix[row][end] == target){
            return true ;
        }
        return false ;
        
    }
}


240Search a 2D Matrix II

這道題是上一題的提高版,還是一個二維矩陣,但是不保證下一行的數都比上一行的數大,只保證每行中後面比前面大, 每列中,下面比上面大,暴力搜尋就是時間複雜度n2,肯定是不行的,我們來看這張圖

先找到第一列的最後一個,他是最後一行最小的,如果比target大就說明最後一行都比target大,就往上挪一個,如果比target小,由於他是第一列最大的,就說明第一列都比target小,就往右挪一個,這樣如圖,最差要用O(n + m)時間就可以找到,以下是程式碼

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return false ; 
        }
        int x = matrix.length - 1 ;
        int y = 0 ; 
        while(x >= 0 && y < matrix[0].length){
            if(matrix[x][y] > target){
                x -- ;
            }else if(matrix[x][y] < target){
                y ++ ; 
            }else{
                return true ;
            }
        }
        return false ;
    }
}

還有一道題278First Bad Version沒什麼說的,找到第一個壞的版本號,基本的二分查詢

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int start = 0 ; 
        int end = n ; 
        int mid ; 
        while(start + 1 < end){
            mid = start + (end - start) / 2 ;
            if(isBadVersion(mid)){
                end = mid ;
            }else{
                start = mid ;
            }
        }
        if(isBadVersion(start)){
            return start ;
        }
        return end ;
    }
}
class Solution {
    public int findPeakElement(int[] nums) {
        int start = 0 ;
        int end = nums.length - 1 ;
        int mid ;
        while(start + 1 < end){
            mid = start + (end - start) / 2 ;
            //     mid -1 < mid > mid + 1  說明mid就是峰值
            if(nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]){
                return mid ; 
            //    mid - 1 < mid < mid + 1 說明 peak在mid右邊
            }else if(nums[mid] < nums[mid + 1] && nums[mid] > nums[mid - 1]){
                start = mid ;
            //剩下兩種情況 凹的話 取左右哪邊都一樣,遞減的話只能取左邊,然後取交集 讓end = mid 
            }else{
                end = mid ;    
            }
        }
        if(nums[end] > nums[start]){
            return end ;
        }
        return start ;
    }
}
26Remove Duplicates from Sorted Array沒什麼說的相等的留下不等的跳過
class Solution {
    public int removeDuplicates(int[] nums) {
        //不能有新的空間意思就是不能建立新的陣列來儲存
        if(nums == null || nums.length == 0){
            return 0 ;
        }
        int size = 0 ;
//         注意不能nums.length - 1 要不然最後一個沒有遍歷到
        for(int i = 0 ; i < nums.length  ; i++){
//             相等就跳過不相等留下
            if(nums[size] != nums[i]){
                size++  ;
                nums[size] = nums[i] ;
                // nums[++size] = nums[i]
            }
        }
        return size + 1 ;
    }
}

80Remove Duplicates from Sorted Array II
這個就是要做一個計數器記錄是否達到了重複兩次,沒有就留下有就跳過
class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0 ;
        }
        int size = 0 ; 
        int count = 1 ;
//         注意i為什麼要從1開始? i直接找的第二個數不是第一個 因為第一個nums[size] 和nums[i]肯定是相等的
        for(int i = 1 ; i < nums.length ; i ++){
//             如果相等看有沒有達到重複兩次
            if(nums[size] == nums[i]){
//                 所以這裡也要注意,並不是才加了一遍, 而是如果這裡相等代表前面已經相等過一次了
                if(count < 2){
                    size ++ ;
                    nums[size] = nums[i] ;
                    count ++ ;
                }
            }else{
                    size ++ ;
                    nums[size] = nums[i] ;
//                 注意要把計數器復原,也可以看作是第一次相等
                    count = 1 ;
            }
        }
        return size + 1 ;
    }
}
88Merge Sorted Array考驗基本功的一題沒什麼難度
public class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
            int a = m - 1 ;
            int b = n - 1 ;
            int k = m + n - 1 ;
            while(a >= 0 && b >= 0){
                if(nums1[a] > nums2[b]){
                    nums1[k--] = nums1[a--];
                }else{
                    nums1[k--] = nums2[b--];
                }
            }
            while(a >= 0){
                nums1[k--] = nums1[a--] ;
            }
            while(b >= 0){
                nums1[k--] = nums2[b--] ;
            }
        
        
        
        
        
        
        
        
        
        
        

    }
}

4Median of Two Sorted Arrays這個題很重要是重點題

 題解:

首先我們先明確什麼是median,即中位數。 

引用Wikipedia對中位數的定義:

計算有限個數的資料的中位數的方法是:把所有的同類資料按照大小的順序排列。如果資料的個數是奇數,則中間那個資料就是這群資料的中位數;如果資料的個數是偶數,則中間那2個數據的算術平均值就是這群資料的中位數。

因此,在計算中位數Median時候,需要根據奇偶分類討論。

解決此題的方法可以依照:尋找一個unioned sorted array中的第k大(從1開始數)的數。因而等價於尋找並判斷兩個sorted array中第k/2(從1開始數)大的數。

特殊化到求median,那麼對於奇數來說,就是求第(m+n)/2+1(從1開始數)大的數。

而對於偶數來說,就是求第(m+n)/2大(從1開始數)和第(m+n)/2+1大(從1開始數)的數的算術平均值。

那麼如何判斷兩個有序陣列A,B中第k大的數呢?

我們需要判斷A[k/2-1]和B[k/2-1]的大小。

如果A[k/2-1]==B[k/2-1],那麼這個數就是兩個陣列中第k大的數。

如果A[k/2-1]<B[k/2-1], 那麼說明A[0]到A[k/2-1]都不可能是第k大的數,所以需要捨棄這一半,繼續從A[k/2]到A[A.length-1]繼續找。當然,因為這裡捨棄了A[0]到A[k/2-1]這k/2個數,那麼第k大也就變成了,第k-k/2個大的數了。

如果 A[k/2-1]>B[k/2-1],就做之前對稱的操作就好。

 這樣整個問題就迎刃而解了。

當然,邊界條件頁不能少,需要判斷是否有一個數組長度為0,以及k==1時候的情況。

以上是百度的別人的部落格,思路就是這個思路,變形就是讓你找第K小元素的時候別不會找了
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
         int len = nums1.length + nums2.length;
        if (len % 2 == 1) {
            return findKth(nums1, 0, nums2, 0, len / 2 + 1);
        }
        return (
            findKth(nums1, 0, nums2, 0, len / 2) + findKth(nums1, 0, nums2, 0, len / 2 + 1)
        ) / 2.0;
    }

    // find kth number of two sorted array
    public static int findKth(int[] nums1, int A_start,
                              int[] nums2, int B_start,
                              int k){    	
		if (A_start >= nums1.length) {
			return nums2[B_start + k - 1];
		}
		if (B_start >= nums2.length) {
			return nums1[A_start + k - 1];
		}

		if (k == 1) {
			return Math.min(nums1[A_start], nums2[B_start]);
		}
		
		int A_key = A_start + k / 2 - 1 < nums1.length
		            ? nums1[A_start + k / 2 - 1]
		            : Integer.MAX_VALUE;
		int B_key = B_start + k / 2 - 1 < nums2.length
		            ? nums2[B_start + k / 2 - 1]
		            : Integer.MAX_VALUE; 
		
		if (A_key < B_key) {
			return findKth(nums1, A_start + k / 2, nums2, B_start, k - k / 2);
		} else {
			return findKth(nums1, A_start, nums2, B_start + k / 2, k - k / 2);
		}
    }
}

151Reverse Words in a String這個也比較簡單
public class Solution {
    public String reverseWords(String s) {
        if(s == null || s.length() == 0){
            return "" ;
        }
        String[] a = s.split(" ") ;
        StringBuilder sb = new StringBuilder() ;
        for(int i = a.length - 1 ; i >= 0 ; i --){
            //注意這裡不要寫 == 
            if(!a[i].equals("")){
                sb.append(a[i]).append(" ") ;
            }
        }
        return sb.length() == 0 ? "" :sb.substring(0 , sb.length() - 1) ;
    }
}
下面出一個思考題,如何把一個Rotated sort array復原?

答:三步翻轉法