NJU-高級算法-子數組的取值範圍

Description

給定數組arr和整數num,求arr的連續子數組中滿足:其最大值減去最小值的結果大於num的個數。請實現一個時間複雜度爲O(length(arr))的算法。

Input

輸入第一行爲測試用例個數。每一個用例有若干行,第一行爲數組,每一個數用空格隔開,第二行爲num。

Output

輸出一個值。

Sample Input 1 

1
3 6 4 3 2
2

Sample Output 1

6

 

思路

1.暴力

代碼:

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <limits.h>
#include <algorithm>
using namespace std;

int main()
{
	int t;
	cin >> t;
	for (int turn = 0; turn < t; turn++) {
		string str;
		stringstream ss;
		int sin;
		vector<int> a;

		cin.ignore();
		getline(cin, str);
		ss << str;
		while (ss >> sin) {
			a.push_back(sin);
		}
		int aLen = a.size();

		int num;
		cin >> num;

		int res = 0;

 		//暴力法
		for (int i = 0; i < aLen; i++) {
			int maxNum = INT_MIN, minNum = INT_MAX;
			for (int j = i; j < aLen; j++) {
				minNum = min(minNum, a[j]);
				maxNum = max(maxNum, a[j]);
				res += maxNum - minNum > num ? 1 : 0;
			}
		}

		if (turn + 1 == t) printf("%d", res);
		else printf("%d\n", res);
	}
    return 0;
}

2.滑動窗口法

首先,對於滿足最大值減去最小值的結果大於num的個數的連續子數組,有這樣一個性質,即包含該子數組的所有其他子數組一定也符合條件,因爲不管包含該子數組的數組再如何擴張,其min值一定只會減小,max值一定只會增大,二者差值一定是隻會增大。但這條性質的前提是,擴張的數組想要符合條件至少要包含最初符合條件的子數組的全部元素。

反之,另一個性質是,若一個子數組不符合條件,那麼在該子數組範圍內部的子數組也不會符合條件,類似第一個性質,這些內部子數組min和max差值一定只會減小,更不可能符合條件。

有了這兩條性質就可以採用滑動窗口來解決本題,用兩個雙端隊列分別存放min值和max值。

設置i, j兩個遍歷指針,初始都指向第一個元素,所以兩個隊列初始值都是第一個元素。

Step1. 首先令j向右移動,把qmin隊尾所有大於a[j]值的元素全部彈出並在其尾部壓入a[j],或把qmax隊尾所有小於a[j]的元素全部彈出並在其尾部壓入a[j],則用當前j指向元素替換之,並判斷此時qmin和qmax隊列的隊首元素差值是否大於num。若當j便利至某元素符合上述條件時,那麼從j所指向元素後一個元素開始一直到最後一個元素與當前i到j之間的子數組組成的所有子數組都符合條件,共有a.size()-j+1個,此時就跳出j的遍歷循環,j固定不動。

Step2. 接下來,先對qmin和qmax兩個隊列進行判斷,若兩個隊列都不爲空,且兩隊首值的差大於num,說明上一輪循環得出的符合條件的子數組的最大值或最小值都還在隊列中,比如測試樣例數組爲4, 6, 4, 3, 2,當i==0時,找到第一個符合條件的子數組是4, 6, 4, 3,最小值3和最大值6由於不是a[0]指向的元素因此沒有彈出,當i==2時,3和6仍在原隊列中且符合條件,那麼[6, 4, 3]及[6, 4, 3, 2]也是符合結果的子數組,這裏做一次res+=a.size()-j+1,防止遍歷中這兩個符合條件的子數組被跳過。

Step3. 然後對qmin和qmax隊列的元素做判斷,i指向的元素是否是qmin或qmax隊首元素,是則要彈出,之後再重複Step1.

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <limits.h>
#include <algorithm>
#include <deque>
using namespace std;

int main()
{
	int t;
	cin >> t;
	for (int turn = 0; turn < t; turn++) {
		string str;
		stringstream ss;
		int sin;
		vector<int> a;

		cin.ignore();
		getline(cin, str);
		ss << str;
		while (ss >> sin) {
			a.push_back(sin);
		}
		int aLen = a.size();

		int num;
		cin >> num;

		int res = 0;

		//滑動窗口法
		deque<int> qmin;
		deque<int> qmax;
		int i = 0, j = 0;
		while(i < aLen) {
			if (!qmin.empty() && !qmax.empty() && (i < j) && a[qmax.front()] - a[qmin.front()] > num) {
				res += aLen - j + 1;
			}
			else {
				while (j < aLen) {
					while (!qmax.empty() && a[j] >= a[qmax.back()]) {
						qmax.pop_back();
					}
					qmax.push_back(j);
					while (!qmin.empty() && a[j] <= a[qmin.back()]) {
						qmin.pop_back();
					}
					qmin.push_back(j);
					j++;
					//若當前遍歷到的子數組中最大最小值差值已經大於num,那麼j再向右遍歷的所有包含這個子數組的所有其他子數組也符合條件,跳出當前循環
					//min只會更小,max只會更大,二者差值只會更大
					if (a[qmax.front()] - a[qmin.front()] > num) {
						//j從當前位置一直遍歷到數組最後一個元素所形成的連續子數組都符合條件
						//此時j已增長1,但實際遍歷過的是j-1
						//[i,...,j-1], [i,...,j], ... , [i,...,aLen-1]
						res += aLen - j + 1;
						break;
					}
				}
			}
			//若當前隊列隊首元素爲i,則將其彈出,因爲其不會再出現在之後的遍歷中
			if (qmin.front() == i) qmin.pop_front();
			if (qmax.front() == i) qmax.pop_front();
			i++;
		}
		if (turn + 1 == t) printf("%d", res);
		else printf("%d\n", res);
	}
    return 0;
}

 

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