二分查找總結


  最近刷leetcode和lintcode,做到二分查找的部分,發現其實這種類型的題目很有規律,題目大致的分為以下幾類:

  1.最基礎的二分查找題目,在一個有序的數組當中查找某個數,如果找到,則返回這個數在數組中的下標,如果沒有找到就返回-1或者是它將會被按順序插入的位置。這種題目繼續進階一下就是在有序數組中查找元素的上下限。繼續做可以求兩個區間的交集。

  2.旋轉數組問題,就是將一個有序數組進行旋轉,然后在數組中查找某個值,其中分為數組中有重復元素和沒有重復元素兩種情況。

  3.在楊氏矩陣中利用二分查找,針對矩陣性質的不同,有多種解法,可以對每一行遍歷然后進行二分,也可以從左下角進行二分查找,也可以從右上角進行二分查找。

  4.函數的極大值問題,就是在一個數組中求極大值,極大值就是某個數大於數組當中左右兩邊的數,位於端點的元素只需大於它挨着的元素就行。典型的例題就是尋找峰值問題。

  5.利用極限和二分查找的思想,進行冪運算,開平方根和對數函數的求解,對於浮點數和整數有何異同。

二分查找的區間控制的三種方式:

  1.閉區間

    取值的范圍是[left,right],一定要保證每次循環結束后left+1或者right-1,結束的狀態left>right,left在右邊,right在左邊,目標值下標確定是left。

  2.開區間

    取值范圍是[left,right),right無法得到,它有兩種形式left+1<right和left<right.

    當是left+1<right這種形式時,left和right都不需要加一或者減一,結束狀態是left<right,無法確定最后的目標值下標是left還是right,最后還需要做一次判斷。

    當是left<right這種形式時,left需要加一,結束狀態是left>right,left在right右邊,left的位置就是所求結果的位置。在集合中進行二分查找,比較nums[mid],此時要與nums[right]進行比較。

1.基礎二分查找,查找上下限,求兩個區間的交集

   例如leetcode278題,題目如下:

 

You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad.

You are given an API bool isBadVersion(version) which will return whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.

 

  這是一個典型的二分查找問題,題目的解如下:

  整個二分查找分為控制區域和判斷函數,控制區域是為了選擇合適的值,判斷函數用來判斷值是否正確。控制區域的邊界是特別容易出錯的地方,一般有閉區間和開區間兩種方式,我做題的時候一般用的是閉區間,假設存在low和high兩個指針,那么閉區間的范圍就是[low,high],一定要保證每次循環結束后low+1或者high-1,結束狀態是low>high,low在右邊,high在左邊,目標值的下標確定是left。

  接着看另一個題目搜索插入位置http://www.lintcode.com/problem/search-insert-position如下:

 

給定一個排序的整數數組(升序)和一個要查找的整數target,用O(logn)的時間查找到target第一次出現的下標(從0開始),如果target不存在於數組中,返回-1。
樣例

[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 class Solution {
    /** 
     * param A : an integer sorted array
     * param target :  an integer to be inserted
     * return : an integer
     */
    public int searchInsert(int[] A, int target) {
        // write your code here
        if (A == null || A.length == 0) {
            return 0;
        }
        int low = 0;
        int high = A.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (A[mid] == target) {
                return mid;
            } else if (A[mid] > target) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }return low;
    }
}

 

另一類題目http://www.lintcode.com/zh-cn/problem/search-for-a-range/是尋找一個有序數組中元素的上下限,也可以是元素在數組中出現的下標范圍,題目如下:

給定一個包含 n 個整數的排序數組,找出給定目標值 target 的起始和結束位置。

如果目標值不在數組中,則返回[-1, -1]

樣例
給出[5, 7, 7, 8, 8, 10]和目標值target=8,

返回[3, 4]

先判斷該元素是否在數組中,如果不在返回[-1,-1],否則,先查找下限位置,在查找上限位置,查找下限時直接判斷nums[mid]<target是否成立,是的話low=mid+1,查找上限時判斷nums[mid]>target是否成立,如果成立high=mid-1,代碼如下:

 

public class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] res = {-1, -1};
        if (nums == null || nums.length == 0) {
            return res;
        }
        int start = findLow(nums, target);
        int end = findHigh(nums, target);
        res[0] = start;
        res[1] = end;
        return res;
    }
    public int findLow(int[] nums, int target) {
        int idx = -1;
        int low = 0;
        int high = nums.length - 1;
        int mid;
        while (low <= high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
            if (nums[mid] == target) idx = mid;
        }
        return idx;
    }
    public int findHigh(int[] nums, int target) {
        int idx = -1;
        int low = 0;
        int high = nums.length - 1;
        int mid;
        while (low <= high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] > target) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
            if (nums[mid] == target) idx = mid;
        }
        return idx;
    }
}

 

 

 

還有另一種解法是先調用Arrays.binarySearch(nums,target)查找到target的位置,然后讓start=mid,當start>=0&&nums[start]==target,start--,循環結束后在進行一次start++,對end也是如此,這樣的時間復雜度也是Olog(n),代碼如下:

 

public class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] res = {-1, -1};
        if (nums == null || nums.length == 0) {
            return res;
        }
        int mid = binarySearch(nums, target);
        if (mid < 0) {
            return res;
        }
        int start = mid;
        while (start >= 0 && nums[start] == target) {
            start--;
        }
        start++;
        res[0] = start;
        int end = mid;
        while (end < nums.length && nums[end] == target) {
            end++;
        }
        end--;
        res[1] = end;
        return res;
    }
    
    public int binarySearch(int[] nums, int target) {
        int idx = -1;
        int low = 0;
        int high =nums.length - 1;
        int mid;
        while (low <= high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] == target) {
                idx = mid;
                break;
            } else if (nums[mid] > target) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return idx;
    }
}

 

 

 

類似的題目有查找元素第一次出現的位置和最后一次出現的位置,如果找到,則返回元素位置,如果沒找到,那么返回-1。例如以下例題,查找元素第一次出現的位置http://www.lintcode.com/zh-cn/problem/first-position-of-target/,題目如下:

 

給定一個排序的整數數組(升序)和一個要查找的整數target,用O(logn)的時間查找到target第一次出現的下標(從0開始),如果target不存在於數組中,返回-1。

樣例
在數組 [1, 2, 3, 3, 4, 5, 10] 中二分查找3,返回2。

 

這道題其實相對簡單,就是查找這個元素,找到的話直接返回位置,如果沒有,則返回start的位置,需要注意的是,這道題在確定范圍時判斷條件不能是start<=end,需要是start<end,原因是當數組是[1,4,4,5,7,7,8,9,9,10],當查找1時,如果是小於等於的話,那么最后由於查找到了,此時end=mid=start,這個時候就會進入死循環。具體的代碼如下:

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 || nums == null) {
            return -1;
        }
        int start = 0;
        int end = nums.length - 1;
        while (start < end) {
            int mid = start + ((end - start) >> 1);
            if (nums[mid] == target) {
                end = mid;
            } else if (nums[mid] > target) {
                end = mid - 1;
            } else {
                start = mid + 1;
            }
        }
        if (nums[start] == target) {
            return start;
        }
        if (nums[end] == target) {
            return end;
        }
        return -1;
    }
}

里一個題就是查找元素最后出現的位置,題目如下:

給一個升序數組,找到target最后一次出現的位置,如果沒出現過返回-1

樣例

給出 [1, 2, 2, 4, 5, 5].

target = 2, 返回 2.

target = 5, 返回 5.

target = 6, 返回 -1.

如果這里的判斷條件依然是start<end的話,代碼就會進入死循環,因為在求mid的時候是向左邊取整的。考慮這樣的一個情況[...,5,5],假設target為5,那么start就會一直向右靠近,最后到n-2的位置,而end此時為n-1,再次進入循環mid等於n-2,所以就進入了死循環。此時建議大家寫成start + 1 < end,最后再判斷start和end(按照所需先后判斷)即可,這種寫法適用於所有的情況,不容易出現問題。關鍵代碼如下:

while (start + 1 < end) {
    int mid = (start + end)>>1;
    if (A[mid] > target) {
    end = mid;
    } else { 
    start = mid;
  }
}

  通過這兩個題可以看出,在進行二分查找時,首先要縮小區間,然后剩下兩個下標,最后再在判斷兩個下標,這樣就把問題簡化了。

另一種題目是求兩個集合的交集,這種題目很簡單,首先是對兩個數組進行排序,然后遍歷一個集合,把每一個數在另一個集合中進行二分查找,如果找到的話,就把該數加入到結果的集合中,時間復雜度是O(mlog(n))具體的代碼如下:

 

    public ArrayList<Integer> FindElements(int[] nums1, int[] nums2) {
        if (nums1.length == 0 || nums2.length == 0 || nums1 == null || nums2 == null) {
            return null;
        }
        ArrayList<Integer> ans = new ArrayList<>();
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int key;
        for (int i = 0; i < nums1.length; i++) {
            key = nums1[i];
            int low = 0;
            int high = nums2.length - 1;
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (nums2[mid] == key) {
                    ans.add(key);
                    break;
                } else if (nums2[mid] < key) {
                    low = mid + 1;
                } else {
                    high = mid - 1;
                }
            }
        }
        return ans;
}

 

 

2.旋轉數組問題

  旋轉數組就是將一個有序的數組,按照某個中軸進行前后旋轉,例如[1,2,3,4,5,6]旋轉后可能變為[3,4,5,6,1,2],這種題目可以分為兩種,在沒有重復數字的旋轉數組中查找某個元素,在有重復數字的數組中查找某個元素,首先看第一種:

  這個題目解得時候,首先查找mid下標元素的值,判斷nums[mid]是否等於target,如果是,返回1;如果不是的話就與low位置的值相比較,判斷nums[low]<nums[mid],如果是,那么這個范圍內的數字是單調遞增的,如果不是,那么這個范圍內的數字不是單調的。如果是單調遞增的,那么判斷這個nums[low]<=target<=nums[mid],是的話那么讓high=mid,否則的話low=mid+1,;如果不是單調遞增的話,那么判斷nums[mid]=<target<=nums[high],如果是的話,令low=mid,否則的話讓high=mid-1。由於區間是low+1<high,所以最后要對結果進行驗證,判斷low和high哪一個符合要求,具體代碼如下:

 

public class Solution {
    public int search(int[] nums, int target) {
        if (nums.length == 0 || nums == null) {
            return -1;
        }
        int low = 0;
        int high = nums.length - 1;
        while(low + 1 < high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[mid] > nums[low]) {//前半部分是升序
                if (target >= nums[low] && target <= nums[mid]) {//待查找的元素再升序子序列中
                    high = mid;
                } else {
                    low = mid + 1;
                }
            } else if (nums[mid] < nums[low]){//前半部分不是升序
                if (target >= nums[mid] && target <= nums[high]) {
                    low = mid;
                } else {
                    high = mid - 1;
                }
            }
        }
        if (nums[low] == target) {
            return low;
        }
        if (nums[high] == target) {
            return high;
        }
        return -1;
    }
}

 

 另一種情況是旋轉數組中存在重復元素的時候,這個時候與上面基本相似,就是加一個判斷如果nums[mid]=nums[low]的話,就是讓low++,具體代碼如下:

 

public class Solution {
    public boolean search(int[] nums, int target) {
        if (nums.length == 0 || nums == null) {
            return false;
        }
        int low = 0;
        int high = nums.length - 1;
        while(low + 1 < high) {
            int mid = low + ((high - low) >> 1);
            if (nums[mid] == target) {
                return true;
            }
            if (nums[mid] > nums[low]) {//前半部分是升序
                if (target >= nums[low] && target <= nums[mid]) {//待查找的元素再升序子序列中
                    high = mid;
                } else {
                    low = mid + 1;
                }
            } else if (nums[mid] < nums[low]){//前半部分不是升序
                if (target >= nums[mid] && target <= nums[high]) {
                    low = mid;
                } else {
                    high = mid - 1;
                }
            } else {
                low++;
            }
        }
        if (nums[low] == target) {
            return true;
        }
        if (nums[high] == target) {
            return true;
        }
        return false;
    }
}

 

 還有一類題目是在旋轉數組中查找最小值。題目如下:

假設一個旋轉排序的數組其起始位置是未知的(比如0 1 2 4 5 6 7 可能變成是4 5 6 7 0 1 2)。

你需要找到其中最小的元素。

你可以假設數組中不存在重復的元素。

 注意事項

You may assume no duplicate exists in the array.

您在真實的面試中是否遇到過這個題? Yes
樣例
給出[4,5,6,7,0,1,2]  返回 0

 

最開始應該判斷nums[low]<nums[high],是的話這個數組就不是旋轉數組,那么最小值肯定是第一個元素;如果不是的話首先是沒有重復元素的旋轉數組當中,這個時候首先要判斷nums[mid]>nums[low],如果是的話,最小值肯定在在mid和high之間,令low=mid+1;否則的話領high=mid,代碼如下:

 

public class Solution {
    public int findMin(int[] nums) {
        if (nums.length == 0 || nums == null) {
            return -1;
        }
        int low = 0;
        int high = nums.length - 1;
        if (nums[low] < nums[high]) {
            return nums[low];
        }
        int mid;
        while (low < high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] > nums[high]) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
}

 

 

 

 

如果是存在重復元素的話,題目如下:

假設一個旋轉排序的數組其起始位置是未知的(比如0 1 2 4 5 6 7 可能變成是4 5 6 7 0 1 2)。

你需要找到其中最小的元素。

數組中可能存在重復的元素。

 注意事項

The array may contain duplicates.

您在真實的面試中是否遇到過這個題? Yes
樣例
給出[4,4,5,6,7,0,1,2]  返回 0

 

那么判斷nums[mid]>nums[high],如果是的話,那么最小值肯定在mid和high中間,然后判斷nums[mid]<nums[low],如果是的話最小值肯定在low和mid中間;否則的話都有可能,這個時候只需要令high--,代碼如下:

 

public class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        if (nums[low] < nums[high]) {
            return nums[low];
        }
        int mid;
        while (low < high) {
            mid = low + ((high - low) >> 1);
            if (nums[mid] > nums[high]) {
                low = mid + 1;
            } else if (nums[mid] < nums[high]) {
                high = mid;
            } else {
                high--;
            }
        }
        return nums[low];
    }
}

 

 其實這幾道題邊界的判斷條件還不是很理解,也就是low和high是小於等於還是小於,這個要回頭看一下,但是題目解法理解。

3.楊氏矩陣問題

   先看一個題目,74. Search a 2D Matrix,題目如下:

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

Integers in each row are sorted from left to right.
The first integer of each row is greater than the last integer of the previous row.
For example,

Consider the following matrix:

[
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
Given target = 3, return true.

 

  這道題目有很多種解法,首先看一種,由於這個二維數組是遞增的。所以可以把它當做一個一維數組,從頭到尾進行二分查找,假設二維數組中每一行有n列,那么在一維數組中,下標為x的元素對應在二維數組中就是nums[x/n][x%n],具體的代碼如下:

/**
     * 由於數組是有序遞增的,那么可以把二維數組轉換為一位數組進行操作
     * 假設二維數組每一行有n列,那么一位數組中下標為x的數對應的二維數組中
     * 的數的為matrix[x/n][x%n],利用二分法對一維數組進行查找即可
     * 時間復雜度為O(log(m*n))
     * @param matrix
     * @param target
     * @return
     */
    public boolean searchMatrix(int[][] matrix, int target) {
        boolean flag = false;
        if (matrix.length == 0 || matrix == null || matrix[0] == null || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int left = 0;
        int right = m * n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            int num = matrix[mid / n][mid % n];
            if (num == target) {
                flag = true;
                break;
            } else if (num > target) {
                right = mid - 1;
            } else if (num < target) {
                left = mid + 1;
            }
        }
        return flag;
    }

  

  另一種解法就是從矩陣的左下角或者右上角開始搜索,以從左下角搜索為例,如果target小於這個數,那么行數減一,如果大於這個數,列數加一。算法的時間復雜度為O(m+n)。具體的代碼如下:

 

/**
     * 從左下角開始查找,如果target小於這個數,則行數減一,如果大於這個數,則列數加一
     * 時間復雜度為O(m)+O(n),m為行數,n為列數
     * @param matrix
     * @param target
     * @return
     */
    public boolean searchMatrix(int[][] matrix, int target) {
        boolean flag = false;
        if (matrix.length == 0 || matrix == null || matrix[0] == null || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int i = m - 1;
        int j = 0;
        while (i >= 0 && j < n) {
            if (matrix[i][j] < target) {
                j++;
            } else if (matrix[i][j] > target) {
                i--;
            } else {
                flag = true;
                break;
            }
        }
        return flag;
    }

 

  還可以將target的值於每一行的第一個元素進行比較,找到第一個不大於target的數,並獲得這一行,在這一行進行二分查找,這種解法的時間復雜度是O(logm)+O(logn),算是時間復雜度最好的解法。具體的代碼如下:

 

public class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) {
            return false;
        }
        int low = 0;
        int high = matrix.length - 1;
        int mid = 0;
        while (low + 1 < high) {
            mid = low + ((high - low) >> 1);
            if (matrix[mid][0] == target) {
                return true;
            } else if (matrix[mid][0] > target) {
                high = mid;
            } else {
                low = mid;
            }
        }
        int index = matrix[high][0] <= target ? high : low;
        low = 0;
        high = matrix[0].length - 1;
        while (low + 1 < high) {
            mid = low + ((high - low) >> 1);
            if (matrix[index][mid] == target) {
                return true;
            } else if (matrix[index][mid] > target) {
                high = mid;
            } else {
                low = mid;
            }
        }
        if (matrix[index][low] == target || matrix[index][high] == target) {
            return true;
        }
        return false;
    }
}

 

  還有一種解法就是對每一行進行二分查找,假設有m行n列,那么時間復雜度就是O(mlog(n)),這個比較簡單,所以就不詳細介紹了。

  關於楊氏矩陣的另一種題目就是這個矩陣只是每一行是單調遞增的,整體不是,這樣的話就沒法將二維數組轉換成一維數組去操作了,但是還是可以從右上角或是左下角去查找,或者是對每一行進行二分查找。

4.函數的極大值問題

  直接上題,162. Find Peak Element,具體的題目如下:

 

A peak element is an element that is greater than its neighbors.

Given an input array where num[i] ≠ num[i+1], find a peak element and return its index.

The array may contain multiple peaks, in that case return the index to any one of the peaks is fine.

You may imagine that num[-1] = num[n] = -∞.

For example, in array [1, 2, 3, 1], 3 is a peak element and your function should return the index number 2.

 

 

  這個題的話就跟函數的極值有關了,首先如果使用線性算法,也就是在O(n)的時間內解決的話,直接比較num[i]>num[i-1]&&num[i]>num[i+1],注意邊界,兩個邊界,只要num[0]>num[1]或者num[n]>num[n-1],這時候邊界的值就是峰值。具體代碼如下:

public class Solution {
    public int findPeakElement(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return 0;
        }
        //不包含兩端的元素
        for (int i = 1; i < n - 1; i++) {
            if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) {
                return i;
            }
        }
        if (nums[0] > nums[1]) {
            return 0;
        }
        if (nums[n - 1] > nums[n - 2]) {
            return n - 1;
        }
        return 0;
    }
}

  

  另外一種是用二分法求解,首先如果是單調遞減,即nums[i]>nums[i+1],那么就往左邊尋找峰值或者i自身就是峰值;如果是單調遞增,即nums[i]<nums[i+1],那么就去i右邊尋找峰值我們很容易在紙上畫出來某個點的四種情況(如下圖所示):

 

第一種情況:當前點就是峰值,直接返回當前值。

第二種情況:當前點是谷點,不論往那邊走都可以找到峰值。

第三種情況:當前點處於下降的中間,往左邊走可以到達峰值。

第四種情況:當前點處於上升的中間,往右邊走可以達到峰值。

 

 具體代碼如下:

public class Solution {
    public int findPeakElement(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return 0;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid;
        while (start < end) {
            mid = start + ((end - start) >> 1);
            if (nums[mid] > nums[mid + 1]) {
                end = mid;
            } else {
                start = mid + 1;
            }
        }
        return start;
    }
}

 5.冪級數,平方根,取對數求解

    首先是求解冪級數,直接看題leetcode50. Pow(x, n),題目如下:

Implement pow(x, n).

  就是求解一個數的冪級數並返回,這道題的一個思路就是利用二分法,判斷n的值,如果n=0,直接返回1,如果n=1,返回x,否則的話判斷n的奇偶性,如果n是偶數,那么x的n次方就可以分解成兩個x的n/2次方相乘,然后繼續分解;如果是奇數,那么直接分解成兩個x的n/2次方相乘再乘以x,然后遞歸的調用分解函數就行,具體代碼如下;

public class Solution {
    public double myPow(double x, int n) {
        if (n == 0) {
            return 1;
        } else if (n == 1) {
            return x;
        } else {
            if (n > 0) {
                return pow(x, n);
            } else {
                return 1 / pow(x, -n);
            }
        }
    }
    public double pow(double x, int n) {
        if (n == 1) {
            return x;
        } else {
            double half = pow(x, n>>>1);
            if (n % 2 == 0) {
                return half * half;
            } else {
                return half * half * x;
            }
        }
    }
}

  

  接下來是求解平方根,首先是求解浮點數的平方根,有兩種方法,一種是二分法,一種是牛頓法,接下來是求解整數的平方根。

  牛頓法的分析如下:

  求浮點數的平方根要求如下:

求解一個浮點數的平方根,要求誤差不能超過0.00001
可以用二分法或是牛頓法實現

  求解的時候要注意比較mid和x/mid,因為如果直接比較mid*mid可能會溢出,而且要對數字進行舍入判斷,具體代碼如下;

  

public class MySqrt {

    public static final double d = 0.00001;
    
    public static final int dd = 1;
    /**
     * 由於是浮點數,所以會存在sqrt(9)=2.99999的情況,這個時候就需要對數進行四舍五入
     * 如果正好是平方根的話比如3*3=9,那么返回四舍五入后的值
     * 否則的話返回傳進來的mid
     * @param x
     * @param mid
     * @return
     */
    private double isSquare(double x, double mid) {
        long a = Math.round(mid);
        if (a * a == x) {
            return a;
        } else {
            return mid;
        }
    }
    
    public double sqrtNewton(double y0) {
        double x = y0;
        double nextX;
        while (true) {
            nextX = x - (x * x - y0) / (2 * x);
            if (Math.abs(nextX - y0 / nextX) <= d) {
                return isSquare(y0, nextX);
            }
            x = nextX;
        }
    }
    public double sqrtBinary(double x) {
        if (x == 0 || x == 1) {
            return x;
        } else {//定義三個指針,start,end,mid
            double start = 0;
            double end = x;
            double mid;
            while (true) {
                mid = (start + end) / 2;
                if (mid == x / mid || Math.abs(mid - x / mid) <= d) {
                    return isSquare(x, mid);
                } else if (mid < x / mid) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
            
        }
    }
    /**
     * 求解整數的平方根
     * @param x
     * @return
     */
    public int mysqrt(int x) {
        if (x == 0 || x == 1) {
            return x;
        } else {//定義三個指針,start,end,mid
            int start = 0;
            int end = x;
            int mid;
            while (true) {
                mid = (start + end) / 2;
                if (mid == x / mid || end - start <= 1) {
                    return mid;
                } else if (mid < x / mid) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
            
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MySqrt s = new MySqrt();
        System.out.println(s.sqrtBinary(2));
        System.out.println(s.sqrtBinary(3));
        System.out.println(s.sqrtBinary(4));
        System.out.println(s.sqrtBinary(5));
        System.out.println(s.sqrtBinary(9.0));
        System.out.println(Math.abs(s.sqrtBinary(10) - Math.sqrt(10)) <= d);
        System.out.println(s.sqrtNewton(2));
        System.out.println(s.sqrtNewton(3));
        System.out.println(s.sqrtNewton(4));
        System.out.println(s.sqrtNewton(5));
        System.out.println(s.sqrtNewton(9.0));
        System.out.println(Math.abs(s.sqrtNewton(10) - Math.sqrt(10)) <= d);
    }

}
View Code

  

  leetcode69. Sqrt(x)題目如下:

Implement int sqrt(int x).

Compute and return the square root of x.

  這個體育上面浮點數的分析類似,不過進行比較的時候要比較end-start<=1,因為整數的時候要取整,而且不需要進行舍入操作,具體的代碼如下:

public class Solution {
    public int mySqrt(int x) {
        if (x == 0 || x == 1) {
            return x;
        } else {
            int start = 0;
            int end = x;
            int mid;
            while (true) {
                mid = (start + end) / 2;
                if (mid == x / mid || end - start <= 1) {
                    return mid;
                } else if (mid < x / mid) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
        }
    }
}

 

  接下來就是求解對數,logab,其實就是求解a的x次方等於b,求解x,既可以用二分法做,也可以用牛頓法做,思路如下:

 

    牛頓法的分析如下:

 

    具體的代碼如下:

public class MyLog {
    /**
     * 時間復雜度大概是log(n)*log(n)
     * @param a
     * @param b
     * @return
     */
    public double logBinary(double a, double b) {
        if (b == 1) {
            return 0;
        } else {
            double start = 0;
            double end = b;
            double mid;
            while (true) {
                mid = (start + end) / 2;
                double pow = Math.pow(a, mid);
                if (Math.abs(pow - b) <= 0.00001) {
                    return isLog(a, mid, b);
                } else if (pow < b) {
                    start = mid;
                } else {
                    end = mid;
                }
            }
        }
    }
    /**
     * 處理正好是對數的情況
     * @param a
     * @param mid
     * @param b
     * @return
     */
    public double isLog(double a, double mid, double b) {
        long c = Math.round(mid);
        if (Math.pow(a, c) == b) {
            return c;
        } else {
            return mid;
        }
    }
    public double logNewton(double a, double b, double init) {
        if (b == 1) {
            return 0;
        } else {
            double lnA = Math.log(a);
            double x = init;
            double nextX;
            while (true) {
                double pow = Math.pow(a, x);
                nextX = x - (pow - b) / (pow * lnA);
                if (Math.abs(Math.pow(a, nextX) - b) <= 0.00001) {
                    return isLog(a, nextX, b);
                }
                x = nextX;
            }
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyLog m = new MyLog();
        System.out.println(m.logBinary(2, 9));
        System.out.println(m.logBinary(3, 243));
        System.out.println(m.logNewton(2, 9, 9));
        System.out.println(m.logNewton(3, 243, 243));
        
    }

}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM