【问题标题】:Divide elements of a sorted array into least number of groups such that difference between the elements of the new array is less than or equal to 1将排序数组的元素分成最少的组,使得新数组的元素之间的差异小于或等于 1
【发布时间】:2019-11-16 19:59:51
【问题描述】:

如何将数组中的元素分成最少数量的数组,使得每个形成的数组的元素值之差不超过1?

假设我们有一个数组:[4, 6, 8, 9, 10, 11, 14, 16, 17]。 数组元素已排序。

我想将数组的元素分成最少数量的数组,以使结果数组中的每个元素的差异不超过 1。

在这种情况下,分组将是:[4], [6], [8, 9, 10, 11], [14], [16, 17]。所以总共会有 5 个组。

我怎样才能为此编写程序?或者你也可以推荐算法。

我尝试了天真的方法: 获取数组的连续元素之间的差异,如果差异小于(或等于)1,我将这些元素添加到新向量中。然而,这种方法非常未优化,并且直接无法显示大量输入的任何结果。

实际代码实现:

#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;

int main() {

    int num = 0, buff = 0, min_groups = 1;    // min_groups should start from 1 to take into account the grouping of the starting array element(s)
    cout << "Enter the number of elements in the array: " << endl;
    cin >> num;

    vector<int> ungrouped;
    cout << "Please enter the elements of the array: " << endl;
    for (int i = 0; i < num; i++)
    {
        cin >> buff;
        ungrouped.push_back(buff);
    }

    for (int i = 1; i < ungrouped.size(); i++)
    {
        if ((ungrouped[i] - ungrouped[i - 1]) > 1)
        {
            min_groups++;
        }
    }

    cout << "The elements of entered vector can be split into " << min_groups << " groups." << endl;

    return 0;
}

【问题讨论】:

  • “但是,这种方法非常未经优化,直接无法针对大量输入进行编译。”。这没有意义,任何大小都应该编译。请在代码中展示您的尝试,这不是免费的代码编写服务。
  • 另外,你的幼稚方法应该是 O(n) ,这是最优的。只需遍历差异,将元素添加到数组中,当遇到 >2 时,创建一个新数组并开始向其添加追加内容。
  • 是什么让你说它未优化?
  • 这个论点值得怀疑。
  • 涉及这种过程的大多数算法都属于“未优化”的一般类别谁说的?

标签: c++ algorithm c++14


【解决方案1】:

受 Faruk 回答的启发,如果将值限制为不同的整数,则可能存在亚线性方法。

确实,如果两个值的差值等于它们的索引差值,就保证它们属于同一个组,不需要看中间值。

您必须按顺序组织对数组的递归遍历。在细分子数组之前,您将第一个和最后一个元素的索引差异与值的差异进行比较,并且仅在不匹配的情况下进行细分。当您按顺序工作时,这将允许您按连续顺序发出组的片段,并检测到间隙。必须注意合并组的各个部分。

最坏的情况将保持线性,因为递归遍历可以退化为线性遍历(但不会比这更糟)。最好的情况可以更好。特别是,如果数组包含一个组,它将在 O(1) 时间内找到。如果我是对的,对于 2^n 和 2^(n+1) 之间的每一组长度,您将节省至少 2^(n-1) 次测试。 (事实上​​,应该可以估计一个输出敏感的复杂度,等于数组长度减去所有组长度的一部分,或类似的。)


或者,您可以通过指数搜索以非递归方式工作:从一组开始,您从一个单位步长开始,每次加倍步长,直到检测到间隙(值的差异太大了);然后你用一个单位步骤重新开始。同样,对于大型组,您将跳过大量元素。反正最好的情况只能是O(Log(N))。

【讨论】:

    【解决方案2】:

    我建议将子集编码为如下定义的偏移数组:

    • 集合 #i 的元素是为索引 j 定义的,这样 offset[i]
    • 子集的个数是offset.size() - 1

    这只需要一次内存分配。

    这是一个完整的实现:

    #include <cassert>
    #include <iostream>
    #include <vector>
    
    std::vector<std::size_t> split(const std::vector<int>& to_split, const int max_dist = 1)
    {
      const std::size_t to_split_size = to_split.size();
      std::vector<std::size_t> offset(to_split_size + 1);
      offset[0] = 0;
      size_t offset_idx = 1;
      for (std::size_t i = 1; i < to_split_size; i++)
      {
        const int dist = to_split[i] - to_split[i - 1];
        assert(dist >= 0);  // we assumed sorted input
        if (dist > max_dist)
        {
          offset[offset_idx] = i;
          ++offset_idx;
        }
      }
      offset[offset_idx] = to_split_size;
      offset.resize(offset_idx + 1);
      return offset;
    }
    
    void print_partition(const std::vector<int>& to_split, const std::vector<std::size_t>& offset)
    {
      const std::size_t offset_size = offset.size();
      std::cout << "\nwe found " << offset_size-1 << " sets";
      for (std::size_t i = 0; i + 1 < offset_size; i++)
      {
        std::cout << "\n";
        for (std::size_t j = offset[i]; j < offset[i + 1]; j++)
        {
          std::cout << to_split[j] << " ";
        }
      }
    }
    
    int main()
    {
      std::vector<int> to_split{4, 6, 8, 9, 10, 11, 14, 16, 17};
      std::vector<std::size_t> offset = split(to_split);
      print_partition(to_split, offset);
    }
    

    哪个打印:

    we found 5 sets
    4 
    6 
    8 9 10 11 
    14 
    16 17 
    

    【讨论】:

    • @YvesDaoust 这是一种经典技术,例如用于存储备用数组...没什么难的,请参阅帖子末尾的说明
    • @YvesDaoust 你不明白什么?
    • 您在我的评论之后添加了底部。我不认为抛出没有解释的代码是一个有效的答案。
    • @YvesDaoust 现在修复了吗?
    • 是的,我更喜欢这个,它提供了我正在寻找的信息。 (尽管下一步是丢弃代码 ;-)
    【解决方案3】:

    遍历数组。每当 2 个连续元素之间的差值大于 1 时,将答案变量加 1。

    `

    int getPartitionNumber(int arr[]) {
        //let n be the size of the array;
        int result = 1;
        for(int i=1; i<n; i++) {
            if(arr[i]-arr[i-1] > 1) result++;
        }
        return result;
    }
    

    `

    【讨论】:

      【解决方案4】:

      而且因为看到更多想法并选择最适合您的想法总是很高兴,这里是直接的 6 行解决方案。是的,它也是 O(n)。但我不确定其他方法的开销是否会使其更快。

      请看:

      #include <iostream>
      #include <string>
      #include <algorithm>
      #include <vector>
      #include <iterator>
      
      using Data = std::vector<int>;
      using Partition = std::vector<Data>;
      
      Data testData{ 4, 6, 8, 9, 10, 11, 14, 16, 17 };
      
      int main(void)
      {
          // This is the resulting vector of vectors with the partitions
          std::vector<std::vector<int>> partition{};  
          // Iterating over source values
          for (Data::iterator i = testData.begin(); i != testData.end(); ++i) {
              // Check,if we need to add a new partition
              // Either, at the beginning or if diff > 1
              // No underflow, becuase of boolean shortcut evaluation
              if ((i == testData.begin()) || ((*i) - (*(i-1)) > 1)) {
                  // Create a new partition
                  partition.emplace_back(Data());
              }
              // And, store the value in the current partition
              partition.back().push_back(*i);
          }
      
          // Debug output:  Copy all data to std::cout
          std::for_each(partition.begin(), partition.end(), [](const Data& d) {std::copy(d.begin(), d.end(), std::ostream_iterator<int>(std::cout, " ")); std::cout << '\n'; });
      
          return 0;
      }
      

      也许这可能是一个解决方案。 . .

      【讨论】:

      • std::prev(partition.end())-&gt;push_back(*i); -- 应该是简单的partition.back().push_back(*i);` -- 更容易理解。
      • @PaulMcKenzie:绝对正确。我当然会更新。谢谢
      【解决方案5】:

      你怎么说你的方法没有优化?如果您是正确的,那么根据您的方法,它需要O(n) 时间复杂度。

      但是您可以在此处使用二进制搜索,这可以在平均情况下进行优化。但在最坏的情况下,这种二分搜索可能会花费超过O(n) 的时间复杂度。

      这里有一个提示, 由于数组已排序,因此您将选择相差最多 1 的位置。

      二分搜索可以很简单地做到这一点。

      int arr[] = [4, 6, 8, 9, 10, 11, 14, 16, 17];
      
      int st = 0, ed = n-1; // n = size of the array.
      int partitions = 0;
      while(st <= ed) {
          int low = st, high = n-1;
          int pos = low;
          while(low <= high) {
              int mid = (low + high)/2;
              if((arr[mid] - arr[st]) <= 1) {
                  pos = mid;
                  low = mid + 1;
              } else {
                  high = mid - 1;
              }
          }
          partitions++;
          st = pos + 1;
      }
      cout<< partitions <<endl;
      

      在一般情况下,它优于O(n)。但在最坏的情况下(答案将等于 n)它需要 O(nlog(n)) 时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-04
        相关资源
        最近更新 更多