数据结构——顺序表习题解(II)

本题目来自王道考研教材,结合答案给出个人的一些写法。
1.【统考真题】一个长度为L的升序序列S,处在第L/2上取整的数字称之为它的中位数,例如,若序列S1=(11,13,15,17,19),则S1的中位数为15,两个序列的中位数是他们所有的元素组成的升序序列的中位数。例如,若S2=(2,4,6,8,20)则S1,S2的中位数是11,现在有两个等长升序序列A,B。试求一个在时间和空间两方面尽可能高效的算法,找出A和B的中位数。要求:
1)给出算法的基本思想
2)写出代码,关键之处给出注释
3)说明此算法的时间复杂度和空间复杂度

难度:适中:定理的证明较难想到,O(logn)的算法不好想,需要对中位数的性质和概念有较深刻的理解。
思想:
若用归并排序,则一定时间复杂度较高,本体应该采用减治法的思想:
设序列A的中位数为am,序列B的中位数为bm
定理1:A和B的中位数cm大小一定介于am和bm之间

简要证明:由中位数定义,假设A和B中有n个数字(为了方便这里设n为奇数,偶数证明同理),则必然有(n-1)/2个数小于等于am,有(n-1)/2个数大于等于bm。
则分析一下:
1.若am=bm时,则中位数必然为am(或者说bm),因为此时必然有(n-1)个数字小于等于am,有(n-1)个数字大于等于am,am必然为中位数。
2.若am<bm(am>bm同理):若此时cm<am,则必然有cm<am<bm,则此时必然有不包含bm的(n-1)/2个数大于等于bm,也有不包含am在内的(n-1)/2个数大于等于am,则必然至少有2*(n-1)/2+1+1个数字大于cm,即至少有(n+1)个数字大于cm,则cm必不可能为中位数。

由上述证明我们思考一下怎么做最快:
我们取A的中位数am,B的中位数bm,由上述定理可知,AB的中位数一定介于am和bm之间,若此时
1.am=bm,由中位数定义,am即为中位数
2.am<bm, 则我们可以舍弃am之前的数字和bm之后的数字(两边舍弃的数量必须相等),这样能保证大于中位数和小于中位数的数字个数相等,然后再舍弃数字后的新数组A’ B’中继续进行这一操作,直到出现1中条件或者两数组都只剩一个元素为止,由定义,这时候取较小的那个元素即可。
3.am>bm,同理
此算法每次将数组规模缩减一半,所以时间复杂度O(logn) 空间复杂度O(1)

#include <iostream>
using namespace std;
int findMid(int *a, int *b, int n)
{
    
    
	int s1 = 0, s2 = 0, d1 = n - 1, d2 = n - 1;
	//用s表示首指针,d表示尾指针。
	while (s1 != d1 || s2 != d2) 
	{
    
    
		int mid1 = (s1 + d1) / 2;
		int mid2 = (s2 + d2) / 2;
		if (a[mid1] == b[mid2]) return a[mid1];
		if (a[mid1] < b[mid2])
		{
    
    
			if ((d1 - s1+1) % 2 == 0)//当序列个数为偶数个时
			{
    
    
				s1 = mid1 + 1;
				d2 = mid2;
			}
			else//序列个数为奇数个时
			{
    
    
				s1 = mid1;
				d2 = mid2;
			}
		}
		else
		{
    
    
			if ((d2 - s2 + 1) % 2 == 0)
			{
    
    
				s2 = mid2 + 1;
				d1 = mid1;
			}
			else
			{
    
    
				s2 = mid2;
				d1 = mid1;
			}
		}
	}
	return a[s1] < b[s2] ? a[s1] : b[s2];
}
//测试代码
int main()
{
    
    
	int a[] = {
    
     11,13,15,17,19 };
	int b[] = {
    
     2,4,6,8,20 };
	int mid = findMid(a,b,5);
	cout << "中位数为:" << mid;

}

在这里插入图片描述

2.【统考真题】已知一个整数序列A=(a0,a1,…an-1) 其中0<=ai<n(0<=i<n) 若存在ap1=ap2=…=apm=x 且m>n/2,则称x为A的主元素,例如A=(0,5,5,3,5,7,5,5)则5为主元素,又如A=(0,5,5,3,5,1,5,7)则A中没有主元素,假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,求出A的主元素。若存在主元素,返回这个元素,若不存在主元素,返回-1.

难度:较难,考场上不推荐花太多时间思考最佳算法。写个快排然后统计倒是不错的选择。
思路:无非是求最大重复元素个数,并进行一个判断。如果直接从前往后进行扫描每个元素出现的位置,则会达到O(n2)的时间复杂度,显然不是一个好的做法,若排序后统计,也至少有O(nlogn)的时间复杂度,
我们思考如下问题:
1.主元素必定唯一:因为m>n/2,剩下的重复元素的个数必然小于n/2,也就是说剩下的元素个数必然少于主元素个数。
思路:
数组中存在主元素时,所有的非主元素的个数和必少于一半。如果让主元素与一个非主元素“配对”,则最后多出来的元素(没有元素与之配对)就是主元素。但如何知道哪个元素是主元素呢?不知道主元素是谁,又如何进行“配对”呢?这无妨。从前向后扫描数组元素,假定遇到的当前值选定为主元素,再次遇到它时,计数加1,遇到与它不等的值时,计数减1。当计数减为0后,将遇到的下一个值重新选定为主元素。扫描完毕,当前选定的元素(计数值大于0)可能是主元素,但未必一定是主元素。还需要对数组再进行一次扫描,记录它出现的实际个数,以判定它是否是主元素。
算法可分为以下两步:

① 选取候选的主元素:依次扫描所给数组中的每个整数,将第一个遇到的整数Num保存到c中,记录Num的出现次数为1;若遇到的下一个整数仍等于Num,则计数加1,否则计数减1;当计数减到0时,将遇到的下一个整数保存到c中,计数重新记为1,开始新一轮计数,即从当前位置开始重复上述过程,直到扫描完全部数组元素。

② 判断c中元素是否是真正的主元素:再次扫描该数组,统计c中元素出现的次数,若大于n/2,则为主元素,否则,序列中不存在主元素。

int Majority ( int A[], int n )
{
    
         int i, c, count = 1; // c用来保存候选主元素,count用来计数
      c= A[0]; //设置A[0]为候选主元素
      for( i = 1; i < n; i++ ) // 查找候选主元素
        if( A[i] == c )  count++;   // 对A中的候选主元素计数
           else
               if( count > 0 ) 
               count--;  // 处理不是候选主元素的情况
                    else{
    
             // 更换候选主元素,重新计数
                     c= A[i];
                     count= 1;
                         }
            if( count > 0 )
               for( i = 0,count = 0; i < n; i++ )    // 统计候选主元素的实际出现次数
                         if ( A[i] == c )  
                         count++;
            if( count > n/2 )  
              return c;   // 确认候选主元素
             else  return -1;     //不存在主元素
}

3.【统考真题】给定一个含n(n>1)个整数的数组,请设计一个时间和空间上尽可能高效的算法,找出数组中未出现的最小的正整数,例如,数组{-5,3,2,3}中未出现的最小正整数为1,{1,2,3}中未出现的最小正整数为4
思路:我们采用空间换时间的做法,再申请一个n维数组B,B表示前n个正整数的出现情况,初值设为0,然后扫描A中数组,若ai>n或ai<=0,则未出现的最小正整数必然在b中,若1<ai<=n,则在B的对应位置上置换为1,表示这个数字已经出现过了,若扫描完数组B满,则证明数组A涵盖了1-n,且每个只出现了一次,那么未出现的最小整数必然为(n+1)

#include <iostream>
using namespace std;
int FindMin(int* a, int n)
{
    
    
	int i = 0;
	int* b;
	b = (int*)malloc(sizeof(int) * n);
	memset(b, 0, sizeof(int) * n);
	for (int i = 0; i < n; i++)
	{
    
    
		if (a[i] > 0 && a[i] <= n)
		{
    
    
			b[a[i] - 1] = 1;
		}
	}
	for  (i = 0; i < n; i++)
	{
    
    
		if (b[i] == 0)
			break;
	}
	return i + 1;
}
int main()
{
    
    
	int a[] = {
    
     -5,3,2,3 };
	int min = FindMin(a, 4);
	cout << "此数组中未出现的最小正整数为" << min;

}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42313342/article/details/108349709