文章目录
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
可以发现旋转之后的数组被一个旋转点切分成了两部分,这个旋转点恰好就是我们要寻找的最小值的位置。
二分查找为什么需要数组是有序的,因为经过一次二分,由于数组是有序的,所以我们就能通过待查找值的大小来判断它在哪个区间中,所以二分实际上就是需要一种:每分出一个区间之后,我们可以快速判断出待查找值处在哪一段区间的依据。
即使数组变成了局部有序,我们依然能制定出一个策略来快速判断二分之后,待查找值处于哪个区间中:
num[mid] > num[right]
,说明左半段数组都是有序的,而旋转点肯定就处在右半段区间中,比如34512。这个时候因为mid下标所在的值大于最右边的值,所以它肯定不是旋转点(旋转点是最小的),所以我们应该把它剔除:left = mid + 1。num[mid] < num[right]
,说明右半段数组都是有序的,而旋转点肯定就处在左半段区间中,比如45123。这个时候mid下标所在的点不可以被排除,所以我们不能将它剔除:right = mid。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
}