【算法】数组中重复数字,二维数组中的查找

本文解析了《剑指Offer》中的数组重复数字查找及二维数组搜索算法,包括实现思路与C++代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在读《剑指Offer》,在作者的GitHub上能找到全部源码,包含测试用例,但注释极少。光是读思路也是纸上得来终觉浅,自己拉到本地运行,一行行地解读一下。

算法一直是自己的弱项之一,不可马虎。

数组问题

数组的时间效率很高,可以根据下标在O(1)的时间读写任何元素;数组的空间效率不是很好,经常会有空闲的区域没有得到充分利用。

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

长度为n的数组中所有数字都在0~n-1的范围内,找出数组中任意一个重复的数字。

如果数组中没有重复的数字,那么从小到大排序后第i位置应是数字i。从头到尾扫描这个数组,扫描到下标i时,如果它的数字m不等于i就和下标m的数字比较重复,不重复就交换(以将其放置到正确位置上),如此反复。

#include<bits/stdc++.h>
using namespace std;

// 参数:
//        numbers:     一个整数数组
//        length:      数组的长度
//        duplication: (输出) 数组中的一个重复的数字
// 返回值:
//        true  - 输入有效,并且数组中存在重复的数字
//        false - 输入无效,或者数组中没有重复的数字
bool duplicate(int numbers[], int length, int* duplication) {
	//nullptr是C++11中的空指针,这里是检查输入的有效性
	if(numbers == nullptr || length <= 0)
		return false;

	//这里也是检查输入数组是否符合值都在0~n-1之间这一要求
	for(int i = 0; i < length; ++i) {
		if(numbers[i] < 0 || numbers[i] > length - 1)
			return false;
	}

	//至此,合法性检查结束,遍历整个数组
	for(int i = 0; i < length; ++i) {
		//只要当前这个数和其下标不等,即m不为i,就一直循环
		while(numbers[i] != i) {
			//在交换之前,先判断一下它们是否重复(相等)
			if(numbers[i] == numbers[numbers[i]]) {
				//如果相等,就说明找到了这个重复元素,那么通过指针传出
				*duplication = numbers[i];
				//找到一个就结束该程序,因为题目就是只找任意一个
				return true;
			}

			//至此,说明判断不相等,要将它们交换以将当前的m放在正确的位置
			int temp = numbers[i];
			numbers[i] = numbers[temp];
			numbers[temp] = temp;
		}
		//如果当前这个数和下标相等,就去顺序地看下一个数
		//如果数组里根本没有i这个数,那么在这个位置上的不断交换一定可以发现重复的数
	}

	//整个数组遍历完都没有return,此时已经全部有序(从0到n-1),没有重复的数
	return false;
}

int main() {
	int a[7]= {3,1,2,0,2,5,3};
	int *p=new int();
	//boolalpha不会将bool值转为1/0输出 
	cout<<boolalpha<<duplicate(a,6,p)<<endl;
	if(nullptr==p)
		cout<<"没有重复的数"<<endl;
	else
		cout<<"重复的数字是"<<*p<<endl;
	delete p;
	return 0;
}
面试题3增强:数组中重复的数字

要求不修改数组,来完成面试题3的要求。

这题在书上实际上是1n-1的范围,这里还是改成0n-1的范围,和上一题一样。

每次将范围二分,然后去看数组中这个范围内的数字是不是比这个范围的长度还大,如果是的话一定在这个范围内存在重复数字,如此缩小地来查找。该方法当找不到重复数字时,未必真的没有重复数字。

#include<bits/stdc++.h>
using namespace std;

// 参数:
//        numbers:     一个整数数组
//        length:      数组的长度
//        start:       范围的起始值
//        end:         范围的终止值
// 返回值:
//        数组中在指定范围内的数字的数目
int countRange(const int* numbers, int length, int start, int end)
{
	//检查输入数组合法性 
    if(numbers == nullptr || length <= 0)
        return 0;

	//统计并返回数组中在指定范围内的数字个数 
    int count = 0;
    for(int i = 0; i < length; i++)
        if(numbers[i] >= start && numbers[i] <= end)
            ++count;
    return count;
}

// 参数:
//        numbers:     一个整数数组
//        length:      数组的长度
// 返回值:             
//        正数  - 输入有效,并且数组中存在重复的数字,返回值为重复的数字
//        负数  - 输入无效,或者数组中没有重复的数字
int getDuplication(const int* numbers, int length)
{
	//检查输入数组合法性 
    if(numbers == nullptr || length <= 0)
        return -1;

	//初始考察的区间范围是0~n-1之间 
    int start = 0;
    int end = length - 1;
    //当这个区间还正确存在 
    while(end >= start)
    {
    	//取区间的中间数,将其划分成两个子区间 
        int middle = ((end - start) >> 1) + start;
        cout<<"middle="<<middle<<"\n";
        //判断一下数组中落在前一半区间中的数的个数 
        int count = countRange(numbers, length, start, middle);
        
        //终止条件:如果这个区间已经收缩到仅剩一个数 
        if(end == start)
        {
        	//这时start=middle=end,刚刚统计的也就是数组中该数的个数
            if(count > 1)//如果该数个数>1
                return start;//那么所要求的重复数就是它 
            else//否则,没有找到重复的数 
                break;//直接退出寻找 
        }

		//后面这部分是没有走[终止条件]时都会运行的
		
		//如果数组中落在前一半区间的数的个数超过了区间长度
		//说明至少存在一个该区间的数,在数组中是重复出现了的 
        if(count > (middle - start + 1))
            end = middle;//因此将考察的范围缩小到这前半个区间上 
        else//否则 
            start = middle + 1;//将考察的范围缩小到后半个区间上 
    }
	//运行至此,没有找到重复的数
	//该算法不能保证真的没有重复的数,试想数组仅0113在0~3的区间上可被跳过 
	return -1;
}


int main(){
	//从0~3区间内,数组中有2,2,3,3正好4个
	//从4~7区间内,数组中有4,5,6,7正好4个
	//识别不出这种情况下的重复数字
	//这组数据在书上从1~n-1的情况下是能识别出来的 
	int a[8]={2,3,5,4,3,2,6,7};
	cout<<getDuplication(a,8)<<"--------------------"<<endl;
	//下面这组数据也找不出重复的1 
	int b[8]={0,1,1,3,4,5,6,7};
	cout<<getDuplication(b,8)<<"--------------------"<<endl;
	//这种数据就能找出来
	int c[7]={3,1,2,0,2,5,3};
	cout<<getDuplication(c,7)<<"--------------------"<<endl;
	return 0;
}
面试题4:二维数组中的查找

在一个int型的二维数组中,每行从左到右递增,每列从上到下递增。任给一个整数,判断该二维数组中是否包含该数。

每次选取所剩二维数组的右上角数字。如果比要查找的数字大,因为该数字是本列最小,所以只要查看去掉该列所剩的左边的二维子数组;如果比要查找的数字小,因为该数字是本行最大,所以只要查看去掉该行所剩的下边的二维子数组。

#include<bits/stdc++.h>
using namespace std;

// 参数:
//        matrix:      一个二维数组
//        rows:        数组的行数 
//        columns:     数组的列数  
//        number:      要查找的数字 
// 返回值:
//        true表示找到了,false表示找不到 
bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;//标识是否找到该数字 

    if(matrix != nullptr && rows > 0 && columns > 0)//输入合法性检查 
    {
    	//从数组右上角(0,columns-1)开始比较 
        int row = 0;
        int column = columns - 1;
        //行号在逐渐增大,列号在逐渐减小,谨防越界 
        while(row < rows && column >=0)
        {
        	//row*columns+column就是二维数组中第row行第column列的数字
			//如果这个数字和number一样,就已经找到它了 
            if(matrix[row * columns + column] == number)
            {
                found = true;//标识置为真 
                break;//退出循环 
            }
            //如果这个数字比要找的数字大 
            else if(matrix[row * columns + column] > number)
                -- column;//那么要刷去当前这一列,到左边的子二维数组去找 
            //如果这个数字比要找的数字小 
			else
                ++ row;//那么要刷去当前这一行,到下边的子二维数组去找
        }
    }

    return found;
}

int main(){
	//C++确定地初始化二维数组,则行数可以自动识别 
	int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
	cout<<boolalpha<<Find((int*)matrix, 4, 4, 7)<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值