二分查找
一些套路啦
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
}