算法设计与分析实验:二分查找

目录

一、有序数组中的单一元素   

1.1思路

1.2 代码实现

1.3 运行结果

二、长度最小的子数组

2.1思路

2.2 代码

2.3 运行结果

三、 山脉数组中查找目标值

3.1 思路

3.2 代码

3.3 运行结果

四、寻找旋转排序数组中的最小值

4.1思路

4.2代码

4.3 运行结果


一、有序数组中的单一元素   

力扣第540题

1.1思路

利用二分查找思想:

根据要求在 O(log n) 的时间复杂度和 O(1) 空间复杂度内找出只出现一次的数,因此可以考虑使用二分查找结合异或运算的方法。

而且由于数组中每个元素都会出现两次,唯有一个数只会出现一次,因此在此题中可以使用异或运算。异或运算具有以下性质:

①任何数和 0 进行异或运算,结果仍然是这个数本身。

②任何数和其自身进行异或运算,结果为 0。

基于这个性质,我们可以利用异或运算来找出只出现一次的那个数。

具体步骤如下:

1.初始化左指针 left = 0 和右指针 right = n-1,其中 n 是数组长度。

2.进入循环,直到 left > right:

·计算中间位置 mid = (left + right) // 2。

·如果 mid 是偶数,判断 nums[mid] 是否等于 nums[mid+1]。如果相等,则说明目标元素在 mid 右边,将 left 指针移到 mid+2;否则,目标元素在 mid 左边,将 right 指针移到 mid。

·如果 mid 是奇数,判断 nums[mid] 是否等于 nums[mid-1]。如果相等,则说明目标元素在 mid 右边,将 left 指针移到 mid+1;否则,目标元素在 mid 左边,将 right 指针移到 mid-1。

3.循环结束后,返回 nums[left]。

这样就可以在 O(log n) 的时间复杂度和 O(1) 空间复杂度内找出只出现一次的数。

1.2 代码实现

def singleNonDuplicate(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = (left + right) // 2
        if mid % 2 == 0:
            if nums[mid] == nums[mid+1]:
                left = mid + 2
            else:
                right = mid
        else:
            if nums[mid] == nums[mid-1]:
                left = mid + 1
            else:
                right = mid - 1
    return nums[left]

# 示例测试
print(singleNonDuplicate([1,1,2,2,3,4,4,8,8]))  
print(singleNonDuplicate([3,3,7,7,10,10,11]))  

1.3 运行结果

代码中的测试的数组:

[1,1,2,2,3,4,4,8,8]  元素3出现一次

[3,3,7,7,10,10,11]  元素11出现一次

二、长度最小的子数组

力扣第209题

2.1思路

为了找到满足总和大于等于 target 的长度最小的连续子数组。

思路:滑动窗口前缀和、二分查找

具体思路如下:

首先初始化左指针 left、右指针 right 和当前窗口内元素的和 window_sum,初始值均为 0。

进入循环,当右指针小于数组长度时执行以下操作:

将右指针 right 对应元素的值加到 window_sum 中。

如果 window_sum 大于等于目标值 target,进入内部循环:

更新最小长度 min_len,为当前窗口长度与之前的最小长度的较小值。

从当前窗口中减去左指针 left 对应元素的值,并将左指针向右移动一位。

将右指针向右移动一位。

返回最小长度 min_len。

如果不存在满足条件的子数组,则返回0。

2.2 代码

from typing import List

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1

        while left < right:
            mid = (left + right) // 2

            if nums[mid] > nums[right]:
                left = mid + 1
            elif nums[mid] < nums[right]:
                right = mid
            else:
                right -= 1

        return nums[left]


if __name__ == "__main__":
    solution = Solution()

    # 测试示例1
    nums_1 = [4, 5, 6, 7, 0, 1, 2]
    result_1 = solution.findMin(nums_1)
    print(f"The minimum element in {nums_1} is: {result_1}")

    # 测试示例2
    nums_2 = [3, 4, 5, 1, 2]
    result_2 = solution.findMin(nums_2)
    print(f"The minimum element in {nums_2} is: {result_2}")

    # 测试示例3
    nums_3 = [1]
    result_3 = solution.findMin(nums_3)
    print(f"The minimum element in {nums_3} is: {result_3}")

2.3 运行结果

由于在[1,1,1,1,1,1,1,1]中无法得到和为11的连续子数组,返回0

由于篇幅限制,下面两题仅展示运行结果

三、 山脉数组中查找目标值

力扣第1095题

3.1 思路

当给定一个山脉数组 mountainArr 和目标值 target,我们需要找到能使得 mountainArr.get(index) 等于 target 的最小下标 index。

解题思路如下:

首先,我们需要确定山峰的位置。山峰是指数组中的一个最大值,满足 A[i-1] < A[i] > A[i+1] 的条件。我们可以使用二分查找来找到山峰的位置。

定义左指针 left 为数组的起始位置,右指针 right 为数组的结束位置。

进入循环,当 left < right 时进行迭代:

计算中间位置 mid,即 (left + right) // 2。

如果 mountainArr.get(mid) < mountainArr.get(mid+1),则说明 mid 左侧为上升趋势,右侧可能存在山峰,将 left 移至 mid + 1。

否则,mid 右侧为下降趋势,左侧可能存在山峰,将 right 移至 mid。

循环结束后,left 或 right 即为山峰所在位置,记为 peak。

接下来,我们可以对山峰的左右两侧分别进行二分查找:

使用标准的二分查找方法在 [0, peak] 区间内查找目标值 target。如果找到目标值,直接返回其下标。

如果左侧未找到目标值,则在 [peak+1, n-1] 区间内进行反向二分查找。即令 mountainArr.get(mid) > target 时移动右指针,反之移动左指针。

如果右侧未找到目标值或左侧未找到目标值,则返回 -1。

综上所述,我们可以定义一个辅助函数 binary_search 来实现二分查找。

在编写示例测试部分时,首先进行 MountainArray 类的设计并在其中实现 length() 和 get(index) 方法。这两个方法分别用于获取数组长度和获取指定下标的元素。

3.2 代码

class Solution:
    def findInMountainArray(self, target: int, mountainArr: 'MountainArray') -> int:
        n = mountainArr.length()
        left, right = 0, n - 1

        # 二分查找山峰所在的位置
        while left < right:
            mid = (left + right) // 2
            if mountainArr.get(mid) < mountainArr.get(mid+1):
                left = mid + 1
            else:
                right = mid

        peak = left   # 左右端点重合处即为山峰位置
        idx = self.binary_search(mountainArr, 0, peak, target)
        if idx != -1:
            return idx
        return self.binary_search(mountainArr, peak+1, n-1, target)

    def binary_search(self, mountainArr, left, right, target):
        while left <= right:
            mid = (left + right) // 2
            cur = mountainArr.get(mid)
            if cur == target:
                return mid
            elif cur < target:
                left = mid + 1
            else:
                right = mid - 1
        return -1


class MountainArray:
    def __init__(self, arr):
        self.arr = arr

    def length(self):
        return len(self.arr)

    def get(self, index):
        return self.arr[index]


# 示例测试
if __name__ == "__main__":
    # 创建山脉数组实例
    mountain_arr_1 = MountainArray([1, 2, 3, 4, 5, 3, 1])
    mountain_arr_2 = MountainArray([0, 1, 2, 4, 2, 1, 0])
    # 创建解决方案实例
    solution = Solution()

    # 测试1查找目标值为 3 的下标
    target_1 = 3
    result_1 = solution.findInMountainArray(target_1, mountain_arr_1)
    print(f"[1, 2, 3, 4, 5, 3, 1]测试一(查找3)结果: {result_1}")
    # 测试2查找目标值为 5 的下标
    target_2 = 5
    result_2 = solution.findInMountainArray(target_2, mountain_arr_2)
    print(f"[0, 1, 2, 4, 2, 1, 0]测试二(查找5)结果: {result_2}")

3.3 运行结果

测试一中,元素3的下标为2,成功找到并输出了其下标。

测试二中,元素5未在数组中出现,按照题目要求返回了-1

四、寻找旋转排序数组中的最小值

力扣第154题

4.1思路

采用二分查找的方法:

首先定义一个 Solution 类,其中包含一个名为 findMin 的方法,用于找到给定数组中的最小元素。

在 findMin 方法中,定义左右指针 left 和 right,初始值分别为 0 和数组长度减 1,表示当前搜索范围为整个数组。

进入循环,当 left 小于 right 时执行以下步骤:

计算中间元素的索引 mid = (left + right) // 2。

比较 nums[mid] 和 nums[right] 的值:

如果 nums[mid] > nums[right],说明最小元素在 mid 的右侧,更新 left = mid + 1。

如果 nums[mid] < nums[right],说明最小元素在 mid 的左侧或者就是 nums[mid],更新 right = mid。

如果 nums[mid] == nums[right],无法判断最小元素在哪一侧,因此将 right 减一,缩小搜索范围。

循环结束后,返回 nums[left](或 nums[right])作为最小元素。

在 main 函数中,创建 Solution 类的实例,然后进行示例测试,验证代码的正确性。

4.2代码

1.from typing import List
2.
3.class Solution:
4.    def findMin(self, nums: List[int]) -> int:
5.        left, right = 0, len(nums) - 1
6.
7.        while left < right:
8.            mid = (left + right) // 2
9.
10.            if nums[mid] > nums[right]:
11.                left = mid + 1
12.            elif nums[mid] < nums[right]:
13.                right = mid
14.            else:
15.                right -= 1
16.
17.        return nums[left]
18.
19.
20.if __name__ == "__main__":
21.    solution = Solution()
22.
23.    # 测试示例1
24.    nums_1 = [4, 5, 6, 7, 0, 1, 2]
25.    result_1 = solution.findMin(nums_1)
26.    print(f"The minimum element in {nums_1} is: {result_1}")
27.
28.    # 测试示例2
29.    nums_2 = [3, 4, 5, 1, 2]
30.    result_2 = solution.findMin(nums_2)
31.    print(f"The minimum element in {nums_2} is: {result_2}")
32.
33.    # 测试示例3
34.    nums_3 = [1]
35.    result_3 = solution.findMin(nums_3)
36.    print(f"The minimum element in {nums_3} is: {result_3}")

4.3 运行结果

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长安er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值