题目描述
题目有点长,我就截个图展示了。
解题思路
这跟我之前做的那个旋转数组有相似之处,都是两个有序序列的组合。 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
使用chrome浏览器需要一个服务器环境,否则canvas的toDataURL方法会报错。 “开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情”