본문 바로가기
algorithm

dfs 유형의 문제 [프로그래머스 & leetcode]

by hoonzii 2021. 9. 20.
반응형

프로그래머스에서 풀었던 문제가 마침 leetcode에서 비슷하게 출제되어 푸는김에 같이 정리하는게 나을듯하다.

dfs란?

(위키피디아 설명)

그림으로 보자면,

마지막 노드까지 전부 확인한 뒤, 이동하는 것을 볼 수 있다.

문제를 보자

 

문제 설명

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다.

  • 1+1+1+1+1 = 3
  • +1-1+1+1+1 = 3
  • +1+1-1+1+1 = 3
  • +1+1+1-1+1 = 3
  • +1+1+1+1-1 = 3

사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target이 매개변수로 주어질 때 숫자를 적절히 더하고 빼서 타겟 넘버를 만드는 방법의 수를 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • 주어지는 숫자의 개수는 2개 이상 20개 이하입니다.
  • 각 숫자는 1 이상 50 이하인 자연수입니다.
  • 타겟 넘버는 1 이상 1000 이하인 자연수입니다.

글로 보니 그래프?가 안떠오를 수 있다. 여러 경우의 수를 그래프로 바꿔보면

직접그린그림 ㅎㅎ

이렇게 보면 노란색 부분 경로를 통해 이동할 경우 마지막 값이 문제가 요구하는 target 값으로 나오는 걸 확인할 수 있다.

그럼 구현전에 로직을 정리해보자

  1. 현재 진행된 순서가 마지막인지 확인한다.
    1. 아니라면 다음 노드로 연산된 결과를 넘긴다.
    2. 맞다면 return 1!
  2. 현재까지 연산된 결과가 target 값인지 확인한다.
    1. 아니라면 return 0!

코드로 확인해보면,

def dfs(numbers, current_num, target, depth):
    if depth == len(numbers): # 현재 진행된 순서가 마지막인지!
        if current_num == target: # 현재 연산결과가 target과 같은지!
            return 1
        else:
            return 0
    else: # 진행이 아직 남았다면 depth를 늘려 더 깊이 들어가기
        depth += 1 
        return 
dfs(numbers, current_num + numbers[depth-1], target, depth) #더하기 부분 노드로 내려가기
+dfs(numbers, current_num - numbers[depth-1], target, depth) #빼기 부분 노드로 내려가기

def solution(numbers, target):
    answer = 0
    
    answer = dfs(numbers, 0, target, 0) # 해당 재귀함수 실행!
    
    return answer

그림으로 보고, 실제 로직을 구성하고, 해당 로직을 코드로 구현하니 dfs에 대해 좀 더 잘 이해할 수 있었다.

이렇게 푼 이후 leetcode에도 동일한 문제를 발견해 하나 풀어 보았다.

먼저 문제 부터 보자.

 

282. Expression Add Operators

Hard

Given a string num that contains only digits and an integer target, return all possibilities to add the binary operators '+', '-', or '*' between the digits of numso that the resultant expression evaluates to the target value.

Example 1:

Input: num = "123", target = 6 Output: ["1*2*3","1+2+3"]

Example 2:

Input: num = "232", target = 8 Output: ["2*3+2","2+3*2"]

Example 3:

Input: num = "105", target = 5 Output: ["1*0+5","10-5"]

Example 4:

Input: num = "00", target = 0 Output: ["0*0","0+0","0-0"]

Example 5:

Input: num = "3456237490", target = 9191 Output: []

Constraints:

  • 1 <= num.length <= 10
  • num consists of only digits.
  • 231 <= target <= 231 - 1

target값을 맞추는 건 똑같지만, 이번엔 answer로 몇개가 아닌 해당 target값을 반환하게 한 연산식들을 반환해야 한다.

로직을 살짝 변형 시키면 된다

  1. 현재 진행된 순서가 마지막인지 확인한다.
    1. 아니라면 다음 노드로 연산된 결과를 넘긴다.
    2. 맞다면 return operation_String
  2. 현재까지 연산된 결과가 target 값인지 확인한다.
    1. 아니라면 return None

풀면서 주의할 점이 몇가지 존재했다.

  1. 프로그래머스 문제의 경우 +/- 이기 때문에 순서에 구애받지 않고 값을 연산해 넘겼다. 하지만 *(곱하기) 연산의 경우 연산자 순서가 우선이기 때문에 마지막에 한꺼번에 계산했어야 했다.
  2. 숫자와 숫자 사이에 연산자가 존재하지 않을 수 도 있었다. 105 → 5의 경우 "10-5" 결과도 나와야 한다.
    1. 위 조건때문에 "1*05" 같은 경우도 연산식 리스트에 후보로 올라가게 된다. 해당 문제는 걸러낼수 있어야 한다.
  3. 연산식 스트링을 연산해주는 eval 함수를 사용했는데, 해당 조건의 경우 "00" → "0"으로 알아서 치환해 풀어준다. 문제점은 연산식 스트링에 해당 결과가 포함된다는 것이다. "1+00+5" 같이

위 주의 사항들을 주의 하면서 풀어야 한번에 풀 수 있다. 어떻게 알았냐면

이미 틀려봤기 때문에...

코드로 살펴보자

import re

def dfs(num, depth, target, operation, answer):
    if len(num) == depth: #마지막 노드에 도달했는지?
        try: # "1+05" 같은 경우 eval 함수 error 발생하므로 try로 제거
            if eval(operation) == target: # 연산결과가 target값과 같은지
                operation_num_list = re.split("[+*-]",operation)
                for op_num in operation_num_list: 
					#연산자를 제거하고 "00"로 시작하는 문자가 존재하면 잘못된 연산식이므로...
                    if op_num.startswith("00"):
                        return
                answer.append(operation)
                return
            else:
                return
        except:
            return
    else:
        depth += 1
				# 분기점을 나눠서 dfs 재귀식 설정
        dfs(num, depth, target, operation+"*"+num[depth-1], answer)
        dfs(num, depth, target, operation+"+"+num[depth-1], answer)
        dfs(num, depth, target, operation+"-"+num[depth-1], answer)
        dfs(num, depth, target, operation+num[depth-1], answer)
    
class Solution:
    def addOperators(self, num: str, target: int) -> List[str]:
        answer = []
        
        dfs(num, 1, target, num[0], answer) # 재귀식 실행
        
        return answer

dfs 알고리즘을 이용해 문제를 풀어보았다.

둘다 재귀를 통해 풀었는데 풀기 전에 하나 유념해야 할 것은 재귀는 depth가 길어질수록 엄청 오래걸리고,

잘못하다가는 stack overflow가 발생할 수 있기 때문에 될 것같은 각일 때만 사용해야 한다.

반응형

댓글