数据结构与算法是计算机发展的基石,现代计算机的起源是数学,数学的核心是算法,计算机历史上每一次大的变革都离不开算法的推动。纵然“条条大路通罗马”,但好的算法永远比提高硬件设备管用。
一个整数数列,元素取值可能是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,只留下个数为奇数的那个元素。
假设这两个数分为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
using namespace std;
void FindElement(int a[] , int length )
{
int s=0;
int i;
int k=0;
for( i=0; iint s1 = s;
int s2 = s;
while( !(s1&1) )
{
s1 = s1>>1;
k++;
}
for( i=0; iif( (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—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)。
最容易想到的就是两重循环迭代,对数组中任意两个数进行求和,看其值是否等于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)。
方法一:枚举法。该方法是最容易、也是最简单的方法,枚举出数组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
#include
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; iif( 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; icout << data[i] << " ";
cout << endl;
return 0;
}
如果仅仅考虑实现,而不考虑时间效率,可以首先通过排序算法,将数组进行排序,然后根据数组下标来访问数组中的第二大的数,最快的排序算法一般为快速排序算法,但是其时间复杂度仍为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
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 = min1int 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;
}
有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
正整数序列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=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)是否是偶数的问题。
判断一个数是否是偶数,是一个比较容易解决的问题。