zoukankan      html  css  js  c++  java
  • 快速寻找满足条件的两个数

    2012-04-18 16:41

    2.12快速寻找满足条件的两个数---程序员编程艺术之五

    第一节、寻找满足条件的两个数
    第14题(数组):
    题目:输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
    要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
    例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

    分析

    咱们试着一步一步解决这个问题(注意阐述中数列有序无序的区别):

    1. 直接穷举,从数组中任意选取两个数,判定它们的和是否为输入的那个数字。此举复杂度为O(N^2)。很显然,我们要寻找效率更高的解法。
    2. 题目相当于,对每个a[i],然后查找判断sum-a[i]是否也在原始序列中,每一次要查找的时间都要花费为O(N),这样下来,最终找到两个数还是需要O(N^2)的复杂度。那如何提高查找判断的速度列?对了,二分查找,将原来O(N)的查找时间提高到O(logN),这样对于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))。
    3. 有没有更好的办法列?咱们可以依据上述思路2的思想,a[i]在序列中,如果a[i]+a[k]=sum的话,那么sum-a[i](a[k])也必然在序列中,,举个例子,如下:
      原始序列:1、 2、 4、 7、11、15     用输入数字15减一下各个数,得到对应的序列为:
      对应序列:14、13、11、8、4、 0      
      第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,如果下面出现了和上面一样的数,即a[*i]=a[*j],就找出这俩个数来了。如上,i,j最终在第一个,和第二个序列中找到了相同的数4和11,,所以符合条件的两个数,即为4+11=15。怎么样,两端同时查找,时间复杂度瞬间缩短到了O(N),但却同时需要O(N)的空间存储第二个数组(@飞羽:要达到O(N)的复杂度,第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,首先初始i指向元素1,j指向元素0,谁指的元素小,谁先移动,由于1(i)>0(j),所以i不动,j向左移动。然后j移动到元素4发现大于元素1,故而停止移动j,开始移动i,直到i指向4,这时,i指向的元素与j指向的元素相等,故而判断4是满足条件的第一个数;然后同时移动i,j再进行判断,直到它们到达边界)。
    4. 当然,你还可以构造hash表,正如编程之美上的所述,给定一个数字,根据hash映射查找另一个数字是否也在数组中,只需用O(1)的时间,这样的话,总体的算法通上述思路3 一样,也能降到O(N),但有个缺陷,就是构造hash额外增加了O(N)的空间,此点同上述思路 3。不过,空间换时间,仍不失为在时间要求较严格的情况下的一种好办法。
    5. 如果数组是无序的,先排序(n*logn),然后用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,然后i++,j--,逐次判断a[i]+a[j]?=sum,如果某一刻a[i]+a[j]>sum,则要想办法让sum的值减小,所以此刻i不动,j--,如果某一刻a[i]+a[j]<sum,则要想办法让sum的值增大,所以此刻i++,j不动。所以,数组无序的时候,时间复杂度最终为O(n*logn+n)=O(n*logn),若原数组是有序的,则不需要事先的排序,直接O(n)搞定,且空间复杂度还是O(1),此思路是相对于上述所有思路的一种改进。(如果有序,直接两个指针两端扫描,时间O(N),如果无序,先排序后两端扫描,时间O(N*logN+N)=O(N*logN),空间始终都为O(1))。(与上述思路2相比,排序后的时间开销由之前的二分的n*logn降到了扫描的O(N))。

    总结

    • 不论原序列是有序还是无序,解决这类题有以下三种办法:1、二分(若无序,先排序后二分),时间复杂度总为O(n*logn),空间复杂度为O(1);2、扫描一遍X-S[i]  映射到一个数组或构造hash表,时间复杂度为O(n),空间复杂度为O(n);3、两个指针两端扫描(若无序,先排序后扫描),时间复杂度最后为:有序O(n),无序O(n*logn+n)=O(n*logn),空间复杂度都为O(1)。
    • 所以,要想达到时间O(N),空间O(1)的目标,除非原数组是有序的(指针扫描法),不然,当数组无序的话,就只能先排序,后指针扫描法或二分(时间n*logn,空间O(1)),或映射或hash(时间O(n),空间O(n))。时间或空间,必须牺牲一个,自个权衡吧。
    • 综上,若是数组有序的情况下,优先考虑两个指针两端扫描法,以达到最佳的时(O(N)),空(O(1))效应。否则,如果要排序的话,时间复杂度最快当然是只能达到N*logN,空间O(1)则是不在话下。


    public static int i = -1;public static int j = -1;void FindSum(int[] arr,n){int p=0,q=n-1;while(p<q){if((arr[p] + arr[q]) == sum){i = p;j = q;return;}else if ((arr[p] + arr[q])>sum)q--;else p++;}

    }

     

    第二节、寻找满足条件的多个数
    第21题(数组)
    2010年中兴面试题
    编程求解:
    输入两个整数 n 和 m,从数列1,2,3.......n 中随意取几个数,
    使其和等于 m ,要求将其中所有的可能组合列出来。

    #include<list>  

    #include<iostream> 

    using namespace std;  

      

    list<int>list1;  

    void find_factor(int sum, int n)   

    {  

        // 递归出口  

        if(n <= 0 || sum <= 0)  

            return;  

          

        // 输出找到的结果  

        if(sum == n)  

        {  

            // 反转list  

            list1.reverse();  

            for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++)  

                cout << *iter << " + ";  

            cout << n << endl;  

            list1.reverse();      

        }  

          

        list1.push_front(n);      //典型的01背包问题  

        find_factor(sum-n, n-1);   //放n,n-1个数填满sum-n  

        list1.pop_front();  

        find_factor(sum, n-1);     //不放n,n-1个数填满sum   

    }  

      

    int main()  

    {  

        int sum, n;  

        cout << "请输入你要等于多少的数值sum:" << endl;  

        cin >> sum;  

        cout << "请输入你要从1.....n数列中取值的n:" << endl;  

        cin >> n;  

        cout << "所有可能的序列,如下:" << endl;  

        find_factor(sum,n);  

        int t;

        cin>>t;

        return 0;  

    }  

     

    题目的大概意思是:快速找出在一个数组内的两个数,让这两个数之和等于一个给定的值。书中给出的解法三觉得应该是(NlogN)复杂度中比较快的,但这种解法为什么完备还要仔细推导一下才知道。因为是找两个数之和,解法二还可以再优化。排序后可以把数组分成两段,以和的一半作为分割点,这样就在二分查找时只需找出前半部分的Sum-arr[i]是否在后半部分中。我用SLT的binary_search实现了一下,但是此法由于排序,只能返回具体的解,如果返回解在原数组中的位置,那要复杂得多。

    [cpp] view plaincopy

    1. //给定条件为SUM()=10  
    2. #include<iostream>  
    3. using namespace std;  
    4. #include<vector>  
    5. #include<algorithm>  
    6. const int maxN=1000;  
    7. int solv=0;  
    8. class list  
    9. {  

    10. private:  

    1. 11.     vector<int> v;  
    2. 12.     int n;  
    3. 13.     vector<int>::iterator cutPoint;  

    14. public:  

    1. 15.     list(int a[],int n1)  
    2. 16.     {  
    3. 17.         n=n1;  
    4. 18.         for(int i=0;i<n;i++)  
    5. 19.             v.push_back(a[i]);  
    6. 20.     }  
    7. 21.     //获得分割点  
    8. 22.     void GetCutPoint()  
    9. 23.     {  
    10. 24.         sort(v.begin(),v.end());  
    11. 25.         cutPoint=v.begin();  
    12. 26.         for(vector<int>::iterator it=v.begin();it!=v.end();it++)  
    13. 27.         {  
    14. 28.             if(*it>=5)  
    15. 29.             {  
    16. 30.                 cutPoint=++it;  
    17. 31.                 break;  
    18. 32.             }  
    19. 33.         }  
    20. 34.     }  
    21. 35.     bool search ( )  
    22. 36.     {  
    23. 37.           
    24. 38.         for(vector<int>::iterator it=v.begin();it!=cutPoint;it++)  
    25. 39.         {  
    26. 40.             int val=10-*it;  
    27. 41.             bool result=binary_search(cutPoint,v.end(),val);  
    28. 42.             if(result)  
    29. 43.             {  
    30. 44.                 solv=val;  
    31. 45.                 return true;  
    32. 46.             }  
    33. 47.         }  
    34. 48.         return false;  
    35. 49.     }  

    50. };  

    51. int main()  

    52. {  

    1. 53.     int a[]={5,6,7,4,7,9,8};  
    2. 54.     list list1(a,7);  
    3. 55.     list1.GetCutPoint();  
    4. 56.       
    5. 57.     if(list1.search())  
    6. 58.     {  
    7. 59.         cout<<"find it:"<<solv<<" "<<10-solv<<endl;  
    8. 60.     }  
    9. 61.     else   
    10. 62.     {  
    11. 63.         cout<<"not find"<<endl;  
    12. 64.     }  
    13. 65.     system("pause");  
    14. 66.     return 1;  

    67. }  

  • 相关阅读:
    多线程实现双色球
    使用google api material icons在网页中插入图标
    网页前端制作好的网站
    n元线性方程非负整数解的个数问题
    Dilworth定理证明
    一个简易的Python全站抓取系统
    gensim word2vec好的教程
    C语言一些常用的功能
    python3正则表达式
    python3创建目录
  • 原文地址:https://www.cnblogs.com/fickleness/p/3154983.html
Copyright © 2011-2022 走看看