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种,根据题目特征的做法我并不能够想到,将其作为了一种思维上的训练
- 这道题思考了不同的做法和变式题目,不能保证今后每个题都这样,或许一个题目只是做出来就已经有难度了,但“
虽(即使)不能至,心向往之
” - 文章
排
版技巧有待提高