面试题 41,和为s的两个数字,引申:和为s的连续正数序列,窗口思想(其实就是是双指针贪心)和中位数法 [LeetCode] Minimum Window Substring
O(n2)的方法自然可以解决。面试时可以先扔出来这个,然后再思考更好的解,让面试官知道你并不是不会,而是在思考更好的解。
后来想出来了一个办法,像这种存在“数组”,“查找两个数”关键字的,一律试试头尾各定义两个指针往中间扫描的思路,大多数都会有一些收获。
书中的解法和我思路一样:在序列首尾各定义指针p1, p2,如果和超过s,p2往中间移,如果和小于s,p1往中间移。
下面是我的代码,没有调试。
bool FindPairForSum(int* list, int size, int s, int* index1, int* index2){ *index1 = *index2 = -1; if(NULL == list) return false; int left = 0; int right = size -1; while(left < right){ if((list[left] + list[right]) == s){ *index1 = left; *index2 = right; return true; } if(list[left] + list[right] < s){ left++; }else{ right--; } } return false; }
书中的代码:
bool FindNumbersWithSum(int data[], int length, int sum, int* num1, int* num2) { bool found = false; if(length < 1 || num1 == NULL || num2 == NULL) return found; int ahead = length - 1; int behind = 0; while(ahead > behind) { long long curSum = data[ahead] + data[behind]; if(curSum == sum) { *num1 = data[behind]; *num2 = data[ahead]; found = true; break; } else if(curSum > sum) ahead --; else behind ++; } return found; }
对比下可以发现,我有一点没有考虑到:数组中两数的和最好用long long来记录,这样才能保证有效的比较。
引申:
这一题我思路和答案完全不同了。。。
书中利用了第一题的方法,将序列(1, 2)作为初始序列,left表示序列的第一个,right表示序列的最后一个。如果序列中和小于所求值,序列的right往前走一个从而把一个更大的数包括到序列中;如果序列中和小于所求值,left往前走一个把原来序列中最小的数排除出去。如果相等,打印出当前序列,right继续往前走一个,来寻找新的序列。
一直到right大于等于所求和的一半了,遍历就停止了。
整个思路就像模拟了一只毛毛虫在正整数集合上蠕动,或者像一个窗口在变化,这种方法本质上依然是贪心,不过是首位双指针的贪心。
我的思路比较数理一点,用sum表示要求的和,比如sum为15的时候,7,8满足条件,7,8之所以满足,是因为 15/2 = 7.5,所以正好左右各取一个数:7和8,就使得和为15。
4,5,6 之所以满足条件,是因为15/3 = 5,正好5可以放在中间,左右再拿一个4和6,所以满足。
因此,对于sum,如果我们想确定它有没有长度为n的连续序列使得这个序列的和等于sum,我们只要算算sum%n,若n是奇数,sum%n == 0,那么就意味着存在这样的序列。而且这个序列的中间那个数就是 sum/n;若n是偶数,sum%n == n/2,也就是说sum除以n的结果是一个以 .5 结尾的数,那么就意味着这样的序列存在,向两边各延伸n/2就是答案。
这种思路的代码会更简单,但是适用范围很窄,如果把可选的数字换成只能从一个递增数组中选择,就只能用窗口思想了。
我的代码:
#include<stdio.h> void PrintContinuousSequence(int small, int big) { for(int i = small; i <= big; ++ i) printf("%d ", i); printf("\n"); } void FindSeqSum(int sum){ if(sum < 3) return; int mid = sum/2 + 1; for(int i = 2;i <= mid;i++){ if(!(i&1)){ if((sum%i) == (i/2) && (sum/i - i/2 + 1) >= 0) PrintContinuousSequence(sum/i - i/2 + 1, sum/i + i/2); }else{ if(!(sum%i) && (sum/i - i/2) >= 0) PrintContinuousSequence(sum/i - i/2, sum/i + i/2); } } } // ====================测试代码==================== void Test(char* testName, int sum) { if(testName != NULL) printf("%s for %d begins: \n", testName, sum); FindSeqSum(sum); } int main() { Test("test1", 1); Test("test2", 3); Test("test3", 4); Test("test4", 9); Test("test5", 15); Test("test6", 100); return 0; }
书上的代码:
void PrintContinuousSequence(int small, int big); void FindContinuousSequence(int sum) { if(sum < 3) return; int small = 1; int big = 2; int middle = (1 + sum) / 2; int curSum = small + big; while(small < middle) { if(curSum == sum) PrintContinuousSequence(small, big); while(curSum > sum && small < middle) { curSum -= small; small ++; if(curSum == sum) PrintContinuousSequence(small, big); } big ++; curSum += big; } } void PrintContinuousSequence(int small, int big) { for(int i = small; i <= big; ++ i) printf("%d ", i); printf("\n"); }
最后,说到窗口思想,也就是双指针贪心,这篇博文也有详细记录。
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2014-03-03 00:44 Felix Fang 阅读(1741) 评论(1) 编辑 收藏 举报