Leetcode-分治法/二分法


q23 合并k个排序链表


题目传送门


题解

使用分治的思想去解决,即归并排序。因为有k个链表,所以遍历这个链表数组,然后逐个排序即可。

func mergeKLists(lists []*ListNode) *ListNode {
	if len(lists) == 0 {
		return nil
	}
	if len(lists) == 1 {
		return lists[0]
	}
	var L *ListNode
	L = mergeList(lists[0], lists[1])
	for i := 2; i < len(lists); i++ {
		L = mergeList(L, lists[i])
	}
	return L
}

func mergeList(L1, L2 *ListNode) *ListNode {
	L := &ListNode{}
	curr := L
	var next *ListNode
	for L1 != nil && L2 != nil {
		if L1.Val < L2.Val {
			next = L1.Next
			curr.Next = L1
			L1.Next = nil
			L1 = next
		} else {
			next = L2.Next
			curr.Next = L2
			L2.Next = nil
			L2 = next
		}
		curr = curr.Next
	}
	if L1 != nil {
		curr.Next = L1
	}
	if L2 != nil {
		curr.Next = L2
	}
	return L.Next
}




q33 搜索旋转排序数组


题目传送门


题解

这道题中,数组不是全局有序,而是局部有序,但是仍然可以使用二分来解决。
可以发现我们旋转数组的时候,一定有一部分的数组是有序的,比如示例中:数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的。
所以在循环时我们可以查看被mid分开的两边,哪边是有序的,并判断target是否在有序的这边,如果不在有序的这边,那就一定在另一边。通过这个思路最终得到答案。

func search(nums []int, target int) int {
	n := len(nums)
	if n == 0 {
		return -1
	}
	if n == 1 {
		if target == nums[0] {
			return 0
		} else {
			return -1
		}
	}
	l, r := 0, n - 1
	for l <= r {
		mid := (l + r) / 2
		if nums[mid] == target {
			return mid
		}
		// 如果左边有序
		if nums[0] <= nums[mid] {
			// 如果target恰好在左边区间
			if nums[0] <= target && target < nums[mid] {
				r = mid - 1
			} else {
				l = mid + 1
			}
		// 如果右边有序
		} else {
			// 如果target恰好在右边区间
			if nums[mid] < target && target <= nums[n - 1] {
				l = mid + 1
			} else {
				r = mid - 1
			}
		}
	}
	return -1
}

q34 在排序数组中查找元素的第一个和最后一个位置


题目传送门


题解

首先使用二分找到值为target的下标midIndex,然后以midIndex为中点,左右寻找target的左右边界。

func searchRange(nums []int, target int) []int {
	l, r, midIndex, n := 0, len(nums) - 1, -1, len(nums)
	for l <= r {
		mid := (l + r) / 2
		if nums[mid] == target {
			midIndex = mid
			break
		}
		if nums[mid] < target && target <= nums[n - 1] {
			l = mid + 1
		} else {
			r = mid - 1
		}
	}
	if midIndex == -1 {
		return []int{-1, -1}
	}

	start, end := 0, 0
	for i := midIndex; i >= 0; i--{
		if nums[i] == target {
			start = i
		} else {
			break
		}
	}
	for i := midIndex; i <= n - 1; i++{
		if nums[i] == target {
			end = i
		} else {
			break
		}
	}
	return []int{start, end}
}

剑指 Offer 04. 二维数组中的查找


题目传送门


题解

因为题目中说每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。所以我们可以把每一行都当成是一维数组去处理,这个一维数组又是有序的,所以可以使用二分查找。

func findNumberIn2DArray(matrix [][]int, target int) bool {
	for i := 0; i < len(matrix); i++ {
		if len(matrix[i]) > 0 && matrix[i][0] > target {
				return false
		}
		if binarySearch(matrix[i], target) {
			return true
		}
	}
	return false
}

func binarySearch(nums []int, target int) bool {
	l, r := 0, len(nums)-1
	for l <= r {
		index := (l + r) / 2
		if nums[index] > target {
			r = index - 1
		} else if nums[index] < target {
			l = index + 1
		} else {
			return true
		}
	}
	return false
}

当然也可以使用go语言提供的二分查找API:

func findNumberIn2DArray1(matrix [][]int, target int) bool {
	for _, row := range matrix {
		i := sort.SearchInts(row, target)
		// 防止row[i]出界
		if i < len(row) && row[i] == target {
			return true
		}
	}
	return false
}

剑指 Offer 11. 旋转数组的最小数字


题目传送门


题解

数组经过旋转之后,从原来的整体有序,变成了局部有序:

1 2 3 4 5 6
5 6 1 2 3 4

可以发现旋转之后的数组被一个旋转点切分成了两部分,这个旋转点恰好就是我们要寻找的最小值的位置。
二分查找为什么需要数组是有序的,因为经过一次二分,由于数组是有序的,所以我们就能通过待查找值的大小来判断它在哪个区间中,所以二分实际上就是需要一种:每分出一个区间之后,我们可以快速判断出待查找值处在哪一段区间的依据。
即使数组变成了局部有序,我们依然能制定出一个策略来快速判断二分之后,待查找值处于哪个区间中:

  1. num[mid] > num[right],说明左半段数组都是有序的,而旋转点肯定就处在右半段区间中,比如34512。这个时候因为mid下标所在的值大于最右边的值,所以它肯定不是旋转点(旋转点是最小的),所以我们应该把它剔除:left = mid + 1。
  2. num[mid] < num[right],说明右半段数组都是有序的,而旋转点肯定就处在左半段区间中,比如45123。这个时候mid下标所在的点不可以被排除,所以我们不能将它剔除:right = mid。
  3. num[mid] == num[right],出现重复值是一种特殊情况,我们无法判断旋转点在哪一边,比如:211和122。这个时候需要向左移动右指针。
func minArray(numbers []int) int {
	left, right := 0, len(numbers)-1
	// 开始二分
	for left < right {
		mid := (left + right) / 2
		// 如何中间的值比右边的值大,说明左半边都有序,旋转点在右半边
		// 这个时候mid绝对不是待选点
		if numbers[mid] > numbers[right] {
			left = mid + 1
		} else if numbers[mid] < numbers[right] {
			// 如果中间的数比右边的数小,说明右半边都是有序的,旋转点在左半边
			// 这个时候mid有可能刚好是待选点
			right = mid
		} else {
			// 出现了重复值,这个时候无法判断,只能手动去移动right指针
			right--
		}
	}
	return numbers[left]
}

剑指 Offer 53 - I. 在排序数组中查找数字 I


题目传送门


题解

因为数组是已经排序好的,所以使用二分搜索是最快的,首先用二分搜索在数组中找到目标值,保存该值的下标为midIndex,然后分别循环遍历midIndex的左边和右边,寻找相同值。

func search(nums []int, target int) (ans int) {
	l, r := 0, len(nums)-1
	midIndex := -1
	for l <= r {
		mid := (l + r) / 2
		if nums[mid] == target {
			midIndex = mid
			break
		} else if nums[mid] > target {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
	if midIndex == -1 {
		return
	}
	ans++
	// 向后遍历
	for i := midIndex + 1; i < len(nums); i++ {
		if nums[i] == target {
			ans++
		} else {
			break
		}
	}
	// 向前遍历
	for i := midIndex - 1; i >= 0; i-- {
		if nums[i] == target {
			ans++
		} else {
			break
		}
	}
	return
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值