简体   繁体   中英

Last repeating character in a string

I am searching for a solution to find the last repeating character in a string which says "how are you doing today", which supposed to give y as the answer. I have tried using Linked Hash Map to get the ordered insertions, but I am getting d as the last repeated one.

String testString  =  "how are you doing today";

    char[] testStringArray = testString.toCharArray();
    Map<Character,Integer> map = new LinkedHashMap<>();

    for( char i : testStringArray) {
     if (!(i==' ')) {
    if(map.containsKey(i) ) {
        map.put(i, map.get(i)+1);
    } else {
        map.put(i, 1);
    }
     }
    }

    System.out.println(map);

    List<Entry<Character, Integer>> list = new ArrayList<>(map.entrySet());
    ListIterator<Entry<Character, Integer>> it = list.listIterator(list.size());    

    while(it.hasPrevious()) {
        if(it.previous().getValue() > 1)
            System.out.println(it.previous().getKey());


    }
}

Help is appreciated.

This code scans from last character and checks if it's present in the remaining substring before first character:

String str = "how are you doing today";
int len = str.length();
int i = len - 1;
for (; i >= 1; i--) {
    if (str.substring(0, i).contains(str.charAt(i) + "")) {
        break;
    }
}
if (i == 0) {
    System.out.println("no repeating character");
} else
    System.out.println(str.charAt(i));

You can use a simple method which uses a Set . Keep adding the elements to the Set after reading them from the String's char array (use toCharArray() ), if the returned value of the add() operation is false, it means that the element was already added. Keep a char variable that contains the "latest last element" . Just print it when the loop ends.

A simple solution is to iterate through the characters backwards, and use lastIndexOf to find the previous position of that character:

for (int i = testString.length() - 1; i > 0; --i) {
  char ch = testString.charAt(i);
  if (testString.lastIndexOf(ch, i - 1) != -1) {
    return ch;
  }
}
// Handle no repeating character found.

That is happening because LinkedHashMap is maintaining the addition order and y is getting inserted before d for given string and when you iterate from last it reads d first.

In order to achieve what you want i would suggest

  1. Reading your string from end and then insert in LinkedHashMap with count
  2. Iterate LinkedHashMap from start to get the desired output.

Hope this helps.

This code you could use ArrayList or Set instead of LinkedHashMap, there is no need. Here is my code :

public static Character getLastRepeatingCharacter(String src) {
    Character defaultChar = null;
    char[] srcCharArr = src.toCharArray();
    List<Character> list = new ArrayList<Character>();
    final int length = srcCharArr.length;
    for(int idx = 0; idx < length; idx++) {
        if (srcCharArr[idx] == ' ') {
            continue;
        }
        if (list.contains(srcCharArr[idx])) {
            defaultChar = srcCharArr[idx];
        }else {
            list.add(srcCharArr[idx]);
        }
    }
    if (list.size() == length) {
        defaultChar = srcCharArr[length-1];
    }
    return defaultChar;
}

There are two bugs in the code that you posted: 1) ordering in LinkedHashMap , and 2) calling previous() twice in your iterator loop.

To get the order you want from LinkedHashMap , you want insertion order. But updating a value already in the map does not change the insertion order. From the specification :

Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)

To get around this, you have to remove the entry and insert it again. Instead of:

    map.put(i, map.get(i) + 1);

you have to do:

    int old = map.get(i);
    map.remove(i);
    map.put(i, old + 1);

The second problem is that you call previous() twice in the iterator loop that's iterating backwards. This has the side effect advancing the iterator again after you've found the entry you want. You need to cache the result of previous() and use it twice. So, instead of:

    while (it.hasPrevious()) {
        if (it.previous().getValue() > 1)
            System.out.println(it.previous().getKey());
    }

you need to do:

    while (it.hasPrevious()) {
        Entry<Character, Integer> prev = it.previous();
        if (prev.getValue() > 1)
            System.out.println(prev.getKey());
    }

(I suspect you might have found and fixed this one already, because with this fix in place, but not the LinkedHashMap fix, the result is d which matches what you see.)

Fundamentally, I think you're taking the right approach. Essentially, it's this:

  1. generate a histogram of the frequency of characters' occurrences in the string; and then
  2. iterate backwards to find the last character whose frequency is greater than 1, meaning it's a repeat.

A simplifying variation is to iterate backward getting characters from the string, and looking in the map to find the first one whose frequency is > 1. The string maintains the order, so the map doesn't have to. You can thus avoid using LinkedHashMap and the extra work necessary to maintain its insertion order. You'd do something like this:

    for (int i = testString.length() - 1; i >= 0; i--) {
        char ch = testString.charAt(i);
        if (map.containsKey(ch) && map.get(ch) > 1) {
            System.out.println(ch);
        }
    }

It turns out that there are even simpler ways to do both of these using Java 8 lambdas and streams. First, to generate a histogram (frequency map) of the occurrence of characters, given a string input , do this:

    Map<Character, Long> hist =
        input.chars()
             .mapToObj(ch -> (char)ch)
             .filter(ch -> ch != ' ')
             .collect(groupingBy(ch -> ch, counting()));

The wrinkle here is that the chars() method returns an IntStream with each char value contained in an int . Thus, you have to cast to char within mapToObj() in order to turn this into a Stream<Character> . Note that I'm relying on static import of java.util.stream.Collectors.* to get the concise grouping/counting collector.

Once you have the histogram, you can stream over the chars again and use a reduce() trick to get the last element:

           input.chars()
                .mapToObj(ch -> (char)ch)
                .filter(ch -> ch != ' ')
                .filter(ch -> hist.get(ch) > 1L)
                .reduce((a, b) -> b);

Putting it all together, you have this:

Optional<Character> lastRepeat(String input) {
    Map<Character, Long> hist =
        input.chars()
             .mapToObj(ch -> (char)ch)
             .filter(ch -> ch != ' ')
             .collect(groupingBy(ch -> ch, counting()));
    return input.chars()
                .mapToObj(ch -> (char)ch)
                .filter(ch -> ch != ' ')
                .filter(ch -> hist.get(ch) > 1L)
                .reduce((a, b) -> b);
}

This returns an Optional containing the last repeated character in the string, or an empty Optional if there are no repeated characters.

    String str="how are you doing today";


    char charArr[]=str.toCharArray();

    HashMap<Character,Integer> hMap=new HashMap();


    for(int i=0;i<charArr.length;i++)
    {
        if(hMap.containsKey(charArr[i]))
        {
            int count=hMap.get(charArr[i]);
            hMap.put(charArr[i],count+1);
        }else
        {
            hMap.put(charArr[i], 1);
        }
    }
    System.out.println(hMap);
    for(int i=charArr.length-1;i>=0;i--)
    {
        if(hMap.get(charArr[i])>1)
        {
            System.out.println("The Last Repeated Character is :"+charArr[i]);
            break;
        }
    }

Output:

    { =4, a=2, d=2, e=1, g=1, h=1, i=1, n=1, o=4, r=1, t=1, u=1, w=1, y=2}

    The Last Repeated Character is : y

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM