[에러 해결] [Python] LeetCode Longest Palindromic Substring: 중첩 루프가 한 번만 실행되고 바로 리턴되는 원인과 해결 방법

1. 에러 발생 상황

안녕하세요! 파이썬 개발자 여러분, LeetCode 문제 풀이 중 뜻밖의 동작으로 당황하신 경험이 있으신가요? 오늘은 LeetCode #5 Longest Palindromic Substring (가장 긴 팰린드롬 부분 문자열) 문제를 풀이하던 중 발생한 흥미로운 에러 상황을 다루어보고자 합니다. 특히 파이썬 초보 개발자분들이 중첩 루프(Nested Loops)와 함수의 반환(return) 메커니즘을 이해하는 데 큰 도움이 될 것입니다.

사용자의 문제 설명

사용자는 LeetCode #5 문제를 해결하기 위해 중첩 루프를 사용했는데, 예상과 달리 루프가 단 한 번만 실행되고 함수가 즉시 원본 문자열을 반환하는 현상을 겪고 있었습니다. 모든 문자열 인덱스를 순회하며 가장 긴 팰린드롬을 찾으려 했지만, 첫 단계부터 막힌 상황입니다.

발생한 코드와 출력

다음은 사용자가 제시한 파이썬 코드와 그 실행 결과입니다.

입력(Input):

s = "babad"

사용자 코드:

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            if len(s) <= 1:
                return s
    
            Max_len = 1
            Max_str = s[0]
    
            for i in range(len(s[1])):
                for j in range(len(s[2])):
                    print(i, j)
                    return s

현재 출력(Current output):

0 0
"babad"

예상대로라면 루프가 문자열의 모든 인덱스를 탐색해야 하지만, 실제로는 0 0만 출력된 후 "babad"가 반환되고 프로그램이 종료됩니다.

2. 명확한 발생 원인

이 문제는 두 가지 주요 원인 때문에 발생합니다. 바로 range() 함수의 오용return 문의 부적절한 위치입니다.

2.1. range() 함수의 오용: 문자열의 길이가 아닌 문자의 길이

사용자 코드의 중첩 루프를 다시 살펴보겠습니다.

            for i in range(len(s[1])):
                for j in range(len(s[2])):

여기서 핵심은 len(s[1])len(s[2])입니다. 입력 문자열 s = "babad"를 예로 들어보죠.

  • s[1]은 인덱스 1에 해당하는 문자 'a'입니다.
  • len('a')1입니다. 따라서 range(len(s[1]))range(1)이 됩니다.
  • 마찬가지로 s[2]는 문자 'b'이며, len('b') 역시 1입니다. 따라서 range(len(s[2]))range(1)이 됩니다.

결과적으로 바깥 루프는 i=0일 때 한 번, 안쪽 루프는 j=0일 때 한 번만 실행되게 됩니다. 개발자가 의도한 것은 문자열 전체의 길이를 기반으로 루프를 돌리는 것이었을 테지만, 개별 문자의 길이를 가져오는 실수를 한 것입니다.

2.2. return 문의 부적절한 위치: 함수 즉시 종료

또 다른 중요한 원인은 return s 문이 중첩 루프 내부에 위치한다는 점입니다.

                    print(i, j)
                    return s

파이썬에서 return 문이 실행되면 해당 함수는 즉시 종료되고, 지정된 값을 반환합니다. 따라서 i=0, j=0일 때 print(0, 0)이 실행된 직후 return s가 호출되어 함수가 종료되고 원본 문자열 "babad"를 반환하게 되는 것입니다. 루프가 더 이상 진행될 수 없었던 이유가 여기에 있습니다.

3. 해결 방법 및 코드 예시

위에서 설명한 두 가지 원인을 해결하면 문제는 간단히 해결됩니다. 핵심은 range() 함수의 인자를 올바르게 사용하고, return 문을 적절한 위치에 배치하는 것입니다.

3.1. range() 함수 인자 수정: 문자열 전체 길이 활용

문자열의 모든 인덱스를 순회하려면 len(s)를 사용해야 합니다. 또한, 부분 문자열을 탐색하는 일반적인 패턴을 고려하여 바깥 루프는 시작 인덱스, 안쪽 루프는 끝 인덱스를 담당하도록 수정할 수 있습니다.

            # 기존: for i in range(len(s[1])):
            # 수정: for i in range(len(s)): # 문자열 s의 전체 길이를 사용
            
            # 기존: for j in range(len(s[2])):
            # 수정: for j in range(i, len(s)): # 시작 인덱스 i부터 문자열 끝까지 탐색 (부분 문자열 생성)

3.2. return 문 위치 조정: 루프 완료 후 최종 결과 반환

return 문은 모든 계산이 완료된 후, 즉 중첩 루프 바깥에서 최종적으로 찾아낸 가장 긴 팰린드롬 문자열(Max_str)을 반환하도록 이동해야 합니다.

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            if len(s) <= 1:
                return s
    
            Max_len = 1
            Max_str = s[0]
    
            # 올바른 루프 범위 설정
            for i in range(len(s)):  # 문자열 전체를 순회하는 시작 인덱스
                for j in range(i, len(s)): # 시작 인덱스 i부터 문자열 끝까지 순회하는 끝 인덱스
                    # print(i, j) # 디버깅용으로 남겨둘 수 있습니다.
                    # TODO: 이 안에서 팰린드롬 여부를 확인하고 Max_len과 Max_str 업데이트 로직 추가
                    
                    # 'return s' 문은 이 위치에서 제거되어야 합니다.
                    # 이 위치에 있으면 루프가 한 번만 돌고 함수가 종료됩니다.
    
            return Max_str # 모든 루프가 끝난 후, 최종적으로 찾은 Max_str을 반환

위 코드 스켈레톤은 루프의 작동 방식을 정상화하며, 이제 팰린드롬 검사 로직과 Max_len, Max_str 업데이트 로직을 추가하여 LeetCode 문제를 마저 해결할 수 있습니다.

4. 향후 예방을 위한 팁

비슷한 실수를 반복하지 않고 효율적으로 코딩하기 위한 몇 가지 팁입니다.

4.1. 파이썬 내장 함수의 작동 방식 정확히 이해하기

len(), range() 등 자주 사용되는 파이썬 내장 함수의 인자와 반환 값을 정확히 이해하는 것이 중요합니다. 특히 len()은 문자열 전체의 길이를 반환하며, 개별 문자의 길이는 항상 1입니다. 자료형(Data Type)에 따라 함수의 동작이 달라질 수 있음을 항상 염두에 두세요.

4.2. return 문의 의미와 스코프 이해하기

return 문은 함수를 종료하고 값을 반환하는 역할을 합니다. 루프 안에 return 문이 있으면 루프의 반복 횟수와 관계없이 해당 return 문이 실행되는 즉시 함수가 종료된다는 것을 명심해야 합니다. 최종 결과를 반환할 때는 모든 계산이 완료된 후, 함수의 가장 바깥 스코프에서 호출되도록 주의 깊게 배치해야 합니다.

4.3. 디버깅 습관 들이기

print() 문을 활용하여 변수의 값이나 코드의 실행 흐름을 추적하는 것은 매우 효과적인 디버깅 방법입니다. 문제가 발생했을 때, 의심되는 부분에 print() 문을 삽입하여 현재 어떤 값이 어떤 순서로 처리되고 있는지 확인하는 습관을 들이는 것이 좋습니다. PyCharm이나 VS Code와 같은 IDE의 디버거를 사용하는 것도 큰 도움이 됩니다.

4.4. LeetCode 문제 단계별 접근

LeetCode와 같은 알고리즘 문제를 풀 때는 한 번에 모든 것을 해결하려 하기보다 작은 단위로 쪼개어 접근하는 것이 좋습니다. 지금처럼 “루프가 제대로 도는지”와 같은 기본적인 동작부터 확인하고, 그 다음 팰린드롬 검사, 최대 길이 업데이트 등의 로직을 차례로 구현해나가는 것이 오류를 줄이고 문제 해결 효율을 높이는 방법입니다.

이 문서를 통해 파이썬 중첩 루프와 return 문 사용에 대한 이해가 깊어지고, 앞으로 더 견고한 코드를 작성하는 데 도움이 되기를 바랍니다. 행복한 코딩하세요!

댓글 남기기