今日頭條筆試題:“最小數字*區間和”的最大值【單調棧】


題目描述:

  給定一段數組,求每個區間的最小值乘這段區間的和,輸出每個區間得到的最大值。

  樣例輸入:[1 2 6],可能有以下幾種情況:

  •   [1]:結果為1*1=1;
  •   [2]:結果為2*2=4;
  •   [6]:結果為6*6=36;
  •   [1,2]:結果為1*(1+2)=3;
  •   [2,6]:結果為2*(2+6)=16;
  •   [1,2,6]:結果為1*(1+2+6)=9;

  最大值為36,輸出36即可。

解法:

  利用單調棧,從前向后和從后向前分別遍歷一遍數組,得到每個元素的左邊界和右邊界(邊界的定義即為碰到比該元素更小的即停止),最后用每個元素乘以每個元素對應的區間和,找出最大值即可。這里有一個技巧,為了防止每個元素重復計算一段區間和,可以提前開一個遞增序列,用於保存某元素之前的各項和(含該元素),求取一段區間和的時候用右邊界的遞增和減去左邊界減一的遞增和即可。

復雜度:

  時間復雜度為O(n);

  空間復雜度為O(n);

代碼(版本一:遍歷兩次分別得到左右邊界):

 1 #include <iostream>
 2 #include <vector>
 3 #include <stack>
 4 using namespace std;
 5 
 6 struct Node{
 7     int val;
 8     int start;
 9     int end;
10 };
11 
12 int main()
13 {
14     std::ios::sync_with_stdio(false);
15     int n;
16     while(cin>>n){
17 
18         /********************************輸入******************************/
19         vector<Node> v(n);
20         vector<long long> inc(n,0);//遞增序列,方便計算一段區間的累加和
21         inc[-1]=0;//為了計算第一個數字的前序(應對v[i].start-1為-1的情況)
22         for(int i=0;i<n;++i){
23             cin>>v[i].val;
24             inc[i]= i==0 ? v[i].val : inc[i-1]+v[i].val;
25             v[i].start=i;
26             v[i].end=i;
27         }
28         /*******************處理得到每個元素的左邊界(正向遍歷)*****************/
29         stack<int> s;
30         int i=0;
31         while(i<(int)v.size()){
32             if(s.empty() || v[i].val > v[s.top()].val){
33                 s.push(i);
34                 ++i;
35             }
36             else{
37                 //插入的值更小的時候,將其start置為將要彈出的元素的start
38                 v[i].start=v[s.top()].start;
39                 s.pop();
40             }
41         }
42         /*******************處理得到每個元素的右邊界(反向遍歷)*****************/
43         while(!s.empty())//清空s,准備反向遍歷
44             s.pop();
45         i=n-1;
46         while(i>=0){
47             if(s.empty() || v[i].val > v[s.top()].val){
48                 s.push(i);
49                 --i;
50             }
51             else{
52                 //插入的值更小的時候,將其start置為將要彈出的元素的start
53                 v[i].end=v[s.top()].end;
54                 s.pop();
55             }
56         }
57 //        for(int i=0;i<n;++i){
58 //            cout<<"("<<v[i].val<<","<<v[i].start<<","<<v[i].end<<")"<<endl;
59 //        }
60 //        for(int i=0;i<n;++i){
61 //            cout<<inc[i]<<endl;
62 //        }
63         /******************得到每個元素的結果,返回最大值即可*****************/
64         long long result=0;
65         int index_start=0,index_end=0;//得到最大值的區間(這里是從0開始計數的)
66         for(int i=0;i<n;++i){
67             long long cur_result=v[i].val*(inc[v[i].end]-inc[v[i].start-1]);
68             if(cur_result>result){
69                 result=cur_result;
70                 index_start=v[i].start;
71                 index_end=v[i].end;
72             }
73         }
74         cout<<result<<"\n";
75         cout<<index_start+1<<" "<<index_end+1<<"\n";//輸出區間標記的時候從1開始計數,更直觀 76     }
77     return 0;
78 }

 

代碼(版本二:遍歷一次即可得到左右邊界):

 1 #include <iostream>
 2 #include <vector>
 3 #include <stack>
 4 using namespace std;
 5 
 6 struct Node{
 7     int val;
 8     int start;
 9     int end;
10 };
11 
12 int main()
13 {
14 //    std::ios::sync_with_stdio(false);
15     int n;
16     while(cin>>n){
17 
18         /********************************輸入******************************/
19         vector<Node> v(n+1);
20         vector<long long> inc(n+1,0);//遞增序列,方便計算一段區間的累加和
21         inc[-1]=0;//為了計算第一個數字的前序(應對v[i].start-1為-1的情況)
22         for(int i=0;i<n;++i){
23             cin>>v[i].val;
24             inc[i]= i==0 ? v[i].val : inc[i-1]+v[i].val;
25             v[i].start=i;
26             v[i].end=i;
27         }
28         v[n].val=0;//注意需要在末尾加入最小值,最終讓棧中的元素全彈出來,更新計算 29         v[n].start=n;
30         v[n].end=n;
31         /**************處理得到每個元素的左右邊界(一次遍歷即可)***************/
32         stack<int> s;
33         int i=0;
34         while(i<(int)v.size()){
35             if(s.empty() || v[i].val > v[s.top()].val){
36                 //元素插入的時候可以得到其start
37                 v[i].start= s.empty() ? v[i].start : s.top()+1;
38                 s.push(i);
39                 ++i;
40             }
41             else{
42                 //元素彈出的時候可以知道其end,同時更新當前待入棧元素的start
43                 v[s.top()].end=i-1;
44                 v[i].start=v[s.top()].start;
45                 s.pop();
46             }
47         }
48 //        for(int i=0;i<n;++i){
49 //            cout<<"("<<v[i].val<<","<<v[i].start<<","<<v[i].end<<")"<<endl;
50 //        }
51 //        for(int i=0;i<n;++i){
52 //            cout<<inc[i]<<endl;
53 //        }
54         /******************得到每個元素的結果,返回最大值即可*****************/
55         long long result=0;
56         int index_start=0,index_end=0;//得到最大值的區間(這里是從0開始計數的)
57         for(int i=0;i<n;++i){
58             long long cur_result=v[i].val*(inc[v[i].end]-inc[v[i].start-1]);
59             if(cur_result>result){
60                 result=cur_result;
61                 index_start=v[i].start;
62                 index_end=v[i].end;
63             }
64         }
65         cout<<result<<"\n";
66         cout<<index_start+1<<" "<<index_end+1<<"\n";
67     }
68     return 0;
69 }

 

這段程序要注意幾點:

  • 結果long long類型,因為int可能容納不下;
  • inc數組要加個-1索引,為了計算第一個值對應的結果;
  • 正向遍歷和反向遍歷算出兩個邊界;如果只用一次也可以,但是時間復雜度就變成O(n^2)了(可以在push元素進棧的時候更新棧中的每個元素的end);
  • 代碼輸出了最大結果的區間標記,可以去掉;

總之,活學活用才是硬道理啊,一定要善於將知識轉化為自己的本領。

另外,要讀清楚題意,我做的時候直接把數組排了個序,以為可以亂序,結果直接從后向前遍歷了,都是淚。。。

除此,這道題可以參見POJ2796,由於ACM系統沒有針對STL的O2,我這段代碼放進去是超時的,不想改了,想改的可以改成數組。

歡迎交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM