3.二分法查找-练习

求开方

69.X的平方根(easy)

题目 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
 

提示:

0 <= x <= 231 - 1

分析
根据题目归类使用二分法,找出某个数的平方为x,或者是两个数平方范围内有x。由于想x^2可能会超出范围,所以使用x/temp与temp进行比较。之后利用二分法比较,大于temp的向左取区间,小于temp的包含左区间向右取区间,等于的直接返回。如果是到最后左右区间重合则可以直接返回区间。还可以多添加一个条件,直接判断右区间是不是小于temp,因为平方和本来就远大于X,第一个小于的,那一定就是平方根的左边界。

var mySqrt = function (x) {
    if (x <= 1) return x;
    else {
        let end = Math.floor(x / 2)
        let start = 0;
        let mid = 0;
        while (start <= end) {
            mid = Math.ceil((start + end) / 2)
            if (end <= x / end) return end;
            if (mid > x / mid) end = mid - 1;
            else if (mid == x / mid) return mid;
            else {
                start = mid;
            }
        }
    }
};

查找区间

34. 在排序数组中查找元素的第一个和最后一个位置(Medium)

题目:给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

输入:nums = [], target = 0
输出:[-1,-1]
 

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

分析
这个题,我本来的想法是,用二分法找到目标值,然后向两边扩展找到区间范围。但是这样写的话,时间复杂度就会超出来了,题目要求log(n).所以可以分两步走,先找左区间,再找右区间。还是用二分法,不过用两次,左边二分法找到目标值记录一下,然后左移,如果是大于那就向左走,小于就像右走。右边的区间查找同理。

var searchRange = function (nums, target) {
    let n = nums.length;
    if (n == 0) return [-1, -1]
    let start = 0;
    let end = n - 1;
    let mid = 0;
    let first = -1
    let last = -1;
    //两次二分法,先找左边界,再找右边界
    while (start <= end) {
        mid = Math.floor((start + end) / 2)
        if (nums[mid] == target) {
            first = mid;
            end = mid - 1;
        } else if (nums[mid] > target) {
            end = mid - 1
        } else {
            start = mid + 1
        }
    }
    start = 0
    end = n - 1;
    while (start <= end) {
        mid = Math.floor((start + end) / 2)
        if (nums[mid] == target) {
            last = mid;
            start = mid + 1
        } else if (nums[mid] > target) {
            end = mid - 1
        } else {
            start = mid + 1
        }
    }
    if (first == last && last == -1) return [-1, -1]
    else return [first, last]
};

旋转数组查找数字

81. 搜索旋转排序数组 II(medium)

题目:已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

你必须尽可能减少整个操作步骤。

 

示例 1:

输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
示例 2:

输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false
 

提示:

1 <= nums.length <= 5000
-104 <= nums[i] <= 104
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104

分析
这一题的关键,在于断开区间。需要判断那个中间值在哪个区间里。因为数组是旋转的,那就会是升-降-升,这样的排序。所以我们在判断时需要注意划分两种情况。一:中间值在升之前,那么左边是升区间,判断数是不是在这个范围里,如果不在,就取另外一个区间。二:中间值在降-升,这是中间值右边的区间是升区间,那么判断,目标值是否在改区间里,在的话取这一段,否则取另一段。总的思想是把复杂的区间简单化。排除简单的,再去划分复杂的。最后如果区间重叠还是没找到,只能是不存在了。但是如果有相等但不是target的情况。左右区间直接缩小。

var search = function (nums, target) {
    // 首先找到数组中的最小值,判断中间点是在翻转的左边还是翻转的右边
    let start = 0;
    let end = nums.length - 1;
    let mid = 0;
    while (start <= end) {
        mid = Math.floor((start + end) / 2)
        if (nums[mid] == target) return true;
        else if (nums[start] == nums[mid] && nums[mid] == nums[end]) {
            start++;
            end--;
        }
        else if (nums[start] <= nums[mid]) {
            // 线在左边
            if (nums[start] <= target && target < nums[mid]) {
                end = mid - 1
            } else {
                start = mid + 1
            }
        } else {
            // 线在右边
            if (nums[mid] < target && target <= nums[end]) {
                start = mid + 1
            } else {
                end = mid - 1
            }
        }
    }
    return false;
};

练习

154. 寻找旋转排序数组中的最小值 II(medium)

题目:已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须尽可能减少整个过程的操作步骤。

 

示例 1:

输入:nums = [1,3,5]
输出:1
示例 2:

输入:nums = [2,2,2,0,1]
输出:0
 

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

分析
这个题目和上面一个题目一样。不过他是在翻转数组里找最小的那个值。考虑时间复杂度,还是用二分法,这次是记录升区间的最小值,然后直接排除升区间,因为要满足,在中间的那种情况。同样有相等值的情况,左右区间值相等的话,跟最小值比较,然后同步向中间靠拢。

var findMin = function (nums) {
    let start = 0;
    let end = nums.length - 1;
    let min = nums[0];
    let mid=0
    while (start <= end) {
       mid = Math.floor((start + end) / 2);
        // 取区间
        if (nums[start] == nums[mid] && nums[start] == nums[end]) {
            if (nums[mid] < min) min = nums[mid]
            start++;
            end--
        } else {
            if (nums[start] <= nums[mid]) {
                if (min > nums[start]) {
                    min = nums[start]
                }
                start = mid + 1
            }
            else if (nums[mid] <= nums[end]) {
                if (nums[mid] < min) {
                    min = nums[mid]
                }
                end = mid - 1;
            }
        }

    }
    return min
};

540. 有序数组中的单一元素(medium)

题目:给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。

请你找出并返回只出现一次的那个数。

你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。

 

示例 1:

输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:

输入: nums =  [3,3,7,7,10,11,11]
输出: 10
 

提示:

1 <= nums.length <= 105
0 <= nums[i] <= 105

分析
看时间需求,那么这道题需要使用二分法。那么可以这样分析,如果中间索引mid是奇数,那么他前面就会奇数个数值,如果他和左边索引对应的值相等,那么左边就都是出现两次的数。因为如果有一个出现一次,那么一定会有另外一个落单的情况,此时区间右移start=mid+1,如果不相等,那么落单的值一定出现在左边,取end=mid-1。如果是偶数,那么左边一定有偶数个值,如果与左边相等,那区间在左边end=mid-1,如果不相等就是在右边start=mid,一定要包含这个mid

var singleNonDuplicate = function (nums) {
    let start = 0;
    let end = nums.length - 1;
    while (start <= end) {
        let mid = Math.floor((start + end) / 2)
        if (start == end) return nums[start]
        if (mid % 2 == 1) {
            // 奇数
            if (nums[mid] == nums[mid - 1]) {
                start = mid + 1
            }
            else {
                end = mid - 1
            }
        } else {
            // 偶数
            if (nums[mid] == nums[mid + 1]) {
                start = mid + 2
            }
            else {
                end = mid
            }
        }
    }

};

进阶

4. 寻找两个正序数组的中位数(hard)

题目:给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

 

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
 
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106

分析
首先,这个题比较抽象,根据需要的时间复杂度来看,只能使用二分法。但是呢,他的目的是找中位数,如果m+n是奇数,那直接取中间的数,如果是偶数,那么就取中间的两个数的平均值。这里有一个快捷地方法无论奇数还是偶数,可以都取第(m+n+1)/2和(m+n+2)/2大的平均数。可以算一下。取实际下标的时候则是需要减一,因为这里判断的是第几大。先取第(m+n+1)/2大的数,设他为K,那么此时可以找num1中k/2大的,和nnums2中k/2大的,小的那个明显不是第K大的值,因为2(k/2-1)<k-1的,他最大也就能到第k-1大,此时小的数组,start区间start右移一位,k-k/2,因为区间右移了,已经有部分小的值放到外面了。之后再重复上述操作,最后当k=1的时候,取第一个大的,在两个数的start下标那里取小的值就可以了。如果中途有某个数组已经取完了,直接在另一个数组取完剩下的全部值。另一个数可以用同样的方法取出来。

var findMedianSortedArrays = function (nums1, nums2) {
    let m = nums1.length;
    let n = nums2.length;
    let left = Math.floor((m + n + 1) / 2)
    let right = Math.floor((m + n + 2) / 2)
    return (fen(nums1, 0, nums2, 0, left) + fen(nums1, 0, nums2, 0, right)) / 2

};
function fen(nums1, start1, nums2, start2, k) {
    let k2 = Math.floor(k / 2);
    if (k2 > nums1.length || k2 > nums2.length) k2 = nums1.length < nums2.length ? nums1.length : nums2.length
    if (start1 >= nums1.length || nums1.length == 0) return nums2[start2 + k - 1]
    else if (start2 >= nums2.length || nums2.length == 0) return nums1[start1 + k - 1]
    if (k == 1) {
        return nums1[start1] < nums2[start2] ? nums1[start1] : nums2[start2]
    }

    if (nums1[start1 + k2 - 1] <= nums2[start2 + k2 - 1]) {
        // 数组一数据右移
        start1 += k2;
    } else {
        start2 += k2;
    }
    if (start1 > nums1.length) k2 = nums1.length;
    if (start2 > nums2.length) k2 = nums2.length;
    return fen(nums1, start1, nums2, start2, k - k2)
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值