和爲S的數字--滑動窗口

一、和爲S的兩個數字

題目描述

面試題57-1:輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。

思路分析

class Solution {
public:
    vector<int> FindNumbersWithSum(const vector<int>& array,int sum){
        vector<int> result;
        if (array.empty())
            return result;
        
        int len = array.size();
        int i = 0, j = len - 1;
        
        while(i < j){
            int CurSum = array[i] + array[j];
            if(CurSum == sum){
                result.push_back(array[i]);
                result.push_back(array[j]);
                break;
            }
            if(CurSum < sum)
                ++i;
            else
                --j;
        }
        return result;
    }
};

劍指offer 參考答案

#include <cstdio>

bool FindNumbersWithSum(int data[], int length, int sum, 
                        int* num1, int* num2)
{
    bool found = false;
    if(length < 1 || num1 == nullptr || num2 == nullptr)
        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;
}

二、和爲S的連續正數序列

題目描述

面試題57-2:小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!

思路分析

如何用滑動窗口解這道題

要用滑動窗口解這道題,我們要回答兩個問題:

第一個問題,窗口何時擴大,何時縮小?
第二個問題,滑動窗口能找到全部的解嗎?
對於第一個問題,回答非常簡單:

當窗口的和小於 target 的時候,窗口的和需要增加,所以要擴大窗口,窗口的右邊界向右移動
當窗口的和大於 target 的時候,窗口的和需要減少,所以要縮小窗口,窗口的左邊界向右移動
當窗口的和恰好等於 target 的時候,我們需要記錄此時的結果。設此時的窗口爲 [i, j)[i,j),那麼我們已經找到了一個 ii 開頭的序列,也是唯一一個 ii 開頭的序列,接下來需要找 i+1i+1 開頭的序列,所以窗口的左邊界要向右移動
對於第二個問題,我們可以稍微簡單地證明一下:
在這裏插入圖片描述
我們一開始要找的是 1 開頭的序列,只要窗口的和小於 target,窗口的右邊界會一直向右移動。假設 1+2+\dots+81+2+⋯+8 小於 target,再加上一個 9 之後, 發現 1+2+\dots+8+91+2+⋯+8+9 又大於 target 了。這說明 1 開頭的序列找不到解。此時滑動窗口的最右元素是 9。

接下來,我們需要找 2 開頭的序列,我們發現,2 + \dots + 8 < 1 + 2 + \dots + 8 < \mathrm{target}2+⋯+8<1+2+⋯+8<target。這說明 2 開頭的序列至少要加到 9。那麼,我們只需要把原先 1~9 的滑動窗口的左邊界向右移動,變成 2~9 的滑動窗口,然後繼續尋找。而右邊界完全不需要向左移動。

以此類推,滑動窗口的左右邊界都不需要向左移動,所以這道題用滑動窗口一定可以得到所有的解。時間複雜度是 O(n)O(n)。

注:這道題當前可以用等差數列的求和公式來計算滑動窗口的和。不過我這裏沒有使用求和公式,是爲了展示更通用的解題思路。實際上,把題目中的正整數序列換成任意的遞增整數序列,這個方法都可以解。

鏈接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
來源:力扣(LeetCode)

class Solution {
public:
    vector<vector<int>> FindContinuousSequence(int target) {
    int i = 1; // 滑動窗口的左邊界
    int j = 1; // 滑動窗口的右邊界
    int sum = 0; // 滑動窗口中數字的和
    vector<vector<int>> res;

    while (i <= target / 2) {
        if (sum < target) { // 右邊界向右移動
            sum += j;
            j++;
        } 
        else if (sum > target) { // 左邊界向右移動
            sum -= i;
            i++;
        } 
        else { // 記錄結果
            vector<int> arr;
            for (int k = i; k < j; k++) {
                arr.push_back(k);
            }
            res.push_back(arr);
            sum -= i; // 左邊界向右移動
            i++;
        }
    }

    return res;
    }
};

三、滑動窗口

在這裏插入圖片描述
滑動窗口的重要性質是:窗口的左邊界和右邊界永遠只能向右移動,而不能向左移動。這是爲了保證滑動窗口的時間複雜度是 O(n)。如果左右邊界向左移動的話,這叫做“回溯”,算法的時間複雜度就可能不止O(n)。

在這道題中,我們關注的是滑動窗口中所有數的和。當滑動窗口的右邊界向右移動時,也就是 j = j + 1,窗口中多了一個數字 j,窗口的和也就要加上 j。當滑動窗口的左邊界向右移動時,也就是 i = i + 1,窗口中少了一個數字 i,窗口的和也就要減去 i。滑動窗口只有 右邊界向右移動(擴大窗口) 和 左邊界向右移動(縮小窗口) 兩個操作,所以實際上非常簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章