“在旋转排序数组中搜索某个值”相关题目在leetcode上有多个,这组题是“寻找旋转排序数组中的最小值”相关题目的扩展。
解题核心思路是:二分查找。
第一题 搜索旋转排序数组
该题关键字:
- 已按升序排序
- 元素互不相同
- 没找到返回-1
代码中,我依然考虑了重复元素的情况。其一,不会给代码运行增加额外负担。其二,将本题解法和下一题解法统一在一起了。
请通过注释了解思路。读者要多思考注释中的例子,通过反证法排除部分区间。
总结一下。对某个区间做分析,有四种可能:
- 区间左端元素小于右端元素
- 区间左端元素大于中间元素
- 区间中间元素大于右端元素
- 区间左端、中间、右端元素相等
充分利用已知条件,尽量缩小搜索区间。
解法如下(代码提交通过)
func search(nums []int, target int) int {
l := len(nums)
if l == 0 {
return -1
}
if l == 1 {
if nums[0] == target {
return 0
}
return -1
}
var (
low int
high = l-1
)
OuterLoop:
for low <= high {
// 中间位置
mid := low + (high-low)>>1
v := nums[mid]
// 找到了
if v == target {
return mid
}
// 区间左端元素小于右端元素,从小到大有序,直接应用二分查找。
// 如 [...,1,2,3,4,5,...]
if nums[low] < nums[high] {
if v < target {
low = mid+1
} else {
high = mid-1
}
continue
}
// 区间左端元素大于中间元素,如 [...,5,6,7,1,2,3,4,...],5 > 1。
if nums[low] > v {
// target可能存在于索引区间 (mid,high]
if target < nums[low] && v < target {
low = mid+1
} else { // target可能存在于索引区间 [low, mid)
high = mid-1
}
continue
}
// 区间中间元素大于右端元素,如 [...,2,3,4,5,6,7,1,...]
if v > nums[high] {
// target可能存在于索引区间 [low,mid)
if target < v && nums[high] < target {
high = mid-1
} else { // target可能存在于索引区间 (mid, high]
low = mid+1
}
continue
}
// 到此处,nums[low] == nums[mid] == nums[high]
for i := low; i < high; i++ {
// 遇到一个不同的值
// 可能是下面某一种情况
// [...,9,5,6,9,9,9,9,9,9,...]
// [...,9,9,9,9,9,9,5,6,9,...]
// [...,9,10,11,9,9,9,9,9,9,...]
// [...,9,9,9,9,9,9,10,11,9,...]
// [...,9,10,2,9,9,9,9,9,9,...]
// [...,9,9,9,9,9,9,11,2,9,...]
if nums[i] != nums[i+1] {
low = i+1
if i+1 < mid {
// 索引区间 [low, i] 和 [mid,high] 的元素都相等,且不等于target。
// 只需要在索引区间 [i+1, mid-1] 中继续搜索。
high = mid-1
} else { // i+1 > mid。不可能出现 i+1 == mid
// 索引区间[low, i]和索引high的元素都相等,且不等于target。
// 只需要在索引区间 [i+1, high-1] 中继续搜索。
high--
}
continue OuterLoop
}
}
// 上面for循环中元素都相等
// 没找到
break
}
return -1
}
第二题 搜索旋转排序数组 II
本题关键字:
- 按非降序排列
- 元素值不必互不相同
- 找到返回true,否则返回false
该题一定要考虑重复元素的情况了。代码中,除了返回值不同,其他逻辑和上一题相同,因为上一题的解法已经考虑重复元素了。
解法如下(代码提交通过)
func search(nums []int, target int) bool {
l := len(nums)
if l == 0 {
return false
}
var (
low int
high = l-1
)
OuterLoop:
for low <= high {
mid := low + (high-low)>>1
v := nums[mid]
if v == target {
return true
}
if nums[low] < nums[high] {
if v < target {
low = mid+1
} else {
high = mid-1
}
continue
}
if nums[low] > v {
if target < nums[low] && v < target {
low = mid+1
} else {
high = mid-1
}
continue
}
if v > nums[high] {
if target < v && nums[high] < target {
high = mid-1
} else {
low = mid+1
}
continue
}
for i := low; i < high; i++ {
if nums[i] != nums[i+1] {
low = i+1
if i+1 < mid {
high = mid-1
} else { // i+1 > mid。不可能出现 i+1 == mid
high--
}
continue OuterLoop
}
}
break
}
return false
}
第三题 搜索旋转数组
本题关键字:
- 已按升序排序
- 存在重复元素
- 要返回索引。若有多个相同元素,返回索引最小的一个。还有一个隐含细节,没找到时返回-1。
解法逻辑和第一题类似。只是针对该题做了一些调整。
解法如下(代码提交通过)
func search(arr []int, target int) int {
l := len(arr)
if l == 0 {
return -1
}
var (
low int
high = l-1
k = -1
)
OuterLoop:
for low <= high {
mid := low + (high-low)>>1
v := arr[mid]
if v == target {
k = mid // 记录target的索引
}
if arr[low] < arr[high] {
// v == target时,尝试找更小的索引
if v >= target {
high = mid-1
} else { // v < target
low = mid+1
}
continue
}
if arr[low] > v {
if target < arr[low] && v < target {
low = mid+1
} else {
high = mid-1
}
continue
}
if v > arr[high] {
if target < v && arr[high] < target {
high = mid-1
} else {
low = mid+1
}
continue
}
if arr[low] == target {
// low就是最小索引,不需要再找了
k = low
break
}
for i := low; i < high; i++ {
if arr[i] != arr[i+1] {
low = i+1
if i+1 < mid {
high = mid-1
} else { // i+1 > mid。不存在 i+1 == mid
high--
}
continue OuterLoop
}
}
// 不需要再找了
break
}
return k
}