4.寻找两个正序数组的中位数
4.1 题目
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。
请你找出并返回这两个正序数组的中位数。
算法的时间复杂度应该为 O(log (m+n))。
示例:
示例 1:
- 输入:
nums1 = [1,3], nums2 = [2]
- 输出:
2.00000
- 解释:合并数组 =
[1,2,3]
,中位数2
- 输入:
示例 2:
- 输入:
nums1 = [1,2], nums2 = [3,4]
- 输出:
2.50000
- 解释:合并数组 =
[1,2,3,4]
,中位数(2 + 3) / 2 = 2.5
- 输入:
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-10^6 <= nums1[i], nums2[i] <= 10^6
4.2 题解
合并重排序
// 方法一:合并重排序
// 将两个已排序的整数数组并成一个数组,然后对该列表进行排序。
// 最后,根据列表的长度和奇偶性确定中位数的计算方法。
static double FindMedianSortedArrays1(int[] nums1, int[] nums2)
{
// 计算合并后数组的长度
int mergeArrayLength = nums1.Length + nums2.Length;
// 创建一个数组来存储合并后的数组
int[] mergeArray = new int[nums1.Length + nums2.Length];
// 将 nums1 和 nums2 数组的元素复制到合并后的数组中
nums1.CopyTo(mergeArray, 0);
nums2.CopyTo(mergeArray, nums1.Length);
// 对合并后的数组进行排序
Array.Sort(mergeArray);
// 判断合并后的数组长度的奇偶性,以确定中位数的计算方法
return mergeArrayLength % 2 == 0
? (mergeArray[mergeArrayLength / 2] + mergeArray[mergeArrayLength / 2 - 1]) / 2.0f
: mergeArray[mergeArrayLength / 2];
}
指针找中位数
// 方法二:指针找中位数
// 不需要合并数组,两数组用两个指针指明当前索引,逐步移动指针
static double FindMedianSortedArrays2(int[] nums1, int[] nums2)
{
// 获取两个数组的长度
int nums1Length = nums1.Length;
int nums2Length = nums2.Length;
// 计算合并后的数组的总长度
int allLength = nums1Length + nums2Length;
// 用于存储中位数左右两侧的值
int left = -1, right = -1;
// 用于追踪两个数组的当前索引位置
int nums1Index = 0, nums2Index = 0;
// 遍历合并后的数组的一半长度(allLength / 2 + 1)
for (int i = 0; i <= allLength / 2; i++)
{
// 将当前右侧的值赋给左侧
left = right;
// 如果数组 nums1 还有元素且 (数组 nums2 没有元素 或 数组 nums1 的元素 小于 数组 nums2 的元素)
if (nums1Index < nums1Length && (nums2Index >= nums2Length || nums1[nums1Index] < nums2[nums2Index]))
{
// 将 numns1 中的元素赋给右侧,并更新 nums1Index
right = nums1[nums1Index++];
}
else
{
// 数组 nums2 的元素更小或者数组 nums1 已经没有元素,将 nums2 中的元素赋给右侧,并更新 nums2Index
right = nums2[nums2Index++];
}
}
// 如果合并后的数组长度为偶数,返回中间两个元素的平均值;否则返回右侧的元素
if ((allLength & 1) == 0)
return (left + right) / 2.0;
else
return right;
}
递归二分查找第k小数
// 方法三:递归二分查找第k小数
// 二分查找,比较两数组的第k/2数,小的哪个说明前面的数不可能是第k小数了,减去小数值前面的元素,递归在新数组找第k小数
static double FindMedianSortedArrays3(int[] nums1, int[] nums2)
{
// 获取输入数组的长度
int nums1Length = nums1.Length;
int nums2Length = nums2.Length;
// 计算奇偶总长度的中点
int left = (nums1Length + nums2Length + 1) / 2;
int right = (nums1Length + nums2Length + 2) / 2;
// 利用辅助函数计算中位数
return (GetKthValueInTwoArrays(nums1, 0, nums1Length - 1, nums2, 0, nums2Length - 1, left) +
GetKthValueInTwoArrays(nums1, 0, nums1Length - 1, nums2, 0, nums2Length - 1, right)) * 0.5;
}
// 查找两个已排序数组中第k个值的辅助函数
private static int GetKthValueInTwoArrays(int[] nums1, int nums1Start, int nums1End, int[] nums2, int nums2Start,
int nums2End, int k)
{
// 计算两个数组的剩余长度
int nums1RemainingLength = nums1End - nums1Start + 1;
int nums2RemainingLength = nums2End - nums2Start + 1;
// 为确保 nums1 是较短的数组,如果 nums1 长度较长,则交换数组
if (nums1RemainingLength > nums2RemainingLength)
return GetKthValueInTwoArrays(nums2, nums2Start, nums2End, nums1, nums1Start, nums1End, k);
// 基本情况:如果 nums1 为空,从 nums2 返回第 k 个元素
if (nums1RemainingLength == 0)
return nums2[nums2Start + k - 1];
// 基本情况:如果 k 为 1,返回两个数组中第一个元素的最小值
if (k == 1)
return Math.Min(nums1[nums1Start], nums2[nums2Start]);
// 计算将数组分为两半的索引
int nums1Index = nums1Start + Math.Min(nums1RemainingLength, k / 2) - 1;
int nums2Index = nums2Start + Math.Min(nums2RemainingLength, k / 2) - 1;
// 比较两个数组的中点值
if (nums1[nums1Index] > nums2[nums2Index])
{
// 在 nums2 的剩余部分递归搜索
return GetKthValueInTwoArrays(nums1, nums1Start, nums1End, nums2, nums2Index + 1, nums2End,
k - (nums2Index - nums2Start + 1));
}
else
{
// 在 nums1 的剩余部分递归搜索
return GetKthValueInTwoArrays(nums1, nums1Index + 1, nums1End, nums2, nums2Start, nums2End,
k - (nums1Index - nums1Start + 1));
}
}
4.3 代码
using System;
class Program
{
static void Main()
{
#region 题目
// 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
// 请你找出并返回这两个正序数组的中位数。
// 算法的时间复杂度应该为 O(log (m+n))。
// 示例 1:
// 输入:nums1 = [1,3], nums2 = [2]
// 输出:2.00000
// 解释:合并数组 = [1,2,3] ,中位数 2
// 示例 2:
// 输入:nums1 = [1,2], nums2 = [3,4]
// 输出:2.50000
// 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
// 提示:
// nums1.length == m
// nums2.length == n
// 0 <= m <= 1000
// 0 <= n <= 1000
// 1 <= m + n <= 2000
// -106 <= nums1[i], nums2[i] <= 106
#endregion
#region 测试
// 示例 1
int[] nums1_1 = { 1, 3 };
int[] nums2_1 = { 2 };
double result11 = FindMedianSortedArrays1(nums1_1, nums2_1);
Console.WriteLine($"示例1 方法1 输出:{result11}");
double result12 = FindMedianSortedArrays3(nums1_1, nums2_1);
Console.WriteLine($"示例1 方法2 输出:{result12}");
double result13 = FindMedianSortedArrays3(nums1_1, nums2_1);
Console.WriteLine($"示例1 方法3 输出:{result13}");
// 示例 2
int[] nums1_2 = { 1, 2 };
int[] nums2_2 = { 3, 4 };
double result21 = FindMedianSortedArrays1(nums1_2, nums2_2);
Console.WriteLine($"示例1 方法1 输出:{result21}");
double result22 = FindMedianSortedArrays2(nums1_2, nums2_2);
Console.WriteLine($"示例1 方法2 输出:{result22}");
double result23 = FindMedianSortedArrays3(nums1_2, nums2_2);
Console.WriteLine($"示例1 方法3 输出:{result23}");
#endregion
}
#region 答案
// 方法一:合并重排序
// 将两个已排序的整数数组并成一个数组,然后对该列表进行排序。
// 最后,根据列表的长度和奇偶性确定中位数的计算方法。
static double FindMedianSortedArrays1(int[] nums1, int[] nums2)
{
// 计算合并后数组的长度
int mergeArrayLength = nums1.Length + nums2.Length;
// 创建一个数组来存储合并后的数组
int[] mergeArray = new int[nums1.Length + nums2.Length];
// 将 nums1 和 nums2 数组的元素复制到合并后的数组中
nums1.CopyTo(mergeArray, 0);
nums2.CopyTo(mergeArray, nums1.Length);
// 对合并后的数组进行排序
Array.Sort(mergeArray);
// 判断合并后的数组长度的奇偶性,以确定中位数的计算方法
return mergeArrayLength % 2 == 0
? (mergeArray[mergeArrayLength / 2] + mergeArray[mergeArrayLength / 2 - 1]) / 2.0f
: mergeArray[mergeArrayLength / 2];
}
// 方法二:指针找中位数
// 不需要合并数组,两数组用两个指针指明当前索引,逐步移动指针
static double FindMedianSortedArrays2(int[] nums1, int[] nums2)
{
// 获取两个数组的长度
int nums1Length = nums1.Length;
int nums2Length = nums2.Length;
// 计算合并后的数组的总长度
int allLength = nums1Length + nums2Length;
// 用于存储中位数左右两侧的值
int left = -1, right = -1;
// 用于追踪两个数组的当前索引位置
int nums1Index = 0, nums2Index = 0;
// 遍历合并后的数组的一半长度(allLength / 2 + 1)
for (int i = 0; i <= allLength / 2; i++)
{
// 将当前右侧的值赋给左侧
left = right;
// 如果数组 nums1 还有元素且 (数组 nums2 没有元素 或 数组 nums1 的元素 小于 数组 nums2 的元素)
if (nums1Index < nums1Length && (nums2Index >= nums2Length || nums1[nums1Index] < nums2[nums2Index]))
{
// 将 numns1 中的元素赋给右侧,并更新 nums1Index
right = nums1[nums1Index++];
}
else
{
// 数组 nums2 的元素更小或者数组 nums1 已经没有元素,将 nums2 中的元素赋给右侧,并更新 nums2Index
right = nums2[nums2Index++];
}
}
// 如果合并后的数组长度为偶数,返回中间两个元素的平均值;否则返回右侧的元素
if ((allLength & 1) == 0)
return (left + right) / 2.0;
else
return right;
}
// 方法三:递归二分查找第k小数
// 二分查找,比较两数组的第k/2数,小的哪个说明前面的数不可能是第k小数了,减去小数值前面的元素,递归在新数组找第k小数
static double FindMedianSortedArrays3(int[] nums1, int[] nums2)
{
// 获取输入数组的长度
int nums1Length = nums1.Length;
int nums2Length = nums2.Length;
// 计算奇偶总长度的中点
int left = (nums1Length + nums2Length + 1) / 2;
int right = (nums1Length + nums2Length + 2) / 2;
// 利用辅助函数计算中位数
return (GetKthValueInTwoArrays(nums1, 0, nums1Length - 1, nums2, 0, nums2Length - 1, left) +
GetKthValueInTwoArrays(nums1, 0, nums1Length - 1, nums2, 0, nums2Length - 1, right)) * 0.5;
}
// 查找两个已排序数组中第k个值的辅助函数
private static int GetKthValueInTwoArrays(int[] nums1, int nums1Start, int nums1End, int[] nums2, int nums2Start,
int nums2End, int k)
{
// 计算两个数组的剩余长度
int nums1RemainingLength = nums1End - nums1Start + 1;
int nums2RemainingLength = nums2End - nums2Start + 1;
// 为确保 nums1 是较短的数组,如果 nums1 长度较长,则交换数组
if (nums1RemainingLength > nums2RemainingLength)
return GetKthValueInTwoArrays(nums2, nums2Start, nums2End, nums1, nums1Start, nums1End, k);
// 基本情况:如果 nums1 为空,从 nums2 返回第 k 个元素
if (nums1RemainingLength == 0)
return nums2[nums2Start + k - 1];
// 基本情况:如果 k 为 1,返回两个数组中第一个元素的最小值
if (k == 1)
return Math.Min(nums1[nums1Start], nums2[nums2Start]);
// 计算将数组分为两半的索引
int nums1Index = nums1Start + Math.Min(nums1RemainingLength, k / 2) - 1;
int nums2Index = nums2Start + Math.Min(nums2RemainingLength, k / 2) - 1;
// 比较两个数组的中点值
if (nums1[nums1Index] > nums2[nums2Index])
{
// 在 nums2 的剩余部分递归搜索
return GetKthValueInTwoArrays(nums1, nums1Start, nums1End, nums2, nums2Index + 1, nums2End,
k - (nums2Index - nums2Start + 1));
}
else
{
// 在 nums1 的剩余部分递归搜索
return GetKthValueInTwoArrays(nums1, nums1Index + 1, nums1End, nums2, nums2Start, nums2End,
k - (nums1Index - nums1Start + 1));
}
}
#endregion
}
4.4 运行结果
示例1 方法1 输出:2
示例1 方法2 输出:2
示例1 方法3 输出:2
示例1 方法1 输出:2.5
示例1 方法2 输出:2.5
示例1 方法3 输出:2.5
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com