【剑指Offer记录】3-数组中重复的数字

Part0. 序言

本人为北航计算机学院在读硕士生,今年找工作,计划每天练习编程题,想先把《剑指Offer》题目做一遍。

回想上次做编程题,似乎至少是两年半(确信)前,虽然之前参加过相关竞赛(非ACM选手,CCF CSP最高310分),做项目时也在写代码,但明显感觉已经几乎没有做题手感,一边做题一边听歌的那段时光暂时是一去不复返了。

做题的目的是以非专业选手的普通玩家身份,提升自己的算法和编程能力,为找一份令自己满意的工作做准备。承蒙前辈“写做题笔记很有助于提升”的箴言,写此做题记录,首先是梳理做题过程和心得,再者想得有理想一些——逐渐打造个人平台,进行业余的分享(穷则分享做题记录,达则分享更多精彩)。

相信自己在越来越好的路上不断前行,也很愿意听听大家的想法意见建议,共勉!

Part1. 我的思路和代码

n最大可以到10000,若用双循环遍历,存在超时问题。已知数组中所有数字的范围在0到n-1,故可以采用哈希思想,利用数组下标与数字的对应关系找出重复数字。初始化一个长度为n最大值的全0数组arr[10000],当输入数字num时arr[num]的值加1,这样如果arr[i]的值大于1,则表示有重复数字i。

遍历输入的复杂度为O(n),之后遍历数组arr找出重复数字的复杂度为O(n),最终复杂度为O(n)。

int duplicate(vector<int>& numbers)
{
    int arr[10000] = {0};
    int ans = -1;

    for(int i=0; i<numbers.size(); i++){
        ++arr[numbers[i]];
    }
    for(int i=0; i<numbers.size(); i++){
        if(arr[i] > 1){
            ans = i;
            break;
        }
    }

    return ans;
}

Part2. 其他做法

1-排序后查找

将数组排序,然后遍历,若下标为i和i+1的两个数字相等,则找到了重复数字。

排序时间复杂度为O(nlogn),遍历查找重复数字时间复杂度O(n)。

int duplicate(vector<int>& numbers)
{
    int ans = -1;

    if(numbers.empty() == false){
        sort(numbers.begin(), numbers.end());
        for(int i=0; i<numbers.size()-1; i++){
            if(numbers[i] == numbers[i+1]){
                ans = numbers[i];
                break;
            }
        }
    }

    return ans;
}

特别记录:起初没有判断输入数组是否为空,导致空数组用例未通过,显示段错误。经过分析,在此IDE中numbers.size()返回类型为unsigned long,根据比较时的自动类型转换机制,如果数组为空返回0时,再-1会变为unsigned long类型的最大值,i小于该值,因此条件成立,会进入for循环,当循环次数超过numbers数组的长度时,造成下标越界,出现段错误。

2-根据题目特征

数组中的数大小都在0~n-1范围内,若没有重复数字,则排序后,下标为i的位置数字的值就是i。从下标为0的位置开始扫描数组,若下标和数字相等,则继续扫描下一个位置的数字;若不相等,则记数字为m,则将这个位置的数字和下标为m位置的数字互换,直到当前i位置的数字等于当前下标i,若在互换过程中,发现两个位置的数字相等,则说明发现了重复数字。

由于每个数字最多交换2次就可以找到属于它自己的位置,所以时间复杂度为O(n)。

int duplicate(vector<int>& numbers)
{
    int ans = -1;

    for(int i=0; (i<numbers.size()) && (ans==-1); i++){
        while(numbers[i] != i){
            int tid = numbers[i];
            if(numbers[i] == numbers[tid]){
                ans = numbers[i];
                break;
            }
            int temp = numbers[i];
            numbers[i] = numbers[tid];
            numbers[tid] = temp;
        }
    }

    return ans;
}

特别记录:为什么每个数字最多交换2次就可以找到属于它自己的位置?为什么不是1次?

Part3. 变式题目

数组长度变为n+1,数字范围为1~n,找出重复数字,但不能修改原数组。

我的想法

题目没有要求不能用额外空间,于是我首先想到的做法还是哈希思想,开辟额外空间将原数组复制一份,空间复杂度为O(n)。

其他做法

根据数组长度和数字范围得知,一定有重复数字,例如长度为n的数组中各位置依次为1,2,…,n,那么剩下1个位置的数字必然会重复。采用二分法思想,根据输入的数组长度n,将其二分为1m和m+1n两个范围,其中m为中间值,统计数组中范围在1m数字的数量,如果大于m,则1m的范围内一定有重复数字,否则重复数字就在m+1~n里,以此类推。

对于书中所给代码,得到思考:二分法的右边界end是数组长度减1,而不是数组长度,是因为数字范围的最大值不是数组长度1+n,而是n。

Part4. 心得体会

  • 思维像派大星脑子里生锈的齿轮,手感像在工位都不敢工作的i人,心情像写“我要当学霸”但“霸”字不会写而时不时想放弃的学渣。不过写完这篇记录时,感觉已经和刚开始做题有明显不同
  • 目前我能想到的思路是先排序后查找、用哈希思想这2种,根据题目特征的做法我并不能够想到,将其作为了一种思维上的训练
  • 这道题思考了不同的做法和变式题目,不能保证今后每个题都这样,或许一个题目只是做出来就已经有难度了,但“虽(即使)不能至,心向往之
  • 文章有待提高
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值