38. 最长连续有序子数组

一. 问题

给定含 n 个整数的数组,找出连续的有序最长子数组。算法的运行时间是多少?

二. 思路

假设有一个序列, data = (1, 2, 3, 4, 2, 1, 3, 0),显然,从元素 1 到元素 4 是升序排列的,并且有 4 个元素。我们为了简单起见,仅查找升序排列的子序列。

要获得子序列的情况,我们需要三个信息:(1)子序列开始下标(2)子序列结尾下标(3)子序列长度。并且还需要三个辅助变量:(1)中间结果子序列开始下标(2)中间结果子序列结束下标(3)中间结果子序列长度。

我们从首元素开始,依次向后比较,同时用上面提到的辅助变量记住需要的下标,如果满足条件,则一直向后推;如果不满足条件,则将这些下标记录在结果中。再重置辅助变量,直到比较完所有元素。因为有升序这一条件,我们便可以保证操作不用回退,即一路推下去。

三. 代码实现

 1 vector<int> max_ordered_sub_sequence(const vector<int>& data) {
 2     vector<int> result;
 3     int start_index = 0, end_index = 0, sequence_len = 1;
 4     int temp_len = 1;
 5 
 6     for (int temp_start = 0, temp_end = 1; temp_end < data.size(); ++temp_end) {
 7         int k = temp_end - 1;
 8         if (data[k] <= data[temp_end]) {
 9             ++temp_len;
10             continue;
11         }
12 
13         if (temp_len > sequence_len) {
14             sequence_len = temp_len;
15             start_index = temp_start;
16             end_index = temp_end -1 ;
17             temp_start = temp_end;
18             temp_len = 1;
19         } else {
20             temp_start = temp_end;
21             temp_len = 1;
22         }
23     }
24 
25     result.push_back(sequence_len);
26     result.push_back(start_index);
27     result.push_back(end_index);
28 
29     return result;
30 }

(1)代码分析

第 2 到 4 行,我们进行变量初始化。只有一个元素的时候,前后下标一致,此时子序列中含有 1 个元素。第 8 到 11 行,元素如果升序,则向下推。第 13 到 18 行,算法执行到这里,说明元素已经出现了逆序,并且当前子序列比原来的子序列更长,那么我们要把辅助变量记住,然后进行重置。第 19 到 22 行说明,虽然出现了逆序,但是当前子序列的长度,并没有超过我们已经记录的子序列,此时只需要重置辅助变量。第 25 到 27 行,执行到这里,说明算法执行完毕,将结果放到 vector 中返回即可。

(2)算法正确性证明

我们一开始考虑,若序列只包含一个元素,那么最长子序列就是它本身。如果序列长度大于 1,那么进行比较。我们记录的下标只有在需要的时候才会进行更新,也就是逆序的时候。而按照升序排列,当前子序列最后一个元素,一定是比该序列前面元素大(可以相等),走到现在产生了逆序,说明这个新元素比子序列所有元素都要大,此时不用回退,而是可以直接将辅助变量的开始下标,拉到结束位置。如果到达末尾,那么结果必将是所求的最长子序列。算法只遍历一遍序列,显然,时间复杂度是 O(n)。

(3)额外优化

我们再更进一步,仔细思考一个小问题:假如前一个子序列是最长的,它的长度过了整个序列的一半。此时它突然遇到一个逆序。这个时候,我们可以直接返回结果,因为后一半元素,就算也是有序的,长度也肯定不会大于前面的子序列。

posted @ 2020-08-26 03:31  Hello_Nolan  阅读(546)  评论(0编辑  收藏  举报