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. }  

  • 相关阅读:
    LeetCode 1110. Delete Nodes And Return Forest
    LeetCode 473. Matchsticks to Square
    LeetCode 886. Possible Bipartition
    LeetCode 737. Sentence Similarity II
    LeetCode 734. Sentence Similarity
    LeetCode 491. Increasing Subsequences
    LeetCode 1020. Number of Enclaves
    LeetCode 531. Lonely Pixel I
    LeetCode 1091. Shortest Path in Binary Matrix
    LeetCode 590. N-ary Tree Postorder Traversal
  • 原文地址:https://www.cnblogs.com/fickleness/p/3154983.html
Copyright © 2011-2022 走看看