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

最近在读《剑指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的要求。

这题在书上实际上是1~n-1的范围,这里还是改成0~n-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;
}
    原文作者:查找算法
    原文地址: https://blog.csdn.net/SHU15121856/article/details/82345306
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞