力扣初级算法(五)【排序和搜索】
力扣初级算法(五)【排序和搜索】
本文中的题目均来自力扣,代码默认以C#实现,伪代码仅用来帮助描述,不严格遵循某种语言的语法。
本章涵盖了在有序结构中的排序和搜索问题。
我们推荐 第一个错误的版本 这道题,作为介绍一个重要的算法的起始点。
88. 合并两个有序数组
难度:简单
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6]
提示:
-10^9 <= nums1[i], nums2[i] <= 10^9
nums1.length == m + n
nums2.length == n
解题思路
-
合并两个有序数组,这很简单,你可以利用双指针分别从两个数组中取出较小的一个插入到新的数组中。
-
不过,本题中要求将给定的nums1数组作为结果的容器。
同时,nums1后面填充了足够的0来存放nums2中的元素。
-
我们是不是依然可以考虑双指针的解法呢?
-
由于已知数组的真实长度,我们完全可以让两个指针分别指向两个数组的队尾,然后其中较大的那个必然是最终结果的最后一个元素,以此类推,我们可以从大到小填充完nums1。
方法一:双指针
public void Merge(int[] nums1, int m, int[] nums2, int n)
{
int p1 = m - 1;
int p2 = n - 1;
for (int i = nums1.Length - 1; p1 >= 0 && p2 >= 0; i--)
{
nums1[i] = nums1[p1] > nums2[p2] ? nums1[p1--] : nums2[p2--];
}
for (; p2 >= 0; p2--)
{
nums1[p2] = nums2[p2];
}
}
278. 第一个错误的版本
难度:简单
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的, 所以错误的版本之后的所有版本都是错的。
假设你有
n
个版本[1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。你可以通过调用
bool isBadVersion(version)
接口来判断版本号version
是否在单元测试中出错。 实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。 调用 isBadVersion(3) -> false 调用 isBadVersion(5) -> true 调用 isBadVersion(4) -> true 所以,4 是第一个错误的版本。
解题思路
-
朴素的想法是,对所有版本进行线性扫描,当找到第一个错误的版本时,返回该版本即可。
Func(n) => Range(1, n).First(x => IsBadVersion(x));
-
不过随着版本数量的增加,我们很快就发现,这不是一个很好的解决问题的办法。
-
稍加思考,对于版本号序列来说,本身其实也是有序的,第一出错版本之前的版本全部是正确的,而之后的版本全都是错误的。
-
我们自然而然地就想到了二分查找的算法。
-
左指针左边的版本全部是正确版本,右指针右边的版本全部是错误版本,每次我们都缩小了一个尽可能大的区间(当前区间的一半)。
-
当左指针超过右指针时,搜索结束,此时我们观察两个指针的位置,因为左指针左边的版本全部是正确版本,我们很容易判断出,第一个错误的版本恰恰就是左指针当前指向的版本。
方法一:二分查找
public int FirstBadVersion(int n)
{
return FindP(1, n);
int FindP(int left, int right)
{
if(left > right) return left; // 递归的终点
var mid = left + (right - left) / 2;
return IsBadVersion(mid) ? FindP(left, mid - 1) : FindP(mid + 1, right);
}
}