日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

two pointers

發布時間:2025/3/20 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 two pointers 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 什么是two pointers
  • 歸并排序
  • 快速排序

什么是two pointers

two pointers是算法編程中一種非常重要的思想,但是很少會有教材單獨拿出來講,其中一個原因是它更傾向于是一種編程技巧,而長得不太像是一個“算法”的模樣。two pointers的思想十分簡潔,但卻提供了非常高的算法效率,下面就來一探究竟。
以一個例子引入:
給定一個遞增的正整數序列和一個正整數M,求序列中的兩個不同位置的數a和b,
使得它們的和恰好為M,輸出所有滿足條件的方案
。
例如給定序列{1,2, 3,4,5, 6)和正整數M=8,就存在2+6=8與3+5=8成立。
本題的一個最直觀的想法是,使用二重循環枚舉序列中的整數a和b,判斷它們的和是否為M,
如果是,輸出方案;如果不是,則繼續枚舉。
代碼如下:

for(int i=0;i<n;i++) {for(int j=i+1;j<n;j++){if(a[i]+a[j]==M){printf("%d %d\n",a[i],a[j]);}} }

顯然,這種做法的時間復雜度為O(n2),對n在105的規模時是不可承受的。
來看看高復雜度的原因是什么:

  • 對一個確定的 a[i] 來說,如果當前的 a[ j ] 滿足 a[ i ] + a[ j ] > M ,顯然也會有a[ i ] + a [ j + 1 ] > M 成立
    (這是由于序列是遞增的),因此就不需要對a[ j ] 之后的數進行枚舉。如果無視這個性質,就會導致 j 進行了大量的無效枚舉,
    效率自然十分低下。
  • 對某個a[ i ]來說,如果找到一個a[j],使得a[ i ]+ a[ j ]> M恰好成立,那么對a[ i+1]來說,一定也有a[ i+1 ] + a[ j ]> M成立,因此在a[ i ]之后的元素也不必再去枚舉。
  • 上面兩點似乎體現了一個問題: i 和 j 的枚舉似乎是互相牽制的,而這似乎可以給優化算法帶來很大的空間。事實上,本題中two pointers將利用有序序列的枚舉特性來有效降低復雜度。
    它針對本題的算法過程如下:
    令下標 i 的初值為0,下標 j 的初值為 n-1,即令i、j分別指向序列的第一個元素和最后一個元素,
    接下來根據a[ i ] + a[ j ]與M的大小來進行下面三種選擇,使 i 不斷向右移動、使 j 不斷向左移動,直到i≥j成立,如圖4-8所示。

    ①如果滿足a[ i ]+a[ j ]=M,說明找到了其中一組方案。 由于序列遞增,不等式a[ i+1]+a[ j ]>M與a[ i ]+a[ j- 1]< M 均成立,
    但是a[ i+ 1]+a[ j- 1]與M的大小未知,因此剩余的方案只可能在[ i+ 1,j- 1]區間內產生,
    令 i=i+1、j = j -1 (即令i向右移動,j向左移動)。

    ②如果滿足a[ i ] +a[ j ]> M,由于序列遞增,不等式a[ i+ 1 ]+a[ j ]>M成立,但是a[ i ]+a[ j - 1]與M的大小未知,
    因此剩余的方案只可能在[i,j- 1]區間內產生,令j=j-1 (即令j向左移動)。

    ③如果滿足a[i]+a[j]<M,由于序列遞增,不等式a[i]+a[j- 1]<M成立,但是a[i+1]+ a[j]與M的大小未知,
    因此剩余的方案只可能在[i+ 1,j]區間內產生,令i=i+1 (即令i向右移動)。

    反復執行上面三個判斷,直到i≥j成立。代碼如下:

    while(i<j) {if(a[i]+a[j]==m){printf("%d %d\n",i,j);i++;j--;}else if(a[i]+a[j]<m){i++;}else{j--;} }

    再來看序列合并問題。假設有兩個遞增序列A與B,要求將它們合并為一個遞增序列C.
    同樣的,可以設置兩個下標i和j,初值均為0,表示分別指向序列A的第一個元素和序列B的第一個元素,然后根據A[i]與B[j]的大小來決定哪一個放入序列C:

  • 若A[ i ] < B[ j ],說明A[ i ]是當前序列A與序列B的剩余元素中最小的那個,因此把A[ i ]加入序列C中,并讓 i 加1 (即讓 i 右移一位).
  • 若A[ i ] > B[ j ],說明B[ j ]是當前序列A與序列B的剩余元素中最小的那個,因此把B[ j ]加入序列C中,并讓 j 加1 (即讓 j 右移一位).
  • 若A[i]==B[j],則任意選一個加入到序列C中,并讓對應的下標加1.
  • 上面的分支操作直到i、j中的一個到達序列末端為止,然后將另一個序列的所有元素依次加入序列C中
    代碼如下:

    int merge(int A[],int B[],int C[],int n,int m) {int i=0,j=0,index=0;while(i<n&&j<m){if(A[i]<=B[j]){C[index]=A[i];i++;}else{C[index]=B[j];j++;}index++;} while(i<n)//將序列A剩余的元素加入序列C {C[index]=A[i];i++;index++;//C[index++]=A[i++]; } while(j<n)//將序列B剩余的元素加入序列C {C[index]=B[j];j++;index++;}return index;//返回序列C的長度 }

    最后,一定有讀者問, two pointers到底是怎樣的一種思想?
    事實上, two pointers最原始的含義就是針對本節第一個問題而言的,
    而廣義上的two pointers則是利用問題本身與序列的特性,使用兩個下標i、j對序列進行掃描(可以同向掃描,也可以反向掃描),
    以較低的復雜度(一般是0(n)的復雜度)解決問題。讀者在實際編程時要能夠有使用這種思想的意識。

    歸并排序


    const int maxn=100; //將數組A的[L1,R1] 與 [L2,R2] 區間合并為有序區間(此處的L2 為R1+1) void merge(int A[],int L1,int R1,int L2,int R2) {int i=L1;int j=L2;int temp[maxn],index=0;//temp臨時的存放合并后的數據,index為下標 while(i<=R1&&j<=R2){if(A[i]<=A[j]){temp[index++]==A[i++];}else{temp[index++]=A[j++]; }}while(L1<=R1){temp[index++]=A[i++];}while(L2<=R2){ temp[index++]=A[j++];}for(i=0;i<index;i++){A[i]=temp[i];//將合并后的序列賦值返回數組A} } //將array數組當前區間[left,right]進行歸并排序 void mergeSort(int A[],int left,int right) {if(left<right){int mid=(left+right)/2;mergeSort(A,left,mid);//遞歸,將左子區間歸并排序 mergeSort(A,mid+1,right);//遞歸,將右子區間遞歸排序 merge(A,left,mid,mid+1,right); //合并左右子區間 } }

    1.遞歸實現
    2-路歸并排序的遞歸寫法非常簡單,只需要反復將當前區間[left, right]分為兩半,
    對兩個子區間[left, mid]與[mid +1, right]分別遞歸進行歸并排序,然后將兩個已經有序的子區間合并為有序序列即可,
    代碼如下,其中merge函數為上一節的代碼改編而來,請讀者注意與上一節代碼的對比:

    const int maxn=100; //將數組A的[L1,R1] 與 [L2,R2] 區間合并為有序區間(此處的L2 為R1+1) void merge(int A[],int L1,int R1,int L2,int R2) {int i=L1;int j=L2;int temp[maxn],index=0;//temp臨時的存放合并后的數據,index為下標 while(i<=R1&&j<=R2){if(A[i]<=A[j]){temp[index++]==A[i++];}else{temp[index++]=A[j++]; }}while(L1<=R1){temp[index++]=A[i++];}while(L2<=R2){ temp[index++]=A[j++];}for(i=0;i<index;i++){A[i]=temp[i];//將合并后的序列賦值返回數組A} } //將array數組當前區間[left,right]進行歸并排序 void mergeSort(int A[],int left,int right) {if(left<right){int mid=(left+right)/2;mergeSort(A,left,mid);//遞歸,將左子區間歸并排序 mergeSort(A,mid+1,right);//遞歸,將右子區間遞歸排序 merge(A,left,mid,mid+1,right); //合并左右子區間 } }

    2.非遞歸實現

    void mergeSort(int A[]) {//step為組內元素個數,step/2為左子區間元素個數,注意等號可以不取for(int step=2;step/2<=n;step*=2){//step為組內元素個數,step/2為左子區間元素個數,//注意 等號可以不取 for(int i=1;i<=n;i+=step){int mid=i+step/2 -1;//左子區間元素個數為step/2 if(mid+1<=n)//右子區間存在元素則合并 {//左子區間為 [ i, mid ],右子區間為[mid+1, min(i+step-1,n) ]merge(A,i,mid,mid+1,min(i+step-1),n));}} } }

    當然,如果題目中只要求給出歸并排序每一趟結束時的序列,那么完全可以使用sort()函數代替
    merge函數(只要時間限制允許),如下所示:

    void mergeSort(int A[]) {//step為組內元素個數,step/2為左子區間元素個數,注意等號可以不取for(int step=2;step/2<=n;step*=2){//step為組內元素個數,step/2為左子區間元素個數,//注意 等號可以不取 for(int i=1;i<=n;i+=step){sort(A,i,mid,mid+1,min(i+step-1),n);} //此處可以輸出歸并排序的某一趟結束的序列 } }

    快速排序


    其實大致的思路就是選一個數作為一個支點,大于它的數都在它的右邊,小于它的數都在它的左邊。

    思路:
    例子:


    #include<cstdio> //對區間[left,right]進行劃分 int Partition(int A[],int left,int right) {int temp=A[left];//將A[left]存放至臨時變量temp while(left<right){while(left<right&&A[right]>temp)right--;A[left]=A[right];while(left<right&&A[left]<temp)left++; A[right]=A[left]; }A[left]=temp;//把temp放到left與right相遇的地方 return left;//返回相遇的下標 }

    接下來就可以正式實現快速排序算法了。
    快速排序的思路是:

  • 調整序列中的元素,使當前序列最左端的元素調整后滿足左側所有元素均不超過
    該元素,右側元素均不大于該元素。
  • 對該元素的左側和右側分別遞歸進行1的調整,直到當前調整區的長度不超過1。
  • 快速排序的遞歸實現如下:

    void quickSort(int A[],int left,int right) {if(left<right)//當前區間的長度超過1 {//將[left,right]按 A[left]一分為2 int pos=Partition(A,left,right);//對左子區間進行快速排序 quickSort(A,left,pos-1);//對右子區間進行快速排序 quickSort(A,pos+1,right);} }


    需要知道的是: rand()函數只可以生成 [ 0, RAND_MAX ] 范圍內的整數。RAND_MAX是stdlib.h中的一個常數,
    在不同系統環境中,該常數的值有所不同,這里的是32767。


    int randPartition(int A[],int left,int right) {//生成[left,right]內的隨機數int P=(round( 1.0*rand()/RAND_MAX*(right-left)) + left ); swap(A[left],A[P]);int temp=A[left];while(left<right){while(left<right&&A[right]>temp)right--;A[left]=A[right];while(left<right&&A[left]<temp)left++; A[right]=A[left]; }A[left]=temp;//把temp放到left與right相遇的地方 return left;//返回相遇的下標 }

    總結

    以上是生活随笔為你收集整理的two pointers的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。