面试题 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");
}

 

最后,说到窗口思想,也就是双指针贪心,这篇博文也有详细记录。

 

posted on 2014-03-03 00:44  Felix Fang  阅读(1473)  评论(1编辑  收藏

导航