数据结构与算法之数组

  • 连续内存且顺序存储,在创建数组时必须指定容量大小;
  • 优点:时间效率高,即查找效率高O(1),而且可以实现简单的哈希表;
  • 缺点:空间效率低,即使只存储一个数据也要为所有数据分配空间,而且插入和删除效率低O(n);

二维数组的查找(剑指offer---面试题3)

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

  • bool Find(int *matrix, int rows, int cols, int number);

解题思路:

(1)首先选取数组中右上角(左下角也可以)的数字,若该数字 = number,则查找过程结束;

(2)若该数字 > number,则剔除该数字所在的列;

(3)若该数字 < number,则剔除该数字所在的行;

旋转数组的最小数组(剑指offer---面试题8)

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小元素为1。

  • int Min(int *number, int length);

解题思路:

(1)两个指针P1和P2分别指向数组的第一个和最后一个元素(正常情况下P1 > P2);

(2)若中间元素Mid > P1,说明Mid处于前面的递增序列中,那么最小元素应该在其后面,所以设置P1 = Mid;

(3)若中间元素Mid < P2,说明Mid处于后面的递增序列中,那么最小元素应该在其前面,所以设置P2 = Mid;

(4)最终P1和P2相邻,即P1指向前面递增序列中的最后一个元素,P2指向后面递增序列中的第一个元素,循环结束返回P2指向的元素;

(5)特殊情况:当P1、P2和Mid三个数相同时,无法判断Mid是处于前面还是后面的递增序列中,因此只能顺序查找

调整数组顺序使奇数位于偶数前面(剑指offer---面试题14)

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分;

  • void RecorderOddEven(int *pData, unsigned int length);

解题思路:

(1)两个指针P1和P2分别指向数组的第一个和最后一个元素,都向中间移动;

(2)P1一直向中间移动直到P1指向偶数为止,P2一直向中间移动直到P2指向奇数为止;

(3)如果P1 < P2则交换P1和P2指向的数字,直到P1=P2时while循环结束;

数组中出现次数超过一半的数字(剑指offer---面试题29)

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数组。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2},由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2;

  • int MoreThanHalfNum(int *numbers, int length);

方法一:基于Partition函数的O(n)算法即查找数组中第Mid(n/2)大的数字,假设Partition的返回值是index,若index > Mid则继续在左边Partition函数查找;若index < Mid则继续在右边部分用Partition函数查找;直到index==Mid,则中位数numbers[Mid]为超过数组长度一半的数字;

解题思路:

(1)两个变量result记录数组中的数字,times表示result这个数字出现的次数,初始化result = numbers[0]、times = 1;

(2)遍历整个数组,若times==0,则更新result为下一个数字并且设置times = 1;若数组中数字和result相同则times++;若数组中数字和result不相同则times--;

(3)数组遍历完成后,result即为出现次数超过数组长度一半的数字;

注意:不管什么方法,找到答案后都要从头遍历验证该数字出现的次数是否超过了一半,即验证输入的有效性;

最小的k个数(剑指offer---面试题30)

题目:输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4;

  • void GetLeastNumbers(int *input, int n, int *out, int k);

方法一:基于Partition函数的O(n)算法即查找数组中第k大的数字,这样位于数组中左边的k个数字就是最小的k个数字;

缺点:修改了数组;

解题思路:构建k大小的最大堆

(1)构建一个最大堆multiset<int, greater<int>> intSet;

(2)for循环遍历整个输入数组,若最大堆个数 < k则直接将该数组元素插入最大堆intSet中;

(3)若最大堆元素个数已满k个,若该数组元素 < 最大堆的最大值(intSet.begin()),则最大堆删除该最大值并插入该数组元素;

(4)否则舍弃该数组元素,继续遍历下一个元素,直到遍历完成,那么intSet中的k个元素即为最小的k个数;

注意:O(nlogk),该方法适合海量数据的输入,即n很大而k很小(k << n);

连续子数组的最大和(剑指offer---面试题31)

题目:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值,要求时间复杂度为O(n);例如输入的数组为{1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2},因此输出为该子数组的和18

  • int FindGreatestSumOfSubArray(int *pData, int length);

解题思路:动态规划,若f(i-1) <= 0或i = 0则f(i) = pData[i],若f(i-1) > 0则f(i) = f(i-1) + pData[i],其中f(i)表示以第i个数字结尾的子数组的最大和

(1)初始化nCurSum = 0、nGreatestSum = 0,其中nCurSum表示f(i),nGreatestSum表示max[f(i)];

(2)for循环遍历数组的每个元素,同时更新nCurSum和nGreatestSum的值,最后遍历完成后返回nGreatestSum;

把数组排成最小的数(剑指offer---面试题33)

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字321323;

  • void PrintMinNumber(int *numbers, int length);

解题思路:大数问题(数字转换为字符串)

(1)构建二维字符数组来保存输入数组的n个数字,用sprintf函数把数字转换为字符串,因此n个数字就是n个字符串;

(2)自定义比较函数compare,调用sort对二维字符数组排序;

(3)把二维字符数组的每个字符串(共n个字符串即n个数字)拼接起来即可;

丑数(剑指offer---面试题34)

题目:我们把只包含因子2、3和5的数称为丑数(Ugly Number),求按从小到大的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7,习惯上把1当做第一个丑数;

  • int GetUglyNumber(int index);

方法一:从1开始按顺序判断每一个整数是否为整数,即可找到第index个整数;

缺点:每个整数都需要判断,即使一个数字不是丑数但还是要对它做求余数和除法运算;

解题思路:构建三个索引M2、M3、M5

(1)构建index大小的数组array,初始化array[0] = 1,设置M2 = array、M3 = array、M5 = array;(第一个乘以i后大于当前最大丑数的结果记为Mi,其中i = 2,3,5)

(2)计算出M2 * 2、M3 * 3、M5 * 5的最小值min,赋值给数组array,同时对应的Mi++;

(3)重复步骤2直到array所有元素都被赋值,最后返回array[index-1]即可;

数组中的逆序对(剑指offer---面试题36)

题目:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字构成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数;

  • int InversePairs(int *data, int length);

方法一:扫描数组的每个数字,同时逐个比较该数字和它后面的每个数字的大小,记录逆序对的数目;

缺点:时间复杂度高O(n^2);

解题思路:归并排序;把数组分隔为子数组,先统计子数组内部的逆序对的数目,再统计两个相邻子数组之间的逆序对的数目;

(1)初始化begin = 0、end = length -1,然后调用InversePairsCore(data, copy, begin, end);

(2)将数组一分为二分成两个子数组,并递归调用InversePairsCore(data, copy, begin, begin+length)和InversePairsCore(data, copy, begin+length+1, end)即统计出两个子数组各自内部的逆序对的数目;

(3)从后向前的归并排序:两个指针P1和P2分别指向两个子数组的末尾,若P1 > P2则逆序对的数目count += P2前面所有元素的个数,且将P1赋值给辅助数组copy;

(4)若P1 <= P2则不构成逆序对,只需将P2赋值给辅助数组copy;

数字在排序数组中出现的次数(剑指offer---面试题38)

题目:统计一个数字在排序数组中的出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3这个数字在这个数组中出现了4次,因此输出4;

  • int GetNumberOfK(int *data, int length, int k);

方法一:二分查找找到一个k,然后在找到的k的左右两边顺序扫描分别找到第一个k和最后一个k,即可得到数字k出现的次数;

缺点:时间复杂度太高O(n);

解题思路:

(1)二分查找找到第一个k的位置first即GetFirstK(int *data, int length, int k, int start, int end);

(2)二分查找找到最后一个k的位置last即GetLastK(int *data, int length, int k, int start, int end);

(3)last-first+1即为数字k出现的次数;

数组中只出现一次的数字(剑指offer---面试题40)

题目一:一个整型数组只有一个数字出现了一次,其他的数字都出现了两次。请写程序找出这个只出现一次的数字,要求时间复杂度O(n),空间复杂度O(1);

解题思路:初始化result = 0并依次和数组中的每个数字都做异或操作,最后得到的结果result就是只出现一次的数字;

题目二:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字,要求时间复杂度O(n),空间复杂度O(1);

  • void FindNumsAppearOnce(int data[], int length, int *num1, int num2);

解题思路:

(1)初始化result = 0并依次和数组中的每个数字都做异或操作,最后得到的结果result就是两个只出现一次的数字异或的结果;

(2)由于有两个数字只出现一次,所以result一定不为0,找到result的二进制表示的第一个为1的位置index;

(3)根据第index位是否为1把数组中的数字分为两个子数组,那么每个子数组一定都分别一个只出现一次的数字;

(3)初始化*num1 = 0并依次和第一子数组中的每个数字都做异或操作,初始化*num2 = 0并依次和第二子数组中的每个数字都做异或操作,也就找到两个只出现一个的数字;

和为s的两个数字 VS 和为s的连续正数序列(剑指offer---面试题41)

题目一:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可;

  • bool FindNumberWithSum(int data[], int length, int sum, int *num1, int *num2);

解题思路:两个指针P1和P2;

(1)两个指针P1和P2分别指向数组的首尾,即初始化P1 = 0、P2 = length -1;

(2)若P1+P2 > sum说明这两个数字的和过大,则P2--;

(3)若P1+P2 < sum说明这两个数字的和过小,则P1++;

(4)若P1+P2 == sum即查找成功;

题目二:输入一个正数s,打印出所有和为s的连续正数序列(至少包含两个数)。例如输入15,由于1+2+3+4=4+5+6=7+8=15,所以结果打印出3个连续序列1~5、4~6和7~8;

  • bool FindContinuousSequence(int sum);

解题思路:两个指针small和big分别指向连续序列的首尾

(1)初始化small = 1、big = 2、curSum = small + big;

(2)若curSum == sum则打印出Samll到big之间的序列;

(3)若curSum > sum说明当前序列和过大,则small++并判断curSum和sum是否相等;

(4)big++且curSum += big,直到small >= Mid(即中间数字);

扑克牌的顺子(剑指offer---面试题44)

题目:从扑克牌中随机抽5张排,判断是不是一个顺子,即这5张排是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字,假设大小王设置为0;

  • bool IsContinuous(int *numbers, int length);

解题思路:

(1)调用qsort函数对数组排序;

(2)统计数组中大小王0的个数numberOfZero;

(3)初始化small = numberOfZero、big = small + 1,遍历整个数组统计相邻数字的空缺总数numberOfGap;(若存在相邻两个数字相等直接返回false)

(4)若numberOfZero >= numberOfGap则返回true,否则返回false;

数组中重复的数字(剑指offer---面试题51)

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内,数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次,请找出数组中任意一个重复的数字。例如输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3;

  • bool duplicate(int numbers[], int length, int *duplication);

方法一:先把输入数组排序,再从头到尾扫描排序后的数组即可找到重复的数字;

缺点:时间复杂度高O(nlogn);

方法二:构建O(n)的哈希表,从头到尾扫描数组,若哈希表没有这个数字则加入到哈希表,若哈希表已经有了该数字则找到了一个重复的数字;

缺点:空间复杂度高O(n);

解题思路:

若数组没有重复的数字,那么当数组排序后数字index将出现在下标为index的位置;

(1)从头到尾扫描数组,当扫描到下表为index的数字时,设置value = numbers[index],然后判断value和index的大小;

(2)若value == index则继续扫描下一个数字;

(3)若value != index则判断numbers[value]和numbers[index]的大小:若相等则找到了重复的数字value,若不相等则交换两个数字,再重复这个比较直到value == index;

数据流中的中位数(剑指offer---面试题64)

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序后位于中间的位置,如果从数据流中读出偶数个数值,那么中位数就是所有数值排序后中间两个数的平均值;

 1 template<typename T> class DynamicArray
 2 {
 3 public:
 4     void Insert(T num);
 5     T GetMidian();
 6 
 7 private:
 8     vector<T> min;
 9     vector<T> max;
10 };

解题思路:类似于面试题29/30,但是数据流是动态的,需要选择容器来保存从流中读出来的数据,容器选择方式如下:

方法一:数组,插入数字O(1),找出中位数O(n)--->Partition函数查找第k大的元素;

方法二:排序数组,插入数字O(n),找出中位数O(1);

方法三:排序链表,插入数字O(n),找出中位数O(1);

方法四:二叉搜索树,插入数字O(logn),找出中位数O(logn)???;最差情况若二叉排序树极度不平衡则复杂度都是O(n);

说明:二叉树结点中添加一个表示子树结点数目的字段;

方法五:平衡的二叉搜索树即AVL树,插入数字O(logn),找出中位数O(1);但是大部分编程语言的函数库没有实现AVL;

说明:插入的时候记录AVL的结点个数number,找出中位数的时候判断number的奇偶性,若为奇数则直接返回根结点的值,若为偶数则从个数较多的子树中找到最值结点(左子树的最大值或右子树的最小值),返回根结点和最值结点的平均值;

方法六:最大堆和最小堆,插入数字O(logn),找出中位数O(1)

说明:最大堆保存左边部分的数据,最小堆保存右边部分的数据,最大堆最大数据 < 最小堆最小数据,且两个堆中数据个数之差<=1;

(1)构建最大堆max和最小堆min,假设max和min的数据总数是偶数时则插入新数据到min中,否则插入新数据到max中;

(2)情况1:偶数时应该插入最小堆min中,但是若插入元素num < max[0]则需要把num先插入到max中,再把max中最大的数拿出来插入到最小堆min中;

(3)情况2:奇数时应该插入最大堆max中,但是若插入元素num > min[0]则需要把num先插入到min中,再把min中最小的数拿出来插入到最大堆max中;

(4)GetMedian:若总数是奇数,则返回min[0],若是偶数则返回(max[0] + min[0]) / 2;

滑动窗口的最大值(剑指offer---面试题65)

题目:给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。例如输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,它们的最大值分别是4、4、6、6、6、5;

  • vector<int> maxInWindows(const vector<int> &num, unsigned int size);

解题思路:双端队列deque

若当前数字比队列的某些数字大,则删除队尾的这些数字,若滑动窗口不包括队头了,则删除队头的数字,最后在入队当前数字;

(1)构建双端队列deque用来存储数组下标,初始化为数组前size个数字的最大值的下标,构建存储最大值的容器resultVector;

(2)从第size个元素开始遍历,首先把deque中队头元素对应的数字存入resultVector中;

(3)判断若当前元素比 >= 队尾元素,则删除队尾元素,while一直到队尾元素 < 当前元素;

(4)判断队头是否仍然还在滑动窗口中,即若队头元素下标 <= 当前元素下标 - size,则删除队头元素

(5)然后把当前元素的下标push_back到deque中,重复步骤2/3/4直到遍历所有数组元素为止,最后返回resultVector;

转载于:https://www.cnblogs.com/bo1990/p/11441961.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值