더북(TheBook)

위 해법은 문자열 내 모든 문자가 확장 아스키표(256 코드)에 속한다고 가정한다. 256보다 코드가 크면 그에 맞게 배열 크기를 늘려야 한다(http://www.alansofficespace.com/unicode/unicd99.htm). 위 해법은 배열 크기가 char 타입의 최댓값, 즉 Character.MAX_VALUE65,535를 넘지 않는 한 잘 동작한다. 이와 달리 Character.MAX_CODE_POINT는 유니코드 코드 포인트의 최댓값인 1,114,111을 반환한다. 이 범위까지 지원하려면 codePointAt()codePoints()에 기반한 다른 구현이 필요하다.

한 번만 순회하므로 위 코드는 상당히 빠르다. 또 다른 해법에서는 각 문자마다 문자열을 순회하며 빈도수를 세야 한다. 문자가 두 번 나오면(중복) 바로 루프를 종료하고 다음 문자로 넘어가는 식으로 알고리즘을 반복한다. 문자열 끝에 도달하면 현재 문자를 반복되지 않는 첫 번째 문자로 반환한다. 이 해법은 이 책에 딸린 예제 코드에 들어 있다.

여기서 소개할 또 다른 해법은 LinkedHashMap을 활용하는 것이다. 자바의 LinkedHashMap 맵은 삽입 순서를 유지하는(insertion-order) 맵으로서(맵에 삽입한 순서대로 키를 유지한다) 이 해법에 사용하면 아주 편리하다. LinkedHashMap은 문자를 키로, 빈도수를 값으로 해서 만들어진다. LinkedHashMap이 완성되면 값이 1인 첫 번째 키를 반환한다. 삽입 순서 유지 기능 덕분에 이 키가 문자열에서 반복되지 않는 첫 번째 문자다.

public char firstNonRepeatedCharacter(String str) {
  Map<Character, Integer> chars = new LinkedHashMap<>();

  // for(char ch: str.toCharArray()) { ... }를 사용해도 된다
  for (int i = 0; i < str.length(); i++) {
    char ch = str.charAt(i);

    chars.compute(ch, (k, v) -> (v == null) ? 1 : ++v);
  }

  for (Map.Entry<Character, Integer> entry: chars.entrySet()) {
    if (entry.getValue() == 1) {
      return entry.getKey();
    }
  }

  return Character.MIN_VALUE;
}
신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.