键指offer——面试题3:数组中重复的数字(p37-43)

27 篇文章 1 订阅
21 篇文章 0 订阅

面试题3:数组中重复的数字

题目一:找出数组中重复的数字

题目:
在一个长度为n的数组里的所有数字都在0~n-1范围内。数组中某些数字是重复的,请找出数组中任意一个重复的数字。例如:输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复数字2或者3。
注意仔细读题,尤其是这些信息: 长度为n;数字范围是0~n-1(因此也一定是有重复数字的);找出任意一个。
动手写代码之前,问清楚面试官的需求,是求任意一个重复字符还是求出全部的重复字符。

分析:

  1. 可以用哈希表:从头到尾扫描数字,每扫描到一个数字就判断哈希表里是否已经包含了该数字,没有则加入哈希表,若有则表明哈希表里已经存在了该数字,就找到了这个重复的数字了。
  2. 数组重排法(本代码选择该方法):从头到尾扫描,比较该数字x是不是等于下标i,若是则接着再扫描下一个数字,若不是则再拿它和第i=x个数字作比较,若相等则找到了一个重复的数字;若不相等,则把第i个数字和第x个数字做交换,把x放到属于它的位置上,重复这个过程只到找出了重复字符。
    这么说起来是有点乱啦,我们来画图举例吧:
    在这里插入图片描述
    就相当于给数组排了个序,一旦找到后边数字有和前边已经排好顺序的数字有重复的,就结束了。

伪代码:

int main()
{
	定义数组a[],数组长度n,当前数值x,结果值r;
	输入n;
	循环输入a[];
	//特殊处理
	输入空指针或者长度<=0的情况;
	元素值超范围:小于0或者大于n-1的情况;
	//操作		(要不要定义成一个函数来被调用?)
	for(int i=0;i<n;i++)
	{
		x=a[i];
		比较下标i值和x值是否相等:
			相等则接着扫描下一个数字;
			continue;
		否则若下标和x值不相等则:
			找到下标x处的值a[x],判断这个a[x]和a[i]值是否相等:
				相等则把这个值放到结果数组 r里:r[j++]=a[x];
				结束循环
			不相等则:
				交换x值和a[x]值;
				继续在i位置处循环上述操作;
				因为i不自加,所以这时的i要-1;
	输出r;
}

代码: DuplicationInArray.cpp

#include<iostream>
using namespace std;

int main()
{
	int *num,n,x,r[1];
	scanf("%d", &n);

	num = (int*)malloc(sizeof(int)*n);
	for(int i=0;i<n;i++)
	{
		scanf("%d", &num[i]);
	}
	//特殊处理
	if(num == nullptr || n<=0)
		return 1;
	for(int i=0;i<n;i++)
	{
		if(num[i]<0 || num[i]>n-1)
			return 1;
	}

	//操作
	int Temp, j=0, flag=1;
	for(int i=0;i<n;i++)
	{

		if(num[i] == i)
		{
			continue;
		}
		else
		{
			if(num[num[i]] == num[i])
			{
				r[j++]=num[i];
				flag = 0;
				continue;
			}
			else
			{
				Temp = num[num[i]];
				num[num[i]] = num[i];
				num[i] = Temp;
				i=i-1;
			}
		}
	}	
	printf("%d", r[0]);

	free(num);
	return 0;
}

为什么这么写呢?
因为题目只要求找到任意一个重复的代码就行了,没有说找到全部的重复代码,所以只输出一个重复的值即可。
这次我命名了DuplicationInArray.cpp这样方便之后复习,也显得更专业点。

源码也有:

//源代码
#include <cstdio>

//定义一个布尔函数:有重复值返回true,否则返回false
//子函在前,主函里就不用声明了
bool duplicate(int numbers[], int length, int* duplication)
{
	//无效值特殊处理
    if(numbers == nullptr || length <= 0)
        return false;
	//非法输入特殊处理
    for(int i = 0; i < length; ++i)
    {
        if(numbers[i] < 0 || numbers[i] > length - 1)
            return false;
    }
	//操作
    for(int i = 0; i < length; ++i)
    {
		//这里对于num[i]与i的值用了while判断
        while(numbers[i] != i)
        {
            if(numbers[i] == numbers[numbers[i]])
            {
                *duplication = numbers[i];
                return true;//有重复值则返回true
            }

            // 交换numbers[i]和numbers[numbers[i]]             
            int temp = numbers[i];
            numbers[i] = numbers[temp];
            numbers[temp] = temp;
        }
    }

    return false;
}

// ====================测试代码====================
//这是啥?答:为了验证这个重复值是否是预期array里重复了的值
bool contains(int array[], int length, int number)
{
    for(int i = 0; i < length; ++i)
    {
        if(array[i] == number)
            return true;
    }

    return false;
}
//测试函数
//testName表测试几
//expected[]表期望结果值,也就是都有哪个值给重复了
//expectedExpected表期望结果值的sizeof
//validArgument表示有没有重复的函数
void test(char* testName, int numbers[], int lengthNumbers, int expected[], int expectedExpected, bool validArgument)
{
	//输出一串字:表示测试几开始了
    printf("%s begins: ", testName);
	//调用这个布尔函数
    int duplication;
    bool validInput = duplicate(numbers, lengthNumbers, &duplication);

    if(validArgument == validInput)//看输入是否有效
    {
        if(validArgument)
        {
            if(contains(expected, expectedExpected, duplication))//duplication是之前duplicate(numbers, lengthNumbers, &duplication);里的
                printf("Passed.\n");
            else
                printf("FAILED.\n");
        }
        else
            printf("Passed.\n");
    }
    else
        printf("FAILED.\n");
}

// 重复的数字是数组中最小的数字
void test1()
{
    int numbers[] = { 2, 1, 3, 1, 4 };//发现了吗?空括号可以这么去定义
    int duplications[] = { 1 };
    test("Test1", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true);
}

// 重复的数字是数组中最大的数字
void test2()
{
    int numbers[] = { 2, 4, 3, 1, 4 };
    int duplications[] = { 4 };
    test("Test2", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true);
}

// 数组中存在多个重复的数字
void test3()
{
    int numbers[] = { 2, 4, 2, 1, 4 };
    int duplications[] = { 2, 4 };
    test("Test3", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), true);
}

// 没有重复的数字
void test4()
{
    int numbers[] = { 2, 1, 3, 0, 4 };
    int duplications[] = { -1 }; // not in use in the test function
    test("Test4", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), false);
}

// 没有重复的数字
void test5()
{
    int numbers[] = { 2, 1, 3, 5, 4 };
    int duplications[] = { -1 }; // not in use in the test function
    test("Test5", numbers, sizeof(numbers) / sizeof(int), duplications, sizeof(duplications) / sizeof(int), false);
}

// 无效的输入
void test6()
{
    int* numbers = nullptr;
    int duplications[] = { -1 }; // not in use in the test function
    test("Test6", numbers, 0, duplications, sizeof(duplications) / sizeof(int), false);
}

void main()
{
    test1();
    test2();
    test3();
    test4();
    test5();
    test6();
}

题目二:不修改数组找出重复的数字

题目:
在一个长度为n+1的数组里所有的数字都在1~n范围内,所以至少有一个数是重复的,在不修改输入数组的情况下找出任意一个重复的数字。

分析:

  1. 思路1:新建一个数组array来复制输入的数组,对应的数放在该对应下标位置处,要是有重复的值立马就能找到。
  2. 思路2:利用二分查找法解决,这也是书中的方法。例如长为8的数组{2 3 5 4 3 2 6 7},数字范围在1-7内,中间数字4将其分为1-4和5-7。统计1-4在整个数组中出现的次数,若大于4,则必定有重复数。将1-4继续分成1-2和3-4,统计1-2在整个数组中的次数为2;再统计3-4在整个数组中出现的次数为3,所以3和4里必定有一个数是重复数,这样就找到了。

思路1伪代码:

定义数组*num,长度n+1,新数组*Array;
输入长度n+1;
遍历输入数组num;
遍历输入数组Array[]并初始化为0;
//特殊处理
输入无效的情况;
数组里的值超范围的情况;
//处理
遍历num[]:
	比较array[i]的值与num[i]的值,若不一样:
		将num[i]放到Array[num[i]]处;
	否则:
		这个num[i]的值就是那个重复的值,赋值给结果r;
		break;
输出r;
return 0;

思路1代码:
记得找到重复值就break跳出循环!由于这个问题导致我半天没解出来。

#include<iostream>
using namespace std;

int main()
{
	int *num,*Array;
	int n, r=0;
	scanf("%d", &n);
	num = (int*)malloc(sizeof(int)*n);
	Array = (int*)malloc(sizeof(int)*n);
	
	for(int i=0;i<n;i++)
	{
		scanf("%d",&num[i]);
		Array[i] = 0;
		//特殊处理
		if(num[i]<=0 || num[i]>n-1)	
			return 1;
	}
	
	//特殊处理
	if(num==nullptr || n<=0)
		return 1;
	
	//操作
	for(int i=0;i<n;i++)
	{
		if(Array[num[i]] != num[i])
			Array[num[i]] = num[i];
		else
		{
			r=num[i];
			break;
		}			
	}
	cout<<r<<endl;

	free(num);
	free(Array);

	return 0;
}

思路2伪代码:

定义数组*num,长度n+1;
输入长度n+1;
遍历输入数组num;
//特殊处理
输入无效的情况;
数组里的值超范围的情况;
//处理
int start = 1;
int end =n-1;
while(end>start)
{
	定义中间值middle=((end-start)>>1)+start;
	定义计数值count;
	count = 中分后的这几个数在整个数组中出现次数;
	若为一个数(输入可能全部为同样的值)时:若出现次数count>1,则该值就是重复值r=start;
	若不为一个数时:
	若出现次数count>数值范围:
		遍历二分后的前半部分值:end=middle;
	否则:
		遍历二分后的后半部分值start=middle+1;
}
输出r;
return 0;

思路2代码:
(end-start)>>1右移相当于(end-start)/2,左进位。

#include<iostream>
using namespace std;

int main()
{
	int *num;
	int n, r=0;
	scanf("%d", &n);
	num = (int*)malloc(sizeof(int)*n);	
	for(int i=0;i<n;i++)
	{
		scanf("%d",&num[i]);
		//特殊处理
		if(num[i]<=0 || num[i]>n-1)	
			return 1;
	}	
	//特殊处理
	if(num==nullptr || n<=0)
		return 1;
	//处理
	int start = 1;
	int end =n-1;
	//迭代循环
	while(end>=start)
	{
		middle = ((end-start)>>1)+start;
		int count=0;
		//统计这几个数在整个数组中出现的次数
		for(int i=0;i<n;i++)//每回都是看这串数在整个数组中的所占的个数,所以i<n
		{
			if(num[i]>=start && num[i]<=end)
			{
				count++;
			}
		}
		//一个数时:start == end就表明这是一个数
		if(end == start)
		{			
			if(count>1)
			{
				r=start;
				break;
			}
			else
			{
				break;
			}
		//不为一个数时:end>start
		else
		{
			if(count>(middle-start+1))
			{
				end = middle;
			}
			else
			{
				start = middle+1;
			}
		}
	}
	cout<<r<<endl;
	
	free(num);

	return 0;
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值