算法学习——二分查找

二分查找

一些套路啦

PS:这里的所有情况都是基于模板条件 for ;left < right

func binSearch(nums []int) {
    left := 0
    right := len(nums) - 1

    for ;left<right; {
        mid = left + (right - left) / 2
        if check(nums[mid]) {
            left = mid
        } else {
            right = mid - 1
        }
    }
}
  • mid的确定,或者说区间的划分
// 就是mid是归于左右区间的哪一个问题
// [left, mid-1] & [mid, right]
mid = (left + right+ 1) / 2
// [left, mid] & [mid+1,right]
mid = (left + right) / 2
  • 避免mid大数溢出
mid = left + (right - left + 1) / 2
mid = left + (right - left) / 2
  • 由于模板条件,最终退出情况一定有 left == right,同时由此,我们有时需要对left进行判断是否符合条件。

一些例题啦

旋转数组相关

没有重复数,首先我们确定区间;
1、明确的是,我们的mid点一定会落在一个有序区间中,不管是左边还是右边,肯定会存在一个有序的区间中。

2、判断哪一个是有序区间,我们使用mid和right的关系进行判断,mid <= right时,右边是有序区间,否则左边是有序区间。

3、确定目标是否在有序区间。比如第一种右边是有序情况下,nums[mid] <= target && target <= nums[right],这个证明target肯定是在右边的,否则就是在左边。这里有个好处就是通过一个明确的区间来确认target位置,else条件不需要考虑直接划分为左区间。同理第二种条件也是这样

func search(nums []int, target int) int {
    left := 0
    right := len(nums) - 1
    for ;left<right; {
        mid := (right+left+1)/2
        if nums[mid] <= nums[right] {
            if nums[mid] <= target && target <= nums[right] {
                left = mid
            } else {
                right = mid - 1
            }
        } else {
            if nums[left] <= target && target <= nums[mid - 1] {
                right = mid - 1 
            } else {
                left =  mid
            }
        }
    }
    if nums[left] == target {
        return left
    }
    return -1    
}

这里比较特殊的是具有重复数字,按照自己的思路来弄,使用mid和right进行判断的话,当midrightleft的情况出现的时候,就无法确定到底是归为那一边了,因此我这里使用的递归的方式,两边都去找一波,然后用来确定左右两边谁才是最终结果。

//我自己的

func search(nums []int, target int) bool {  
    left := 0
    right := len(nums) - 1
    if right == -1 {
        return false
    }
    return getResult(nums,target, left,right)
}

func getResult(nums []int, target, left, right int) bool {
    left := 0
    right := len(nums) - 1

    for ;left < right;  {
        mid := (left + right + 1) / 2
        if nums[mid] == nums[left] && nums[left] == nums[right] {
            return getResult(nums,target, left,mid-1) || getResult(nums, target, mid, right)
        }
        if nums[mid] <= nums[right] {
            if nums[mid] <= target && nums[right] >= target {
                left = mid
            } else {
                right = mid - 1
            }
        } else {
            if nums[left] <= target && nums[mid - 1] >= target {
                right = mid - 1
            } else {
                left = mid                
            }
        }
    }
    
    if nums[left] == target {
        return true
    }
    
    return false
    
}

但是别人的比较巧妙,当left==right的情况出现的时候,只是跳过这个left,也就是left++,然后继续重新计算mid就好了

// 别人的
func search(nums []int, target int) bool {  
    left := 0
    right := len(nums) - 1
    if right == - 1{
        return false
    }
    for ;left < right;  {
        mid := (left + right + 1) / 2
        if nums[left] == nums[right] {
            left++
            continue
        }
        if nums[mid] <= nums[right] {
            if nums[mid] <= target && nums[right] >= target {
                left = mid
            } else {
                right = mid - 1
            }
        } else {
            if nums[left] <= target && nums[mid - 1] >= target {
                right = mid - 1
            } else {
                left = mid                
            }
        }
    }
    
    if nums[left] == target {
        return true
    }
    
    return false
}

还有下面这两个跟上面比较像的题目,一个没有重复数字一个有重复数字

func findMin(nums []int) int {
    left := 0
    right := len(nums) - 1
    
    for ;left < right; {
        mid := (left+right)/2
        if nums[mid] > nums[right] {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return nums[left]
}
func findMin(nums []int) int {
    left := 0
    right := len(nums) - 1
    
    for ;left < right; {
        mid := (left+right)/2
        if nums[left] == nums[right] {
            left++ // 重复加加
            continue
        }
        if nums[mid] > nums[right] {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return nums[left]
}


未归类

func findPeakElement(nums []int) int {
	left := 0
	right := len(nums) - 1
	for ; left < right; {
		mid := (right + left + 1) / 2
		if nums[mid] > nums[left] {
            if nums[mid-1] < nums[mid]{ // 判断一下是否真的有顶峰在右边,不确定的话,就走左边吧,反正没关系这个
                left = mid
            } else {
                right = mid - 1
            }	
		} else {
			right = mid - 1
		}
	}
	return left
}
func searchRange(nums []int, target int) []int {
    left := getLeft(nums, target)
    right := getRight(nums,target)
    
    return []int{left, right}
}

func getRight(nums []int, target int) int {
    right := len(nums)
    if right == 0 {
        return -1
    }
    right--
    left := 0
    
    for ; left < right; {
        mid := (right + left + 1) / 2
        if nums[mid] <= target {
            left = mid
        } else {
            right =  mid - 1
        }
    }
    
    if nums[left] != target {
        return -1
    }
    return left    
}

func getLeft(nums []int, target int) int {
    right := len(nums)
    if right == 0 {
        return -1
    }
    right--
    left := 0
    
    for ; left < right; {
        // fmt.Println(left,right)
        mid := (right + left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else {
            right =  mid
        }
    }
    
    if nums[left] != target {
        return -1
    }
    return left    
}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mp5BHu8a-1640531388515)(binSearch.assets/image-20210331214536419.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMB7fSJh-1640531388516)(binSearch.assets/image-20210331214553537.png)]

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
    k := len(nums1) + len(nums2)
    if k % 2 == 1 {
        return float64(getK(nums1, nums2, k / 2 + 1))
    } else {
        return float64(getK(nums1, nums2, k / 2 + 1) + getK(nums1, nums2, k / 2)) / 2.0
    }
}

func getK(nums1, nums2 []int, k int) int {
    length1, length2 := len(nums1), len(nums2)
    offset1, offset2 := 0, 0
    half := k / 2 - 1
    for {
        if offset1 == length1 { 
            return nums2[offset2 + k - 1]
        }
        if offset2 == length2 {
            return nums1[offset1 + k - 1]
        }
        if k == 1 {
            return min(nums1[offset1], nums2[offset2])
        }
        half = k / 2
        index1 := min(offset1 + half, length1) - 1 // 边界需要确认,要不然数据越界了
        index2 := min(offset2 + half, length2) - 1
        if nums1[index1] <= nums2[index2] {
            k -= (index1 - offset1 + 1) // 确定去掉的数有多少个
            offset1 = index1 + 1
        } else {
            k -= (index2 - offset2 + 1)
            offset2 = index2 + 1
        }
    }
}


func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值