用于成对比较和跟踪最大/最长序列的 STL 算法

Posted

技术标签:

【中文标题】用于成对比较和跟踪最大/最长序列的 STL 算法【英文标题】:STL algorithms for pairwise comparison and tracking max/longest sequence 【发布时间】:2020-09-03 17:57:07 【问题描述】:

考虑这个相当简单的算法问题:

给定一个(未排序的)数字数组,找出最长的相邻数字序列的长度。例如,如果我们有1,4,2,3,5,我们期望结果为 3,因为 2,3,5 给出了相邻/连续元素的最长递增序列。请注意,对于非空数组,例如 4,3,2,1,最小结果将为 1。

这行得通:

#include <algorithm>
#include <iostream>
#include <vector>

template <typename T, typename S>
T max_adjacent_length(const std::vector<S> &nums) 
  if (nums.size() == 0) 
    return 0;
  
  T maxLength = 1;
  T currLength = 1;

  for (size_t i = 0; i < nums.size() - 1; i++) 
    if (nums[i + 1] > nums[i]) 
      currLength++;
     else 
      currLength = 1;
    
    maxLength = std::max(maxLength, currLength);
  
  return maxLength;


int main() 
  std::vector<double> nums = 1.2, 4.5, 3.1, 2.7, 5.3;
  std::vector<int> ints = 4, 3, 2, 1;
  std::cout << max_adjacent_length<int, double>(nums) << "\n"; // 2
  std::cout << max_adjacent_length<int, int>(ints) << "\n"; // 1
  return 0;

作为我自己的练习,我想知道是否有 STL 算法可以达到相同的效果,从而(理想情况下)避免我拥有的原始 for 循环。这样做的动机是更多地了解 STL 算法,并练习使用抽象算法来使我的代码更加通用和可重用。

这是我的想法,但它们并没有完全实现我想要的。

std::adjacent_find 实现了成对比较,可用于查找非递增对的索引,但不容易促进保持当前和最大长度并比较两者的能力。可以将这些状态变量作为我的谓词函数的一部分,但这似乎有点错误,因为理想情况下您希望您的谓词函数没有任何副作用,对吧? std::adjacent_difference 很有趣。可以使用它来构建相邻数字之间差异的向量。然后,从第二个元素开始,根据差异是正还是负,我们可以再次跟踪看到的连续正差异的最大数量。这实际上非常接近于实现我们想要的。请参见下面的示例代码:
#include <numeric>
#include <vector>

template <typename T, typename S> T max_adjacent_length(std::vector<S> &nums) 
  if (nums.size() == 0) 
    return 0;
  
  std::adjacent_difference(nums.begin(), nums.end(), nums.begin());
  nums.erase(std::begin(nums)); // keep only differences
  T maxLength = 1, currLength = 1;
  for (auto n : nums) 
    currLength = n > 0 ? (currLength + 1) : 1;
    maxLength = std::max(maxLength, currLength);
  
  return maxLength;

这里的问题是,如果我们想计算差异,我们会丢失nums 的常量,或者我们必须牺牲空间并创建nums 的副本,这是一个禁忌,因为原始解决方案是O(1) 空间复杂度。

是否有我忽略的想法/解决方案以简洁易读的方式实现我想要的?

【问题讨论】:

你真的认为有必要将结果类型模板化吗?例如std::vector::size_type 就足够了。 不,我从size_t 开始,然后再玩模板,以防人们可能知道最大长度可能在预期范围内。无论哪种方式,它与我的问题都不是特别相关,所以我保持原样。 我认为更通用和更有用的是一个函数,它接受一对迭代器和一个比较器函子并返回一对最长序列的迭代器。 【参考方案1】:

在您的代码 sn-ps 中,您都在遍历一个范围(在第一个版本中,使用基于索引的循环,在第二个版本中使用 range-for 循环)。如果您想使用标准算法,这不是您应该编写的那种代码,标准算法与范围内的迭代器一起工作。如果您开始考虑成对的迭代器,而不是将范围视为元素的集合,那么选择正确的算法会变得更容易。

对于这个问题,这里有一个合理的写这段代码的方法:

auto max_adjacent_length = [](auto const & v)

    long max = 0;
    
    auto begin = v.begin();
    
    while (begin != v.end()) 
        auto next = std::is_sorted_until(begin, v.end());
        max = std::max(std::distance(begin, next), max);
        begin = next;              
    
    
    return max;
;

这是demo。

请注意,在选择合理的算法方面,您已经走上了正确的道路。这也可以通过adjacent_find 解决,只需多做一点工作。

【讨论】:

“如果您考虑成对的迭代器,那么选择正确的算法会变得更容易......”只需检查我是否理解您的意思 - 您在这里提到的一对是开始和结束迭代器向量,而不是我明确迭代元素的循环? 是的,但不仅仅是范围的开始和结束。请注意,开始不断变化,distance 使用不同的结束迭代器。它是真正代表子范围的任何对迭代器。

以上是关于用于成对比较和跟踪最大/最长序列的 STL 算法的主要内容,如果未能解决你的问题,请参考以下文章

关于用动态规划法求最大公共子序列的问题

O(nlogn)最长递增子序列算法,如何输出所求子序列?

算法 LC 动态规划 - 最大递增子序列

01--STL算法(算法基础)

将一个序列调整为单调序列的最小代价问题

动态规划之最大递增子序列