【问题标题】:Create substring using loop使用循环创建子字符串
【发布时间】:2020-05-20 02:15:40
【问题描述】:

我得到一个字符串,我必须从中找出满足以下条件的子字符串

  • 子字符串中的所有字符都相同。例如:aa,bbb,cccc。

  • 除中间字符外的所有字符都必须相同。 如:aba、bbabb等

我做了一个类似这样的算法

我使用两个循环来获取字符串第一个循环保存第一个字符,第二个循环遍历字符串。

然后我将子字符串发送到 vet() 以查看子字符串是否包含小于或等于两个字符。 如果子字符串包含两个字符,那么我检查它是否是回文


public static int reverse(String s)
    {
        String wrd="";
        for(int i = s.length()-1 ;i>=0;i--)
            wrd = wrd + s.charAt(i);

        if(s.equals(wrd))
            return 1;
        else
            return 0;

    }

    public static boolean vet(String s)
    {
        HashSet<Character> hs = new HashSet<>();
        for(char c : s.toCharArray())
        {
            hs.add(c);
        }
        if(hs.size() <= 2)
            return true;
        else
            return false;
    }

    static long substrCount(int n, String s) {
        List<String> al = new ArrayList<>();

        for(int i=0;i<s.length();i++)
        {
            for(int j=i;j<s.length();j++)
            {
                        if(vet(s.substring(i,j+1)))
                        {
                            if(reverse(s.substring(i,j+1)) == 1)
                                al.add(s.substring(i,j+1));
                        }

            }
        }
        return al.size();
    }


此代码适用于小字符串,但是如果字符串很大,例如一万个字符,则此代码将引发时间限制异常。

我怀疑破坏字符串并在 substrCount() 中创建子字符串的循环会导致时间复杂度,因为它具有嵌套循环。

请查看此代码并提供更好的方法来中断字符串,或者如果由于其他部分而导致复杂性增加,请告诉我。

链接:https://www.hackerrank.com/challenges/special-palindrome-again/problem?h_l=interview&playlist_slugs%5B%5D=interview-preparation-kit&playlist_slugs%5B%5D=strings

【问题讨论】:

  • 为什么使用 python 标签
  • 我已经删除了标签....它是一个基于算法的问题,所以它写在哪个语言上并不重要。
  • 应该返回重叠的子字符串吗?例如。输入"ababa"可以说有子串["aba", "bab", "aba"],或者输入"aabaacaa"可以说有子串["aa", "aabaa", "aba", "aa", "aacaa", "aca", "aa"]
  • 你能不能用一个计数数组{Dictionary/Map}来计算在字符串中找到字符的次数,因此你可以找到解决方案。
  • @Abal 链接到问题?

标签: java string algorithm substring time-complexity


【解决方案1】:

您当前的解决方案运行时间为 O(n^4)。您可以通过删除子字符串中的字符数并优化回文检查部分将其减少到 O(n^2logn)。

为此,您必须预先计算一个数组,例如“counter”,其中“counter”数组的每个位置表示从起始索引到该位置的不同字符的数量。

构造数组后,可以通过减去counter数组的结束位置和开始位置值来检查子字符串是否在O(1)中超过两个字符。如果值为 1,则子字符串中只有一个字符。如果该值为 2,那么您可以在计数器数组中的子字符串开始和结束位置之间进行二进制搜索,以找到单个字符的位置。找出单个字符的位置后,直接检查子字符串是否为回文。

更新! 让我用一个例子来解释:

假设字符串是“aaabaaa”。 因此,计数器数组将是 = [1, 1, 1, 2, 2, 2, 2]; 现在,让我们假设特定时间,外部 for 循环值 i = 1,内部 for 循环值 j = 5;所以子字符串是“aabaa”。 现在通过以下代码查找子字符串中的字符数:

noOfDifferentCharacter = counter[j] - counter[i-1] + 1

如果 noOfDifferentCharacter 为 1,则无需检查回文。如果 noOfDifferentCharacter 是 2,就像我们的例子一样,我们需要检查子字符串是否是回文。要检查子字符串是否为回文,必须在计数器数组中从索引 i 到 j 执行二进制搜索,以检查值大于其先前索引的位置。在我们的例子中,位置是 3,那么您只需要检查该位置是否是子字符串的中间位置。请注意,计数器数组已排序。

希望这会有所帮助。如果您不明白任何步骤,请告诉我。编码愉快!

【讨论】:

  • 能否请您详细介绍一下计数器数组,我没听懂。
  • 你能不能说一下你不明白的具体部分?
  • 在我创建了具有索引值的计数器数组 = 直到索引之前没有不同的字符......之后我该怎么办
  • 我已经用一个例子更新了答案。请检查!
【解决方案2】:
  • 您可以在 2 个单独的数组中从字符串的左侧和右侧收集计数。
  • 现在,我们以以下方式收集计数:如果前一个字符等于当前字符,则将计数加 1,否则将其设置为 1。

示例:

a  a  b  a  a  c  a  a

1  2  1  1  2  1  1  2 // left to right
2  1  1  2  1  1  2  1 // right to left
  • 对于所有字符都相等的字符串,我们只是在迭代自身时收集所有这些字符串。

  • 对于除中间字符外全部相等的字符串,可以使用上表上方,收集字符串如下:

伪代码:

if(str.charAt(i-1) == str.charAt(i+1)){ // you will add checks for boundaries
    int min_count = Math.min(left[i-1],right[i+1]);
    for(int j=min_count;j>=1;--j){
        set.add(str.substring(i-j,i+j+1));
    }
}

更新:

以下是我接受的解决方案。

static long substrCount(int n, String s) {
    long cnt = 0;
    int[] left  = new int[n];
    int[] right = new int[n];
    int len = s.length();
    for(int i=0;i<len;++i){
        left[i] = 1;
        if(i > 0 && s.charAt(i) == s.charAt(i-1)) left[i] += left[i-1];
    }

    for(int i=len-1;i>=0;--i){
        right[i] = 1;
        if(i < len-1 && s.charAt(i) == s.charAt(i+1)) right[i] += right[i+1];
    }

    for(int i=len-1;i>=0;--i){
        if(i == 0 || i == len-1) cnt += right[i];
        else{
            if(s.charAt(i-1) == s.charAt(i+1) && s.charAt(i-1) != s.charAt(i)){
                cnt += Math.min(left[i-1],right[i+1]) + 1;
            }else if(s.charAt(i) == s.charAt(i+1)) cnt += right[i];
            else cnt++;
        }
    }

    return cnt;
}

算法:

  • 算法与上面解释的相同,但有一些额外的东西。
  • 如果字符在边界处,比如0len-1,我们只需查看right[i] 来计算字符串,因为这里没有left
  • 如果一个字符在这个边界内,我们会做如下检查:

    • 如果前一个字符等于下一个字符,我们检查前一个字符是否不等于当前字符。我们这样做是因为,我们希望避免将来在当前迭代本身中添加字符串(比如像 aaaaa 这样的字符串,我们在中间 a)。
    • 第二个条件是s.charAt(i) == s.charAt(i+1),意思是,我们又有aaa这样的字符串,我们在第一个a。所以我们只添加right[i] 来表示添加a,aa,aaa等字符串。
    • 第三个cnt++表示添加单个字符。
  • 您可以进行一些优化,例如完全避免 right 数组等,但我将其留给您作为练习。

  • 时间复杂度:O(n),空间复杂度:O(n)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-08
    • 2014-07-06
    • 2013-04-14
    • 2012-09-25
    • 1970-01-01
    相关资源
    最近更新 更多