zoukankan      html  css  js  c++  java
  • 寻找序列中满足条件的元素

      一、基础篇

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

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

      思路一:首先想到的解法是穷举法,即判断序列中任意的两个元素之和是否等于给定的数值。算法的时间复杂度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++;
            }
        }
    }

      

  • 相关阅读:
    大话设计模式-原型模式
    Unity3D实习生面试题总结-图形学相关
    UnityShader入门精要-渲染流水线
    大话设计模式-工厂方法模式
    大话设计模式-代理模式
    C#中的值类型和引用类型
    【树结构】树链剖分简单分析
    HEOI2016排序-解题报告
    普通筛法时间界的证明
    可持久化线段树
  • 原文地址:https://www.cnblogs.com/wangbogong/p/3193879.html
Copyright © 2011-2022 走看看