寻找序列中满足条件的元素
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++; } } }