第三章、寻找满足条件的两个或多个数及其扩展

第一节、寻找满足条件的两个数

题目:输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是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)则是不在话下。

第二节、寻找满足条件的多个数
编程求解:输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来。
解法一、用递归的方法进行求解
[cpp]  view plain copy
  1. #include<list>  
  2. #include<iostream>  
  3. using namespace std;  
  4.   
  5. list<int>list1;  
  6. void find_factor(int sum, int n)   
  7. {  
  8.     // 递归出口  
  9.     if(n <= 0 || sum <= 0)  
  10.         return;  
  11.       
  12.     // 输出找到的结果  
  13.     if(sum == n)  
  14.     {  
  15.         // 反转list  
  16.         list1.reverse();  
  17.         for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++)  
  18.             cout << *iter << " + ";  
  19.         cout << n << endl;  
  20.         list1.reverse();      
  21.     }  
  22.       
  23.     list1.push_front(n);      //典型的01背包问题  
  24.     find_factor(sum-n, n-1);   //放n,n-1个数填满sum-n  
  25.     list1.pop_front();  
  26.     find_factor(sum, n-1);     //不放n,n-1个数填满sum   
  27. }  
  28.   
  29. int main()  
  30. {  
  31.     int sum, n;  
  32.     cout << "请输入你要等于多少的数值sum:" << endl;  
  33.     cin >> sum;  
  34.     cout << "请输入你要从1.....n数列中取值的n:" << endl;  
  35.     cin >> n;  
  36.     cout << "所有可能的序列,如下:" << endl;  
  37.     find_factor(sum,n);  
  38.     return 0;  
  39. }  
解法二(此思路不是太懂)
这个问题属于子集和问题(也是背包问题)。本程序采用 回溯法+剪枝
X数组是解向量,t=∑(1,..,k-1)Wi*Xi, r=∑(k,..,n)Wi
若t+Wk+W(k+1)<=M,则Xk=true,递归左儿子(X1,X2,..,X(k-1),1);否则剪枝;
若t+r-Wk>=M && t+W(k+1)<=M,则置Xk=0,递归右儿子(X1,X2,..,X(k-1),0);否则剪枝;

本题中W数组就是(1,2,..,n),所以直接用k代替WK值。
代码如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <memory.h>  
  4.   
  5. /**  
  6.  * 输入t, r, 尝试Wk 
  7.  */  
  8. void sumofsub(int t, int k ,int r, int& M, bool& flag, bool* X)  
  9. {  
  10.     X[k] = true;   // 选第k个数  
  11.     if (t + k == M) // 若找到一个和为M,则设置解向量的标志位,输出解  
  12.     {  
  13.         flag = true;  
  14.         for (int i = 1; i <= k; ++i)  
  15.         {  
  16.             if (X[i] == 1)  
  17.             {  
  18.                 printf("%d ", i);  
  19.             }  
  20.         }  
  21.         printf("\n");  
  22.     }  
  23.     else  
  24.     {   // 若第k+1个数满足条件,则递归左子树  
  25.         if (t + k + (k+1) <= M)  
  26.         {  
  27.             sumofsub(t + k, k + 1, r - k, M, flag, X);  
  28.         }  
  29.         // 若不选第k个数,选第k+1个数满足条件,则递归右子树  
  30.         if ((t + r - k >= M) && (t + (k+1) <= M))  
  31.         {  
  32.             X[k] = false;  
  33.             sumofsub(t, k + 1, r - k, M, flag, X);  
  34.         }  
  35.     }  
  36. }  
  37.   
  38. void search(int& N, int& M)  
  39. {  
  40.     // 初始化解空间  
  41.     bool* X = (bool*)malloc(sizeof(bool) * (N+1));  
  42.     memset(X, falsesizeof(bool) * (N+1));  
  43.     int sum = (N + 1) * N * 0.5f;  
  44.     if (1 > M || sum < M) // 预先排除无解情况  
  45.     {  
  46.         printf("not found/n");  
  47.         return;  
  48.     }  
  49.     bool f = false;  
  50.     sumofsub(0, 1, sum, M, f, X);  
  51.     if (!f)  
  52.     {  
  53.         printf("not found/n");  
  54.     }  
  55.     free(X);  
  56. }  
  57.   
  58. int main()  
  59. {  
  60.     int N, M;  
  61.     printf("请输入整数N和M/n");  
  62.     scanf("%d%d", &N, &M);  
  63.     search(N, M);  
  64.     return 0;  
  65. }  
第三节、寻找满足条件的数的扩展问题
1. 从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的(没有理解)

分析:双端 LIS 问题,用 DP 的思想可解,目标规划函数 max{ b[i] + c[i] - 1 }, 其中 b[i] 为从左到右, 0 ~ i 个数之间满足递增的数字个数; c[i] 为从右到左, n-1 ~ i 个数之间满足递增的数字个数。最后结果为 n - max + 1。其中 DP 的时候,可以维护一个 inc[] 数组表示递增数字序列,inc[i] 为从小到大第 i 大的数字,然后在计算 b[i] c[i] 的时候使用二分查找在 inc[] 中找出区间 inc[0] ~ inc[i-1] 中小于 a[i] 的元素个数(low)。
2.2、有两个序列a,b,大小都为n,序列元素的值任意整数,无序;要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:  var a=[100,99,98,1,2, 3];var b=[1, 2, 3, 4,5,40];(没有理解)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值