交换某些字符后具有最大数量的给定子字符串的字符串?

Posted

技术标签:

【中文标题】交换某些字符后具有最大数量的给定子字符串的字符串?【英文标题】:String having maximum number of given substrings made after swapping some characters? 【发布时间】:2015-06-12 21:08:47 【问题描述】:

所以,这是我正在经历的一个面试问题。

我有字符串a, b, and c。我想通过交换a 中的一些字母来获取字符串k,这样k 应该包含尽可能多的非重叠子字符串等于bc。字符串x 的子串是由x 的连续字符段组成的字符串。如果字符串x中存在i的位置,则字符串x的两个子串重叠。

输入:第一行包含字符串a,第二行包含字符串b,第三行包含字符串c (1 ≤ |a|, |b|, |c| ≤ 10^5,其中|s|表示字符串 s) 的长度。

所有三个字符串都只包含小写英文字母。

b 和 c 可能重合。

输出:找到可能的字符串 k 之一。

Example: 
I/P 
abbbaaccca
ab
aca

O/P
ababacabcc
this optimal solutions has three non-overlaping substrings equal to either b or c on positions 1 – 2 (ab), 3 – 4 (ab), 5 – 7 (aca).

现在,我能想到的方法是为每个字符串创建一个字符计数数组,然后继续。基本上,遍历原始字符串 (a),检查 b 和 c 的出现。如果不存在,则交换尽可能多的字符以生成 b 或 c(以较短者为准)。但是,显然这不是最佳方法。 任何人都可以提出更好的建议吗? (只有伪代码就足够了) 谢谢!

【问题讨论】:

【参考方案1】:

首先你需要做的是计算每个字符串中每个字符出现的次数。 a 的出现次数将是你的背包,你需要用尽可能多的 bc 填充它。

请注意,当我说背包时,我指的是 a 的字符计数向量,而将b 插入到a 将意味着将a 的字符计数向量减少b 的字符计数向量。 我的数学证明有点短,但你需要

    在背包中插入尽可能多的b

    在背包中插入尽可能多的c(在 1 之后的空间中)。

    如果从背包中移除b 将允许插入更多c,则从背包中移除b。否则,结束。

    尽可能多地将c填入背包

    重复 3-4。

在整个程序中,计算背包中 b 和 c 的数量,输出应为:

[b_count times b][c_count times c][char_occurrence_left_in_knapsack_for_char_x times char_x for each char_x in lower_case_english]

这应该可以解决您在 O(n) 时遇到的问题。

【讨论】:

【参考方案2】:

假设允许的字符具有 ASCII 码 0-127,我会编写一个函数来计算字符串中每个字符的出现次数:

int[] count(String s) 
    int[] res = new int[128];
    for(int i=0; i<res.length(); i++)
        res[i] = 0;
    for(int i=0; i<a.length(); i++)
        res[i]++;
    return res;

我们现在可以计算每个字符串中出现的次数:

int aCount = count(a);
int bCount = count(b);
int cCount = count(c);

然后我们可以编写一个函数来计算一个字符串可以从另一个字符串的字符中分割出多少次:

int carveCount(int[] strCount, int[] subStrCount) 
    int min = Integer.MAX_VALUE;
    for(int i=0; i<subStrCount.length(); i++) 
        if (subStrCount[i] == 0)
            continue;
        if (strCount[i] >= subStrCount[i])
            min = Math.min(min, strCount[i]-subStrCount[i]);
        else 
            return 0;
        
    
    for(int i=0; i<subStrCount.length(); i++) 
        if (subStrCount[i] != 0)
            strStrCount[i] -= min;
    
    return min;

并调用函数:

int bFitCount = carve(aCount, bCount);
int cFitCount = carve(aCount, cCount);

编辑:我没有意识到你想要所有字符最初都在 a 中,在这里修复。

最后,产生输出:

StringBuilder sb = new StringBuilder();
for(int i=0; i<bFitCount; i++) 
    sb.append(b);
for(int i=0; i<cFitCount; i++) 
    sb.append(c);
for(int i=0; i<aCount.length; i++) 
    for(int j=0; j<aCount[i]; j++) 
        sb.append((char)i);

return sb.toString();

还有一条评论:如果目标是最大化重复次数(b)+重复次数(c),那么如果 c 更短,您可能需要先擦拭 b 和 c。这样,如果他们共享一些字符,您就有更好的机会增加结果。

算法可以进一步优化,但它的复杂度应该是 O(n),其中 n 是三个字符串的长度之和。

【讨论】:

您提供的代码有很多错误。第三个说法,y是什么?另外,在函数carveCount 中,Subcount 是什么。这些是一些可以忽略的错误,但是,根据您的逻辑,首先检查较短字符串(b 或 c)的最大出现次数,看看它可以被雕刻多少次,然后检查较长的字符串。再加上两个,对吧? 对不起,我在手机上写的,没办法测试。关于第二个问题,是的,如果目的是最大化总和(b 的出现 + c 的出现),那么我相信你需要先雕刻最短的【参考方案3】:

一个相关的问题叫做Knapsack problem。 这基本上是@Tal Shalti 描述的解决方案。 我试图让所有内容都可读。

我的程序返回 abbcabacac 作为出现次数最多的字符串之一 (3)。

为了获得所有排列而不重复排列,我使用来自algorithmstd::next_permutation。主要功能中没有发生太多事情。如果实现了更高的出现次数,我只存储出现次数和排列。

int main()

    std::string word = "abbbaaccca";
    std::string patternSmall = "ab";
    std::string patternLarge = "aca";


    unsigned int bestOccurrence = 0;
    std::string bestPermutation = "";


    do 
        // count and remove occurrence
        unsigned int occurrences = FindOccurences(word, patternLarge, patternSmall);

        if (occurrences > bestOccurrence) 
            bestOccurrence = occurrences;
            bestPermutation = word;

            std::cout << word << " .. " << occurences << std::endl;
        


     while (std::next_permutation(word.begin(), word.end()));


    std::cout << "Best Permutation " << bestPermutation << "  with " << bestOccurrence << " occurrences." << std::endl;

    return 0;

这个函数处理基本算法。 pattern1 是较长的模式,因此将在最后搜索。如果找到一个模式,它将被字符串“@@”替换,因为这在英语中应该是非常罕见的。 变量occurrenceCounter 跟踪发现的出现次数。

unsigned int FindOccurrences(const std::string& word, const std::string& pattern1, const std::string& pattern2)

    unsigned int occurrenceCounter = 0;

    std::string tmpWord(word);

    // '-1' makes implementation of while() easier
    std::string::size_type i = -1;

    i = -1;
    while (FindPattern(tmpWord, pattern2, ++i)) 
        occurrenceCounter++;
        tmpWord.replace(tmpWord.begin() + i, tmpWord.begin() + i + pattern2.size(), "@@");
    

    i = -1;
    while (FindPattern(tmpWord, pattern1, ++i)) 
        occurrenceCounter++;
        tmpWord.replace(tmpWord.begin() + i, tmpWord.begin() + i + pattern1.size(), "@@");
    

    return occurrenceCounter;

此函数返回找到的模式的第一个位置。如果未找到该模式,则std::string::nposstring.find(...) 返回。同样string.find(...) 开始搜索以索引i 开头的模式。

bool FindPattern(const std::string& word, const std::string& pattern, std::string::size_type& i)

      std::string::size_type foundPosition = word.find(pattern, i);

      if (foundPosition == std::string::npos) 
         return false;
      

      i = foundPosition;

     return true;

【讨论】:

Findoccurences 函数中,当您使用unsigned int finish = pattern1.size() + pattern2.size() - 1; std::string strSub = tmpWord.substr(i, finish); 获取相关子字符串时,我并没有真正得到该部分。我的意思是,finish 在这里代表什么?你能再详细一点吗? 这是我的代码中的错误。我尝试获取可以与 pattern2 重叠的 pattern1 周围的区域。所以我从pattern1的开头i开始,走pattern1的距离pattern1.size(),后退一个字符-1,走pattern2的距离pattern2.size()。但是忘记了,我更改了(d)那部分代码。

以上是关于交换某些字符后具有最大数量的给定子字符串的字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用负前瞻(NOT lookbehind)来匹配在特定位置不包含给定子字符串的字符串?

正则表达式具有未知数量参数的字符串

指定子设备号创建字符设备

字符串string中指定子字符串pattern的个数

markdown 字符串中去除指定子字符串

通过指定子字符串的第一个和最后一个来提取字符串中的子字符串