트리 이론을 공부한 후 가장 처음으로 건드려보려고 한 문제가 바로 이 문제다.

preorder (root --> left --> right) traversal의 순서는 알았지만, 

정확히 이것을 리스트로 받고 처리를 하는 것인지에 대해서 고민이 많았다.

 

그리고 현재 계속 "재귀"에 대한 이해를 제대로 잡고 가기 위해서 이번에는 재귀를 제대로 잡자는 생각으로 재귀로 코드를 짜 보고자 하였다.

 

우선 과거에 자료구조를 공부할 때, C언어로 잠깐했었는데, 언뜻 기억났던 코드가 바로 다음과 같은 코드였다.

void traversal(node){
    if ( node){
        visit(node); preorder
        traversal(node -> left);
        #visit(node); inorder
        traversal(node -> right);   
        #visit(node); postorder
    }  
}

 

그럼 위의 코드를 이제 리트코드에 준 문제에 맞게 만들어야 했는데,

정말로 고민을 많이 했다.

 

어디서 어떻게 코드가 들어가야 하고,

재귀는 정확히 어떻게 이루어지는지가 고민이었다.

 

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        visited = []
        if root is not None:
            visited.append(root.val)
            # print(root.val, "afterAppend", visited)
            visited.extend(self.preorderTraversal(root.left))
            # print(root.val, "afterLeft", visited)
            visited.extend(self.preorderTraversal(root.right))
            # print(root.val, "afterRight", visited)
        return visited

 

여기서 visited.extend도 핵심이다.

 

처음에 나는 그냥 self.preorderTraversal(root.left)라고 했었다.

그런데 이렇게 하다보니, visited를 return 했을 때, 제대로 이 값들을 추가를 시켜주지 못했다.

그러다 보니 답이 [1]에서 멈춰있었다. 

 

그래서 제대로 어떻게 코드가 처리되고 있는지를 알기 위해서 위에 주석 처리된 print문들을 찍게 되었고,

이를 직어보니 [1] [2] [3]과 같이 재귀를 돌 때 제대로 숫자는 리턴 하지만,

최종적으로 값들을 하나의 리스트에 저장하지 못했다는 점이 있었다.

 

따라서, 이를 해결하기 위해서, 재귀를 해서 리턴하는 값을 리스트에 넣는 코드로 수정이 필요했고,

이를 위해. extend()를 사용하게 되었다.

 


아직도 해당 코드를 보아도 제대로 이해를 했다고 보기는 어려운 것 같다.

다른 사람에게 설명하라고 하면 내가 제대로 설명을 하고 있는가에 대해서 스스로 의문을 가지고, 

설명하는 도중에도 왠지 막힐 것 같다는 생각이 든다.

 

따라서, 해당 문제는 계속 더 공부가 필요하다고 느낀다.

 

 

 

최근에 트리 이론과 재귀를 제대로 이해하고 이제 직접 코딩으로 구현을 해보는 연습을 하고 있는 중이다.

그 중에 내가 풀어놓고 답인지 아닌지 햇갈렸던 문제 중 하나가 위의 문제였다.

일단 답은 다음과 같이 간단하게(?) 구현이 가능하다.

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        else:
            return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

 

일단 if not root 부분은 base 코드, 즉 재귀를 끝내기 위한 코드인 것은 금방 추론이 가능하다.

문제는 return 부분이다.

 

최대한 쉽게 말로 풀이를 해보자면 이렇게 된다.

  1. 우선 3으로 root가 시작된다
  2. 3의 root.left는 9가 된다.
  3. self.maxDepth(root.left)에는 9가 들어가는 것이므로, maxDepth(self, 9)가 된다.
  4. 9가 들어오면 일단 다시 else문으로 가게 된다.
  5. 9일 때, self.maxDepth(root.left)와 self.maxDepth(root.right)는 둘 다 null이므로, return max(0, 0)+1이 되므로 return 1이 된다.
  6. 따라서 이제 9일 때 1이 나오기 때문에 재귀한 것을 타고 올라가면, 맨 처음 root가 3이었을 때, max(1, self.maxDepth(root.right))+1인 것을 알 수 있다.
  7. 이제 self.maxDepth(root.right)도 똑같이 해주면 (이 경우에는 20이 들어가고, 들어간 후 각각 15, 7이 나올 것이다)), 결국 root가 3이었을 때, return self.maxDepth(1,2)+1이 된다.
  8. 따라서, 결론적으로는 return 2+1 즉, return 3이 나온다.

재귀는 계속 풀어봐야하는 것을 느낀다.

과거에 여러 번 코딩테스트 시도를 실패했던 첫 번째 관문 중 하나가 나에게는 항상 재귀였다 (이를 활용한 트리도...).

계속 여러 문제를 이번에는 끈덕지게 풀어보고자 한다.

이 문제도 시간이 많이 걸렸다.

최근에 느낀 것이지만 문제를 풀 때 이상하게 append와 pop을 많이 사용하는 경향이 있었는데,

왜 그런가 고민해보니 C언어스럽게 나도 모르게 파이썬 코드를 짜고 있었다는 것을 알게 되었다.

스트링이나 숫자들을 모두 리스트에 넣고, 리스트의 인덱스를 사용해서 만드는 방법이 대표적으로 C언어스럽다는 것이다.

이 문제도 결국 그렇게 풀고,

문제 푼 코드의 지저분함을 느끼고 계속 쳐다본 결과 그렇게 된 것 같다.

 

그리고 코드를 볼 때 참고해야할 사항이 몇 가지 있는데...

1. 조건인 constraint를 제대로 읽지 않았다. 알고보니 testcase의 입력값들이 모두 양수로 되어있다는 것이었다. 음수값의 경우도 모두 처리해야하는 줄 알고 if elif else문을 사용했다.

 

2. contraint를 알고 원래 만들었던 코드를 그대로 사용해보았더니 그래도 지저분했다.

 

3. 그래서 파이써닉한 코드를 한 번 만들어보고 "remove()" 함수를 알게 되어 "pop"대신 사용할 수 있게 되었다.

-그동안 pop()을 계속 사용한 이유는 remove()를 제대로 몰랐기 때문.

-pop()은 index값을 넣어야 하고, remove()는 지우고자 하는 숫자/문자를 넣으면 된다.

 

4. 마지막의 코드는 sort()를 사용한 것인데, 가장 간단하고 직관적인 코드이다. 다만, 효율성을 배제하고 생각해야 한다. 시간적인 면에서는 가장 느리다는 특징이 있다.

#constraint 제대로 확인 안하고 음수까지 모두 처리 (77ms)
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        nums_abs = [abs(ele) for ele in nums] # 절대값으로 바꾼다. O(n)
				nums_abs is ...
				list of abs(ele) for each ele in nums
				dictionary of ...

        top_maxIndex = nums_abs.index(max(nums_abs))
        top_positive_max = nums_abs.pop(top_maxIndex)
        next_maxIndex = nums_abs.index(max(nums_abs))
        next_positive_max = nums_abs.pop(next_maxIndex)


        if nums[top_maxIndex] > 0 and nums[next_maxIndex] > 0:
            return ((top_positive_max-1) * (next_positive_max-1))
        elif nums[top_maxIndex]<0 and nums[next_maxIndex]<0:
            return ((top_positive_max-1) * (next_positive_max-1))
        else:
            if nums[top_maxIndex] < 0:
                nums.pop(next_maxIndex)
                # print("if", nums[top_maxIndex])
                return ((max(nums)-1) * (next_positive_max-1))
            else:
                nums.pop(top_maxIndex)
                # print("else", nums[next_maxIndex])
                # print((max(nums)))
                return ((max(nums)-1) * (top_positive_max-1))



# constraint 제대로 보고 처리하면 다음과 같은 코드 가능(66ms)
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        top_maxIndex = nums.index(max(nums))
        top_positive_max = nums.pop(top_maxIndex)
        next_maxIndex = nums.index(max(nums))
        next_positive_max = nums.pop(next_maxIndex)
        return ((top_positive_max-1) * (next_positive_max-1))

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
		mx = max(nums)
		nums.remove(mx)

		next_mx = max(nums)
		# nums.remove(next_mx)

		return (mx-1)*(next_mx-1)


# 더 짧고 빠르게 sort()를 활용하여 하면 다음과 같은 코드
# 그런데 sort() 함수가 속도가 많이 느린 것인지 속도 자체는 위의 2개의 코드 중 제일 느리게 나온다 (101ms)
# 효율성을 떠나 가장 파이썬스러운 코드
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        nums.sort()
        return (nums[-1]-1) * (nums[-2]-1)

 

 

출처: https://leetcode.com/problems/maximum-product-of-two-elements-in-an-array/

 

Maximum Product of Two Elements in an Array - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

해당 문제를 굉장히 헤맸다.

# time limit exceeded 3번
# wrong answer 4번
# runtime error 1번 - leetcode는 print 대신 return사용해야되는데 print 사용함

총 8번을 틀렸는데...

 

wrong answer는 총 3가지의 경우 때문에 나왔다.

(1) len(nums) <= k에서 =를 실수로 넣지 않아서

(2) update_max/k 를 update_max만 리턴해서

(3) 음수를 염두해야하는데 특정 변수의 초기값을 0으로 해놓고 코드에서 제대로 업데이트를 시켜주지 않아서

 

time limit exceeded 3번이 정말로 힘들었다.

계속 여러 번 시도해본 결과 문제는 아마도 sums를 계속 k개의 숫자만큼 계산을 n.length-(k-1)만큼 반복하게 해서 그런듯 하다.

오류의 코드는 다음과 같다.

#Time Limit Exceeded
nums_four_list = []
total = []
if len(nums) == k:
    print(sum(nums)/k)
for i in range(len(nums)-(k-1)):
    nums_four_list = nums[i:i+k]
    total.append(sum(nums_four_list))

print(max(total)/k)
#Time Limit Exceeded
initial_max_total = sum(nums[0:k])
new_max = 0
update_max = 0
if len(nums) < k:
    print(sum(nums)/k)
for i in range(len(nums)-(k-1)):
    # print(update_max)
    new_max = sum(nums[i:i+k])
    if i == 0:
        if new_max >= initial_max_total:
            update_max = new_max
            newmax = 0
    else:
        if new_max > update_max:
            update_max = new_max
            newmax = 0

print(update_max/k)
#Time Limit Exceeded
update_max = sum(nums[0:k])
new_max = -9999999
update_max = -9999999
if len(nums) <= k:
    print(sum(nums)/k)
    
for i in range(len(nums)-(k-1)):
    new_max = sum(nums[i:i+k])
    if new_max > update_max:
        update_max = new_max
    # print(update_max)

# print(update_max/k)
print(update_max/k)

 


 

계속 틀리다가 결국 solution을 보게 되었다.

solution에서 주는 하나의 풀이는 sums의 계산을 단 1번만 초기에 하고,

반복적으로 {

현재값과 다음 index의 값을 더한 후, 이전에 구했던 index들 중 가장 초기값을 빼는 것 ----- (a)

그리고 이렇게 구한 이전의 max값과 (a)의 결과를 max를 통해 구한 후,

이를 best값으로 정한다.

}

© https://leetcode.com/problems/maximum-average-subarray-i/solution/
## solution
best = 0
now = sum(nums[0:k])
best = now
for i in range(k,len(nums)):
    now += nums[i] - nums[i-k]
    best = max(best,now)
print(best/k)

 

확실히 solution을 보니 time limit exceeded가 나온 나의 코드보다 훨씬 계산이 줄어들었다는 것을 깨달았다.

이런 형태를 아마 "sliding window"라고 하는 듯하다.

이런 문제해결 방법은 아예 생각을 하지 못했으니 후회는 없지만,

확실히 아쉽다는 생각이 든다.

 

 

출처: https://leetcode.com/problems/maximum-average-subarray-i/

 

Maximum Average Subarray I - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

결론부터 말하자면 폴더명이나 파일명에 "괄호"를 삭제하는 것이다!!!!!


파이썬 공부를 다시 시작하려고 하는데 맥북 프로로 처음 시작하게 되어 다시 모든 개발환경을 다시 설치하게 되었다.

그러면서 그동안 프로그래밍을 공부하면서 단 한 번도 접하지 못한 이놈의 오류 때문에 2~3일 동안 헤맸던 것 같다...

 

zsh: no matches found: 

 

VSCode를 사용해서 파이썬을 run / build시키려고 할 때 발생했던 문제이다.

 

오류가 정확히 뜨는 형태는 다음과 같다.

/usr/local/bin/python3  ../usr/~~~/Desktop/특정폴더명/특정파일명.py
zsh: no matches found:

 

나 같은 경우, f5로 디버깅 하려고 할 때와 extension을 통해 설치한 run code 버튼을 누를 때마다 발생하는 문제였다.

그러면서 또 명령창에

python3 특정파일명.py

로 하면 프로그램이 잘 돌아간다...

 


해당 문제를 해결하는 방법은 너무도 간단하다.

 

위의 오류 코드에 보이는 "특정폴더명"과 "특정파일명"에 괄호가 들어가지 않도록 하는 것이다.

내가 사용했던 폴더명은 "tutoring(code)"였는데,

이를 "tutoring_code"로 바꾸니 아주 잘 실행되기 시작했다... 허허

 

아마 "특정파일명.py"에 괄호가 있었다면,

python3 특정파일명.py

위의 명령어도 제대로 실행되지 않았을 것이라고 추측된다. 

제대로 실행된 화면 예시

Python Overview

Features of python

  • 플랫폼 독립적인 인터프리터 언어
  • 완전 객체 지향 언어
  • 동적 타이핑 언어

 

Variable & Operator

How to Name Variables

  • 알파벳, 숫자, 언더스코어(_)로 선언
  • 변수명은 그 변수의 특징이 잘 살아 있게 하자(가독성)
  • 변수명은 대소문자가 구분
  • 변수명으로 쓸 수 없는 예약어가 존재

 

Out-place VS In-place 연산

  • Out-place: 명시적으로 새로운 객체 생성  // a = a + 1
  • In-place: 기존 객체를 수정 시도하고, 불가능할 시 새로운 객체 생성  //  a += 1

 

Primitive Data Types(Immutable / Mutable Types)

  • Immutable Type (불변 타입)이다
  • Python의 모든 것은 객체  Primitive Data Type 들 역시 객체
  • *불변 타입들은 저장된 값이 변하지 않는다!
  • 모든 타입은 Physical Memory 주소를 가르침
  • Primitive Data Type과 Tuple을 제외한 다른 모든 파이썬 객체는 Mutable Type (가변 타입)

파이썬에서 대입은 메모리 주소 복사의 원칙적 

  • 값을 복사하지 않고 같은 주소를 공유
  • immutable인데 수정이 필요하면 새로운 객체 생성

primitive data 크기에 따른 객체 할당 방법

  • 흔한 객체는 기존 객체를 들고 온다
  • 복잡한 값을 가지면 객체를 새로 형성

 

Types

* Dynamic Typing:

데이터 타입은 코드 실행 지점에서 정해진다 // a = 10  VS  int a = 10


* Implicit Type Conversion

: bool → int → float → complex 순서로 타입이 정해진다


* Explicit Type Conversion

: [Type]([value])로 명시적 형 변환


* Type Checking

: type(), isinstance([variable], [type])

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# '-'를 기준으로 나눠서 입력 받는다.
equation = input().split('-')
minList = []
print(equation)
# '-'로 나뉜 각 index들의 합을 구해서 리스트로 만든다.
for i in equation:
    # '+'이면 합을 구하면 되므로, 숫자만 있는 리스트로 바꾼 후, sum을 구한다.
    beforeSum = list(map(int, i.split('+')))
    print(beforeSum)
    minList.append(sum(beforeSum))
    # print(beforeSum, minList)
print(minList)
res = minList[0]
for i in minList[1:]:
    res = res - i
print(res)
 
cs

 

문제 출처: https://www.acmicpc.net/problem/1541

 

1541번: 잃어버린 괄호

첫째 줄에 식이 주어진다. 식은 ‘0’~‘9’, ‘+’, 그리고 ‘-’만으로 이루어져 있고, 가장 처음과 마지막 문자는 숫자이다. 그리고 연속해서 두 개 이상의 연산자가 나타나지 않고, 5자리보다

www.acmicpc.net


다음과 같이 코드를 만들면 된다.

1
2
3
4
5
6
7
8
9
from itertools import combinations
num =  int(input())
nums = []
for i in range(1, num):
    nums.append(i)
# print(nums)
combis = list(combinations(nums, 3))
# print(combis)
print(len(combis))
cs

 

 

이전에 https://gettingtoknowit.tistory.com/129?category=919340 에서 combination 함수를 사용했던 적이 있다.

 

[백준] 2798_블랙잭 파이썬 (combination함수 vs 3중for문)

1번 방법: combination함수 우선 상당히 복잡하게 푼 방법부터 보이자면 다음과 같다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from itertools import combinations N, M = map(int,..

gettingtoknowit.tistory.com

똑같이 이번 문제에서도 사용하면 된다.

 

그리고 중요한 부분은 언제나 그렇듯 "입력값"을 제대로 아는 것이다.

입력값을 보면 "The input will be the positive integer J (1 ≤ J ≤ 99), which is the jersey number of the goal scorer."라고 되어있다.

즉, goal scorer의 번호를 입력하기 때문에, 4명 중 마지막 4번째 사람의 번호를 입력한다는 뜻이고, 

문제의 조건에 따라 마지막 1명은 항상 고정된 숫자로 만든다면,결국 조합은 3명의 조합의 갯수만 구하면 된다! (그래서 7행을 보면 3만 뽑아서 쓴다)

 

 

문제 출처: https://www.acmicpc.net/problem/6768

 

6768번: Don’t pass me the ball!

A CCC soccer game operates under slightly different soccer rules. A goal is only counted if the 4 players, in order, who touched the ball prior to the goal have jersey numbers that are in strictly increasing numeric order with the highest number being the

www.acmicpc.net


등급이 실버III 이라고 되어있긴 하지만, 의외로 간단하게 해결이 가능하다.

다만, 그리디 알고리즘인만큼, 숫자들에 대한 규칙을 잘 찾아내는 것이 좋다고 생각된다.

1
2
3
4
5
6
7
8
= int(input())
line = list(map(int, input().split()))
line.sort() # 가장 큰 수를 가장 마지막으로 보내버리자
total = 0
for i in range(n):
    for j in range(i+1):
        total += line[j]
print(total)
cs

가장 큰 숫자를 가장 뒤로 옮기면서, 정렬시켜서 다음과 같이 덧셈을 처리하는 과정을 찾을 수 있다.

1 = 1
1 + 2 = 3
1 + 2 + 3 = 6
1 + 2 + 3 + 3 = 9
1 + 2 + 3 + 3 + 4 = 13
==> 1 + 3 +  6 + 9 + 13 = 32

이를 코드로만 옮겨서 만들면 끝!

 

 

 

문제 출처: https://www.acmicpc.net/problem/11399

 

11399번: ATM

첫째 줄에 사람의 수 N(1 ≤ N ≤ 1,000)이 주어진다. 둘째 줄에는 각 사람이 돈을 인출하는데 걸리는 시간 Pi가 주어진다. (1 ≤ Pi ≤ 1,000)

www.acmicpc.net

 


일단 코드는 다음과 같이 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cityNum = int(input())
roads = list(map(int, input().split()))
price = list(map(int, input().split()))
 
minPrice = price[0]  # 주유소 리터당 가격이 가장 작은 것
res = 0
# 마지막 도시(=도착지)의 L는 따질 필요가 없다.(=n-1 도시의 수 만큼 반복)
for i in range(cityNum-1):
    if minPrice <= price[i]:
        res = res + (minPrice * roads[i])
    elif minPrice > price[i]:
        minPrice = price[i]
        res = res + (minPrice * roads[i])
print(res)
cs

 

전략 설명
[예시 그림]
   3      2      1
A ---> B ---> C ---> D
5L     2L     3L     1L

1. 첫 번째 도시에서는 무조건 다음 도시까지의 거리만큼을 주유해야 한다.
따라서, 가장 작은 주유소 리터당 가격(=minPrice)를 첫 번째 도시에서 가지게 된다.
[예시] A도시에서 주유를 하면 무조건 5L 를 3만큼 가야 한다.
따라서, minPrice = 5로 시작해서, res=5L*3이 된다.

2. 다음 도시부터는 도시에서 가지는 리터당 주유소 가격이 가장 작은 값으로 계속 그다음 도시까지의 거리만큼 넣으면 된다.
[예시] B도시에서 주유를 하면 이전도시(A도시)보다 주유소 리터당 가격이 작으므로 (5L > 2L), minPrice = 2L로 바뀌고,
다음 도시(C도시)까지의 거리만큼 이동해서 res = res + (2L * 2) 가 된다.

3. 마지막 도시에 도착할 때까지 2번(minPrice와 res 업데이트) 반복
[예시] C도시에서 B도시의 리터당 주유소 가격이 더 크므로 (2L < 3L), 
더 작은 가격을 가졌던 이전 도시(B도시)에서 C에서 D로 가는 거리만큼 더 넣어야 최소비용이 들어감을 알 수 있다.
따라서, res = res + (2L * 1)이 된다.

최종 값은 (5L*3) + (2L * 2) + (2L * 1) 가 된다.



반성
전략 자체에 대해서 떠오르는데 어려웠다...

 

 

문제 출처: https://www.acmicpc.net/problem/13305

 

13305번: 주유소

표준 입력으로 다음 정보가 주어진다. 첫 번째 줄에는 도시의 개수를 나타내는 정수 N(2 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 인접한 두 도시를 연결하는 도로의 길이가 제일 왼쪽 도로부터 N-1

www.acmicpc.net

+ Recent posts