数据结构与算法:数组(二)

来源:互联网 发布:地图开疆 知乎 编辑:程序博客网 时间:2024/06/06 02:04

数据结构与算法是计算机发展的基石,现代计算机的起源是数学,数学的核心是算法,计算机历史上每一次大的变革都离不开算法的推动。纵然“条条大路通罗马”,但好的算法永远比提高硬件设备管用。

  • 如何判断一个数组中的数值是否是连续相邻
  • 找出数组中出现奇数次的元素
    • 引申由n个元素组成的数组n-2个数出现了偶数次两个数出现了奇数次这两个数不相等如何用O1的空间复杂度找出这两个数
  • 找出数列中符合条件的数对的个数
    • 方法一蛮力法
    • 方法二先对数组进行排序然后使用二分查找方法
    • 方法三 用计数排序
    • 引申如果是任意数组而不是本题的有规律数组如何求解数组对即给定一个任意整数数组arrayn寻找数组中和值为SUM的数对
    • 引申已知大小分别为mn的两个无序数组AB和一个数常数C求满足AiBjc的所有Ai 和 Bj
  • 如何寻找出数列中缺失的数
  • 如何判定数组是否存在重复元素
  • 如何重新排列数组使得数组左边为奇数右边为偶数
    • 引申此问题可以引申到大小写字母数字排序问题和荷兰国旗问题
  • 如何把一个整型数组中重复的数字去掉
  • 找出一个数组中第二大的数
  • 寻找数组中的最小值和最大值
  • 如何将数组的后面m个数移动为前面m个数
  • 如何计算出序列的前n项数据
  • 判断一个整数x是否可以表示成 nn2个连续正整数的和

如何判断一个数组中的数值是否是连续相邻

一个整数数列,元素取值可能是0-65535中的任意一个数,相同数值不会重复出现:0是例外,可以反复出现。设计一个算法,当从该数列中随意选取5个数值时,判断这5个数值是否连续相邻。需要注意以下4点:

(1)5个数值允许是乱序的,如 8 7 5 0 6。 
(2)0可以通配任意数值,如 8 7 5 0 6 中的 0 可以通配成9 或者是 4. 
(3) 0 可以多次出现 
(4)全 0 算连续, 只有一个非 0 算连续。

解题思路:由题中的条件可以分两种情况讨论: 
(1)如果没有 0 的存在,要组成连续的数列,最大值和最小值的差距必须是 4 ,存在 0 的最小值,时间复杂度为 O(n)。 
(2)如果非 0 最大 - 非 0 最小 + 1 <=5 (即非0最大-非0最小 <=4)则这5个数值连续相邻。否则,不连续相邻。 
因此,总体复杂度为O(n)。

所以,需要先遍历一遍数组,记录下5个数的最大值和最小值。然后,求最大值和最小值的差值。 
这里写图片描述

找出数组中出现奇数次的元素

给定一个含有n个元素的整型数组array,其中只有一个元素出现奇数次,找出这个元素。

对于任意一个数k,有k^k = 0, k^0=k,所以将array中所有元素进行异或,那么个数为偶数的元素异或后都变成了0,只留下个数为奇数的那个元素。

引申:由n个元素组成的数组,n-2个数出现了偶数次,两个数出现了奇数次(这两个数不相等),如何用O(1)的空间复杂度,找出这两个数?

假设这两个数分为a、b,将数组中所有元素异或之后结果为 x,因为 a!=b,所以, x = a^b ,且 x!=0,判断x中位为1的位数,只需要知道某一个位为1的位数k,如 00101100,k可以取2或者3,或者5,然后将x与数组中第k位为1的数进行异或,异或结果就是a或b中的一个,然后用x异或,就可以求出另外一个。

因为x中第k位为1表示a或者b中有一个数的第k位也为1,假设为a,将x与数组中第k位为1的数进行异或时,也即将x与a以及其他第k位为1的出现过偶数次的数进行异或,化简即为x与a异或,最终结果即为b。

#include<iostream>using namespace std;void FindElement(int a[] , int length ){    int s=0;    int i;    int k=0;    for( i=0; i<length; i++)    {        s = s^a[i];    }    int s1 = s;    int s2 = s;    while( !(s1&1) )    {        s1 = s1>>1;        k++;    }    for( i=0; i<length; i++ )    {        if( (a[i]>>k) & 1 )            s = s^a[i];    }    int p = s^s2 ;    cout << s << "   " << p << endl;}int main(){    int data[] = { 1,2,2,3,3,4,1,5 };    int len = sizeof( data )/sizeof( data[0] );    FindElement( data,len );    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

找出数列中符合条件的数对的个数

一个整数数组,元素取值可能是1—N(N是一个较大的正整数)中的任意一个数,相同数值不会重复出现。设计一个算法,找出数列中符合条件的数对的个数,满足数对中两数的和等于N+1.

方法一:蛮力法。

这是最简单的方法,枚举出数组中所有可能的数对,看其和是否为N+1,如果是,则输出。但这种方法一般效率不高。

方法二:先对数组进行排序,然后使用二分查找方法

用两个指示器(front 和 back)分别指向第一个和最后一个元素,然后从两端同时向中间遍历,直到两个指针交叉。

(1)如果A[front]+A[back]>N+1,则back–。 
(2)如果A[front]+A[back]>N+1,则计数器加1,back–,同时front++ 
(3)如果A[front]+A[back]>N+1,则front++

重复上述步骤,O(n)时间就可以找到所有数对,因此总体复杂度为O(nlogn)。

这里写图片描述

方法三: 用计数排序

将1—N个数放在一块很大的空间里面,比如1放在1号位,N放在n号位置,O(n)的时间复杂度,然后取值,也是O(n)的时间复杂度。因此,总体复杂度为O(n)。

引申:如果是任意数组而不是本题的有规律数组,如何求解数组对?即给定一个任意整数数组array[n],寻找数组中和值为SUM的数对

最容易想到的就是两重循环迭代,对数组中任意两个数进行求和,看其值是否等于SUM。由于需要两重迭代,所以时间复杂度为O(n*n)

此方法时间复杂度太高,其实可以参照题目的方法二,先将数组排序后(一般最快的排序算法时间复杂度为O(nlogn)),然后设两个指针指向数组两端,判断两个指针对应元素之和是否为SUM,如果等于SUM,则找到了,继续查找;如果小于SUM,那么首指针递增;如果大于SUM,尾指针递减,直到两个指针相遇时,如果还是没有和为SUM的元素对出现,那么返回false。

除了上述方法外,还可以参照上例中的方法三,将数组存储到hash表中红,对每个数m,在hash表中寻找SUM-m,此时时间复杂度为O(n)。需要注意的是,如果数组空间很大,超过了内存的容量,那么可以按照hash(max(m,SUM-m))%g,将数据分到g个小的组中,然后对每个小组进行单独处理,此时时间复杂度还是O(n)。

引申:已知大小分别为m、n的两个无序数组A、B和一个数常数C,求满足A[i]+B[j]=c的所有A[i] 和 B[j]。

方法一:枚举法。该方法是最容易、也是最简单的方法,枚举出数组A和数组B中所有的元素对,判断其和是否为c,如果是,则输出

方法二:排序+二分查找法。 首先,对两个数组中较大数组(不妨设为A)排序;然后,对于B中每个元素B[i]在A中二分查找c-B[i],如果找到,直接输出。此方法的时间复杂度为O(mlogm+nlogm)

方法三:排序+线性扫描法。该方法是方案二的进一步加强,需要对两个数组排序。 
首先,对A和B进行排序;然后用指针p从头扫描A,用指针q从尾扫描B, 
如果A[p]+B[q]==c,则输出A[p]和B[q],且p++,q–; 
如果A[p]+B[q]>c,则q–;否则p++。 
时间复杂度为O(mlogm+nlogn)

方法四:hash法。 
首先,将两个数组中较小的数组(不妨设为A)保存到HashTable中,然后,对于B中每个元素B[i],也采用相同的hash算法在HashTable中查找C-B[i]是否存在,如果存在,则输出。时间复杂度为O(m+n),空间复杂度为O(min{m,n}) 
这里写图片描述

如何寻找出数列中缺失的数

给一个由n-1个整数组成的未排序的序列,其元素都是 1~n 中的不同的整数。如何寻找出序列中缺失的整数?写出一个线性时间算法。

可以通过累加求和。首先将该 n-1 个整数相加,得到sum,然后用 (1+n)n/2 减去 sum,得到的差即为缺失的整数。因为 1~n 一共有n个数,n个数的和为 (1+n)n/2,而为排序数列的和为sum,多余的这个数即为缺失的数目。

如何判定数组是否存在重复元素

假设数组a有n个元素,元素取值范围是 1~n,如何判定数组是否存在重复元素?

方法一:对数组进行排序(可以效率比较高的排序算法,如快速排序、堆排序等),然后比较相邻的元素是否相同。时间复杂度为O(nlogn),空间复杂度为O(1)。

方法二:使用bitmap(位图)方法。定义长度为N/8的char 数组,每个bit表示对应数字是否出现过。遍历数组,使用bitmap对数字是否出现进行统计。时间复杂度为O(n),空间复杂度为O(n)。

方法三:遍历数组,假设第 i 个位置数字为 j,则通过交换将 j 换到下标为 j 的位置上。直到所有数字都出现在自己对应的下标处,或发生了冲突。此时的时间复杂度为O(n),空间复杂度为O(1)。

如何重新排列数组使得数组左边为奇数,右边为偶数

给定一个存放整数的数组,如何重新排列数组使得数组左边为奇数,右边为偶数?要求:空间复杂度为O(1),时间复杂度为O(n)。

类似快速排序的处理。可以用两个指针分别指向数组的头和尾,头指针正向遍历数组,找到第一个偶数,尾指针逆向遍历数组,找到第一个奇数,交换两个指针指向的数字,然后两指针沿着相应的方向继续向前移动,重复上述步骤,直到头指针大于等于尾指针为止。

引申:此问题可以引申到大小写字母、数字排序问题和荷兰国旗问题

有关荷兰国旗问题和大小写字母数字排序问题详解 包含代码

如何把一个整型数组中重复的数字去掉

方法一,也是最容易想到的,就是遍历数组中的每一个元素,将元素与它前面的已经遍历过的元素进行逐一比较,如果发现与前面的数字有重复,则去掉;如果没有重复,则继续遍历下一个元素。由于每一个元素都要与之前所有的元素进行比较,所以时间复杂度为O(n^2)

方法一中这种最原始的方法在n比较小时,效率低下的缺点并不明显,但当数组元素比较多时,效率就会非常低下,于是想到了方法二,将原数组的下标值存在一个辅助数组中(也就是说辅助数组为{ 0,1,2,3…}),然后根据下标指向的值对辅助数组排序,对排序后的辅助数组去重(也是根据下标指向的值)。然后再按下标本身的值对去重后的辅助数组排序。之后顺次独处剩下的各下标指向的值即可。

例如, 
原数组为 { 1,2,0,2,-1,999,3,999,88 }, 
辅助数组为 { 0,1,2,3,4,5,6,7,8} , 
对辅助数组按下标指向的值的排序的结果为{ 4,2,0,1,3,6,8,5,7}, 
对辅助数组按下标指向的值去重的结果为{ 4,2,0,1,6,8,5}, 
对辅助数组按下标本身排序的结果为{ 0,1,2,4,5,6,8 },最后得到的结果为{ 1,2,0,-1,999,3,88 }

主要的时间在两次排序,所以时间复杂度为O(nlogn)。

方法三:首先通过快速排序,时间复杂度为O(nlogn),然后对排好序的数组经过一次遍历,将其重复元素通过交换,最终达到删除重复元素的目的。以数组 a[5] = { 1,2,1,2,3 } 为例,经过快速排序后,数组序列变为 { 1,1,2,2,3 },此时标记两个变量 k=0,i=1,此时(a[k]=a[0]) = (a[1]=a[i]),于是执行i++;i 变为2,(a[i]=a[2])!=(a[0]=a[k])则执行k++;k变为1,a[1]=a[2]=2,然后执行i++;i变为3,继续执行,(a[i]=a[3])=(a[1]=a[k]),于是执行i++;i 变为4,(a[i]=a[4])!=(a[1]=a[k]),则执行k++;k变为2,a[2]=a[4]=3,执行完毕,返回k的值,即去除重复数字后的数组长度为3.所以,总的时间复杂度为O(nlogn)

#include<iostream>#include<stdlib.h>using namespace std;int int_cmp( const void *a, const void *b ){    const int *ia = ( const int *)a;    const int *ib = ( const int *)b;    return *ia - *ib;}int unique( int *arr, int number ){    int k=0;    for( int i=1; i<number; i++ )    {        if( arr[k] != arr[i] )        {            arr[ k+1 ] = arr[i];            k++;        }    }    return ( k+1 );}int Unique_QuickSortMethod( int *arr, int elements ){    //Standard C function in library    qsort( arr,elements, sizeof(int), int_cmp );    return unique( arr,elements );}int main(){    int data[5] = { 1,2,1,2,3 };    int len = sizeof( data )/sizeof( data[0]);    int size = Unique_QuickSortMethod( data, len );    for( int i=0; i<size; i++ )        cout << data[i] << "  ";    cout << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

找出一个数组中第二大的数

如果仅仅考虑实现,而不考虑时间效率,可以首先通过排序算法,将数组进行排序,然后根据数组下标来访问数组中的第二大的数,最快的排序算法一般为快速排序算法,但是其时间复杂度仍为O(nlogn),根据下标访问需要遍历一遍数组,时间复杂度为O(n),所以总的时间复杂度为O(nlogn)。

降低时间复杂度,可以只通过一遍扫描数组即可找出数组中第二大的数,即通过设置两个变量来进行判断。首先定义一个变量来存储数组的最大数,初始值为数组首元素;另一个变量用来存储数组元素的第二大数,初始值为最小负数,然后遍历数组元素。如果数组元素的值比最大数变量的值大,则将第二大变量的值更新为最大数变量的值,最大数变量的值更新为该元素的值;如果数组元素的值比最大数的值小,则判断该数组元素的值是否比第二大数的值大,如果大,则更新第二大数的值为该数组元素的值。

寻找数组中的最小值和最大值

对于本题,一般有下面5种解法:

(1)问题分解法 
把本题看做是两个独立的问题,每次分别找出最小值和最大值,此时需要遍历两次数组,比较次数为2N次。

(2)取单元素法 
维持两个变量min和max,min标记最小值,max标记最大值,每次去除一个元素,先与已找到的最小值比较,再与已找到的最大值比较。此方法只需要遍历一次数组即可。

(3)取双元素法

维持两个变量min和max,min标记最小值,max标记最大值,每次比较相邻两个数,较大者与max比较,较小者与min比较,找出最大值和最小值。比较次数为1.5N次。

(4)数组元素移位法

将数组中相邻的两个数分在一组,每次比较两个相邻的数,将较大值交换至这两个数的左边,较小值放于右边。对大者组扫描一次找出最大值,对小者组扫描一次找出最小值。此种方法需要比较1.5N-2N次,但需要改变数组结构。

(5)分治法 
将数组划分成两半,分别找出两边的最小值、最大值,则最小值、最大值分别是两边最小值的较小者、两边最大值的较大者。此种方法比较次数为1.5N次。

#include<iostream>using namespace std;void GetMaxMin( int a[], int low, int high, int &max, int &min ){    int k, max1, min1, max2, min2;    if( high-low==1 || high-low==0 )        a[low] > a[high]? ( max=a[low],min=a[high]) : ( max=a[high],min=a[low]);    else    {        k = ( high+low )/2;        GetMaxMin( a,low,k, max1,min1 );        GetMaxMin( a,k+1,high,max2,min2 );        max = max1>max2? max1 : max2;        min = min1<min2 ? min1 : min2;    }}int main(){    int max, min;    int data[] = { 8,6,5,2,3,9,4,1,7 };    int num = sizeof(data) / sizeof(data[0]);    GetMaxMin( data,0,num-1, max,min );    cout << "max : " << max << endl;    cout << "min : " << min << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

如何将数组的后面m个数移动为前面m个数

有n个整数,使前面各数后移m个位置,最后m个数变成最前面的m个数。例如,有10个数的数组,即 n=10,它们的值分别是 1,2,3,4,5,6,7,8,9,10,,如果取m=5的话,经过位置调整后,变为6,7,8,9,10,1,2,3,4,5.

可以通过递归的方法实现调整: 
(1)将前m个元素的顺序颠倒 
(2)将后面n-m个元素的顺序颠倒 
(3)将n个元素的顺序全部颠倒

通过以上3个步骤的执行,就可以把数组的元素颠倒。以上例而言,第一步以后,数组顺序变为 5,4,3,2,1,6,7,8,9,10;第二步以后,数组顺序变为5,4,3,2,1,10,9,8,7,6;第三步以后,数组顺序变为 6,7,8,9,10,1,2,3,4,5

如何计算出序列的前n项数据

正整数序列Q中的每个元素都至少能被正整数a和b中的一个整除,现给定a和b,如何计算出Q的前几项?例如,当a=3,b=5,N=6时,序列为 3,5,6,9,10,12

可以与归并排序联系起来,给定两个数组A、B,数组A存放:3*1,3*2,3*3,… 数组B存放:5*1,5*2,5*3,… 有两个指针 i、j ,分别指向A、B的第一个元素,取Min(A[i],B[j]),并将较小值的指针前移,然后继续比较。当然,编程实现的时候,完全没有必要申请两个数组,用两个变量就可以。

判断一个整数x是否可以表示成 n(n>=2)个连续正整数的和

假设x可以表示成n(n>=2)个连续正整数的和,那么数学表达式如下: x=m+(m+1)+(m+2)+…+(m+n-1),其中m为分解成的连续整数中最小的那一个,由于m是大于等于1的正整数,可知 x=(2m+n-1)*n/2,变换之后 m=(2*x/n-n+1)/2,由m的范围可以知道(2*x/n-n+1)/2>=1,以上就是x和n的关系。给定一个n,看是否x能分解成n个连续整数的和,可以判断是否存在m,也就是转换成(2*x/n-n+1)是否是偶数的问题。

判断一个数是否是偶数,是一个比较容易解决的问题。