題目描述
題目有點長,我就截個圖展示了。
解題思路
這跟我之前做的那個旋轉數組有相似之處,都是兩個有序序列的組合。
JavaScript:leetcode_33. 搜索旋轉排序數組(二分法)
看題目限制,肯定又是不能用遍歷的O(n)。而且對獲取mountain的值有100次的限制。
那麼自然就想到了二分法,結合題目,mountain長度爲10000那麼大概分十幾次就完事兒了。基本不會把一百次用完。
我的思路是,用二分法找到mountain的山頂top,將其分爲兩個有序序列,然後分別用二分法查找。
最終算法時間複雜度爲 O(log n)
- findTop 查找山頂top
- findLeftTarget 左序查找target,左序列爲升序序列。
- 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的下標
- left爲 0,right爲 4, 聲明函數
find(left,right)
- 求出中間點
mid = left + ((right - left) >> 1)
。 (>> 爲位運算,相當於縮小2倍) - 得到 mid 爲 2;判斷
nums[2] === 1 ?
,若等返回mid
,nums[2] 爲 3,不等 1 ,進入下一步 - 判斷
nums[mid] > 1
,由於nums[2] ===3 > 1,進入左序列find(0, 2)
- 求出中間點
mid = 0+ ((2- 0) >> 1)
, - 得到mid 爲 1;判斷
nums[1] === 1 ?
,若等返回mid
,nums[1] 爲 2,不等 1 ,進入下一步 - 判斷
nums[mid] > 1
,由於nums[1] ===2 > 1,進入左序列find(0, 1)
- 求出中間點
mid = 0+ ((1 - 0) >> 1)
, - 得到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