第五章、寻找满足和为定值的两个或多个数

来源:互联网 发布:怎么在阿里云建数据库 编辑:程序博客网 时间:2024/04/29 09:58

原链接:http://blog.csdn.net/v_JULY_v/article/details/6419466
1、如果在返回找到的两个数的同时,还要求你返回这两个数的位置列?
2、如果把题目中的要你寻找的两个数改为“多个数”,或任意个数列?(请看下面第二节)
3、二分查找时: left <= right,right = middle - 1;left < right,right = middle;

 

//算法所操作的区间,是左闭右开区间,还是左闭右闭区间,这个区间,需要在循环初始化,
//循环体是否终止的判断中,以及每次修改left,right区间值这三个地方保持一致,否则就可能出错.

//二分查找实现一
int search(int array[], int n, int v)
{
    int left, right, middle;
 
    left = 0, right = n - 1;
 
    while (left <= right)
    {
        middle = left + (right-left)/2;   
        if (array[middle] > v)
        {
            right = middle - 1;
        }
        else if (array[middle] < v)
        {
            left = middle + 1;
        }
        else
        {
            return middle;
        }
    }
 
    return -1;
}

//二分查找实现二
int search(int array[], int n, int v)
{
    int left, right, middle;
 
    left = 0, right = n;
 
    while (left < right)
    {
        middle = left + (right-left)/2;    
  
        if (array[middle] > v)
        {
            right = middle;
        }
        else if (array[middle] < v)
        {
            left = middle + 1;
        }
        else
        {
            return middle;
        }
    }
 
    return -1;
}


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

解法一
我想,稍后给出的程序已经足够清楚了,就是要注意到放n,和不放n个区别,即可,代码如下:

  1. // 21题递归方法  
  2. //copyright@ July && yansha  
  3. //July、yansha,updated。  
  4. #include<list>  
  5. #include<iostream>  
  6. using namespace std;  
  7.   
  8. list<int>list1;  
  9. void find_factor(int sum, int n)   
  10. {  
  11.     // 递归出口  
  12.     if(n <= 0 || sum <= 0)  
  13.         return;  
  14.       
  15.     // 输出找到的结果  
  16.     if(sum == n)  
  17.     {  
  18.         // 反转list  
  19.         list1.reverse();  
  20.         for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++)  
  21.             cout << *iter << " + ";  
  22.         cout << n << endl;  
  23.         list1.reverse();      
  24.     }  
  25.       
  26.     list1.push_front(n);      //典型的01背包问题  
  27.     find_factor(sum-n, n-1);   //放n,n-1个数填满sum-n  
  28.     list1.pop_front();  
  29.     find_factor(sum, n-1);     //不放n,n-1个数填满sum   
  30. }  
  31.   
  32. int main()  
  33. {  
  34.     int sum, n;  
  35.     cout << "请输入你要等于多少的数值sum:" << endl;  
  36.     cin >> sum;  
  37.     cout << "请输入你要从1.....n数列中取值的n:" << endl;  
  38.     cin >> n;  
  39.     cout << "所有可能的序列,如下:" << endl;  
  40.     find_factor(sum,n);  
  41.     return 0;  
  42. }  

解法二
@zhouzhenren:
这个问题属于子集和问题(也是背包问题)。本程序采用 回溯法+剪枝
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值。

代码编写如下:

  1. //copyright@ 2011 zhouzhenren  
  2.   
  3. //输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,  
  4. //使其和等于 m ,要求将其中所有的可能组合列出来。  
  5.   
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <memory.h>  
  9.   
  10. /**  
  11.  * 输入t, r, 尝试Wk 
  12.  */  
  13. void sumofsub(int t, int k ,int r, int& M, bool& flag, bool* X)  
  14. {  
  15.     X[k] = true;   // 选第k个数  
  16.     if (t + k == M) // 若找到一个和为M,则设置解向量的标志位,输出解  
  17.     {  
  18.         flag = true;  
  19.         for (int i = 1; i <= k; ++i)  
  20.         {  
  21.             if (X[i] == 1)  
  22.             {  
  23.                 printf("%d ", i);  
  24.             }  
  25.         }  
  26.         printf("/n");  
  27.     }  
  28.     else  
  29.     {   // 若第k+1个数满足条件,则递归左子树  
  30.         if (t + k + (k+1) <= M)  
  31.         {  
  32.             sumofsub(t + k, k + 1, r - k, M, flag, X);  
  33.         }  
  34.         // 若不选第k个数,选第k+1个数满足条件,则递归右子树  
  35.         if ((t + r - k >= M) && (t + (k+1) <= M))  
  36.         {  
  37.             X[k] = false;  
  38.             sumofsub(t, k + 1, r - k, M, flag, X);  
  39.         }  
  40.     }  
  41. }  
  42.   
  43. void search(int& N, int& M)  
  44. {  
  45.     // 初始化解空间  
  46.     bool* X = (bool*)malloc(sizeof(bool) * (N+1));  
  47.     memset(X, falsesizeof(bool) * (N+1));  
  48.     int sum = (N + 1) * N * 0.5f;  
  49.     if (1 > M || sum < M) // 预先排除无解情况  
  50.     {  
  51.         printf("not found/n");  
  52.         return;  
  53.     }  
  54.     bool f = false;  
  55.     sumofsub(0, 1, sum, M, f, X);  
  56.     if (!f)  
  57.     {  
  58.         printf("not found/n");  
  59.     }  
  60.     free(X);  
  61. }  
  62.   
  63. int main()  
  64. {  
  65.     int N, M;  
  66.     printf("请输入整数N和M/n");  
  67.     scanf("%d%d", &N, &M);  
  68.     search(N, M);  
  69.     return 0;  
  70. }  

扩展:

1、从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的(网易)。

2、有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:  
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];(微软100题第32题)。

    @well:[fairywell]:
给出扩展问题 1 的一个解法:
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)。
源代码如下:

  1. /** 
  2. * The problem: 
  3. * 从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的(网易)。 
  4. * use binary search, perhaps you should compile it with -std=c99 
  5. * fairywell 2011 
  6. */  
  7. #include <stdio.h>  
  8.   
  9. #define MAX_NUM    (1U<<31)  
  10.   
  11. int  
  12. main()  
  13. {  
  14.     int i, n, low, high, mid, max;  
  15.       
  16.     printf("Input how many numbers there are: ");  
  17.     scanf("%d/n", &n);  
  18.     /* a[] holds the numbers, b[i] holds the number of increasing numbers 
  19.     * from a[0] to a[i], c[i] holds the number of increasing numbers 
  20.     * from a[n-1] to a[i] 
  21.     * inc[] holds the increasing numbers 
  22.     * VLA needs c99 features, compile with -stc=c99 
  23.     */  
  24.     double a[n], b[n], c[n], inc[n];  
  25.       
  26.     printf("Please input the numbers:/n");  
  27.     for (i = 0; i < n; ++i) scanf("%lf", &a[i]);  
  28.       
  29.     // update array b from left to right  
  30.     for (i = 0; i < n; ++i) inc[i] = (unsigned) MAX_NUM;  
  31.     //b[0] = 0;  
  32.     for (i = 0; i < n; ++i) {  
  33.         low = 0; high = i;  
  34.         while (low < high) {  
  35.             mid = low + (high-low)*0.5;  
  36.             if (inc[mid] < a[i]) low = mid + 1;  
  37.             else high = mid;  
  38.         }  
  39.         b[i] = low + 1;  
  40.         inc[low] = a[i];  
  41.     }  
  42.       
  43.     // update array c from right to left  
  44.     for (i = 0; i < n; ++i) inc[i] = (unsigned) MAX_NUM;  
  45.     //c[0] = 0;  
  46.     for (i = n-1; i >= 0; --i) {  
  47.         low = 0; high = i;  
  48.         while (low < high) {  
  49.             mid = low + (high-low)*0.5;  
  50.             if (inc[mid] < a[i]) low = mid + 1;  
  51.             else high = mid;  
  52.         }  
  53.         c[i] = low + 1;  
  54.         inc[low] = a[i];  
  55.     }  
  56.       
  57.     max = 0;  
  58.     for (i = 0; i < n; ++i )  
  59.         if (b[i]+c[i] > max) max = b[i] + c[i];  
  60.         printf("%d number(s) should be erased at least./n", n+1-max);  
  61.         return 0;  
  62. }  

@yansha:fairywell的程序很赞,时间复杂度O(nlogn),这也是我能想到的时间复杂度最优值了。不知能不能达到O(n)。

扩展题第2题

当前数组a和数组b的和之差为
    A = sum(a) - sum(b)

a的第i个元素和b的第j个元素交换后,a和b的和之差为
    A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i])
           = sum(a) - sum(b) - 2 (a[i] - b[j])
           = A - 2 (a[i] - b[j])

设x = a[i] - b[j],得
    |A| - |A'| = |A| - |A-2x|

    假设A > 0,

    当x 在 (0,A)之间时,做这样的交换才能使得交换后的a和b的和之差变小,x越接近A/2效果越好,
    如果找不到在(0,A)之间的x,则当前的a和b就是答案。

所以算法大概如下:
    在a和b中寻找使得x在(0,A)之间并且最接近A/2的i和j,交换相应的i和j元素,重新计算A后,重复前面的步骤直至找不到(0,A)之间的x为止。 

接上,@yuan:
a[i]-b[j]要接近A/2,则可以这样想,
我们可以对于a数组的任意一个a[k],在数组b中找出与a[k]-C最接近的数(C就是常数,也就是0.5*A)
这个数要么就是a[k]-C,要么就是比他稍大,要么比他稍小,所以可以要二分查找。

查找最后一个小于等于a[k]-C的数和第一个大于等于a[k]-C的数,
然后看哪一个与a[k]-C更加接近,所以T(n) = nlogn。

除此之外,受本文读者xiafei1987128启示,有朋友在stacoverflow上也问过一个类似的题,:-),见此:http://stackoverflow.com/questions/9047908/swap-the-elements-of-two-sequences-such-that-the-difference-of-the-element-sums。感兴趣的可以看看。

本章完。

0 0
原创粉丝点击