数组的扩展操作_legend

来源:互联网 发布:古典白话小说推荐知乎 编辑:程序博客网 时间:2024/05/16 01:23

顺序表sequeceList的扩展操作 :


(1)数组中的最小元素,以及最小 的K个元素 :
(2)数组中重复次数最多的元素 :mostRepeated
(2.1)数组中出现次数超过一半的元素 :
(2.2)出现次数刚好为一半的元素 :
(3)数组A和数组B通过相同下标来构建名值对 key-value:
(4)数组的循环右移 rotate right/cycle shift right :
(5)数组的反转reverse :
(6)最大子序列和:maxSumSubarray :
(7)最长升序子数组: longestSortedSubarray:
(8)查找数组中和为定值value的两个元素 :
(9) 两个数组的公共元素:

(分析:使用hash来实现)

--------------------------------------------------------------------------------------------------------
(1)数组中的最小元素,以及最小 的K个元素 :
/*数组中最大值
初始时*maxValue=array[0];
*indexOfmax=0;
*/
void maxEle(elementType *array ,int length,elementType* maxValue,int* indexOfMax){
*maxValue=array[0];
*indexOfMax=0;


for(int i=1;i<length;i++){


if(array[i]>*maxValue){
*maxValue=array[i];
*indexOfMax=i;
}
}
}


求最小的k个元素:
(1.1)思路一:对n个元素进行升序排序,然后遍历前k个。
O(n^2)或者O(n*lgn) (后者是快速排序)


(1.2)思路二:利用选择排序中每次从后面的数组中选择出一个最小值。
所以,外层循环为i : 0~k-1;所以复杂度为O(k*n)


 (1.3)  思路三:建立最小堆。


(1.4)思路四: 使用最小堆初始化这个数组,然后取优先队列的前k个数。


注意:求元素的最大值(最小值),步骤:


(1)一般需要创建两个变量。
1, maxValue  最大值
2.  tempValue 临时值


(2)不断求得tempValue , 与 maxValue比较,更新maxValue;


如1:求数组中最大和子数组,需要创建maxSum,tempSum;

不断求得tempSum与maxSum比较,更新maxSum;


如2: 求得数组中的最大值,tempValue直接为数组中的元素,不需要求得。


如3: 有序数组中出现次数最多的 元素,
http://blog.csdn.net/legend050709/article/details/26580249
tmp_ele  对于 mostRepeatedEle;
tmp_count 对于 mostRepeatedCount;







(2)数组中重复次数最多的元素 :mostRepeated :


http://blog.csdn.net/legend050709/article/details/26580249
(1)先快速排序然后顺序遍历: O(n*lng+n);


(2)循环嵌套 : O(n*n);


(3)hashTable: O(2*n);




(2.1)数组中出现次数超过一半的元素 :


(1)思路一:先将数组快速排序,然后直接输出n/2处的元素即可。
分析:快速排序为O(n*lgn),然后为什么n/2处是所找元素。
极限思想:如果所找的元素是最小的,则位于排序后数组的最前端,
超过n/2个,所以n/2处也是这个元素;
如果所找元素是最大的,同理。

(2)思路二: hash table, 时间复杂度为O(n),空间复杂度为O(n)


哈希表的键值(Key)为数组中的元素,值(Value)为该元素对应的次数。
直接遍历整个hash 表,找出每一个元素在对应的位置处出现的次数,输出那个
出现次数超过一半的元素即可。

(3)思路三 :hash table ,时间复杂度为O(n),空间复杂度为O(1)。
如果每次删除两个不同的数(不管是不是我们要查找的那个
出现次数超过一半的数字),那么,在剩下的数中,我们要查找的数
(出现次数超过一半)出现的次数仍然超过剩余总数的一半。

(4)思路四:时间复杂度为O(n),空间复杂度为O(1) 。
数组中某个元素出现的 次数超过了数组长度的一半,那么该元素出现的
次数比其他元素出现次数之和还要多。


在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次
数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保
存的数字相同,则次数加1。如果下一个数字和我们之前保存的数字不同,
则次数减1。如果次数为零,我们需要保存下一个数字,并把次数重新设为1。
实现如下 :
Type Find(Type* a, int N) //a 代表数组,N 代表数组长度
{
Type candidate;
int nTimes, i;
// nTimes为元素出现的次数。
/*对于出现次数最多的元素,则每次与其他元素不同时,则nTimes--,
但是nTimes仍然大于0.
所以到最后candidate保存的就是出现次数最多的元素。
*/
for(i = nTimes = 0; i < N; i++)
{
if(nTimes == 0)
{
candidate = a[i], nTimes = 1;
}
else
{
if(candidate == a[i])
nTimes++;
else
nTimes--;
}
}
return candidate;
}


(2.2)出现次数刚好为一半的元素 :
/*用两个临时变量来保存元素,两个nTimes来保存次数*/
int Find(int* a, int N)
{
int candidate1,candidate2;
int nTimes1, nTimes2, i;
for(i = nTimes1 = nTimes2 =0; i < N; i++)
{
if(nTimes1 == 0)
{
candidate1 = a[i], nTimes1 = 1;
}
else if(nTimes2 == 0 && candidate1 != a[i])
/*条件二是:第二个变量是否等于第一个*/
{
candidate2 = a[i], nTimes2 = 1;


}
else
{
if(candidate1 == a[i])
nTimes1++;
else if(candidate2 == a[i])
nTimes2++;
else
{
nTimes1--;
nTimes2--;
}
}
}
return nTimes1>nTimes2?candidate1:candidate2;
}


(3)数组A和数组B通过相同下标来构建名值对 key-value:


(4)数组的循环右移 rotate right/cycle shift right :O(n)


  如: 1,2,3,4 循环右移2位 得到 3,4 ,1,2


  思路:循环右移m位,可以先将前n-m个元素reverse,然后将后m个元素reverse,然后整体reverse.
  如1,2,3,4 循环右移得到3,4,1,2 
  1,2 reverse得到2,1;
  3,4 reverse 得到4,3
  2,1,4,3反转得到3,4,1,2


  void rotate_right_n(eleType * array,int length,int n){


  n=n%length;
  reverse(array,0,length-n-1);
  reverse(array,length-n,length-1);
  reverse(array,0,length-1);
  /*整体复杂度为O(n)
   n为数组长度
  */
  }


(5)数组的反转reverse :O(n/2)


void reverse(int * array,int start,int end){
      while(start<end){
      swap(&array[start],&array[end]);
      start++;
      end--;
      }
}


(6)最大子段和 :maxSubsequenceSum :


(6.1)思路一:O(n)


elementType maxSubsequenceSum(elementType* array,int length){
elementType tmpSum=0,maxSum=0;


for(int i=0;i<length;i++){
tmpSum+=array[i];
if(tmpSum>maxSum){
maxSum=tmpSum;
}
else if(tmpSum<0){
tmpSum=0;
}


}
return maxSum;
}


/*不足:如果全是负数,则返回的是0,按理说应该返回的是
最大的那个负数。*/


改进:
将array[0]设置为数组中最大的数。
初始化时 maxSum=array[0];


(6.2)思路二:循环嵌套 :O(n^2)


elementType maxSubsequenceSum(elementType* array,int length,int *startIndex,int* endIndex){
elementType tmpSum=0,maxSum=0;
*startIndex=0;
*endIndex=0;
for(int i=0;i<length;i++){
tmpSum=0;
for(int j=i;j<length;j++){
tmpSum+=array[j];
if(tmpSum>maxSum)
*startIndex=i;
*endIndex=j;
maxSum=tmpSum;
}
}
return maxSum;
}


/*思路二:可以求得最大子序列和,以及开始元素,结束元素;
*/


(6.3)思路三 :动态规划 :O(n)


(6.3.1)解析:


设b[i]表示以a[i]结尾 的子数组的最大子段和。


在计算b[i]时,可以考虑以下三种情况:


(1)b[i] = b[i-1]+a[i],当b[i-1]>0时,这时候的b[i]中包含a[i]。
( 注意:
此中不是根据a[i]的正负决定的,而是根据
b[i-1]的正负决定的,因为如果a[i]为负,然后a[i+1]是一个比a[i]
绝对值大的正数。则显然a[i],a[i+1]均是要加入的。



(2)b[i] = a[i],当b[i-1]<=0,这时候以a[i]重新作为b[i]的起点。


(3)b[i]不包含a[i]的情况,这种情况在计算b[i]之前已经计算处结果,保存在b[0~i-1]中。


所以:b[i] = max{ b[i-1]+a[i],a[i]}


(6.3.2)代码实现 :


/*
函数返回最大子数组的和,以及起始位置,结束位置。
*/
elementType mostSumSubsequence(elementType* array,
int length ,int * start,int * end )


{
int tmpStart,tmpEnd;
elementType tmpSum,maxSum;


*end=*start=0;
tmpStart=tmpEnd=0;


/*
初始化均为0.
*end,*start的初始化与maxSum的初始化是对应的。
同时与tmpStart,tmpEnd,tmpSum也是对应的。
*/
tmpSum=maxSum=array[0];


for(int i=1;i<length;i++){


if(tmpSum>0){


tmpSum+=array[i];

tmpEnd=i;


/* a[i]>0
b[i]=b[i-1]+a[i];
同时更新end。
*/
}


else{


tmpSum=array[i];
tmpEnd=i;
tmpStart=i;
/*
b[i-1]<0
b[i]=a[i];
*/
}


if(maxSum<tmpSum){
maxSum=tmpSum;
*start=tmpStart;
*end=tmpEnd;
}
}
return maxSum;
}


(6.3.3)解析二以及代码实现二 :


(1) 解析 :


令cursum(i)表示数组下标以i为起点的最大连续下标最大的和,而maxsum(i)表示前i个元素的最大子数组之和。那么我们就可以推出下一个maxsum(i+1)应该为cursum(i+1)和maxsum(i)中选取一个最大值。递推式为:
cursum(i) = max{A[i],cursum(i-1)+A[i]};


maxsum(i) = max{maxsum(i-1),cursum(i+1)};
/*是否更新maxSum*/


(2)代码如上  :




----------


(6.4)思路四 : 分治法  O(n*lgn) :(划分,解决,合并)


(6.4.1)分析 :


把数组A[1..n]分成两个相等大小的块:A[1..n/2]和A[n/2+1..n],最大的子数组只可能出现在三种情况:


1.在A[0..n/2]中、
2.在A[n/2+1,n-1]中、
3.跨过A[0..n/2]和A[n/2+1,n-1]、


(1)划分以及解决 :


前两种情况的求法和整体的求法是一样的,因此递归求得。


第三种,我们可以采取的方法也比较简单,沿着第n/2向左搜索,直到左边界,找到最大的和maxleft,以及沿着第n/2+1向右搜索找到最大和maxright,那么总的最大和就是maxleft+maxright。


(2)合并:数组A的最大子数组和就是这三种情况中最大的一个。


(6.4.2)伪代码 :


int maxSumSubArray(int *A,int l,int r) {
    if l<r do 
        mid = (l+r)/2;
        ml = maxSumSubArray(A,0,mid); //分治 
        mr = maxSumSubArray(A,mid+1,r);
        for i=mid downto l do 
            search maxleft; 
        for i=mid+1 to r do 
            search maxright; 
        return max(ml,mr,maxleft+maxright); //归并 
        then //递归出口 
            return A[l]; 
}


(6.4.3)代码实现 :
/*
输入:
arr : 输入的数组;
l,表示数组arr的左边界,
r,表示数组的右边界
输出 :
left ,最大子数组的左边界 ;
right, 最大子数组的右边界;
返回值, 最大子数组的和;
*/
int max_sub_array(int arr[],int l,int r,int &left,int &right)
{
    if(l<r){
        int mid=(l+r)/2;
        int ll,lr;
        int suml=max_sub_array(arr,l,mid,ll,lr);
        int rl,rr;
        int sumr=max_sub_array(arr,mid+1,r,rl,rr);


        int sum_both=0;
        int max_left=-INF;
        int ml,mr;
        for(int i=mid;i>=l;i--)
        {
            sum_both+=arr[i];
            if(sum_both>max_left){
                max_left=sum_both;
                ml=i;
            }
        }


        int max_right=-INF;
        sum_both=0;
        for(int i=mid+1;i<=r;i++)
        {
            sum_both+=arr[i];
            if(sum_both>max_right){
                max_right=sum_both;
                mr=i;
            }
        }


        sum_both=max_left+max_right;


        if(suml<sumr) {
            if(sumr<sum_both) {
                left=ml;
                right=mr;
                return sum_both;
            }
            else {
                left=rl;
                right=rr;
                return sumr;
            }


        }
        else{
            if(suml<sum_both) {
                left=ml;
                right=mr;
                return sum_both;
            }
            else {
                left=ll;
                right=lr;
                return suml;
            }


        }
    }
    else {
        left=l;
        right=r;
        return arr[l];
    }
}




(7)最长升序子序列 longestSortedSubarray : O(n)


/*
解析:动态规划思想;


设maxLen[i]为当前最长的升序子序列的长度,


if(a[i-1]<a[i])
tmpLen[i]++;
else tmpLen[i]=1;
maxLen[i]=max{maxLen[i-1],tmpLen[i]};
*/


int longestSortedSubarray(int* array,int length,int *start,int* end){


      int tmpLen,longestLen;
      int tmpStart,tmpEnd;
      /*初始化*/
      *start=0;
      *end=0;
      tmpStart=0;
      tmpEnd=0;
      tmpLen=1;
      longestLen=1;


      for(int i=1;i<length;i++){


      if(array[i]>array[i-1])
      {
          tmpLen++;
          tmpEnd=i;
      }
      else {
      tmpLen=1;
      tmpStart=i;
      tmpEnd=i;
      }


      if(tmpLen>longestLen){
      longestLen=tmpLen;
      *start=tmpStart;
      *end=tmpEnd;
      }


      }


return longestLen;
}


(8)查找数组中和为定值value的两个元素 :


(8.1)思路一:对每个a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的时间
都要花费为O(N),这样下来,最终找到两个数还是需要O(N^2)的复杂度;


(8.2)思路二:二分查找:时间复杂度为O(n*lgn)
N 个a[i],都要花logN 的时间去查找相对应的sum-a[i]是否在原始序列中,总的时间复杂度已降为O(N*logN),且空间复杂度为O(1)。
(如果有序,直接二分O(N*logN),如果无序,先排序后二分,复杂度同样为O
(N*logN+N*logN)=O(N*logN),空间总为O(1))。


(8.3)思路三 :hashtable :时间复杂度O(n),空间复杂度O(n);
给定一个数字,根据hash映射查找另一个数字是否也在数组中,只需用O(1)的时间.

0 0
原创粉丝点击