一、和爲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。滑動窗口只有 右邊界向右移動(擴大窗口) 和 左邊界向右移動(縮小窗口) 兩個操作,所以實際上非常簡單。