LintCode二分查找題總結

LC上二分查找那一章有這麼些題:



二分查找的題經常用於考,因爲它雖然看似簡單,但其實要完全正確卻不容易,很容易寫出死循環的程序。一個二分查找的程序可以很容易判斷出一個人功底扎不紮實。


457. Classical Binary Search

這是一道非常經典的二分查找題,給出一個有序數組以及一個目標值target,要求返回target在數組中的位置,若數組裏不存在target,則返回-1。套用經典的二分查找模板即可:

    public int findPosition(int[] A, int target) {
        if (A == null || A.length == 0) {
            return -1;
        }
        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;
        }
        if (A[end] == target) {
            return end;
        }
        return -1;
    }

14. First Position of Target

給定一個有序數組和一個目標值,要求在O(logn)的時間內找到那個目標值第一個出現的位置(數組中可能存在多個相同的目標值)

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

需要注意的是,當找到一個數和target相等時,要把end指針,即右指針指向target,並繼續循環搜索,直到整個區間的長度<=2,這個時候就跳出循環,看看這最後兩個數哪個是第一個就返回它。


458. Last Position of Target

給定一個有序數組和一個目標值,要求在O(logn)的時間內找到那個目標值最後1個出現的位置(數組中可能存在多個相同的目標值)。跟上道題類似,需要注意的是,當找到一個數和target相等時,要把start指針,即左指針指向target,並繼續循環搜索,直到整個區間的長度<=2,這個時候就跳出循環,看看這最後兩個數哪個是最後一個就返回它。

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

459. Closest Number in Sorted Array

在一個有序數組中,給定一個target,要求在數組中找到離target最近的數。

Given [1, 4, 6] and target = 3, return 1.

Given [1, 4, 6] and target = 5, return 1 or 2.

Given [1, 3, 3, 4] and target = 2, return 0 or 1 or 2.

還是用標準的二分查找模板,因爲它最後始終會把區間縮小爲長度爲2的區間內,這時候再去比較就可以得到答案了。

    public int closestNumber(int[] A, int target) {
        if (A == null || A.length == 0) {
            return -1;
        }
        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;
            }
        }
        int left = Math.abs(A[start] - target);
        int right = Math.abs(A[end] - target);
        return left < right ? start : end;
    }

462. Total Occurrence of Target

要在一個有序數組中求某個target出現了多少次,這道題可以在上上一道題的基礎上來做,找到數組中第一個出現target的位置,然後再數它出現了幾次就行。

    public int totalOccurrence(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) {
                start = mid;
            } else {
                end = mid;
            }
        }
        int count;
        if (A[start] == target) {
            count = 0;
            for (int i = start; i < A.length; i++) {
                if (A[i] == target) {
                    count++;
                }
            }
            return count;
        }
        if (A[end] == target) {
            count = 0;
            for (int i = end; i < A.length; i++) {
                if (A[i] == target) {
                    count++;
                }
            }
            return count;
        }
        return 0;
    }


60. Search Insert Position

給定一個排序數組和一個目標值,如果在數組中找到目標值則返回索引。如果沒有,返回到它將會被按順序插入的位置。你可以假設在數組中無重複元素。

[1,3,5,6],5 → 2

[1,3,5,6],2 → 1

[1,3,5,6],7 → 4

[1,3,5,6],0 → 0

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


28. Search a 2D Matrix

要求在一個二維數組中找到某個數target。這個矩陣具有以下特性:每行中的整數從左到右是排序的。每行的第一個數大於上一行的最後一個整數。

由於這個數組是從左到右有序、從上到下有序的。所以可以把它重排成一維的有序數組。利用除法和取模運算,把它映射到一位數組上,然後再用一位數組的二分搜索來做。

    public boolean searchMatrix(int[][] matrix, int target) {
        // write your code here
        if (matrix == null || matrix.length == 0) {
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        if (n == 0) {
            return false;
        }
        
        int left = 0, right = m * n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (matrix[mid/n][mid%n] == target) {
                return true;
            } else if (matrix[mid/n][mid%n] < target) {
                left = mid + 1;
            } else if (matrix[mid/n][mid%n] > target) {
                right = mid - 1;
            }
        }
        return false;
    }


547. Intersection of Two Arrays

求兩個數組的交集,最後的交集必須是唯一的,不得出現重複元素。有多種方法可以求解。我先只列出HashSet的解法:

    public int[] intersection(int[] nums1, int[] nums2) {
        if (nums1 == null || nums2 == null) {
            return null;
        }
        
        HashSet<Integer> set1 = new HashSet<Integer>();
        for (int i = 0; i < nums1.length; i++) {
            set1.add(nums1[i]);
        }
        
        HashSet<Integer> res = new HashSet<Integer>();
        for (int i = 0; i < nums2.length; i++) {
            if (set1.contains(nums2[i]) && !res.contains(nums2[i])) {
                res.add(nums2[i]);
            }
        }
        
        int[] result = new int[res.size()];
        int index = 0;
        for (Integer tmp : res) {
            result[index] = tmp;
            index++;
        }
        
        return result;
    }
解法一:用HashSet,掃描第一個數組,加進HashSet1中,得到的HashSet1是唯一的。然後掃描第二個數組,如果第二個數組的元素在HashSet1中存在,則加進HashSet2中。最後得到的HashSet2就是答案了。

解法二:先排序,然後掃描第二個數組,在掃描第二個數組的過程中使用binarySearch,binarySearch即在第一個數組中找第二個數組的某個元素,如果找到了則加入HashSet,這樣能保證答案是唯一的。

548. Intersection of Two Arrays II

跟上道題類似,但是不同的是最後的答案是可以出現重複元素的,即在原數組中出現了幾次,最後就得出現幾次。這道題得用一個HashMap,把第一個數組的元素以及出現次數存進HashMap中,然後再對第二個數組進行掃描。掃描的過程中,如果發現有匹配的,就加進res裏,並且把HashMap中對應的出現次數減1。

    public int[] intersection(int[] nums1, int[] nums2) {
        if (nums1 == null || nums2 == null) {
            return null;
        }
        HashMap<Integer, Integer> map = new HashMap();
        for (int i = 0; i < nums1.length; i++) {
            if (map.containsKey(nums1[i])) {
                map.put(nums1[i], 1 + map.get(nums1[i]));
            } else {
                map.put(nums1[i], 1);
            }
        }
        
        ArrayList<Integer> res = new ArrayList<Integer>();
        for (int i = 0; i < nums2.length; i++) {
            if (map.containsKey(nums2[i]) && map.get(nums2[i]) > 0) {
                res.add(nums2[i]);
                map.put(nums2[i], map.get(nums2[i]) - 1);
            }
        }
        
        int[] result = new int[res.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = res.get(i);
        }
        return result;
    }

141. Sqrt(x)

給一個整數,要求它的平方數,當然不能直接調用庫函數,這道題得用二分來模擬平方的操作。記得要用long來處理,因爲可能會出現溢出。

    public int sqrt(int x) {
        long start = 0, end = x;
        while (start + 1 < end) {
            long mid = start + (end - start) / 2;
            if (mid * mid > x) {
                end = mid;
            } else if (mid * mid == x) {
                return (int)mid;
            } else {
                start = mid;
            }
        }
        if (start * start <= x && end * end > x) {
            return (int)start;
        }
        return (int)end;
    }


586. Sqrt(x) II

要求出一個數的開根號,只不過從整數換成了小數。當然小數是沒辦法精確地計算根號值的,只能近似的去模擬,比如誤差不大於多少1e-10。

有2種方法可以解決這個問題,第一種是二分法。因爲根號值的數肯定是大於0的,如果那個數大於1,那麼它的根號值就肯定也大於1,如果那個數小於1,那麼他的根號值肯也小於1.這樣我們就有了查詢的初始區間,也就是[0, n]。然後我們去用二分法逼近,直到區間的長度縮小到了一定範圍,這樣我們就能得到近似值了。

第二種方法是牛頓法逼近,這篇博客講的很詳細:點擊打開鏈接 可以在紙上畫圖體驗一下牛頓逼近法的美妙之處。

public class Solution {
    /**
     * @param x a double
     * @return the square root of x
     */
    public double sqrt(double x) {
        return binary_search_sqrt(x);
    }
    public double newton_sqrt(double n) {
        double result = 1.0;
        double eps = 1e-12;
        while (Math.abs(result * result - n) > eps) {
            result = (result + n / result) / 2;
        }
        return result;
    }
    public double binary_search_sqrt(double n) {
        double start = 0, end = n > 1 ? n : 1.0;
        while (end - start > 1e-12) {
            double mid = (start + end) / 2;
            if (mid * mid > n) {
                end = mid;
            } else {
                start = mid;
            }
        }
        return start;
    }
}

428. Pow(x, n)

要自己實現一個求冪函數。

最直觀容易想到的方法就是用遞歸方法求n個x的乘積,注意考慮n的正負號,時間複雜度爲O(n)

    public double myPow(double x, int n) {
        if (n == 0) {
            return 1.0;
        } 
        if (n < 0) {
            return 1.0/myPow(x, -n);
        }
        return x * myPow(x, n - 1);
    }

考慮到n個x相乘式子的對稱關係,可以對上述方法進行改進,從而得到一種時間複雜度爲O(logn)的方法,遞歸關係可以表示爲pow(x,n) = pow(x,n/2)*pow(x,n-n/2)

    public double myPow(double x, int n) {
        if (n == 0) {
            return 1.0;
        } 
        if (n < 0) {
            return 1.0/myPow(x, -n);
        }
        double half = myPow(x, n / 2);
        if (n % 2 == 0) {
            return half * half;
        }
        return x * half * half;
    }

74. First Bad Version

二分法變種題,類似於first postion of target那道題

    public int findFirstBadVersion(int n) {
        int start = 1, end = n;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (!SVNRepo.isBadVersion(mid)) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (SVNRepo.isBadVersion(start)) {
            return start;
        } else {
            return end;
        }
    }

75. Find Peak Element

找一個數組的峯值,峯值也就是他這個點比左右兩邊的元素都要大。只要找到一個峯值就行。所以根據定義,數組的第一個元素和最後一個元素不可能是峯值。

二分的時候會有四種情況:

1)mid落在了上升區間,那這個時候的峯值肯定在mid右邊的區間

2)mid落在了下降區間,那這個時候的峯值肯定在mid左邊的區間

3)mid就在峯值,那就直接return

4)mid在極小值,那就隨便往右邊或者往左邊走都行

    public int findPeak(int[] A) {
        int start = 0, end = A.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (A[mid - 1] <= A[mid] && A[mid] <= A[mid + 1]) {
                start = mid;
                continue;
            }
            if (A[mid - 1] >= A[mid] && A[mid] >= A[mid + 1]) {
                end = mid;
                continue;
            }
            if (A[mid - 1] <= A[mid] && A[mid] >= A[mid + 1]) {
                return mid;
            }
            end = mid;
        }
        if (A[start] < A[end]) {
            return end;
        }
        return start;
    }

61. Search for a Range

在一個有序數組裏,找到一個數出現的範圍。跟那道total occurrence of target是一樣的思想。就是求一個數出現了幾次,只不過這道題是要返回第一個出現的位置以及最後一個出現的位置:

    public int[] searchRange(int[] A, int target) {
        int[] res = new int[2];
        if (A == null || A.length == 0) {
            return new int[]{-1, -1};
        }
        int start = 0, end = A.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (A[mid] < target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        if (A[start] == target) {
            res[0] = start;
            for (int i = start; i < A.length; i++) {
                if (A[i] == target) {
                    res[1] = i;
                }
            }
            return res;
        }
        if (A[end] == target) {
            res[0] = end;
            for (int i = end; i < A.length; i++) {
                if (A[i] == target) {
                    res[1] = i;
                }
            }
            return res;
        }
        return new int[]{-1, -1};
    }


447. Search in a Big Sorted Array

在一個很大的全都是正數的有序數組流裏面找target,你只能通過API來獲取特定位置的元素。由於二分法是需要邊界才能進行的,而這個數組流來說,我們需要找到它的右邊界,即第一個 ≥ target的元素的位置。這裏要用到倍增法。只要當前get到的元素比target小,我就把區間乘以2,一直到get到一個比target大的元素爲止。這時候我們就在Ologn的時間內把一個無限的數組流轉換成了一個有限有序數組。然後再用有序數組裏二分就好了。

不過有一個地方需要注意的是,這個不是真正無限的數組,他有一定邊界,但是他沒有告訴你邊界,但是如果你通過API get到的那個值爲-1的時候,就說明越界了,所以在倍增的同時需要保持不越界。

    public int searchBigSortedArray(ArrayReader reader, int target) {
        int index = 1;
        while (reader.get(index - 1) < target && reader.get(index - 1) != -1) {
            index *= 2;
        }
        int start = 0, end = index - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (reader.get(mid) == -1) {
                end = mid;
                continue;
            }
            if (reader.get(mid) < target) {
                start = mid;
            } else {
                end = mid;
            }
        }
        
        if (reader.get(start) == target) {
            return start;
        }
        if (reader.get(end) == target) {
            return end;
        }
        return -1;
    }


159. Find Minimum in Rotated Sorted Array

在一個旋轉有序數組裏找到最小值。旋轉有序的數組的意思是:(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2)。數組裏的元素都是唯一的,沒有重複。

總之這個數組一定是有序的,要麼是整段有序,要麼就是由2段有序數組組成。並且沒有重複元素的存在。

假如是整段有序,那就直接返回第一個元素就是最小值了。假如是後面那種分2段有序的情況,那就用二分查找找那個0的位置。比前一個元素小,比後一個元素要小的就是最小值了。我們需要指定一個target,找到第一個小於等於target的數,在這裏我們需要指定數組的最後一個元素爲target。

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


160. Find Minimum in Rotated Sorted Array II

跟上道題類似,但是數組可能會出現重複元素。比如{1,1,1,1,1,1}。這個時候,就沒法用二分來做了,所以只有從頭到尾遍歷所有元素纔可以。


62. Search in Rotated Sorted Array

在一個旋轉有序數組裏找到某個target。旋轉有序的數組的意思是:(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2)。並且裏面沒有重複元素。

總之這個數組一定是有序的,要麼是整段有序,要麼就是由2段有序數組組成。如果是整段有序,那用二分查找很好做。但是關鍵是處理後面那種分段有序的情況稍微棘手一點。

如果最後一個元素比第一個元素大,那就說明這個是單調遞增的有序數組,可以直接用二分查找模板來做。假如最後一個元素比第一個元素小的話,那麼則首先看它是在哪段區間裏,如果start對應的元素比mid對應的元素小,則是左邊那段上升區間。如果start對應的元素比mid對應的元素大,則是右邊那段上升區間。

    public int search(int[] A, int target) {
        if (A == null || A.length == 0) {
            return -1;
        }
        int start = 0, end = A.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (A[mid] == target) {
                return mid;
            }
            // 左邊上升區間
            if (A[start] < A[mid]) {
                if (A[start] <= target && target <= A[mid]) {
                    end = mid;
                } else {
                    start = mid;
                }
            } else { // 右邊上升區間
                if (A[mid] <= target && target <= A[end]) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
        }
        if (A[start] == target) {
            return start;
        }
        if (A[end] == target) {
            return end;
        }
        return -1;
    }

63. Search in Rotated Sorted Array II

跟上道題類似,但是數組可能會出現重複元素。比如{1,1,1,1,1,1}。這個時候,就沒法用二分來做了,所以只有從頭到尾遍歷所有元素纔可以。


38. Search a 2D Matrix II

在一個二維數組裏找給定值出現了幾次,二維數組的每一行都是有序的,每一列都是有序的。每一行、每一列中都沒有重複元素。

[
  [1, 3, 5, 7],
  [2, 4, 7, 8],
  [3, 5, 9, 10]
]
比如這個數組裏找3,則返回3出現的次數:2次。

有個巧妙的方法,就是從左下角開始搜索,如果左下角元素比target小,則往右移動;若比target大,則往上移動;若相等,則count++,並同時x--,y++

    public int searchMatrix(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0) {
            return 0;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        if (n == 0) {
            return 0;
        }
        int x = m - 1;
        int y = 0;
        int count = 0;
        while (x >= 0 && y < n) {
            if (matrix[x][y] < target) {
                y++;
            } else if (matrix[x][y] > target) {
                x--;
            } else {
                count++;
                x--;
                y++;
            }
        }
        return count;
    }

460. K Closest Numbers In Sorted Array

要求在一個數組中找到K個離target最近的數。我們可以先找到第一個大於等於target的數在數組中的位置,然後再從這個位置往兩邊用雙指針同時掃描,掃描到K個數的時候,就停止掃描,這樣就得到了K個最近的數了。

    // find the first index of element >= target
    private int firstIndex(int[] A, int target) {
        int start = 0, end = A.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (A[mid] >= target) {
                end = mid;
            } else {
                start = mid;
            }
        }
        
        if (A[start] >= target) {
            return start;
        }
        if (A[end] >= target) {
            return end;
        }
        return A.length;
    }

    public int[] kClosestNumbers(int[] A, int target, int k) {
        if (A == null || A.length == 0) {
            return A;
        }
        if (k > A.length) {
            return A;
        }
        int index = firstIndex(A, target);
        
        int start = index - 1, end = index;
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            if (start < 0) {
                res[i] = A[end++];
            } else if (end >= A.length) {
                res[i] = A[start--];
            } else {
                if (target - A[start] <= A[end] - target) {
                    res[i] = A[start--];
                } else {
                    res[i] = A[end++];
                }
            }
        }
        return res;
    }

183. Wood Cut

切木頭問題,給定幾塊木頭,和一個需要的木頭塊數目,你要如何砍這些木頭,使得在達到需要的木頭塊數目的同時又保證每塊木頭的長度一樣並且最大。

For L=[232, 124, 456], k=7, return 114. 比如給了你三塊長度分別爲232、124、456的木頭,你需要把他們均分成7塊等長的木頭,並且保證木頭長度最大。

在這裏的話其實是一個二分問題,114就是我們要找的值。而區間則是[0, 最長的木頭的長度]。然後我在這個區間內進行二分,113短了,115大了,只有114纔是滿足條件的。

    private int count(int[] L, int n) {
        int res = 0;
        for (int i = 0; i < L.length; i++) {
            res += L[i] / n;
        }
        return res;
    }
    public int woodCut(int[] L, int target) {
        int max = 0;
        for (int i = 0; i < L.length; i++) {
            max = Math.max(max, L[i]);
        }
        int start = 1, end = max;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (count(L, mid) < target) {
                end = mid;
            } else {
                start = mid;
            }
        }
        if (count(L, end) >= target) {
            return end;
        }
        if (count(L, start) >= target) {
            return start;
        }
        return 0;
    }


248. Count of Smaller Number

這道題最好用線段樹來做,不過暫時還沒接觸線段樹,就可以先用排序+二分查找的方法來做。先對數組排序,然後對於每個查詢,用二分查找在數組中進行查詢,找到原數組中第一個比查詢數大的數,然後再從後往前統計。

    private int count(int[] num, int target) {
        if (num == null || num.length == 0) {
            return 0;
        }
        int start = 0, end = num.length - 1;
        while (start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (target <= num[mid]) {
                end = mid;
            } else {
                start = mid;
            }
        }
        if (num[start] >= target) {
            return start;
        }
        if (num[end] >= target) {
            return end;
        }
        return 0;
    }
    public ArrayList<Integer> countOfSmallerNumber(int[] A, int[] queries) {
        Arrays.sort(A);
        ArrayList<Integer> res = new ArrayList<Integer>();
        for (int i = 0; i < queries.length; i++) {
            int n = count(A, queries[i]);
            res.add(n);
        }
        return res;
    }


633. Find the Duplicate Number

解法:時間複雜度O(n * log n) 二分查找(Binary Search)+ 鴿籠原理(Pigeonhole Principle) 參考維基百科關於鴿籠原理的詞條鏈接:https://en.wikipedia.org/wiki/Pigeonhole_principle “不允許修改數組” 與 “常數空間複雜度”這兩個限制條件意味着:禁止排序,並且不能使用Map等數據結構 小於O(n2)的運行時間複雜度可以聯想到使用二分將其中的一個n化簡爲log n 參考LeetCode Discuss:https://leetcode.com/discuss/60830/python-solution-explanation-without-changing-input-array 二分枚舉答案範圍,使用鴿籠原理進行檢驗 根據鴿籠原理,給定n + 1個範圍[1, n]的整數,其中一定存在數字出現至少兩次。 假設枚舉的數字爲 n / 2: 遍歷數組,若數組中不大於n / 2的數字個數超過n / 2,則可以確定[1, n /2]範圍內一定有解, 否則可以確定解落在(n / 2, n]範圍內。

可以考慮構造出邊界條件數組:[5,6,6,6] 來幫助確定二分查找的邊界條件

public class Solution {
    /**
     * @param nums an array containing n + 1 integers which is between 1 and n
     * @return the duplicate one
     */
    public int findDuplicate(int[] nums) {
        int start = 1, end = nums.length - 1;
        while (start + 1 < end) {
            int mid = (start + end) / 2;
            if (checkSmaller(nums, mid) <= mid) {
                start = mid;
            } else if (checkSmaller(nums, mid) > mid) {
                end = mid;
            }
        }
        if (checkSmaller(nums, start) > start) {
            return start;
        }
        return end;
    }
    
    public int checkSmaller(int[] nums, int target) {
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] <= target) {
                count++;
            }
        }
        return count;
    }
}

617. Maximum Average Subarray

一個數組既有正數也有負數,要求出長度大於等於K的最大平均子數組的平均長度是多少。也就是說需要找出一個長度大於等於K的子數組,這個子數組的平均數是最大的。只需要返回那個平均數即可,不需要求出整個子數組是什麼。比如說一個數組是[1, 12, -5, -6, 50, 3], k = 3。那麼就返回15.667。因爲 (-6 + 50 + 3) / 3 = 15.667

如果是要把整個子數組打印輸出,那可能這道題就沒啥可以優化的地方了,直接就循環比較,找出平均數最大的子數組輸出。但是問題是這道題並不要求把整個數組打印出來,而是需要你找到一個最大的平均數。要找一個最大的數,那其實可以想到或許可以用二分搜索去搜索這個數。

而平均數是一個小數,我們只要滿足精度的要求就好了,那麼二分搜索的循環中止條件就可以是左右區間的精度小於某個可以接受的範圍。

假設數組是A,最後需要返回的平均數是avg。假如子數組{A[1], A[2], A[3]}是我們想要的結果的話,那sum[3] = A[1] + A[2] + A[3] - avg - avg - avg應該是大於等於0的。

假設數組的最大值和最小值分別是max和min,那麼avg肯定是介於max和min之間的。所以二分搜索初始化的時候可以吧left和right賦值爲min和max。搜索的時候,假如遍歷數組發現有sum值是大於0的,那麼就可以肯定目標avg值是會比當前的mid要大的。反之則比mid小。

checksum的時候只需要檢查長度大於等於K的sum和就好,如果發現前面有badAss拖後腿,則直接刪掉badAss。然後badAss是要隨着遍歷數組的進行而不斷更新的。

    public boolean checkRest(int[] nums, int k, double mid) {
        double[] sum = new double[nums.length + 1];
        sum[0] = 0;
        double badAss = 0.0;
        for (int i = 1; i <= nums.length; i++) {
            sum[i] = sum[i - 1] + nums[i - 1] - mid;
            if (i >= k) {
                if (sum[i] - badAss >= 0) {
                    return true;
                }
                badAss = Math.min(badAss, sum[i - k + 1]);
            }
        }
        return false;
    }
    public double maxAverage(int[] nums, int k) {
        double min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] >= max) {
                max = nums[i];
            }
            if (nums[i] <= min) {
                min = nums[i];
            }
        }
        
        while (max - min > 1e-6) {
            double mid = (min + max) / 2.0;
            if (checkRest(nums, k, mid)) {
                min = mid;
            } else {
                max = mid;
            }
        }
        
        return min;
    }


600. Smallest Rectangle Enclosing Black Pixels

這道題給我們一個二維矩陣,表示一個圖片的數據,其中1代表黑像素,0代表白像素,現在讓我們找出一個最小的矩陣可以包括所有的黑像素,還給了我們一個黑像素的座標。這裏需要注意的一點是,所有黑色像素都是連通的。

那我們需要找的就是最小矩陣的上邊界、下邊界、左邊界、右邊界。

再來通過如下的圖來理解一下,假設有如下的矩陣:

"000000111000000"
"000000101000000"
"000000101100000"
"000001100100000"
那其實投影后就變成了如下的樣子:

"000001111100000"
由於二維矩陣裏黑像素是連通的,所以投影到一維的時候肯定是連續的。

row search爲例, 所有含有black pixel的column,映射到row x上時,必定是連續的。這樣我們可以使用binary search,在0到y裏面搜索最左邊含有black pixel的一列。接下來可以繼續搜索上下和右邊界。

public class Solution {
    /**
     * @param image a binary matrix with '0' and '1'
     * @param x, y the location of one of the black pixels
     * @return an integer
     */
    public int minArea(char[][] image, int x, int y) {
        if (image == null || image.length == 0) {
            return 0;
        }
        int left = binarySearchLeft(image, 0, y);
        int right = binarySearchRight(image, y, image[0].length - 1);
        int high = binarySearchHigh(image, x, image.length - 1);
        int low = binarySearchLow(image, 0, x);
        return (right - left + 1) * (high - low + 1);
    }
    
    public int binarySearchLeft(char[][] image, int left, int right) {
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            boolean black_flag = false;
            for (int i = 0; i < image.length; i++) {
                if (image[i][mid] == '1') {
                    black_flag = true;
                    break;
                }
            }
            if (black_flag) {
                right = mid;
            } else {
                left = mid;
            }
        }
        for (int i = 0; i < image.length; i++) {
            if (image[i][left] == '1') {
                return left;
            }
        }
        return right;
    }
    
    public int binarySearchRight(char[][] image, int left, int right) {
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            boolean black_flag = false;
            for (int i = 0; i < image.length; i++) {
                if (image[i][mid] == '1') {
                    black_flag = true;
                    break;
                }
            }
            if (black_flag) {
                left = mid;
            } else {
                right = mid;
            }
        }
        for (int i = 0; i < image.length; i++) {
            if (image[i][right] == '1') {
                return right;
            }
        }
        return left;
    }
    
    public int binarySearchHigh(char[][] image, int left, int right) {
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            boolean black_flag = false;
            for (int i = 0; i < image[0].length; i++) {
                if (image[mid][i] == '1') {
                    black_flag = true;
                    break;
                }
            }
            if (black_flag) {
                left = mid;
            } else {
                right = mid;
            }
        }
        for (int i = 0; i < image[0].length; i++) {
            if (image[right][i] == '1') {
                return right;
            }
        }
        return left;
    }
    
    public int binarySearchLow(char[][] image, int left, int right) {
        while (left + 1 < right) {
            int mid = (left + right) / 2;
            boolean black_flag = false;
            for (int i = 0; i < image[0].length; i++) {
                if (image[mid][i] == '1') {
                    black_flag = true;
                    break;
                }
            }
            if (black_flag) {
                right = mid;
            } else {
                left = mid;
            }
        }
        for (int i = 0; i < image[0].length; i++) {
            if (image[left][i] == '1') {
                return left;
            }
        }
        return right;
    }
    
}

390. Find Peak Element II

在二維數組裏面找peak,peak的定義是它比上下左右四個方向的數字都要小。而且只要找到一個peak就可以返回了。首先對每行進行二分,找到一行,然後用二分法找到這行裏面最大的元素,如果這個元素比上面的小,那就繼續往上進行二分。如果這個元素比下面的小,那就往下面進行二分。如果這個元素比上下都大,那就可以返回了。由於根據題意,每行每列都是滿足先變大後變小的,所以方法是奏效的。

    public List<Integer> findPeakII(int[][] A) {
        List<Integer> res = new ArrayList<Integer>();
        int low = 1, high = A.length - 2;
        while (low <= high) {
            int mid = (low + high) / 2;
            int col = findPeak(A, mid);
            if (A[mid][col] < A[mid - 1][col]) {
                high = mid - 1;
            } else if (A[mid][col] < A[mid + 1][col]) {
                low = mid + 1;
            } else {
                res.add(mid);
                res.add(col);
                return res;
            }
        }
        return res;
    }
    public int findPeak(int[][] A, int row) {
        int col = 0;
        for (int i = 0; i < A[row].length; i++) {
            if (A[row][i] > A[row][col]) {
                col = i;
            }
        }
        return col;
    }
假設輸入是n*n的矩陣,findPeak函數的時間複雜度是n,而findPeak被執行了logn次,所以最壞的時間複雜度是logn * n, 最好的情況是logn + n(橫豎各掃一遍就找到peak了)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章