본문 바로가기

데이터사이언스/알고리즘

[LeetCode] Add Two Numbers : 파이썬 Class 활용하기

지금까지 내가 해왔던 파이썬은 주로 주가데이터 등을 끌고와서 그냥 Dataframe에서 간단한 데이터 전처리하는 정도에 불과했다. 그저 그래프만 간단하게 그리면 완전 뿌듯해서 끝났던 거 같은데, 그 결과 데이터 프레임을 9개를 그냥 날코딩으로 하는 경지에 이르렀고 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 결국 취업 준비 겸 알고리즘 스터디에 들어갔다,,, 그러나 문제는 파이썬 초급만 3회독 했는데 여전히 함수만드는 거랑 클래스 만드는 건 쩔쩔 매고 있다는 거.... ㅠ_ㅠ

# 파이썬 클래스의 "Self"

 

 

# Leetcode Add Two numbers

문제 링크와 공부할 때 참고 했던 동영상도 함께 첨부합니다.

https://leetcode.com/problems/add-two-numbers

 

Add Two Numbers - 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

https://www.youtube.com/watch?v=SbcCpAw_8Dg

* 해결하는 데 어려웠던 점?

  1. 인풋이 어떻게 들어가는지 몰랐음
  2. ListNode가 무엇을 의미하는 지 몰랐음.

1) 원리 (알고리즘)

출처 : LeetCode Solution

어릴 때 덧셈할 때나 지금도 가끔 4자리 5자리 수 덧셈 오랜만에 할 때 항상 위에 자릿수 올림 1 을 숫자 위에 조그맣게 적었던 기억들이 있을 것이다, 바로 그 원리를 이용해 각 자릿수 덧셈을 하는 것이다. 처음에 문제를 접근할 적에는 자릿수를 거꾸로 해야 한다는 점에서 연산 과정이 추가 되는게 아닐까? 생각했다. 그런데 생각해보면 1의 자리 수끼리 덧셈 -> 10의 자리수 덧셈 -> 100의 자리수 순서로 덧셈을 계산한다는 점에서 오히려 reversed 된 상태가 알고리즘 만들기 더 쉬운(?) 방법이다.

2) 대략의 알고리즘 (Pseudo Code)

  1. ListNode() 함수를 이용해 Linked List를 만드는 함수를 만든다. (이미 리트코드에서 제공)
  2. ListNode 함수의 숫자를 문제에서 원하는 꼴로 출력하는 함수를 만든다.
  3. 현재 노드와 carrier를 초기화한다.
  4. 일단 각 자리수에 맞춰 더한다. 각 자리수의 합이 10 이 넘으면 1의 자리수만 남기고 carrier에 1을 넘겨준다.
  5. 현재 값을 추가해야 다음 자리 수에 더할 수 있음.
  6. 각 자리수를 합하는 과정 : Linked List를 반복시켜서 (Add base cases for iterating linked listed)
  7. l1, l2에 들어갈 숫자를 하나하나 리스트 노드로 이어주고, l3로 결과 값을 원하는 형식에 맞게 출력한다.

 

3) 각 과정의 코드 구성

1. ListNode() 함수를 이용해 Linked List를 만드는 함수를 만든다. 

## 우선 데이터가 어떻게 들어오고 나가는 지 파악하기 위해

# 연결리스트 함수 만들기, ListNode 함수.
class ListNode(object):
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

문제에서 기본으로 제공하는 클래스 코드. 이 ListNode라는 클래스에 Object에 2를 넣으면, 기본값 0이던 val이 2 가 된다. 그리고  self.next에 다음 자리 숫자를 넣으면 self.next에 숫자가 계속(?) 저장된다. 사실 정확히 어떤 구조를 갖고 있는지는 모르겠다. next함수에 대해 추가적으로 공부가 필요할 듯.

#243 넣기 
god2=ListNode(2)
god2.next=ListNode(4)
god2.next.next=ListNode(3)

god2.val  
>>> 2
god2.next.val
>>> 4
god2.next.next.val
>>> 3

god2라는 객체에 243 (원래는 342)를 하나하나 리스트 노드를 이용해 linked list 형태로 표현했다. 위와 같이 .next.val을 붙이는 형태로 각 값을 꺼내야 한다. 

 

2. ListNode 함수의 숫자를 문제에서 원하는 꼴로 출력하는 함수를 만든다.

def linked_list_print(l):
    if l is None: 
        return ''
    return str(l.val)+'->'+linked_list_print(l.next)


linked_list_print(god2)  
>>> 2->4->3->

linked_list_print라는 함수를 만들어 leetcode에서 원하는 꼴로 출력할 수 있도록 했다.

 

3. 현재 노드와 carrier를 초기화한다.

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        p1 = l1
        p2 = l2 #l1, l2는 listnode
        carry=0
        head = cur = ListNode(0) #초기화 처리
        #'cur' 변수를 선언하여 트래버스(traverse) 작업을 지원하고 노드를 새 목록에 추가합니다.
        #'head' 변수를 목록의 머리글로 선언합니다.

우선 class Solution 안에는 addTwoNumbers라는 유일한 함수가 들어가 있다. addTwoNumbers는 나중에 ListNode() 함수를 통해 linked list 형태로 들어갈  l1(342->243), l2(465->564)가 들어갈 것이다. 그리고 우리가 덧셈 시 자릿수 올림(carry over)를 우선 0으로 초기화한다,

여기서 head와 cur이라는 변수도 ListNode(0)을 통해 0으로 초기화 해주는 데, 참고 동영상에서는 이 방법이 가장 단순하게 초기화 할 수 있는 방법이라고 소개하고 있다. 코드 후반부에서 cur과 next라는 변수에 각 자릿수의 덧셈값을 차례로 누적하고,  l3에 할당 된 후 출력된다.  아무튼 우선은 cur과 next 라는 변수는 결과값을 위한 변수라는 정도로 이해하고 넘어가자.

 

4. 일단 각 자리수에 맞춰 더한다. 각 자리수의 합이 10 이 넘으면 1의 자리수만 남기고 carrier에 1을 넘겨준다.

 while p1 or p2 or carry:
            print(p1.val, p2.val, carry)
            
            # CurrentVal과 carryover 를 계산
            currentval = carry  #첨에 0으로 초기화
            currentval +=  0 if p1 is None else p1.val
            currentval += 0 if p2 is None else p2.val
            if currentval >= 10 :
                currentval -=10
                carry = 1
            else:
                carry = 0
            
            print(currentval, carry)

이 과정에서 각 자리 수끼리의 덧셈이 이뤄지고, 자리수올림이 if currentval~ 이하를 통해 연산이 이루어진다, 즉, 자리수 끼리 덧셈이 5+6=11 이라면, 10을 뺀 1만이 currentval에 남고, carry=1이 되어 다음 자릿수 연산에 더해진다. print(currentval, carry)를 통해 중간중간 자릿수 덧셈이 잘 이뤄지는 지 확인할 수 있다.

>>>2 5 0
>>>7 0

>>>4 6 0
>>>0 1

>>>3 4 1
>>>8 0

간단히 설명하자면 342+465를 더할 때, 1의 자리 연산은 2+5 = 7, carry =0이 되고, 10의 자리 연산에서 4+6=10 이므로 currentval =0, carry=1 이 되어 다음 100의 자리에 자릿수 올림으로 더해진다.

 

5.

 # 현재값을 추가한다. 현재값은 항상 1 이상이여야 한다.
cur.next = ListNode(currentval)  #cur에 linkedlist(ListNode)이용해서 연결한다.
cur = cur.next

 

 

6.

if p1 is None and p2 is None:
	break   # 왜 자꾸 out of loop?
elif p1 is None:
	p2=p2.next
elif p2 is None:
	p1=p1.next
else:
	p1=p1.next
	p2=p2.next
        
return head.next

 

7.

# l1, l2 리스트 노드로 엮어주기

l1=ListNode(2)
l1.next=ListNode(4)
l1.next.next = ListNode(3)
print(linked_list_print(l1))


l2=ListNode(5)
l2.next=ListNode(6)
l2.next.next = ListNode(4)
print(linked_list_print(l2))

# Step 6: l3로 덧셈값 인쇄.
#add linked lists
s = Solution()
l3 = s.addTwoNumbers(l1,l2)
print(linked_list_print(l3))

 

# 최종 출력결과

2->4->3->
5->6->4->
2 5 0
7 0
4 6 0
0 1
3 4 1
8 0
7->0->8->

 

 

총 코드

## 우선 데이터가 어떻게 들어오고 나가는 지 파악하기 위해

# 연결리스트 함수 만들기, ListNode 함수
class ListNode(object):
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

#243 넣기 
god2=ListNode(2)
god2.next=ListNode(4)
god2.next.next=ListNode(3)

god2.val  
print(god2)

# recursive print linked list : 재귀 출력 linked list.
def linked_list_print(l):
    if l is None: 
        return ''
    return str(l.val)+'->'+linked_list_print(l.next)


linked_list_print(god2)  # 2->4->3 프린트 됨.

#-----------------------------------------------------------



# step 1 : Node, carry를 초기화 해준다.
class Solution(object):
    def addTwoNumbers(self, l1, l2):
        p1 = l1
        p2 = l2 #l1, l2는 listnode
        carry=0
        head = cur = ListNode(0) #초기화 처리
        #'cur' 변수를 선언하여 트래버스 작업을 지원하고 노드를 새 목록에 추가합니다.
        #'head' 변수를 목록의 머리글로 선언합니다.
        
        # Step 2 : 자릿수올림방법을 이용해 덧셈하는 부분 계산
        while p1 or p2 or carry:
            print(p1.val, p2.val, carry)
            
            # CurrentVal과 carryover 를 계산
            currentval = carry  #첨에 0으로 초기화
            currentval +=  0 if p1 is None else p1.val
            currentval += 0 if p2 is None else p2.val
            if currentval >= 10 :
                currentval -=10
                carry = 1
            else:
                carry = 0
            
            print(currentval, carry)  #70
            
            # Step 3 : 현재값을 추가한다. 현재값은 항상 1 이상이여야 한다.
            cur.next = ListNode(currentval)  #cur에 linkedlist(ListNode)이용해서 연결한다.
            cur = cur.next
            
            # Step 4 : Add base cases for iterating linked listed
            if p1 is None and p2 is None:
                break   # 왜 자꾸 out of loop?
            elif p1 is None:
                p2=p2.next
            elif p2 is None:
                p1=p1.next
            else:
                p1=p1.next
                p2=p2.next
        
        return head.next
    


#---------------------------------------------

# step 5: l1, l2 리스트 노드로 엮어주기

l1=ListNode(2)
l1.next=ListNode(4)
l1.next.next = ListNode(3)
print(linked_list_print(l1))


l2=ListNode(5)
l2.next=ListNode(6)
l2.next.next = ListNode(4)
print(linked_list_print(l2))

# Step 6: l3로 덧셈값 인쇄.
#add linked lists
s = Solution()
l3 = s.addTwoNumbers(l1,l2)
print(linked_list_print(l3))