【问题标题】:Find the number of repeated subarray in an array查找数组中重复子数组的数量
【发布时间】:2013-07-14 10:39:03
【问题描述】:

有一个从0-n 索引的数组(即size=n)包含来自0-m 的元素,其中m < n(假设m 比n 小100 或1000 倍,即m 远小于n),所以很多的元素或子数组必须重复,我们必须找到大小为 1 或大于 1 的重复子数组的数量。例如,考虑一个数组
1 2 4 1 2 4 1 5 6 3 5,这里
1 是重复 2 次
2 重复 1 次
4 重复 1 次
5 重复 1 次
1 2 重复 1 次
2 4 是重复 1 次
4 1 重复 1 次
1 2 4 重复 1 次
2 4 1 重复 1 次
1 2 4 1 重复 1 次
所以最终的答案是这些的总和,即 11
任何人都可以建议一些数据结构或有效的算法来解决这个问题。我正在考虑散列 m 并注意它出现的索引值但没有正式化它。
假设n,m < 10^5

【问题讨论】:

  • 我可以给你一个“模式”:Floor[12412415635 / (10 ^ (10 - n))] mod 10。它有效,但这对你来说是一个可以接受的模式吗?可能不是。您需要指定究竟您认为什么是“模式”。除非你能给出一个规范,否则你无法实现它。
  • @phant0m 我已经编辑了这个问题。希望现在很清楚。我的意思是计算重复的子数组
  • 您可能可以为此使用后缀数组,但我不确定算法究竟是什么。
  • "所以最终的答案是这些的总和,即 8" 为什么是 8? 241 也重复一次。所以这将是 9 的总和。还是我错了?
  • @FabianBigler 哎呀我错过了 :)

标签: arrays algorithm


【解决方案1】:
  1. 创建一个以整数作为键,以整数作为值的可扩展列表(例如链表)的哈希。

  2. 通读您的输入列表。将每个被扫描的输入元素视为一个键;将以下元素的索引附加到关联的值列表中。

  3. 现在,您重复的 1 元素计数为 n - |keys|

  4. 接下来,检查您的密钥。对于每个键,将原始列表的索引元素视为新的输入列表。创建一个新的哈希并按照步骤 1 继续。请注意,当我们存储索引时,我们会继续使用原始列表的索引。

示例:1 2 4 1 2 4 1 5 6 3 5(假设我们是零索引)。这里,n=11。

  • 1 -> [1, 4, 7]
  • 2 -> [2, 5]
  • 3 -> [10]
  • 4 -> [3, 6]
  • 5 -> [8, 无]
  • 6 -> [9]

重复 1-elt 的数量是 11 - 6 = 5。

现在,我们来看看钥匙。我们从 1 开始。索引列表是 ​​[1, 4, 7],它对应于元素 [2, 2, 5]。

  • 2 -> [2, 5]
  • 5 -> [8]

这会选择 3 - 2 = 1 个重复的 2 元素列表。

等等……

运行时间:输入+输出线性

【讨论】:

  • 这是一种创新方法。对于非 1 元素,如果索引不相邻,则它们不是模式的一部分!非常好!
【解决方案2】:

我使用了一种基于后缀树的方法,来自Skienna's book on the design of algorithms。后缀树是trie tree 的一种特殊类型,您可以将给定字符串的每个后缀插入到特里树中。 Trie 树是一种在单个树中存储多个单词的方式:根是空字符串,每条边添加一个字符。我使用了一个非常简单的实现作为概念证明,如下所示。

#include <iostream>
#include <string>
using std::string;
using std::cout;

class Node
{
    Node* m_descendants[10];
    int m_count;
public:
    Node()
    {
        for(int i=0; i<10; ++i)  { m_descendants[i] = nullptr; }
        m_count = 0;
    }
    void Add(const char* str) {
        // Increment number of times we've reached this node
        m_count++;

        const int firstDigit = str[0] - '0';
        if (!(0 <= firstDigit && firstDigit <= 9)) return; // recursion base case 

        // Access descendant node correponding to current digit
        Node*& descendant = m_descendants[firstDigit];
        if (descendant == 0) { // create descendant if necessary
            descendant = new Node;
        }

        // Recurse on rest of string
        descendant->Add(str +1);
    }
    void Print(const string& str, int countAdj=0) const // debug function
    {
        for(int nextDigit=0; nextDigit<10; ++nextDigit) {
            const Node* node = m_descendants[nextDigit];
            if (node) {
                const int adjustedCount = node->Count() - countAdj;
                if (adjustedCount > 0) {
                    char c = '0' + nextDigit;
                    string strWithC = str;
                    strWithC += c;
                    cout << strWithC << ": " << adjustedCount << "\n";
                    node->Print(strWithC, countAdj);
                }
            }
        }
    }
    int SumRepeated() const
    {
        int sumRepeated = 0;
        for(int nextDigit=0; nextDigit<10; ++nextDigit) {
            const Node* node = m_descendants[nextDigit];
            if (node) {
                const int repeatCount = node->Count() -1;
                if (repeatCount > 0) {
                    sumRepeated += repeatCount;
                    sumRepeated += node->SumRepeated();
                }
            }
        }
        return sumRepeated;
    }
    int Count() const { return m_count; }
};

int main(int argc, const char* argv[])
{
    // Construct
    Node* const root = new Node;
    for(const char* str = "12412415635"; *str; ++str) {
        root->Add(str);
    }

    // Print
    root->Print(string(), 1);

    cout << "Sum repeated is " << root->SumRepeated() << "\n";

    return 0; // (nodes are leaked)
}

输出

1: 2
12: 1
124: 1
1241: 1
2: 1
24: 1
241: 1
4: 1
41: 1
5: 1
Sum repeated is 11

注意这里还有一个额外的重复子字符串,即 1241。

正如我所说,这是概念证明,因此,例如,您希望使用字典而不是数组来存储后代,并使用更大的m。此外,即使就整体算法而言,这种实现也不是最优的:它在时间和空间上都是 O(n^2)。您可以通过折叠没有分支的路径来优化以获得 O(n) 空间,并使用巧妙的构造算法来获得 O(n) 时间。此外,正如其他答案之一所指出的那样,您不需要处理最多为原始长度一半的子字符串。

【讨论】:

    【解决方案3】:

    这是 JavaScript 中的一些内容:(输出或多或少是即时的,我认为 JavaScript 是最慢的之一):

    <script>
    
    function f (s, m) {
      var i = 0, n = s.length, c = 0, H = {}, Hi = {}
      while (i < n-1) {
        for (var j=i+1; j<=Math.min (i + 1 + m,n - 1); j++) {
          if (s[i] == s[j]) {
            var i_= i, j_= j, tmp = ''
            while (s[i_] == s[j_] && i_ < j) {
              tmp += String (s[i_])
              i_++
              j_++
            }
            var k = tmp.length - 1
            while (k >= 0) {
              if (!H[tmp]) {
                H[tmp] = 1
                Hi[tmp] = i
                c++
              }
              else if (i == Hi[tmp]) {
                H[tmp]++
                c++
              }
              tmp = tmp.substr(0,k--)
            }
          }
        }
        i++
      }
      var t1 = ''
      for (var i in H) t1 += i + ", "
      return [t1,c]
    }
    
    var a1 = [1,2,4,1,2,4,1,5,6,3,5],
        a2 = []
    
    for (var i=0;i<100000;i++) a2.push(Math.floor(Math.random()*10))
    
    console.log(a1 +"\n"+ f(a1,10))
    console.log(f(a2,10))
    
    </script>
    

    输出:

    1,2,4,1,2,4,1,5,6,3,5
    1, 2, 4, 5, 12, 24, 41, 124, 241, ,10
    
    ["0...6358, 3582, 038, 4304, 2068, 095, 
     9252, 37866, 3786, 7866, 7893, 8701, 0669, ", 771]
    

    【讨论】:

      猜你喜欢
      • 2016-08-15
      • 2013-11-01
      • 2018-11-21
      • 2018-11-11
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2016-01-17
      • 1970-01-01
      相关资源
      最近更新 更多