修炼算法内功——two pointers

内容:

  1. two pointers简介
  2. a+b=M
  3. 序列合并问题
  4. 归并排序
  5. 快速排序

1、two pointers简介

 two pointers是算法编程中一种非常重要的思想,它更倾向于一种编程技巧,为我们编程提供了非常高的算法效率。

2、a+b=M

以一个例子引入:给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得他们的和恰好为M,输出所有满足条件的方案。

最直接的方法:

使用二重循环枚举序列A和B,找到这样的a和b,使他们的和为M。

for(int i=0;i

这种做法的时间复杂度是O(n^2)级别的,对于我们算法要求是完全不够的。

复杂度高的原因: 

对于已经确定的A[i],如果满足A[i]+B[j]>M,显然也会有A[i]+B[j+1]>M的,这是因为这是个递增序列,即无论如何增加j都是无法得到和为M了,导致j进行了大量的无效枚举。

使用two pointers降低复杂度到O(n)

算法步骤:

  1. 令i=0,j=n-1;
  2. 如果A[i]+B[j]==M,即满足条件,同时右移i,左移就,即i++,j--;
  3. 如果A[i]+B[j]
  4. 如果A[i]+B[j]>M,无论如何移动i都无法使得2成立时,只能左移j使得2成立,即j--;
  5. 反复判断2、3、4直到i>=j;

 代码如下:

while(i

 时间复杂度为O(n),显然,相对第一种做法大大优化了算法效率。可以发现,two pointers的思想充分利用了序列递增的性质,以很浅显的思想降低了复杂度。

3、序列合并问题

 假如有两个递增序列A和B,要求它们合并为一个递增序列C。

算法过程:

  1. 令i=0,j=0,index=0,i作为序列A的起始位,j作为B的起始位,index作为C的起始位;
  2. 如果A[i]
  3. 如果A[i]>B[j],将B[i]加入序列C中,并让j右移一位,即j++;
  4. 如果A[i]==B[j],则任意一个加入序列C中,并对应下标加1;
  5. 注意:完成上述过程并不代表程序结束,两组序列存在一组提前加完的情况,即仍需要继续将剩余序列加入到C中;

代码如下: 

int merge(int A[],int B[],int C[],int n,int m){
     int i=0,j=0,index=0;
     while(i

4、归并排序

归并排序的思想其实很简单,概括说来,就是“二分排序,有序合并”。将一个序列二分为两个子序列,让这两个子序列去完成各自的排序工作后再合并为一个序列;同时这个子序列也是可以执行同样的操作,再将自己二分为两个规模更小的子序列,排序再合并,直到规模为1是结束。

(1)、归并排序的递归实现——2—路归并排序

排序函数实现:

int mergeSort(int arr[],int l,int r){//传入数组,左边第一个元素和右边最后一个元素
    if(l

 合并已经排序完成的序列操作实现:

void merge(int arr[],int l,int mid,int r){
  int i=l,j=mid+1;
  int temp[maxn],index=0;
  while(i

(2)、非递归实现

2—路归并排序的非递归实现主要考虑到这样一点:每次分组时组内元素个数上限都是2的幂次。

于是就有了这样的思路:

  1. 令步长step的初值为2;
  2. 然后将数组中的每step个元素作为一组,将其内部进行排序;
  3. 再令step乘以2,重复上面操作,直到step/2超过元素个数n。

 代码如下: 

void mergeSort(int arr[]){
    for(int step=2;step/2<=n;step*=2){
        for(int i=1;i<=n;i+=step){
           int mid=i+step/2-1;
           if(mid+1<=n){
              merge(arr,i,mid,min(i+step-1,n));
           }
        }
    }
}

当然,如果题目只要求给出归并排序每一趟结束时的序列,那么完全可以使用sort函数来代替merge函数(只要时间限制允许),如下所示:

sort(arr+i,arr+min(i+step,n+1)); 

5、快速排序

快速排序的算法思想是,记首元素地址arr[0],将后面的元素逐一划分,大于arr[0]的元素放在右边,小于arr[0]的元素放在左边;形成两个子序列,再将子序列重复上述操作,直到子序列规模很小时,也就是递归到底时,两边就会直接形成有序序列,整个序列自然而然也就是有序序列了。

那么我们第一步是要实现元素的划分

 

 代码如下:

 

  1. 令临时变量temp保存arr[0],并令下标l和r指向序列左右两端,即l=0,r=n-1;
  2. 如果arr[r]>temp,r右移,直到不大于temp,将arr[r]的值赋给arr[l],执行下一步;
  3. 如果arr[l]
  4. 重复23直到l和r相遇,最后把temp放到相遇的位置即可。
void partition(int arr[],int l,int r){
   int temp=arr[l];
   while(ltemp)
            r--;             //反复左移r
      arr[l]=arr[r];
      while(l

 第二步是实现快速排序

void quickSort(int arr[],int l,int r){
    if(r>l){
      int pos=Partition(arr,l,pos-1);
      quickSort(arr,l,pos-1);
      quickSort(arr,pos+1,r);
    }
}

快速排序再最坏时时间复杂度是O(n^2),即当序列接近有序时。

总结:应用好two pointers是编程提高效率的一种重要途径!!!

 

 

不忘初心,方得始终!

 

你可能感兴趣的:(修炼算法内功)