一道題,帶你搞定二分法! (JavaScript)

題目描述

題目有點長,我就截個圖展示了。
在這裏插入圖片描述

解題思路

這跟我之前做的那個旋轉數組有相似之處,都是兩個有序序列的組合。
JavaScript:leetcode_33. 搜索旋轉排序數組(二分法)

看題目限制,肯定又是不能用遍歷的O(n)。而且對獲取mountain的值有100次的限制。

那麼自然就想到了二分法,結合題目,mountain長度爲10000那麼大概分十幾次就完事兒了。基本不會把一百次用完。

我的思路是,用二分法找到mountain的山頂top,將其分爲兩個有序序列,然後分別用二分法查找。

最終算法時間複雜度爲 O(log n)

  1. findTop 查找山頂top
  2. findLeftTarget 左序查找target,左序列爲升序序列。
  3. findRightTarget 右序列查找target,右序列爲降序序列。(也只是在判斷條件上有所區別)

如果不太清楚二分法,那麼請先看一下 文末擴展

題解:

/**
 * // This is the MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * function MountainArray() {
 *     @param {number} index
 *     @return {number}
 *     this.get = function(index) {
 *         ...
 *     };
 *
 *     @return {number}
 *     this.length = function() {
 *         ...
 *     };
 * };
 */

/**
 * @param {number} target
 * @param {MountainArray} mountainArr
 * @return {number}
 */
var findInMountainArray = function(target, mountainArr) {
    let length = mountainArr.length();
    let left_v = mountainArr.get(0);
    let right_v = mountainArr.get(length - 1);
    if (target < left_v && target < right_v) {
        return -1
    }
    let resMid = findTop(0, length - 1, mountainArr)
    let top = resMid.mid;
    let topV = resMid.mid_v;
    if (target > topV) {
        return -1;
    }
    let left_target = findLeftTarget(0, top, mountainArr, target);
    return left_target === -1 ? findRightTarget(top, length-1, mountainArr, target) : left_target;
};
var findTop = function(left, right, mountainArr) {
    let mid = left + ((right - left) >> 1);
    let mid_lv = mountainArr.get(mid - 1);
    let mid_v = mountainArr.get(mid);
    let mid_rv = mountainArr.get(mid + 1);
    if (mid_v > mid_lv) {
        if (mid_v > mid_rv) {
            return {
                mid: mid,
                mid_v: mid_v,
            }
        } else {
            return findTop(mid, right, mountainArr)
        }
    } else if (mid_v < mid_lv) {
        return findTop(left, mid, mountainArr)
    }
}
var findLeftTarget = function(left, right, mountainArr, target) {
    if (right - left <= 1) {
        let left_v = mountainArr.get(left);
        if (left_v === target) {
            return left;
        }
        let right_v = mountainArr.get(right);
        if (right_v === target) {
            return right;
        }
        return -1
    }
    let mid = left + ((right - left) >> 1);
    let mid_v = mountainArr.get(mid);
    if (mid_v < target) {
        return findLeftTarget(mid+1, right, mountainArr, target)
    } else if (mid_v > target) {
        return findLeftTarget(left, mid, mountainArr, target)
    } else {
        return mid;
    }
    return -1
}
var findRightTarget = function(left, right, mountainArr, target) {
    if (right - left <= 1) {
        let left_v = mountainArr.get(left);
        if (left_v === target) {
            return left;
        }
        let right_v = mountainArr.get(right);
        if (right_v === target) {
            return right;
        }
        return -1
    }
    let mid = left + ((right - left) >> 1);
    let mid_v = mountainArr.get(mid);
    if (mid_v > target) {
        return findLeftTarget(mid+1, right, mountainArr, target)
    } else if (mid_v < target) {
        return findLeftTarget(left, mid, mountainArr, target)
    } else {
        return mid;
    }
    return -1
}

擴展 二分法詳解

解釋:

算法:當數據量很大適宜採用該方法。採用二分法查找時,數據需是排好序的。

  • 基本思想:假設數據是按升序排序的,對於給定值key,從序列的中間位置k開始比較, 如果當前位置arr[k]值等於key,則查找成功;
  • 若key小於當前位置值arr[k],則在數列的前半段中查找,arr[low,mid-1];
  • 若key大於當前位置值arr[k],則在數列的後半段中繼續查找arr[mid+1,high],
  • 直到找到爲止,時間複雜度:O(log(n))
使用方法
  • 序列必須是有序的,無序序列無法使用二分法。
  • 通過遞歸查找,直至序列長度縮小到2或者1。
模擬實現

以 nums[1,2,3,4,5]爲例,找到數組中值爲1的下標

  1. left爲 0,right爲 4, 聲明函數find(left,right)
  2. 求出中間點 mid = left + ((right - left) >> 1) 。 (>> 爲位運算,相當於縮小2倍)
  3. 得到 mid 爲 2;判斷 nums[2] === 1 ?,若等返回mid,nums[2] 爲 3,不等 1 ,進入下一步
  4. 判斷nums[mid] > 1,由於nums[2] ===3 > 1,進入左序列find(0, 2)
  5. 求出中間點 mid = 0+ ((2- 0) >> 1),
  6. 得到mid 爲 1;判斷 nums[1] === 1 ?,若等返回mid,nums[1] 爲 2,不等 1 ,進入下一步
  7. 判斷nums[mid] > 1,由於nums[1] ===2 > 1,進入左序列find(0, 1)
  8. 求出中間點 mid = 0+ ((1 - 0) >> 1),
  9. 得到mid 爲 0;判斷 nums[0] === 1 ?,若等返回mid,nums[0] 爲 1,等 1 ,return mid;

至此得到最後結果 下標爲 0;

代碼實現
let nums = [1,2,3,4,5] ;
let target = 1;
var find = function(left, right, target, nums) {
    let mid = (left + ((right - left) >> 1));  
    if (nums[mid] == target) {
        return mid;
    }
    if (nums[mid] > target) {
	    return find(left, mid, target, nums);
    }
    if (nums[mid] < target) {
        return find(mid + 1, right, target, nums);
    }
}
find(0,4,1, nums)

這種是數組中一定包含target的情況下。如果不確定是否包含,需要在值只剩下1-2個的時候做出判斷。

let nums = [1,2,3,4,5] ;
let target = 1;
var find = function(left, right, target, nums) {
	if (right- left <= 1) {
		return nums[left] === target ? left : (nums[right] ===target ? right: -1)
	}
    let mid = (left + ((right - left) >> 1));  
    if (nums[mid] == target) {
        return mid;
    }
    if (nums[mid] > target) {
	    return find(left, mid, target, nums);
    }
    if (nums[mid] < target) {
        return find(mid + 1, right, target, nums);
    }
}
find(0,4,1, nums)

這樣如果不存在返回 -1

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