Java 에서 문자열이 회문인지 확인하는 방법

JavaBeginner
지금 연습하기

소개

이 랩에서는 Java 에서 문자열이 회문 (palindrome) 인지 확인하는 방법을 배우게 됩니다. 문자열을 뒤집어 원래 문자열과 비교하는 기본적인 방법부터 시작하여 다양한 접근 방식을 탐구할 것입니다.

이를 바탕으로, 두 개의 포인터 (two-pointer) 접근 방식을 사용하여 더욱 효율적인 기술을 배우게 됩니다. 마지막으로, 대소문자와 문자가 아닌 문자를 무시하도록 회문 검사를 개선하여 검사의 견고성을 높일 것입니다.

문자열 뒤집기 및 비교

이 단계에서는 문자열을 뒤집은 다음 원래 문자열과 비교하는 간단한 Java 프로그램을 만드는 것으로 시작합니다. 이는 Java 에서 문자열 조작을 이해하는 기본적인 단계이며, 문자열이 회문인지 확인하기 위한 구성 요소 역할을 합니다.

먼저, 새로운 Java 파일을 만들어 보겠습니다. WebIDE 의 왼쪽 File Explorer 에서 ~/project 디렉토리를 마우스 오른쪽 버튼으로 클릭하고 "New File"을 선택한 다음 이름을 StringReverse.java로 지정합니다.

이제 편집기에서 StringReverse.java 파일을 열고 다음 코드를 추가합니다.

public class StringReverse {

    public static void main(String[] args) {
        String originalString = "hello";
        String reversedString = "";

        // Loop through the original string from end to beginning
        for (int i = originalString.length() - 1; i >= 0; i--) {
            // Append each character to the reversedString
            reversedString += originalString.charAt(i);
        }

        System.out.println("Original string: " + originalString);
        System.out.println("Reversed string: " + reversedString);

        // Compare the original and reversed strings
        if (originalString.equals(reversedString)) {
            System.out.println("The string is the same forwards and backwards.");
        } else {
            System.out.println("The string is NOT the same forwards and backwards.");
        }
    }
}

이 코드를 자세히 살펴보겠습니다.

  • String originalString = "hello";: originalString이라는 String 변수를 선언하고 "hello" 값을 할당합니다.
  • String reversedString = "";: reversedString이라는 빈 String 변수를 선언합니다. 여기에 뒤집힌 문자열을 만들 것입니다.
  • for (int i = originalString.length() - 1; i >= 0; i--): originalString의 마지막 문자 (인덱스 length() - 1) 부터 시작하여 첫 번째 문자 (인덱스 0) 까지 내려가는 for 루프입니다.
  • reversedString += originalString.charAt(i);: 루프 내부에서 originalString.charAt(i)는 현재 인덱스 i에 있는 문자를 가져옵니다. 그런 다음 이 문자를 reversedString에 추가합니다.
  • System.out.println(...): 이 줄은 원래 문자열과 뒤집힌 문자열을 콘솔에 출력합니다.
  • if (originalString.equals(reversedString)): 이 if 문은 equals() 메서드를 사용하여 originalStringreversedString을 비교합니다. Java 에서는 문자열의 내용을 비교할 때 == 연산자가 아닌 equals()를 사용해야 합니다.
  • ifelse 블록은 문자열이 동일한지 여부에 따라 다른 메시지를 출력합니다.

StringReverse.java 파일을 저장합니다 (Ctrl+S 또는 Cmd+S).

이제 WebIDE 하단의 터미널을 엽니다. ~/project 디렉토리에 있는지 확인합니다. 그렇지 않은 경우 cd ~/project를 입력하고 Enter 키를 누릅니다.

javac 명령을 사용하여 Java 코드를 컴파일합니다.

javac StringReverse.java

오류가 없으면 StringReverse.class 파일이 ~/project 디렉토리에 생성됩니다.

마지막으로, java 명령을 사용하여 컴파일된 Java 프로그램을 실행합니다.

java StringReverse

다음과 유사한 출력을 볼 수 있습니다.

Original string: hello
Reversed string: olleh
The string is NOT the same forwards and backwards.

이 경우 "hello"는 앞뒤가 같지 않으므로 프로그램은 이를 올바르게 식별합니다.

회문을 위한 Two-Pointer 접근 방식 사용

이전 단계에서는 전체 문자열을 뒤집어 회문인지 확인했습니다. 이 방법은 작동하지만, 새로운 문자열을 생성해야 하므로 매우 긴 문자열의 경우 비효율적일 수 있습니다. 더 효율적인 접근 방식은 두 개의 포인터 (two-pointer) 기법을 사용하는 것입니다.

두 개의 포인터 기법은 문자열의 서로 다른 끝에서 시작하여 중앙으로 이동하는 두 개의 포인터 (인덱스 위치를 저장하는 변수) 를 사용하는 것입니다. 이러한 포인터의 문자를 비교합니다. 포인터가 안쪽으로 이동할 때 항상 동일하면 문자열은 회문입니다. 불일치를 발견하면 회문이 아닙니다.

이를 구현하기 위해 새로운 Java 프로그램을 만들어 보겠습니다. 왼쪽 File Explorer 에서 ~/project 디렉토리에 PalindromeChecker.java라는 새 파일을 만듭니다.

PalindromeChecker.java를 열고 다음 코드를 추가합니다.

public class PalindromeChecker {

    public static void main(String[] args) {
        String testString = "madam"; // Let's test with a palindrome
        boolean isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }

        testString = "racecar"; // Another palindrome
        isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }

        testString = "hello"; // Not a palindrome
        isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }
    }

    // Method to check if a string is a palindrome using two pointers
    public static boolean checkPalindrome(String str) {
        // Initialize two pointers
        int left = 0; // Pointer starting from the beginning
        int right = str.length() - 1; // Pointer starting from the end

        // Loop while the left pointer is less than the right pointer
        while (left < right) {
            // Compare characters at the left and right pointers
            if (str.charAt(left) != str.charAt(right)) {
                // If characters don't match, it's not a palindrome
                return false;
            }
            // Move the pointers inwards
            left++;
            right--;
        }

        // If the loop completes without finding any mismatch, it's a palindrome
        return true;
    }
}

새로운 코드에서 일어나는 일은 다음과 같습니다.

  • String을 입력으로 받아 boolean을 반환하는 checkPalindrome이라는 별도의 메서드를 만들었습니다 (회문이면 true, 그렇지 않으면 false). 이렇게 하면 코드가 더 체계적이고 재사용 가능해집니다.
  • checkPalindrome 내부에서 left를 0(첫 번째 문자의 인덱스) 으로, rightstr.length() - 1(마지막 문자의 인덱스) 로 초기화합니다.
  • while (left < right) 루프는 왼쪽 포인터가 오른쪽 포인터의 왼쪽에 있는 동안 계속됩니다.
  • if (str.charAt(left) != str.charAt(right))는 현재 leftright 위치의 문자를 비교합니다. 다르면 즉시 회문이 아니라는 것을 알고 false를 반환합니다.
  • 문자가 일치하면 포인터를 안쪽으로 이동합니다. left++는 왼쪽 포인터를 증가시키고, right--는 오른쪽 포인터를 감소시킵니다.
  • 루프가 false를 반환하지 않고 완료되면 비교된 모든 문자가 일치했으므로 문자열은 회문이고 true를 반환합니다.
  • main 메서드에서 다른 테스트 문자열로 checkPalindrome을 호출하고 결과를 출력합니다.

PalindromeChecker.java 파일을 저장합니다.

이제 터미널에서 프로그램을 컴파일하고 실행합니다.

javac PalindromeChecker.java
java PalindromeChecker

다음 출력을 볼 수 있습니다.

"madam" is a palindrome.
"racecar" is a palindrome.
"hello" is not a palindrome.

이는 두 개의 포인터 접근 방식이 전체 문자열을 뒤집지 않고도 회문을 효율적으로 확인하는 방법을 보여줍니다.

회문에서 대소문자 및 문자 외 문자 무시

이전 단계에서 회문 검사기는 "madam" 및 "racecar"와 같은 간단한 문자열에 대해 작동했습니다. 그러나 실제 회문은 대소문자 구분 및 구두점을 무시하는 경우가 많습니다. 예를 들어 "A man, a plan, a canal: Panama"는 회문으로 간주됩니다.

이 단계에서는 문자 외 문자를 무시하고 대문자와 소문자를 동일하게 처리하여 PalindromeChecker를 개선하여 이러한 경우를 처리합니다.

WebIDE 편집기에서 PalindromeChecker.java 파일을 엽니다. checkPalindrome 메서드를 수정합니다.

기존 checkPalindrome 메서드를 다음 업데이트된 버전으로 바꿉니다.

    // Method to check if a string is a palindrome using two pointers,
    // ignoring case and non-letter characters
    public static boolean checkPalindrome(String str) {
        // Initialize two pointers
        int left = 0; // Pointer starting from the beginning
        int right = str.length() - 1; // Pointer starting from the end

        // Loop while the left pointer is less than the right pointer
        while (left < right) {
            // Move left pointer past non-letter characters
            while (left < right && !Character.isLetter(str.charAt(left))) {
                left++;
            }

            // Move right pointer past non-letter characters
            while (left < right && !Character.isLetter(str.charAt(right))) {
                right--;
            }

            // If left and right pointers haven't crossed and characters don't match (ignoring case)
            if (left < right && Character.toLowerCase(str.charAt(left)) != Character.toLowerCase(str.charAt(right))) {
                // If characters don't match, it's not a palindrome
                return false;
            }

            // Move the pointers inwards
            left++;
            right--;
        }

        // If the loop completes without finding any mismatch, it's a palindrome
        return true;
    }

변경 사항은 다음과 같습니다.

  • 메인 while (left < right) 루프 내에 두 개의 while 루프를 추가했습니다.
  • 첫 번째 내부 while 루프 (while (left < right && !Character.isLetter(str.charAt(left)))) 는 left 포인터가 right 포인터의 왼쪽에 있고 left 포인터의 문자가 문자가 아닌 경우 (!Character.isLetter(...)) left 포인터를 앞으로 이동합니다.
  • 두 번째 내부 while 루프 (while (left < right && !Character.isLetter(str.charAt(right)))) 는 right 포인터가 right 포인터의 왼쪽에 있고 right 포인터의 문자가 문자가 아닌 경우 right 포인터를 뒤로 이동합니다.
  • 비교 if (left < right && Character.toLowerCase(str.charAt(left)) != Character.toLowerCase(str.charAt(right)))는 이제 두 가지 중요한 검사를 포함합니다.
    • left < right: 이는 문자가 아닌 문자를 건너뛴 후 포인터가 교차하지 않은 경우에만 문자를 비교하도록 합니다.
    • Character.toLowerCase(...): 이는 두 문자를 비교하기 전에 소문자로 변환하여 대소문자를 효과적으로 무시합니다.
  • 문자가 (소문자로 변환된 후) 일치하지 않으면 false를 반환합니다.
  • 일치하면 두 포인터를 안쪽으로 이동 (left++, right--) 하여 다음 문자 쌍을 확인합니다.

이제 main 메서드를 수정하여 공백, 구두점 및 대소문자가 혼합된 문자열로 테스트해 보겠습니다. main 메서드의 기존 테스트 케이스를 다음으로 바꿉니다.

    public static void main(String[] args) {
        String testString = "A man, a plan, a canal: Panama"; // A classic palindrome
        boolean isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }

        testString = "No 'x' in Nixon"; // Another palindrome
        isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }

        testString = "hello world"; // Not a palindrome
        isPalindrome = checkPalindrome(testString);

        if (isPalindrome) {
            System.out.println("\"" + testString + "\" is a palindrome.");
        } else {
            System.out.println("\"" + testString + "\" is not a palindrome.");
        }
    }

PalindromeChecker.java 파일을 저장합니다.

터미널에서 업데이트된 프로그램을 컴파일하고 실행합니다.

javac PalindromeChecker.java
java PalindromeChecker

이제 다음 출력을 볼 수 있으며, 문자 외 문자 및 대소문자가 혼합된 경우에도 회문을 올바르게 식별합니다.

"A man, a plan, a canal: Panama" is a palindrome.
"No 'x' in Nixon" is a palindrome.
"hello world" is not a palindrome.

회문 검사기를 성공적으로 개선하여 더 복잡한 문자열을 처리할 수 있습니다!

요약

이 Lab 에서는 Java 에서 문자열이 회문인지 확인하는 방법을 배웠습니다. 먼저 문자열을 뒤집는 메서드를 구현하고 이를 원래 문자열과 비교하여 회문인지 확인하는 것으로 시작했습니다. 여기에는 Java 파일을 만들고, 문자열을 역순으로 반복하는 코드를 작성하고, 비교를 위해 equals() 메서드를 사용하는 것이 포함되었습니다.

그런 다음 새로운 뒤집힌 문자열을 생성하지 않고 회문을 확인하는 데 더 효율적인 방법인 두 개의 포인터 (two-pointer) 접근 방식을 탐구했습니다. 마지막으로, 실제 시나리오에서 검사를 더 강력하게 만들기 위해 대소문자 및 문자 외 문자를 무시하도록 회문 검사를 개선했습니다.