Algorithm

[프로그래머스]콜라츠 추측 - Kotlin / 꼬리 재귀에 관해

jwkane 2024. 4. 22. 22:54

1.문제

1-1 문제 이해

 

1. 주어진 값 짝수 -> 나누기 2

2. 주어진 수 홀수 -> 곱하기3 더하기1

3. 위 과정을 주어진 수가 1이 될 때 까지 반복

 

각 주어진 수에 따라 몇번 반복했는지 

반복 횟수를 구하는 문제

 

단, 500번이 넘어가면 -1 리턴

 

1-2 풀이 과정

1. 1이 될 때 까지

 

특정 조건까지 계속 반복해야 하기에 while을 이용했다.

class Solution {
    fun solution(num: Int): Int {
        var input = num
        while(input != 1){ // 1이 될때까지
            
        }
    }
}

2. 짝수면 나누기 2 / 홀수면 곱하기3 더하기1

while속에 if문을 통해 

주어진 수를 2로 나눈 나머지가 0인게 맞으면 짝수이므로 

주어진 수 나누기 2

 

아니라면

주어진 수 곱하기3 더하기 1 조건을 주었다.

class Solution {
    fun solution(num: Int): Int {
        var input = num
        
        while(input != 1){
            if(input % 2 == 0){     	//주어진 수를 2로 나눈 나머지가 0인게 맞으면 짝수
                input /= 2          	// 주어진 수 나누기 2
            }else{ 						// 아니라면
                input = input *3 +1     // 주어진 수 곱하기3 더하기 1
            }
        }
    }
}

3. 반복 횟수 카운트

각 작업 총 횟수를 구하기 위해 if문 속에

한 작업을 할때마다 answer 변수의 값을 1씩 증가 시켰다.

class Solution {
    fun solution(num: Int): Int {
        var answer = 0 				// 초기화
        var input = num
        
        while(input != 1){
            if(input % 2 == 0){
                input /= 2
                answer += 1			// 작업당 1씩 증가
            }else{
                input = input *3 +1
                answer+= 1			// 작업당 1씩 증가
            }       
        }
        return answer				
    }
}

 

4. 반복 횟수 500회 넘어갈 시 -1 리턴

만약 작업 횟수가 500회가 넘으면 

-1 리턴 

 

break로 빠져나오기

class Solution {
    fun solution(num: Int): Int {
        var answer = 0
        var input = num
        
        while(input != 1){
            if(input % 2 == 0){
                input /= 2
                answer += 1
            }else{
                input = input *3 +1
                answer+= 1
            }
            if(answer > 500){ // 작업 횟수가 500회가 넘으면
                answer = -1   // -1 리턴
                break         // while문 빠져나오기
            }
        }
        return answer
    }
}

 

 

엇.. 마지막 부분의 문제점을 발견했다.

 

정말 여러가지 생각해봤는데 

 

혹시 자료형의 문제는 아닐까라고 생각하여

 

int를 Long으로 바꾸어 보았다.

class Solution {
    fun solution(num: Int): Int {
        var answer = 0
        var input:Long = num.toLong() 	 // Long으로 형변환
        
        while(input != 1.toLong()){ 	 // Long으로 형변환
            if(input % 2 == 0.toLong()){ // Long으로 형변환
                input /= 2
                answer += 1
            }else{
                input = input *3 +1
                answer+= 1
            }
            
            if(answer == 500){
                answer = -1
                break
            }
            
            
        }
        return answer
    }
}

 

 

 

 

오 맞았다!

 

2. 다른 문제 풀이

 

2-1 tailrec(꼬리재귀)

 

# when을 품은 재귀 함수 생성

	tailrec fun collatzAlgorithm(n:Long, c:Int):Int = // 매개변수로 주어질 수, 카운트
        when{										  
            c > 500 -> -1						// 카운트가 500을 넘기면 -1 리턴
            n == 1L -> c						// 주어진 수가 1이 되면 카운트 리턴
            else -> collatzAlgorithm(if( n%2 == 0L ) n/2 else (n*3)+1, c+1)
            // 두 조건 중 하나 될 때까지 스스로 계속 루프(문제의 조건)
        }

 

 

# 최종 답변

 

class Solution {
    fun solution(num: Int): Int = collatzAlgorithm(num.toLong(),0)
    							// num 받아오기, 카운트 시작
  	tailrec fun collatzAlgorithm(n:Long, c:Int):Int = // 매개변수로 주어질 수, 카운트
        when{										  
            c > 500 -> -1						// 카운트가 500을 넘기면 -1 리턴
            n == 1L -> c						// 주어진 수가 1이 되면 카운트 리턴
            else -> collatzAlgorithm(if( n%2 == 0L ) n/2 else (n*3)+1, c+1)
            // 두 조건 중 하나 될 때까지 스스로 계속 루프(문제의 조건)
        }
}

 

tailrec은 꼬리재귀(tail recursive)라는 의미

추가적인 연산 없이

자신 스스로 재귀적으로 호출하다가

어떤 값을 리턴하는 함수

 

특정 조건 충족될 때까지

반복적 로직 처리해야 하는 상황에 쓰일 수 있음

 

 

사실 이럴 때

while문을 사용하는 것이 일반적 + 성능상의 이점 제공

 

하지만

 

코드의 가독성 저하 가능성 있음

 

반면

재귀 함수 사용 시  코드 가독성 개선

But

성능 문제, 스택 오버플로우 위험