Given a string, find the longest substring with th

2020-07-13 11:16发布

问题:

Given a string, find the longest substring with the same number of vowels and consonants.

CLARIFICATION: I am unsure, whether we can generate a new string, or the substring has to be part of the original string? So far I have this,

Code Snippet :

    Scanner scanner = new Scanner(System.in);
    String string = new String(scanner.next());
    int lengthOfString = string.length();
    int vowelCount = 0;
    int consCount = 0;

    for (int i = 0; i < lengthOfString; i++) {
        if (string.charAt(i) == 'u' || string.charAt(i) == 'e'  || string.charAt(i) == 'i'
                || string.charAt(i) == 'o' || string.charAt(i) == 'a' ) {
            vowelCount++;


        } else {

            consCount++;
        }

    }

    System.out.println(vowelCount);

EDIT I got the count working, but how do I create a substring?

回答1:

This can be solved in O(n) time and space using the "net" values computed by this answer in combination with the following observation:

  • A substring s[i .. j] has the same number of consonants and vowels if and only if net[1 .. i-1] = net[1 .. j], where net[i .. j] is the sum of the "net" values (1 for a vowel, -1 for a consonant) for each character between positions i and j, inclusive.

To see this, observe that the condition that tells us that a substring s[i .. j] is the kind we're looking for is that

net[i .. j] = 0.

Adding net[1 .. i-1] to both sides of this equation gives

net[1 .. i-1] + net[i .. j] = net[1 .. i-1]

with the LHS then simplifying to just

net[1 .. j] = net[1 .. i-1]

Algorithm

That means that we can create a table containing two entries (first position seen and last position seen) for each possible distinct value that we could get as we calculate a running sum of net values. This running total could range as low as -n (if every character is a consonant) or as high as n (if every character is a vowel), so there are at most 2n+1 distinct such sums in total, so we'll need that many rows in our table. We then march through the string from left to right calculating a running total net value, and updating the pair in the table that corresponds to the current running total, noticing whenever this update produces a new, maximum-length substring. In pseudocode, with zero-based array indices and using separate arrays to hold the elements in each pair:

  1. Create 2 arrays of length 2n+1, first[] and last[], initially containing all -2s, except for first[n] which is -1. (Need to use -2 as a sentinel since -1 is actually a valid value!)
  2. Set bestFirst = bestLast = bestLen = -1.
  3. Set the running total t = n. (n "means" 0; using this bias just means we can use the running total directly as a nonnegative-valued index into the arrays without having to repeatedly add an offset to it.)
  4. For i from 0 to n-1:
    • If s[i] is a vowel, increment t, otherwise decrement t.
    • If first[t] is -2:
      • Set first[t] = i.
    • Otherwise:
      • Set last[t] = i.
      • If last[t] - first[t] > bestLen:
        • Set bestLen = last[t] - first[t].
        • Set bestFirst = first[t] + 1.
        • Set bestLast = last[t].

A maximum-length range will be returned in (bestFirst, bestLast), or if no such range exists, these variables will both be -1.

I remember seeing this solution, or one very similar to it, somewhere on SO a while back -- if anyone can find it, I'll gladly link to it.



回答2:

Here is an updated version of my original answer which runs in O(n^2) time. It achieves this by employing a trick, namely keeping track of a single variable (called 'net') which tracks the difference between the number of vowels and consonants. When this number is zero, a given substring is balanced.

It takes O(n^2) to iterate over every possible substring in the worst case, but it doesn't take any additional time check each substring for strings and vowels, because it keeps the net up to date with each new step to choose a substring. Hence, it reduces the complexity from O(n^3) to O(n^2).

public String findLongestSubstring(String input) {
    String longest = "";

    for (int window = inputz.length(); window >=2; --window) {
        String substr = input.substring(0, window);
        String consonants = input.substring(0, window).toLowerCase()
                .replaceAll("[aeiou]", "");
        int vowelCount = input.substring(0, window).length() - consonants.length();
        int consonantCount = consonants.length();

        int net = vowelCount - consonantCount;

        for (int i=window; i <= input.length(); ++i) {
            if (net == 0) {
                longest = input.substring(i-window, i);
                return longest;
            }

            // no-op for last window
            if (i == input.length()) break;

            // update tally by removing high character
            if ("aeiou".indexOf(input.charAt(i)) != -1) {
                ++net;
            }
            else {
                --net;
            }
            // update tally by adding low character
            if ("aeiou".indexOf(input.charAt(i-window)) != -1) {
                --net;
            }
            else {
                ++net;
            }
        }
    }

    return longest;
}


回答3:

To find the longest substring where the number of consonants and vowels are equal, start finding substrings at the largest length, and steadily decrease the length needed until you find a substring that matches the criteria.

This will allow you to short-circuit the operation.

public static String findLongestSubstring(String str) {
    for (int len = str.length(); len >= 2; len--) {
        for (int i = 0; i <= str.length() - len; i++) {
            String substr = str.substring(i, i + len);
            int vowels = countVowels(substr);
            int consonants = len - vowels;
            if (vowels == consonants) {
                return substr;
            }
        }
    }
    return "";
}

private static int countVowels(String str) {
    return str.replaceAll("[^AEIOUaeiou]+", "").length(); 
}


回答4:

I think this could be decision for your task (for not too long input string):

import org.junit.Test;

/**
 * Created by smv on 19/09/16.
 */
public class MainTest {

    public static boolean isVowel(char c) {
        return "AEIOUaeiou".indexOf(c) != -1;
    }

    public int getVowelCount(String str) {
        int res = 0;
        for(int i=0; i < str.length(); i++){
            if(isVowel(str.charAt(i))) {
                res++;
            }
        }
        return res;
    }

    public int getConsonantCount(String str) {
        int res = 0;
        for(int i=0; i < str.length(); i++){
            if(!isVowel(str.charAt(i))) {
                res++;
            }
        }
        return res;
    }

    @Test
    public void run() throws Exception {
        String string = "aasdaasggsertcwevwertwe";
        int lengthOfString = string.length();
        String maxSub = "";
        int maxSubLength = 0;

        // find all substrings of given string
        for( int c = 0 ; c < lengthOfString ; c++ )
        {
            for( int i = 1 ; i <= lengthOfString - c ; i++ )
            {
                String sub = string.substring(c, c+i);

                // comparing count vowels and consonants 
                if (getVowelCount(sub) == getConsonantCount(sub)) {
                    if (sub.length() > maxSubLength) {
                        maxSub = sub;
                        maxSubLength = sub.length();
                    }
                }
            }
        }
        System.out.println(maxSub);
    }
}


回答5:

Well the requirements are very vague here of course. It does not mention if numbers or other keys are included in the input. I assumed a starting index of zero since counts are equal at that point.

    Scanner scanner = new Scanner(System.in);
    String string = new String(scanner.next());
    int lengthOfString = string.length();
    int vowelCount = 0;
    int consCount = 0;
    int maxIndex = -1;

    for(int i = 0; i < lengthOfString; i++) 
    {
        System.out.println("Char: " + string.charAt(i));

        if(string.charAt(i) == 'u' || string.charAt(i) == 'e' || string.charAt(i) == 'i'
            || string.charAt(i) == 'o' || string.charAt(i) == 'a') 
        {
            vowelCount++;
        } 
        else 
        {
            consCount++;
        }

        if(vowelCount == consCount)
        {
            System.out.println("count equal with: " + string.substring(0, (i + 1)));
            maxIndex = i + 1;
        }
    }

    if(maxIndex > 0)
    {
        System.out.println("Longest sub string with equal count of vowels and consonants is: " 
            + string.substring(0, maxIndex));
    }
    else
    {
        System.out.println("No substring existed with equal count of vowels and consonants.");
    }