代码改变世界

寻找序列中满足条件的元素

2013-07-16 22:11  夜与周公  阅读(637)  评论(0编辑  收藏  举报

  一、基础篇

  在<<编程之美>>这本书中,给出了这样的一个问题:快速找出一个数组中的两个元素,让这两个元素之和等于一个给定的值,假设数组中至少存在一对这样的元素。

  对于这个问题有两种解决思路:

  思路一:首先想到的解法是穷举法,即判断序列中任意的两个元素之和是否等于给定的数值。算法的时间复杂度O(N2),显然这种蛮力解法不是我们想要的,需要继续寻找更高效的解法。

  思路二:假设预定的两个元素的和等于SUM,对于某个原始array[i],实际上就是要判断SUM- array[i]是否在数组中。如果元素是无序的,算法的复杂度仍然是O(N2),没有任何改进。但如果先进行排序,算法的复杂度是O(NlogN),然后在有序的序列中查找SUM-array[i](二分查找排序),算法的复杂度是O(log2N),因而总的复杂度O(NlogN)+O(log2N) =O(NlogN)。算法的C++实现代码如下:

  思路三:夹逼法,首先还是要进行排序,然后利用在序列左右两边设置下标,分别向中间逼近。假设左边下标i,右边下标j,如果array[i] + array[j] = sum,则满足条件;如果大于sum,则j=j-1;如果小于sum则,i=i+1。

#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    const int array_size = 7;
    int sum = 10;
    int my_array[array_size] = {5, 6, 1, 4, 7, 9 , 8};
    int* p_begin = my_array;
    int* p_end = my_array + array_size;

    sort(p_begin, p_end); 
    
    for (int i = 0; i != array_size; i++)
    {
        int value = sum - my_array[i];
        if (binary_search(p_begin + i, p_end, value))
        {
            cout<<my_array[i]<<"+"<<value<<"="<<sum<<endl;
        }
    }

    system("pause");
    return 0;
}

  算法的执行结果:

int main()
{
    const int array_size = 7;
    int sum = 10;
    int my_array[array_size] = {5, 6, 1, 4, 7, 9 , 8};
    int* p_begin = my_array;
    int* p_end = my_array + array_size;

    sort(p_begin, p_end);
    
    for (int i = 0, j = array_size -1; i <= j ;)
    {
        if (my_array[i] + my_array[j] == sum)
        {
            cout<<my_array[i]<<"+"<<my_array[j]<<"="<<sum<<endl;
            i++;
            j--;
        }
        else if (my_array[i] + my_array[j] < sum)
        {
            i++;
        }
        else
        {
            j--;
        }    
    }

    system("pause");
    return 0;
}

  程序运行的结果同上。需要注意的是,这种双向夹逼的算法思想(二分查找排序已经快速排序都使用了这种类似的思想),因此,我们应尝试掌握这种编程技法。

  二、拓展篇

  (1)将原问题中“两个数”的条件限制去除,要求序列中任意的k个元素之和等于给定的数值m。虽然删除了“两个数”的限制条件,问题的解决思路,却有着很大的改变,实际上使用到了回溯算法的思想,C++的代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void k_sum(const vector<int>& value_vec,  vector<int>& ret_vec, int pos, int reamin_sum)
{
    /************************************************************************
    * 函数功能: 在value_vec[pos:]寻找和等于reamin_sum的元素,并加入到ret_vec
    * 输入参数: value_vec, 待寻找的元素序列
    *           pos,待寻找元素的起始位置
    *           remain_sum, 待求元素之和
    * 输出参数:
    *           ret_vec,存储满足条件的元素            
    *************************************************************************/
    if (pos >= value_vec.size() || reamin_sum < 0) //递归的出口
    {
        return;
    }

    if (0 == reamin_sum)                           //找到了满足条件的解
    {
        for (vector<int>::reverse_iterator rev_iter = ret_vec.rbegin(); rev_iter != ret_vec.rend(); rev_iter++)
        {
            cout<<*rev_iter<<" ";
        }

        cout<<endl;
        return;
    }

    if (reamin_sum >= value_vec[pos])             //减枝
    {
        ret_vec.push_back(value_vec[pos]);
        k_sum(value_vec, ret_vec, pos + 1, reamin_sum - value_vec[pos]);
        ret_vec.pop_back();                      
    }

    k_sum(value_vec, ret_vec, pos + 1, reamin_sum);

}

int main()
{

    const int array_size = 7;
    int sum = 10;
    int my_array[array_size] = {5, 6, 1, 4, 7, 9 , 8};
    vector<int> value_vec(my_array, my_array + array_size);
    vector<int> ret_vec;
    k_sum(value_vec,ret_vec, 0, sum);

    system("pause");
    return 0;
}

   (2)增加对问题(1)限制,“随机的”K个数改成连续的K个数。显然问题(2)是问题(1)解的子空间,也可以利用“回溯”算法,在判断解的时候要求K的元素是连续的,但正是由于“连续”的条件,我们可以利用夹逼算法思路:利用两个指针pLeft, pRight,计算[pLeft, pRight]直接的和sum,如果sum<m,则pLeft向左移动,如果sum>m,则pRight向左移动,而sum=m,找到了一个满足条件的区域,此时pRight继续向右移动, 继续奋斗直到pLeft >= (m+1) / 2。

代码如下:

void find_seq_sum(int m)
{
    if (m < 3)
    {
        return;
    }

    int left = 1;
    int right = 2;
    int t = (m+1) / 2;

    while (left < t)
    {
        int sum = ((left + right)*(right - left + 1)) / 2;
        if (sum == m)
        {
            for (int i = left; i <= right; i++)
            {
                cout<<i<<" ";
            }
            cout<<endl;
            right++;
        }

        else if (sum < m)
        {
            right++;
        }
        
        else
        {
            left++;
        }
    }
}