【问题标题】:Maximum product prefix string最大产品前缀字符串
【发布时间】:2015-08-16 23:30:28
【问题描述】:

以下是来自名为 codility 的编码面试网站的演示问题:

字符串 S 的前缀是 S 的任何前导连续部分。例如,“c”和“cod”是字符串“codility”的前缀。为简单起见,我们要求前缀不为空。

字符串 S 的前缀 P 的乘积是 P 的出现次数乘以 P 的长度。更准确地说,如果前缀 P 由 K 个字符组成,并且 P 在 S 中恰好出现 T 次,则乘积等于 K * T.

例如,S = "abababa" 有以下前缀:

  • “a”,其乘积等于 1 * 4 = 4,
  • “ab”,其乘积等于 2 * 3 = 6,
  • “aba”,其乘积等于 3 * 3 = 9,
  • “abab”,其乘积等于 4 * 2 = 8,
  • “ababa”,其乘积等于 5 * 2 = 10,
  • “ababab”,其乘积等于 6 * 1 = 6,
  • “abababa”,其乘积等于 7 * 1 = 7。

最长前缀与原始字符串相同。目标是选择使产品价值最大化的前缀。在上面的例子中,最大乘积是 10。

以下是我在 Java 中需要 O(N^2) 时间的糟糕解决方案。显然可以在 O(N) 中做到这一点。我在想 Kadanes 算法。但我想不出任何方法可以在每一步编码一些信息,让我找到运行的最大值。谁能想到一个 O(N) 算法呢?

import java.util.HashMap;

class Solution {
    public int solution(String S) {
        int N = S.length();
        if(N<1 || N>300000){
            System.out.println("Invalid length");
            return(-1);
        }
        HashMap<String,Integer> prefixes = new HashMap<String,Integer>();
        for(int i=0; i<N; i++){
            String keystr = "";
            for(int j=i; j>=0; j--) {
                keystr += S.charAt(j);
                if(!prefixes.containsKey(keystr))
                    prefixes.put(keystr,keystr.length());
                else{
                    int newval = prefixes.get(keystr)+keystr.length();
                    if(newval > 1000000000)return 1000000000;
                    prefixes.put(keystr,newval);
                }
            }
        }
        int maax1 = 0;
        for(int val : prefixes.values())
            if(val>maax1)
                maax1 = val;
        return maax1;
    }
}

【问题讨论】:

  • 内循环(j)不应该按升序迭代吗?
  • 我在这里猜测,但我认为,对于后缀的 O(1) 查询,倒置字符串(建立在 O(n) 时间上)的后缀树可能会在所需的范围内解决问题时间限制
  • Little Santi - 如果我们只是想找到最大的产品也没关系。我之所以这样,是因为我认为 o(n) 解决方案可能也会向后看。
  • 您的解决方案不是 O(N^2)。它有两个 O(N) 嵌套循环,但在内部循环中有一个 O(N) containsKey(因为 keystr 有 O(N) 个字符,并假设一个 O(1) 哈希图)。所以算法实际上是O(N^3)。
  • 复制了这个 2 岁的错误标题问题:stackoverflow.com/questions/20251645/…

标签: algorithm performance dynamic-programming kadanes-algorithm


【解决方案1】:

这是一个基于后缀数组的 O(n log n) 版本。后缀数组有O(n)个构造算法,我就是没耐心写代码。

示例输出(这个输出不是 O(n),只是说明我们确实可以计算所有分数):

4*1 a
3*3 aba
2*5 ababa
1*7 abababa
3*2 ab
2*4 abab
1*6 ababab

基本上,您必须反转字符串,并计算后缀数组 (SA) 和最长公共前缀 (LCP)。

然后您向后遍历 SA 数组,寻找与整个后缀(原始字符串中的前缀)匹配的 LCP。如果匹配,则递增计数器,否则将其重置为 1。每个后缀(前缀)都会收到一个“分数”(SCR),它对应于它在原始字符串中出现的次数。

#include <iostream>
#include <cstring>
#include <string>
#define MAX 10050
using namespace std;

int RA[MAX], tempRA[MAX];
int SA[MAX], tempSA[MAX];
int C[MAX];                
int Phi[MAX], PLCP[MAX], LCP[MAX];

int SCR[MAX];

void suffix_sort(int n, int k) {
    memset(C, 0, sizeof C);        

    for (int i = 0; i < n; i++)        
        C[i + k < n ? RA[i + k] : 0]++;

    int sum = 0;
    for (int i = 0; i < max(256, n); i++) {                     
        int t = C[i]; 
        C[i] = sum; 
        sum += t;
    }

    for (int i = 0; i < n; i++)        
        tempSA[C[SA[i] + k < n ? RA[SA[i] + k] : 0]++] = SA[i];

    memcpy(SA, tempSA, n*sizeof(int));
}

void suffix_array(string &s) {             
    int n = s.size();

    for (int i = 0; i < n; i++) 
        RA[i] = s[i] - 1;              

    for (int i = 0; i < n; i++) 
        SA[i] = i;

    for (int k = 1; k < n; k *= 2) {     
        suffix_sort(n, k);
        suffix_sort(n, 0);

        int r = tempRA[SA[0]] = 0;
        for (int i = 1; i < n; i++) {
            int s1 = SA[i], s2 = SA[i-1];
            bool equal = true;
            equal &= RA[s1] == RA[s2];
            equal &= RA[s1+k] == RA[s2+k];

            tempRA[SA[i]] = equal ? r : ++r;     
        }

        memcpy(RA, tempRA, n*sizeof(int));
    } 
}

void lcp(string &s) {
    int n = s.size();

    Phi[SA[0]] = -1;         
    for (int i = 1; i < n; i++)  
        Phi[SA[i]] = SA[i-1];  

    int L = 0;
    for (int i = 0; i < n; i++) {
        if (Phi[i] == -1) { 
            PLCP[i] = 0; 
            continue; 
        }
        while (s[i + L] == s[Phi[i] + L]) 
            L++;

        PLCP[i] = L;
        L = max(L-1, 0);                      
    }

    for (int i = 1; i < n; i++)                 
        LCP[i] = PLCP[SA[i]];
}

void score(string &s) {
    SCR[s.size()-1] = 1;

    int sum = 1;
    for (int i=s.size()-2; i>=0; i--) {
        if (LCP[i+1] < s.size()-SA[i]-1) {
            sum = 1;
        } else {
            sum++; 
        }
        SCR[i] = sum;
    }
}

int main() {
    string s = "abababa";
    s = string(s.rbegin(), s.rend()) +".";

    suffix_array(s);
    lcp(s);
    score(s);

    for(int i=0; i<s.size(); i++) {
        string ns = s.substr(SA[i], s.size()-SA[i]-1);
        ns = string(ns.rbegin(), ns.rend());
        cout << SCR[i] << "*" << ns.size() << " " << ns << endl;
    }
}

大部分代码(特别是后缀数组和 LCP 实现)我已经在比赛中使用了几年。这个版本特别改编自this one I wrote some years ago

【讨论】:

  • 这不是 codegolf.SE。像tempSA[C[SA[i] + k &lt; n ? RA[SA[i] + k] : 0]++] = SA[i]; 这样的行可以真的 使用 cmets,或者被分成多行。您的算法听起来不错,但不够有趣,无法像其他人编写的代码那样涉水。
  • @PeterCordes 这段代码的目的不是展示如何实现后缀数组(任何人都可以在其他地方学习),而是展示如何使用后缀数组来解决这个特殊问题.后缀数组部分我都没写,改编自哈利姆兄弟的suffix array algorithm in their book
【解决方案2】:
public class Main {
    public static void main(String[] args) {
        String input = "abababa";
        String prefix;
        int product;
        int maxProduct = 0;
        for (int i = 1; i <= input.length(); i++) {
            prefix = input.substring(0, i);
            String substr;
            int occurs = 0;
            for (int j = prefix.length(); j <= input.length(); j++) {
                substr = input.substring(0, j);
                if (substr.endsWith(prefix))
                    occurs++;
            }
            product = occurs*prefix.length();
            System.out.println("product of " + prefix + " = " +
                prefix.length() + " * " + occurs +" = " + product);
            maxProduct = (product > maxProduct)?product:maxProduct;
        }
        System.out.println("maxProduct = " + maxProduct);
    }
}

【讨论】:

  • 您能否为您的答案添加一些解释?
  • 这似乎是二次的,因为有两个循环不是吗?
【解决方案3】:

我在这个挑战上工作了超过 4 天,阅读了大量文档,我找到了 O(N) 的解决方案。

我得到了 81%,这个想法很简单,使用窗口幻灯片。

def 解决方案(s: String): Int = {

var max = s.length  // length of the string 
var i, j = 1  // start with i=j=1 ( is the beginning of the slide and j the end of the slide )
val len = s.length // the length of the string 
val count = Array.ofDim[Int](len)  // to store intermediate results 

while (i < len - 1 || j < len) {
  if (i < len && s(0) != s(i)) {
    while (i < len && s(0) != s(i)) {  // if the begin of the slide is different from 
                                      // the first letter of the string skip it 
      i = i + 1
    }
  }
  j = i + 1
  var k = 1


  while (j < len && s(j).equals(s(k))) { // check for equality and update the array count 
    if (count(k) == 0) {
      count(k) = 1
    }
    count(k) = count(k) + 1
    max = math.max((k + 1) * count(k), max)
    k = k + 1
    j = j + 1
  }

  i = i + 1

}

max // return the max 

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 2020-06-22
    • 2016-04-28
    • 2014-07-05
    • 1970-01-01
    • 2012-01-24
    • 1970-01-01
    相关资源
    最近更新 更多