二分法:两个有序数组长度为N,找到第N、N+1大的数

221 阅读2分钟

题目

两个有序数组长度为N,找到第N、N+1大的数

思路1:双指针,O(N)复杂度

简述思路:
如果当前A指针指向的数组A的内容小于B指针指向的数组B的内容,那么A指针往右移动,然后nums(当前已经遍历过的数字个数)也加一。
如果此时已经遍历过的数字个数等于N那么九江移动之前的A指针指向的A数组的内容送入result。
其他情况,以相反的逻辑进行。

//双指针法求解
vector<int> GetMid_On(vector<int>& A, vector<int>& B, int N)
{
	int A_index = 0;
	int B_index = 0;
	int nums = 0;
	vector<int> result;
	while (A_index < N && B_index < N)
	{
		if (A[A_index] < B[B_index])
		{
			A_index++;
			nums++;
			if (nums == N)
			{
				result.push_back(A[A_index - 1]);
			}
			if (nums == N + 1)
			{
				result.push_back(A[A_index - 1]);
				return result;
			}
		}
		else 
		{
			B_index++;
			nums++;
			if (nums == N)
			{
				result.push_back(B[B_index - 1]);
			}
			if (nums == N + 1)
			{
				result.push_back(B[B_index - 1]);
				return result;
			}
		}
		//如果两个元素相同
	}
	return result;
}



int main()
{
	vector<int> A = { 2,4,5,6,9 };
	vector<int> B = { 1,3,7,8,10 };
	vector<int> result = GetMid_On(A, B, 5);
	cout << result[0] << endl;
	cout << result[1] << endl;
	cout << "结束" << endl;
	return 0;
}

思路2:迭代法二分

简述思路:
先初始化ab数组的start,end,mid;
然后比较各自mid指向的值的大小。
如果A[a_mid] > B[b_mid],说明,第N大的值在A数组a_mid的左边,在B数组b_mid的右边,所以对a_end以及b_start做出更新:
长度为奇数的时候,正好
a_end = a_mid;
b_start = b_mid;
当然还要考虑到长度为偶数的情况:
a_end = a_mid;
b_start = b_mid + 1;
这里只是对start进行修改,对于end值不需要修改。可以举例:
A = {2,4,6,8};
B= {1,3,5,7};
a_start = 0,a_end = 3
b_start = 0,b_end = 3
a_mid = b_mid = 3/2 =1;
A[a_mid] > B[b_mid] ,并且,长度为偶数,所以
a_end = a_mid =1;
b_start = b_mid +1 =2;
所以A被分割为:{2,4};
B被分割为:{5,7};
a_start = 0,a_end = 1
b_start = 2,b_end = 3
a_mid = 1/2 =0;
b_mid = (2+3)/2= 2;
A[a_mid] < B[b_mid] ,并且,长度为偶数,所以
a_start = a_mid =1;
b_end = b_mid =2;
此时达成a_start == a_end || b_start == b_end条件,所以可以判断两个start的值的大小,取较小值可得到第N大的数:

//迭代二分法去解
vector<int> GetMid(vector<int>& A, vector<int>& B, int N)
{
	vector<int> result;
	int a_start = 0, a_end = N - 1;
	int b_start = 0, b_end = N - 1;
	//初始化中间位置
	int a_mid = (a_start + a_end) / 2;
	int b_mid = (b_start + b_end) / 2;
	//循环结束条件:当数组起始点与结束点重合的时候,说明存在第N大的数
	while (a_start != a_end || b_start != b_end)
	{
		//更新中间坐标
		a_mid = (a_start + a_end) / 2;
		b_mid = (b_start + b_end) / 2;
		//如果此时的A中位数与B的中位数相同,说明两个都是第N大的数
		if (A[a_mid] == B[b_mid])
		{
			result.push_back(A[a_mid]);
			result.push_back(A[a_mid] > B[b_mid] ? B[b_mid]: A[a_mid]);
			return result;
		}
		//如果A的中位数>B的中位数,说明此时第N大的数在A的左边,B的右边,所以需要更新a_end以及b_start
		else if (A[a_mid] > B[b_mid])
		{
			//如果当前a_start-a_end之间的长度为偶数,那么中间值就是mid
			if ((a_start + a_end) % 2 == 0)
			{
				a_end = a_mid;
				b_start = b_mid;
			}
			else
			{
				a_end = a_mid;
				b_start = b_mid + 1;
			}
		}
		//如果A的中位数<B的中位数,说明此时第N大的数在A的右边,B的左边,所以需要更新a_start以及b_end
		else
		{
			//如果当前a_start-a_end之间的长度为奇数,那么中间值就是mid
			if ((a_start + a_end) % 2 == 0)
			{
				a_start = a_mid;
				b_end = b_mid;
			}
			//如果当前a_start-a_end之间的长度为偶数,那么中间值就是mid+1
			else
			{
				a_start = a_mid + 1;
				b_end = b_mid;
			}
		}
	}
	//判断两个start的值的大小
	if (A[a_start] > B[b_start])
	{
		result.push_back(B[b_start]);
		if (b_start + 1 < N)
		{
			if (A[a_start] > B[b_start + 1])
				result.push_back(B[b_start + 1]);
			else
				result.push_back(A[a_start]);
		}
		else
			result.push_back(A[a_start]);
	}
	else
	{
		result.push_back(A[a_start]);
		if (a_start + 1 < N)
		{
			if (A[a_start + 1] <= B[b_start])
				result.push_back(A[a_start + 1]);
			else
				result.push_back(B[b_start]);
		}
		else
			result.push_back(B[b_start]);
	}
	return result;
}

int main()
{
	vector<int> A = { 2,4,5,6,9 };
	vector<int> B = { 2,4,5,6,9 };
	//vector<int> B = { 1,3,7,8,10 };
	vector<int> result = GetMid(A, B, 5);
	for(int i = 0;i < result.size();i++)
		cout << result[i] << endl;
	cout << "结束" << endl;
	return 0;
}

思路3:递归法二分

写完迭代法之后,递归将a_start,a_end,b_start,b_end,作为递归函数的参数放入。递归函数的一开始写终止条件,参考迭代法。
终止条件后面,写根据中间值的大小,对a_start,a_end,b_start,b_end进行不同赋值,达到分割数组的目的。

//递归二分法求解
vector<int> GetMid(vector<int>& A, vector<int>& B, int a_start,int a_end,int b_start,int b_end,int N)
{
	vector<int> result;
	//初始化中间位置
	int a_mid = (a_start + a_end) / 2;
	int b_mid = (b_start + b_end) / 2;
	//如果有答案了
	if (A[a_mid] == B[b_mid])
	{
		result.push_back(A[a_mid]);
		result.push_back(A[a_mid] > B[b_mid] ? B[b_mid] : A[a_mid]);
		return result;
	}
	if (a_start == a_end || b_start == b_end)
	{
		//判断两个start的值的大小
		if (A[a_start] > B[b_start])
		{
			result.push_back(B[b_start]);
			if (b_start + 1 < N)
			{
				if (A[a_start] > B[b_start + 1])
					result.push_back(B[b_start + 1]);
				else
					result.push_back(A[a_start]);
			}
			else
				result.push_back(A[a_start]);
		}
		else
		{
			result.push_back(A[a_start]);
			if (a_start + 1 < N)
			{
				if (A[a_start + 1] <= B[b_start])
					result.push_back(A[a_start + 1]);
				else
					result.push_back(B[b_start]);
			}
			else
				result.push_back(B[b_start]);
		}
		return result;
	}
	//递归更新边界值
	if (A[a_mid] > B[b_mid])
	{
		//如果当前a_start-a_end之间的长度为偶数,那么中间值就是mid
		if ((a_start + a_end) % 2 == 0)
		{
			return GetMid(A, B, a_start, a_mid, b_mid, b_end, N);
		}
		else
		{
			return GetMid(A, B, a_start, a_mid, b_mid + 1, b_end, N);
		}
	}
		//如果A的中位数<B的中位数,说明此时第N大的数在A的右边,B的左边,所以需要更新a_start以及b_end
	else
	{
		//如果当前a_start-a_end之间的长度为偶数,那么中间值就是mid
		if ((a_start + a_end) % 2 == 0)
		{
			return GetMid(A, B, a_mid, a_end, b_start, b_mid, N);
		}
		else
		{
			return GetMid(A, B, a_mid + 1, a_end, b_start, b_mid, N);
		}
	}
}

int main()
{
	vector<int> A = { 2,4,5,6,9 };
	//vector<int> B = { 2,4,5,6,9 };
	int N = A.size();
	vector<int> B = { 1,3,7,8,10 };
	vector<int> result = GetMid(A, B, 0, N-1,0,N-1,N);
	for(int i = 0;i < result.size();i++)
		cout << result[i] << endl;
	cout << "结束" << endl;
	return 0;
}