代码随想录算法训练营第1天 | lc704、lc35、lc34、lc27
(本合集全部为Go语言实现)
Leetcode704
状态:秒了
实现过程中的难点:着重注意左右指针是否包含在当前未扫描的区间内。(建议开区间也转换为闭区间进行考虑,因为闭区间不会很绕)
对撞指针的情况分析
(
left
/right
/mid
分别对应nums[left]
/nums[right]
/nums[mid]
)
对于两指针间隔的所有情况,可以挨个进行分析:
left - 1 = right
,直接不符合循环准入条件left = right
时,mid = left = right
- 若
mid < target
,则left - 1 = right
- 若
mid > target
,则left - 1 = right
- 若
left + 1 = right
时,mid = left
- 若
mid < target
,则left = right
- 若
mid > target
,则left - 1 = right
- 若
left + 2 = right
时,mid = left + 1
- 若
mid < target
,则left = right
- 若
mid > target
,则left = right
- 若
- 其他间隔更大的情况,均会像第三种情况一样,最终退化成前两种情况
在上边的三种结果为
left - 1 = right
的情况时,可以推出:此时right < target < left
(但注意这是在数组元素不重复的情况下)
左闭右闭写法
func search(nums []int, target int) int {
left, right := 0, len(nums) - 1
for left <= right {
mid := left + ((right - left) >> 1);
if nums[mid] < target {
left = mid + 1;
} else if nums[mid] > target {
right = mid - 1;
} else {
return mid
}
}
return -1
}
Leetcode35
状态:改了几次
实现过程中的难点:本题是聚焦退出循环时对撞指针和target
值之间的关系
问题分析
(
left
/right
/mid
分别对应nums[left]
/nums[right]
/nums[mid]
)
- 如果可以在序列中找到
target
,那么会被直接返回 - 如果没有找到,那么最终状态一定是
left - 1 = right
,此时一定是right < target < left
左闭右闭写法
func searchInsert(nums []int, target int) int {
left, right := 0, len(nums) - 1
for left <= right {
mid := left + ((right - left) >> 1);
fmt.Println(mid, nums[mid])
if nums[mid] < target {
left = mid + 1;
} else if nums[mid] > target {
right = mid - 1;
} else {
return mid
}
}
return left
}
Leetcode35
状态:暴力写法秒的。题解中的巧妙写法看了挺久才看懂
实现过程中的难点:巧妙写法中通过将命中target
的分支归到非等值分支中,实现了向非等值分支方向的查找,从而定位到该方向的首个target
值
题解写法分析
题解写法中,将命中target
的情况归到扫描边界向左收敛(即right = mid - 1
)的情况当中,这使得出循环时一定处于right < target = left
的状态
- 会不会向左收敛时,
right
跨过了target
?- 会。但一定是恰好跨过,也就是跨过时的循环内
mid = target
,这样才会因为right = mid - 1
而跨过target
,否则如果当时mid < target
,那么就是向右收敛 - 此后,循环就一定是一直向右收敛,因为
mid < target
,最终出循环时right < target = left
- 会。但一定是恰好跨过,也就是跨过时的循环内
- 如果数组中没有
target - 1
,那么结果对么?- 如果数组中有
target - 1
,那么就转换为找target - 1
的左边界 - 如果数组中没有
target - 1
,那么就退化为在无重复值的非递减数组中找到target - 1
的插入位置(即上边的Leetcode35
)的问题
- 如果数组中有
左闭右闭题解写法
暴力写法略(在命中
target
时进行左右遍历确定左右边界)
func searchRange(nums []int, target int) []int {
Search := func(a []int, t int) int {
left := 0
right := len(a) - 1
for left <= right {
mid := (right - left) / 2 + left
if a[mid] < t {
left = mid + 1
} else {
right = mid - 1
}
}
return left
}
leftIdx := Search(nums, target)
if leftIdx == len(nums) || nums[leftIdx] != target {
return []int {-1, -1}
}
rightIdx := Search(nums, target + 1) - 1
return []int {leftIdx, rightIdx}
}
Leetcode27
状态:秒了
实现过程中的难点:注意赋值slow
后自增fast
个人写法
func removeElement(nums []int, val int) int {
slow, fast := 0, 0
for fast < len(nums) {
for fast < len(nums) && nums[fast] == val {
fast++
}
if fast >= len(nums) {
return slow
}
nums[slow] = nums[fast]
slow++
fast++
}
return slow
}
今日收获
- 复习了二分搜索的写法
- 学会了可以基于将命中
target
的分支归到其他两分支之一,来实现获取target
边界下标
学习时长:2小时左右,时间比较分散