剑指Offer——面试题41:和为s的两个数字 VS 和为s的连续正数序列
题目1:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得他们的和正好是s。如果有多对数字的和等于s,全部输出。
题目2:输入一个正数s,打印所有的和为s的连续正数序列(至少含有两个数)。例如输入15,由于1+2+3+4+5 = 4+5+6 = 7+8 = 15,所以打印三个连续序列,即1-5, 4-6, 7-8。
思路:两个题目的特殊之处,都用黑体标出来了。另外题目一的要求,我略作改动,与原书不同。两个题总体思路都是一致的,两个指针一前一后,根据当前指向或当前范围确定和,在和输入的s比较。如果一致,就输出;如果不一致,就动态调整两个指针。
第一个题目中,前边的指针指向数组最前位置,后边的指针指向数组最后位置,设cursum为两个指针指向的数字之和,cursum>s时,前一个指针后移,否则后一个指针前移。当cursum==s时,输出这两个数字。另外,我们假设输入的数组中不出现重复的数字,那么在输出这两个符合条件的数字后,需要同时移动前后两个指针。按照这个规则移动指针,直到两个指针相遇。这个题目还算简单,这样基本就OK了。
第二个题目中,由于是需要打印连续的正整数序列,所以不需要单独开空间存放数组,直接使用两个int类型的变量即可。两个指针之间的数字之和为cursum,当cursum>s时,前一个指针head移动(只允许后移),否则后一个指针rear移动(也是后移)。当cursum==s时,输出两个指针之间的数字,并同时移动两个指针。
这里还有一点问题,就是什么时候需要停下来。根据题目要求,至少含有两个数的时候,才称为序列。那么两个连续的数字,其和只可能是奇数,且一个是(s+1)/2,另一个是(s-1)/2。考虑到这些以后,我们只需要让rear不超出(s+1)/2即可。
之后的另一个问题就是,怎么保证head和rear不重叠,即保证序列最少包含两个数。可以发现,当两个指针相邻时,如果还没有达到(s+1)/2,那么cursum必然会小于s,这时rear一定会后移。这样就可以保证head在(s+1)/2的范围之内,永远不会与rear重叠。
下面是第一题的代码:
void PrintSumOfTwoNum(const int data[], const int length, const int sum){ if(length < 2) return; int head = 0, rear = length - 1; long cursum = 0L; while(head < rear){ cursum = data[head] + data[rear]; if(cursum == sum) cout << data[head++] << ", " << data[rear--] << "." << endl; //Assume that each number appears only once. else{ if(cursum > sum) --rear; else ++head; } }//End while }//End func
下面是第二题的代码:
long SumTo(int head, int rear){ long cursum = 0L; while(head <= rear) cursum += head++; return cursum; } void PrintSumSequence(const int sum){ if(sum <= 2) return; int head = 1, rear = 2; long cursum = 0L; while(rear <= (sum / 2) + 1){ //End at half of sum plus 1. cursum = SumTo(head, rear); if(cursum == sum){ for(int i = head; i < rear; ++i) cout << i << ", "; cout << i << "." << endl; ++head; ++rear; } else{ if(cursum < sum) ++rear; else ++head; } }//End while }//End func
下面是测试代码:
#include <iostream.h> const int L = 7; int main(){ int test[L] = {1, 2, 4, 7, 11, 13, 15}; PrintSumOfTwoNum(test, L, 15); PrintSumSequence(15); return 0; }
上边没有讨论边界情况。另外测试用例还可以多改一改。都不赘述了。