스택 (Stack)

이 정리는 위키백과를 참고하였습니다.

  • LIFO: 후입선출(Last In First Out), 제일 마지막에 들어온 것이 제일 먼저 나간다는 뜻입니다.

    • 예시: 엘리베이터, 홈페이지 뒤로가기, ctrl + z
  • 스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형의 자료 구조입니다.

  • 가장 마지막에 들어온 데이터의 위치를 top(혹은 peek)이라고 합니다.

  • 스택은 그래프 자료구조 탐색방법 중 DFS(Depth First Search, 깊이우선 탐색)에 쓰입니다.

  • 기본적으로 추상 자료형(Abstract Data Type, ADT)이지만 여기서는 자료 구조의 영역까지 다룰 것입니다. (구현까지)

 

구현 방법

  1. 1차원 배열: 구현하기 쉽지만 input의 개수를 알아야 합니다.
  2. 연결 리스트: 구현이 배열보다 어렵고 메모리를 좀 더 사용하지만, 유동적으로 input에 대처할 수 있습니다.

 

함수

가장 보편적으로 사용되는 함수를 골라보았습니다. 이외에 size()등의 함수들도 사용자가 정의하여 쓸 수 있습니다.

  • S.top(): 스택의 가장 위 데이터(top)를 반환합니다.
  • S.pop(): top을 반환하고 스택에서 삭제합니다.
  • S.push(): 새로운 데이터를 삽입합니다. 이 때 들어가는 데이터가 top이 됩니다.
  • S.isEmpty(): 스택이 비었으면 True, 아니면 False을 반환합니다.

 

스택 구현 코드 (Python, C++)

  • Python: 스택 자료구조는 따로 제공하지 않기 때문에 list를 이용하여 구현해 보았습니다.
class stack:
    def __init__(self):                    # stack() 선언 시 빈 리스트 생성
        self.items = []
    def push(self, item):                # append로 빈 리스트에 요소 추가
        self.items.append(item)
    def pop(self):                        # pop으로 top값 반환 및 삭제
        return self.items.pop()
    def isEmpty(self):                    # 빈 리스트는 False인 것 이용
        return not self.items
    def top(self):                        # 끝 idx 값을 반환 (삭제 X)
        return self.items[-1]

s = stack()
print(s.isEmpty())                         # True 출력
s.push(1)
s.push(2)
print(s.pop())                             # 2 출력
s.push(3)
print(s.top())                             # 3 출력
print(s.isEmpty())                        # False 출력
  • C++: 노드를 이용한 연결리스트로 스택을 구현해 보았습니다.
#include <iostream>

using namespace std;

struct Node {
    int data;
    Node* next;
    Node() {                            // 노드 생성할 때 초기화
        next = NULL;                    // 가리키는 곳을 NULL로 설정
        data = 0;                        // data는 0으로 설정
    }

    Node(int i, Node* ptr) {            // ptr 이후에 data i를 가진 노드를 생성
        data = i;                    
        next = ptr->next;                // 원래 ptr의 다음 노드를 가리키게 설정
        ptr->next = this;                // ptr의 다음 노드를 this(이 노드)로 설정
    }
};

struct Stack {
    Node* peek;
    int count;
    Stack() {                            // stack 생성할 때 초기화
        peek = new Node();                // null값인 초기화 노드
        count = 0;                        // stack 내 node의 수를 count
    }

    int pop() {
        if (count != 0) {
            Node* tmp = peek
            int res = peek->data;        // res에 peek의 data값 저장
            peek = peek->next;            // 다음 노드로 peek값 갱신
            count--;                    // count - 1 (삭제하기 때문에)
            delete tmp;                    // 노드 삭제
            return res;                    // data값 반환
        }
    }

    int top() {
        if (count != 0) {                // 스택에 아무것도 들어있지 않은 경우를 제외
            return peek->data;
        }
    }

    bool isEmpty() {                    // count가 0이면 true, 아닐 경우 false 반환
        if (count == 0) {
            return true;
        }
        else return false;
    }

    void push(int data) {
        Node* node = new Node();
        node->data = data;
        node->next = peek;
        peek = node;
        count++;
    }
};

int main() {
    Stack* s = new Stack();
    cout << s->isEmpty() << '\n';        // 1 출력
    s->push(1);
    s->push(2);
    cout << s->pop() << '\n';            // 2 출력
    s->push(3);
    cout << s->top() << '\n';            // 3 출력
    cout << s->isEmpty() << '\n';        // 0 출력
}

'자료구조' 카테고리의 다른 글

자료구조: 큐(Queue)  (0) 2020.07.26
자료구조: 배열(Array)와 리스트(List)  (0) 2020.07.24

Control Flow

제어문 (Control Statement)

  • 조건문
  • 반복문

제어문을 사용하여 순서도(Flow Chart)를 코드로 표현할 수 있습니다.

조건문 (Conditional Statement)

if조건문

if문은 반드시 참/거짓을 판단할 수 있는 조건과 함께 사용되어야 합니다.

  • if 조건이 참일 경우 : 이후의 문장을 수행합니다.
  • else, elif는 선택적으로 사용 가능합니다.
주의사항
  • 반드시 들여쓰기에 유의해야 합니다.
    • 파이썬에서는 코드 블록을 {}가 아닌 들여쓰기로 판단합니다.
    • 보편적으로 PEP-8에서 권장하는 4spaces를 사용합니다.

elif복수 조건

2개 이상의 조건을 활용할 경우 elif <조건>:을 활용합니다

중첩 조건문 (Nested Conditional Statement)

조건문은 다른 조건문에 중첩될 수도 있습니다.

조건 표현식 (Conditional Expression)

일반적으로 조건에 따라 값을 정할 때 활용하며, 삼항 연산자(Ternary Operator)라고 부릅니다.

활용법
num = int(input('숫자를 입력하세요: '))
value = num if num >= 0 else -num
print(value)                # 절대값 출력

반복문 (Loop Statement)

  • while
  • for

while반복문

while문은 조건식이 참(True)인 경우 반복적으로 코드를 실행합니다.

주의사항
  • while문 역시 들여쓰기에 주의하여야 하며, 반드시 종료조건을 설정해야 합니다.

for

for문은 시퀀스(String, Tuple, List, Range)나 다른 순회가능한 객체(iterable)의 요소들을 순회합니다.

  • for문에서 요소 값에 다른 값을 할당해도 다음 반복구문에 영향을 주지 않습니다.
for i in range(5):
    print(i)
    i = 5                    # dead code
리스트(list) 순회에서 index의 활용하기
  • range(): 순회할 list의 길이를 활용하여 index를 조작할 수 있습니다.
enumerate(): 인덱스(index)와 값(value)을 함께 활용 가능
lunch = ['짜장면', '초밥', '피자', '햄버거']

for idx, menu in enumerate(lunch):    # 추가 변수 사용
    print(idx, menu)

lunch_list = list(enumerate(lunch))
print(lunch_list)            #[(0, '짜장면'), (1, '초밥'), (2, '피자'), (3, '햄버거')]
  • enumerate(lunch, start = 1)으로 1부터 카운트 할 수도 있습니다.

반복제어 (break, continue, for-else)

break

for이나 while문에서 빠져나갑니다.

continue

이후의 코드를 수행하지 않고 다음 요소부터 계속하여 반복을 수행합니다.

for-else

끝까지 반복문을 시행한 이후에 실행됩니다.

  • 반복에서 리스트의 소진이나 (for의 경우) 조건이 거짓이 돼서 (while의 경우) 종료할 때 실행됩니다.
  • break를 통해 중간에 종료되지 않은 경우만 실행합니다.

pass

아무것도 하지 않지만, 문법적으로 문장이 필요할 때 사용합니다. (continue와 구분!)

Data Container

 

컨테이너 (Container)

  • 여러 개의 값을 저장할 수 있는 것 (우리가 알고 있는 컨테이너와 같다고 볼 수 있습니다.)
    • 시퀀스(Sequence) 형: 순서가 있는(ordered) 데이터
    • 비 시퀀스(Non-sequence)형: 순서가 없는(unordered) 데이터

 

시퀀스형 컨테이너

시퀀스(Sequence)는 데이터가 순서대로 나열된 형식을 나타냅니다. (정렬: sorted와 다름)

특징

  1. 순서를 가질 수 있습니다.
  2. Indexing이 가능합니다.

종류

  1. 리스트(list)
  2. 튜플(tuple)
  3. 레인지(range)
  4. 문자형(string)
  5. 바이너리(binary)

리스트 (list)

리스트는 대괄호 []list()를 통해 만들 수 있고, 값에 대한 접근은 list[i]로 합니다.

튜플 (tuple)

튜플은 리스트와 유사하지만, ()로 묶어 표현합니다.

  • python_intro 파일에 있는 swap, divmod 예제 또한 튜플과 유사하거나 활용하는 기능입니다.
  • 하나의 항목으로 구성된 튜플은 값 뒤에 쉼표를 붙여서 만듭니다.
a = (3,)
print(a)            # (3,) 출력 (오류 X)

레인지 (range())

range는 숫자의 시퀀스를 나타내기 위해 사용됩니다.

  • range(n): 0부터 n-1까지의 값을 가짐
  • range(n, m): n부터 m-1까지의 값을 가짐
  • range(n, m, s): n부터 m-1까지 s만큼 증가/감소하는 값을 가짐(s의 부호에 따라)

시퀀스에서 활용할 수 있는 연산자 / 함수

operation 설명
in/not in containment test
+ concatenation
s*n s를 n번 반복하여 더하기
s[i : j : k] i번째부터 j-1번째까지 k간격으로 slicing
len(s) s의 길이
min(s)/max(s) 최솟/최댓값
s.count(x) s에서 x의 개수

 

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# containment test
3 in numbers        # True
10 not in numbers    # False

# concatenation
a = [7, 1, 3]
numbers + a

b = [0] * 6
print(b)            # [0, 0, 0, 0, 0, 0]

c = list(range(0, 31, 3))
print(c)            # [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

비 시퀀스형 컨테이너

  • set
  • dictionary

set

  • set은 순서가 없는 자료구조입니다.
    • 수학에서의 집합과 동일하게 처리되며, {}로 만듭니다.
    • 값이 중복되지 않고(여러 번 넣어도 한 개로 처리), 빈 셋은 set()로만 생성가능합니다. ({}X)
    • 문자의 경우 알파벳 순으로, 숫자의 경우 오름차순으로 자동정렬됩니다.
  • set의 연산자는 | &가 있으며, 각각 합집합과 교집합을 구할 수 있습니다.
set1 = {'v1', 'v2'}
set2 = {'v1', 'v1', 'v3', 'v4'}

# 합집합/교집합 연산
print(set1 | set2)        # {'v1', 'v2', 'v3', 'v4'}
print(set1 & set2)        # {'v1'}

# 중복값 없음
set2                    # {'v1', 'v3', 'v4'}

# list의 중복값 없애기
l = [1,1,3,5,2,2,6,4,3,6,6,6,2]
l = set(l)

print(list(l))            # [1, 2, 3, 4, 5, 6]

dictionary

  • dictionarykeyvalue가 쌍으로 이루어져 있는 궁극의 자료구조입니다.
    • {}dict()로 생성할 수 있습니다. (빈 set{}로 생성할 수 없는 이유!)
      • 요소 추가는 (dictionary[key] = value로도 가능)
    • key는 변경 불가능한 데이터만 가능합니다. (string, integer, float, boolean, tuple, range)
      • key는 중복 불가능합니다.
    • valuelist, dictionary를 포함한 모든 것이 가능합니다.
# 빈 dictionary 생성
dic1 = {}
dic2 = dict()

# key/value 확인하기
dic1 = {'서울': '02', '경기': '031'}
dic1.keys()            # dict.keys(['서울', '경기'])
dic1.values()        # dict_values(['02', '031'])
dic1.items()        # dict_items([('서울', '02'), ('경기', '031')])

컨테이너형 형변환

  string list tuple range set dictionary
string   O O X O X
list O   O X O X
tuple O O   X O X
range O O O   O X
set O O O X   X
dictionary O O(key만) O(key만) X O(key만)  

 

데이터의 분류 (중요!)

mutable vs. immutable

변경 불가능한 (immutable) 데이터

  • 숫자(Number)
  • 글자(String)
  • 참/거짓(Boolean)
  • range()
  • tuple()
  • frozenset()

변경 가능한(mutable) 데이터

  • list
  • dict
  • set
  • 사용자가 만든 데이터 타입

Python 기초

기초 문법 (Syntax)

주석 (Comment)

  • 한 줄 주석은 #(pound, hash, sharp)로, 여러 줄 주석은 공식적으로는 없습니다.
  • 여러 줄 주석을 표현하고 싶을 때에는 #으로 한 줄씩 작성하거나, ''' 또는 """(multiline string)을 사용할 수 있습니다.(multiline string은 원래 함수/클래스를 설명하기 위한 기능)
# 주석은 이렇게 입력합니다
'''
여러줄 주석은
이렇게 할 수 있습니다.
'''

코드 라인

  • 파이썬 코드는 '1 Line 1 Statement(1줄에 1문장)'가 원칙입니다.
  • Statement은 파이썬이 실행 가능(executable)한 최소한의 코드 단위입니다.
print('hello')
print('world')
  • 이 때 print문 두 개를 한 줄에 출력하고자 하는 경우 ;(semi-colon)을 통해 표현할 수 있지만, 파이썬에서 이를 권장하지 않습니다.
# 여기서 ';'가 빠지면 SyntaxError가 발생합니다.
print('hello'); print('world')
  • 또한 여러 줄의 string을 한 번에 출력하기 위해 \를 사용할 수 있지만, 이것도 자주 쓰이진 않습니다.
    • 단, [] {} ()\ 없이도 가능합니다.
print('\
안녕\
나는\
python이야\
')

변수 (Variable)

할당 연산자

  • 변수는 =을 통해 할당(assignment) 됩니다.
  • 변수의 데이터 타입은 type(), 메모리 주소는 id()로 확인할 수 있습니다.
x = 'ssafy'
type(x)        # 'str'
id(x)        # 숫자 메모리주소
  • 같은 값을 동시에 할당하거나 다른 값을 동시에 할당할 수 있습니다.
    • 변수의 개수가 할당하는 값보다 많거나 적을 때 오류가 발생합니다.
# 같은 값 동시에 할당
x = y = 'ssafy'

# 다른 값 동시에 할당
a, b = 2020, 4
  • 이를 활용하면 중간 변수 없이 값을 swap할 수 있습니다.
a, b = 10, 20
a, b = b, a        # (a, b) = (b, a) tuple 개념을 이용함!

식별자 (Identifiers)

파이썬에서 식별자는 변수, 함수, 모듈, 클래스 등을 식별하는데 사용되는 이름(name)입니다.

  • 식별자는 알파벳, _, 숫자로 구성됩니다.
  • 첫 글자에 숫자가 올 수 없으며, 대소문자를 구별하고, 예약어와 내장함수(built-in-function)를 사용할 수 없습니다.
    • 내장함수로 식별자를 설정한 경우 구동은 되지만, 후에 기본 기능과 충돌하여 프로그램에 혼란을 줄 수 있습니다.
# 예약어인 만큼 파이썬 환경에서 쓰면 단어의 색이 다른 것이 보입니다.
False, None, True, and, as, assert, break, class, continue, def, del, elif ...
# 이 예약어가 궁금할 경우
import keyword
print(keyword.kwlist)

print = 'ssafy'        # 오류 안남
print(print)        # 오류 발생 (print가 내장함수인지 문자열인지 프로그램에서 혼동)
del print
print('hello')        # 정상 동작

데이터 타입 (Datat Type)

숫자 (Number) 타입

(1) int (정수, integer)

모든 정수는 int로 표현됩니다. (파이썬 3.x 버전에서는 long타입 없음)

  • 파이썬에서 표현할 수 있는 가장 큰 수

    • 파이썬은 정수 자료형(integer)에서 오버플로우가 없습니다. (arbitrary-precision arithmetic 사용)
    • 가장 큰 숫자를 활용하기 위해 sys모듈을 사용합니다.
  • arbitrary-precision arithmetic

    • 사용할 수 있는 메모리 양이 정해져 있는 기존의 방식과 달리, 현재 남아있는 만큼의 가용 메모리를 모두 수 표현에 끌어다 쓸 수 있는 형태입니다.
  • n진수 만들기

# 2진수
binary_num = 0b10    # 2 출력

# 8진수
octal_num = 0o10    # 8 출력

# 16진수
hexa_num = 0x10        # 16 출력

(2) float (부동소수점, 실수, floating point number)

실수는 float로 표현되는데, 컴퓨터가 이를 표현하는 과정에서 부동소수점을 사용하며 항상 같은 값으로 일치하지 않습니다. (floating point rounding error)

  • e, E를 사용하여 컴퓨터식 지수 표현을 할 수 있습니다.
  • 실수의 연산
    • 실수의 덧셈은 쉽게 가능하지만, 뺄셈에 있어 주의가 필요합니다. ★매우 중요!★
      • 따라서 뺄셈에 있어 반올림 함수인 round(), math 모듈, sys 모듈을 활용할 수 있습니다.
# 실수의 덧셈
3.5 + 3.2        # 6.7 출력

# 실수의 뺄셈
3.5 - 3.2        # 0.29999999 출력
a = 3.5 - 3.2
b = 0.3
round(a, 1)        # 0.3 출력
# sys 모듈 | math 모듈 사용
import sys
import math

abs(a - b) <= sys.float_info.epsilon
math.isclose(a, b)                        # True일 경우 같다고 인지

(3) complex (복소수, complex number)

실수로 표현되는 실수부와 허수부로 구성되어 있으며, 허수부는 j로 표현합니다.

  • 문자열을 복소수로 변환할 수 있는데, 이 때 연산자 주위에 공백이 있으면 안 됩니다.
a = 3 + 4j
b = complex('3+4j')        # 변환 가능
c = complex('3 + 4j')    # 오류 발생

문자 (String) 타입

기본 활용법

  • 문자열은 Single quotes(')나 Double quotes(")를 활용하여 표현 가능합니다.
  • 문자열을 묶을 때 동일한 문장부호를 활용해야 하며, PEP-8에서는 하나의 문장부호를 선택하여 유지하도록 하고 있습니다.
  • 사용자에게 받은 입력은 기본적으로 str 타입입니다. (연산 등에 typecasting 필요)

따옴표 사용

  • 문자열 안에 문장부호(',")가 사용될 경우 이스케이프 문자(\)나 다른 문장부호를 사용할 수 있습니다.
"내 이름은 \"배하영\"입니다."
"내 이름은 '배하영'입니다."
  • 여러 줄에 걸쳐 있는 문장은 다음과 같이 표현 가능합니다. (PEP-8 메뉴얼)
print("""
이것은
여러 줄에 걸친
문자열입니다.
""")
  • 문자열은 + 연산자로 이어 붙이거나, * 연산자로 반복시킬 수 있습니다.
    • 변수화해서도 사용가능합니다.
    • 연산자 없이도 두 개 이상의 문자열이 연속해서 나타나면 프로그램에서 자동으로 이어붙입니다.
'hello' + 'violet'        # 'helloviolet'
'hello' * 2                # 'hellohello'
a = 'hello'                
a + 'violet'            # 'helloviolet'
a 'violet'                # 오류 발생

이스케이프 시퀀스

\를 활용하여 문자열을 조작합니다.

예약문자 내용(의미)
\n 줄 바꿈
\t
\r 캐리지리턴
\0 널(Null)
\ \
' 단일인용부호(')
" 이중인용부호(")
  • print()의 default 인자인 end='\n'을 조작해서도 비슷한 결과를 낼 수 있습니다.
print('hello\tviolet')
print('hello', end='\t')
print('violet')                # 'hello    violet'

String interpolation

  • %-formatting
  • str.format()
  • f-strings: 파이썬 3.6 이후 버전에서 지원
    • 여러 줄 문자열에서도 사용 가능하며, 연산 및 출력형식 지정이 가능합니다.
name = 'violet'
print('내 이름은 %s 입니다.' % name)
print('내 이름은 {} 입니다.'.format(name))
print(f'내 이름은 {name} 입니다.')        # 내 이름은 violet 입니다.
import datetime
now = datetime.datetime.now()

# '오늘은 2020년 이번달은 07월 오늘은 20일'
f'오늘은 {now:Y}년 이번달은 {now:%m}월 오늘은 {now:d}일'

# 연산 및 출력형식 지정
pi = 3.141592
r = 10
print(f'{pi:.3} 넓이는: {pi * r * r:.3}')        # .3은 3번째 자리에서 반올림이라는 뜻

참/거짓 (Boolean) 타입

비교/논리 연산 수행 등에서 사용되는 bool 타입 변수: True, False

  • 다음은 False로 변환됩니다.
0, 0.0, (), [], {}, '', None

None 타입

  • None의 type은 NoneType이며 변수에 넣었을 때 출력되는 bool값은 False입니다.

형변환 (Type conversion, Typecasting)

파이썬에서 데이터타입은 암시적 또는 명시적으로 변환할 수 있습니다.

암시적 형변환 (Implicit Type Conversion)

bool, Numbers(int,float,complex)끼리 가능합니다.

명시적 형변환 (Explicit Type Conversion)

  • string -> integer: 형식에 맞는 숫자만 가능
  • integer -> string: 모두 가능
number = input('숫자를 입력해주세요: ')        ## str type
number * 2                                    ## 오류 발생 int(number) 캐스팅 필요
int('3.5')                                    ## 오류 발생 int(float('3.5')) 더블캐스팅 필요

연산자 (Operator)

  • 산술 연산자
  • 비교 연산자
  • 논리 연산자
  • 복합 연산자
  • 기타 연산자

산술 연산자

Python에서는 기본적인 사칙연산이 가능합니다.

  • **는 제곱(^)을 나타냅니다.

  • divmod는 몫과 나머지를 동시에 반환하는 함수입니다.

a, b = divmod(5, 2)        ## (2, 1)

비교 연산자

우리가 수학에서 배운 연산자와 동일하게 값을 비교할 수 있습니다.

  • isis not은 값이 아닌 객체 비교 연산자입니다.
    • 따라서 None,True,False 등을 비교할 때 효과적입니다.
a = 3.0
b = 3
a == b            # True
a is b            # False

논리 연산자

& | 는 파이썬에서 비트 연산자를 의미합니다.

  • and, or, not을 사용합니다.
    • 파이썬에서 and는 a가 거짓이면 a를 리턴하고, 참이면 b를 리턴합니다.
    • 파이썬에서 or은 a가 참이면 a를 리턴하고, 거짓이면 b를 리턴합니다.

단축평가

첫 번째 값이 확실할 때, 두 번째 값은 확인하지 않기 때문에 속도를 향상시킬 수 있는 기술입니다.

vowels = 'aeiou'
('a' and 'b') in vowels        # False
('a' or 'b') in vowels        # True

복합 연산자

연산과 대입이 함께 이뤄지는 연산자로 반복문에서 자주 활용됩니다.

기타 주요 연산자

Concentration

  • 숫자가 아닌 자료형은 +연산자를 통해 합칠 수 있습니다.

Containment Test

  • in연산자를 통해 요소가 속해있는지 여부를 확인합니다.

Identity

  • is연산자를 통해 동일한 객체인지 확인합니다.

Indexing/Slicing

  • []를 통해 값을 접근하고(Indexing), [:]을 통해 리스트를 자를 수 있습니다(Slicing).
a = [1, 2, 3, 4, 5]
# Indexing
a[3]            # 4
# Slicing
a[1:3]            # [2, 3]

연산자 우선순위

  1. ()을 통한 grouping
  2. Slicing
  3. Indexing
  4. **
  5. +,- (음수/양수 부호)
  6. *, /, %
  7. +,-
  8. in,is
  9. not
  10. and
  11. or

표현식 (Expression) & 문장 (Statement)

표현식

표현식 => evaluate => 값

  • 하나의 값(value)로 환원(reduce)될 수 있는 문장을 말합니다.
  • 식별자,,연산자로 구성됩니다.
    • 할당문은 표현식이 아닙니다.

문장

파이썬이 실행 가능한 최소한의 코드 단위 (a syntatic unit of programming)

브루트 포스 (Brute Force)

컴퓨터 공학에서의 브루트 포스 알고리즘을 다룹니다.

완전탐색 알고리즘으로 많이 알려져 있습니다. 가능한 모든 경우의 수를 탐색하여 100% 정답을 도출하며, 무식하게(brute) 탐색하는 만큼 단점은 속도입니다.

백준 알고리즘: #1436 영화감독 숌

이 문제는 solved.ac에서 silver5단계로 마크되어 있습니다.

문제

666은 종말을 나타내는 숫자라고 한다. 따라서, 많은 블록버스터 영화에서는 666이 들어간 제목을 많이 사용한다. 영화감독 숌은 세상의 종말 이라는 시리즈 영화의 감독이다. 조지 루카스는 스타워즈를 만들 때, 스타워즈 1, 스타워즈 2, 스타워즈 3, 스타워즈 4, 스타워즈 5, 스타워즈 6과 같이 이름을 지었고, 피터 잭슨은 반지의 제왕을 만들 때, 반지의 제왕 1, 반지의 제왕 2, 반지의 제왕 3과 같이 영화 제목을 지었다.

하지만 숌은 자신이 조지 루카스와 피터 잭슨을 뛰어넘는다는 것을 보여주기 위해서 영화 제목을 좀 다르게 만들기로 했다.

종말의 숫자란 어떤 수에 6이 적어도 3개이상 연속으로 들어가는 수를 말한다. 제일 작은 종말의 숫자는 666이고, 그 다음으로 큰 수는 1666, 2666, 3666, .... 과 같다.

따라서, 숌은 첫 번째 영화의 제목은 세상의 종말 666, 두 번째 영화의 제목은 세상의 종말 1666 이렇게 이름을 지을 것이다. 일반화해서 생각하면, N번째 영화의 제목은 세상의 종말 (N번째로 작은 종말의 숫자) 와 같다.

숌이 만든 N번째 영화의 제목에 들어간 숫자를 출력하는 프로그램을 작성하시오. 숌은 이 시리즈를 항상 차례대로 만들고, 다른 영화는 만들지 않는다.

입력

첫째 줄에 숫자 N이 주어진다. N은 10,000보다 작거나 같은 자연수이다.

출력

첫째 줄에 N번째 영화의 제목에 들어간 수를 출력한다.

풀이

6이 세 개 이상 연속해서 나오는 수는 666 앞에 자리만 바꿔서 될 게 아니라는 것을 명심합시다.

처음엔 규칙이 있으려니 하고 노트에 하나하나 다 적어보았는데요.. 그 나름의 규칙은 있었지만 N번째 숫자를 받아서 이걸 계산할 엄두가 나지 않았습니다.

그래서 브루트 포스의 정수(..)인 노가다로 구해보았습니다! (너무 간단해서 전체 코드에 주석으로 설명을 조금 붙여보았습니다.)

  • 너무너무너무 간단함에도 이 포스팅을 하는 이유는 저도 이 방법까지 오는데는 시간을 좀 썼기 때문입니다.. 혹시 너무 답답하셔서 오신 분들은 이 방법 사용해 보세요!

전체 코드

N = int(input())
number = 666

while N:
    if "666" in str(number):
        N -= 1                        # N번째 수를 구하면 루프 종료
    number += 1                        # 수를 1씩 늘려가며 루프 돌기 (666, 667, ...)

print(number - 1)

사담

역시 너무 간단하게 막 풀어서일까... 시간이 어마어마합니다.

지금은 실버단계라 시간도 널널해서 통과될지 모르겠지만, (일단 이런 문제가 나중에 나오긴 할까..)

좀 더 빨리 찾을 수 있는 방법이 있을지 저도 구글링해보러 가렵니다🤣🤣

브루트 포스 (Brute Force)

컴퓨터 공학에서의 브루트 포스 알고리즘을 다룹니다.

완전탐색 알고리즘으로 많이 알려져 있습니다. 가능한 모든 경우의 수를 탐색하여 100% 정답을 도출하며, 무식하게(brute) 탐색하는 만큼 단점은 속도입니다.

백준 알고리즘: #1018 체스판 다시 칠하기

이 문제는 solved.ac에서 silver5단계로 마크되어 있습니다.

문제

지민이는 자신의 저택에서 MN개의 단위 정사각형으로 나누어져 있는 M * N 크기의 보드를 찾았다. 어떤 정사각형은 검은색으로 칠해져 있고, 나머지는 흰색으로 칠해져 있다. 지민이는 이 보드를 잘라서 8 * 8 크기의 체스판으로 만들려고 한다.

체스판은 검은색과 흰색이 번갈아서 칠해져 있어야 한다. 구체적으로, 각 칸이 검은색과 흰색 중 하나로 색칠되어 있고, 변을 공유하는 두 개의 사각형은 다른 색으로 칠해져 있어야 한다. 따라서 이 정의를 따르면 체스판을 색칠하는 경우는 두 가지뿐이다. 하나는 맨 왼쪽 위 칸이 흰색인 경우, 하나는 검은색인 경우이다.

보드가 체스판처럼 칠해져 있다는 보장이 없어서, 지민이는 8 * 8 크기의 체스판으로 잘라낸 후에 몇 개의 정사각형을 다시 칠해야겠다고 생각했다. 당연히 8 * 8 크기는 아무데서나 골라도 된다. 지민이가 다시 칠해야 하는 정사각형의 최소 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 M이 주어진다. N과 M은 8보다 크거나 같고, 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 보드의 각 행의 상태가 주어진다. B는 검은색이며, W는 흰색이다.

출력

첫째 줄에 지민이가 다시 칠해야 하는 정사각형 개수의 최솟값을 출력한다.

풀이

범위 내의 모든 8 * 8 사각형을 검사하는 방법을 찾아봅니다. 그리고 어떻게 최솟값을 알아낼지도 생각해야 합니다.

미리 White로 시작하는 체스판과 Black으로 시작하는 체스판 (이하 W체스판, B체스판) 을 만들어 두고 비교하는 방법과, 체스판의 규칙을 파악한 후 그 칸에 있어야 하는 색인지 체크하는 방법이 있을 것입니다.

저는 후자의 방법을 이용했습니다.

  • 가로, 세로의 인덱스가 모두 홀수이거나 모두 짝수인 경우: 각 체스판의 메인 색
    • W체스판일 경우 W, B체스판일 경우 B
  • 그 외: 메인의 반대 색

이 규칙을 이용하여 체스판 위 모든 index의 색을 비교하고, 고쳐야 하는 칸의 수를 나타내는 cnt변수를 증가시켰습니다.

cnt = 0
for i in range(8):                        # 8 * 8 체스판 인덱스 탐색
    for j in range(8):
        if ((i % 2 == 0) and (j % 2 == 0)) or ((i % 2 and j % 2)):
            if s[i][j] == 'W':            # 인덱스가 모두 홀수거나 모두 짝수일 경우
                cnt += 1
        else:
            if s[i][j] == 'B':
                cnt += 1

위의 함수를 W체스판과 B체스판이라는 2가지 경우의 수에 따라 체크한 후, 더 작은 값의 cnt를 반환하는 check()함수를 만들었습니다.

그리고 입력되는 보드의 칸 수에 따라 이 check()함수를 반복하고, 함수 호출마다 한 번씩 반환되는 cnt들을 result리스트에 저장한 후 그 안에서 최소값을 출력하는 코드를 완성했습니다.

이 때 반복 범위 설정에 주의해야 합니다! for i in range(N - 7): for j in range(M - 7):

또한 check()함수에 넣을 sample도 적절한 slicing을 통해 만들어주어야 합니다. 다음은 전체코드에서 확인하실 수 있습니다.

전체 코드

N, M = map(int, input().split())
boards = [list(input()) for i in range(N)]
result = []                                # 각 case별 최솟값을 저장할 리스트

def check(s):
    cnt1 = 0                            # B가 시작점인 체스판일 경우
    for i in range(8):
        for j in range(8):
            if ((i % 2 == 0) and (j % 2 == 0)) or ((i % 2 and j % 2)):
                if s[i][j] == 'W':
                    cnt1 += 1
            else:
                if s[i][j] == 'B':
                    cnt1 += 1

    cnt2 = 0                            # W가 시작점인 체스판일 경우
    for i in range(8):
        for j in range(8):
            if ((i % 2 == 0) and (j % 2 == 0)) or ((i % 2 and j % 2)):
                if s[i][j] == 'B':
                    cnt2 += 1
            else:
                if s[i][j] == 'W':
                    cnt2 += 1
    return min(cnt1, cnt2)

for i in range(N - 7):
    for j in range (M - 7):
        sample = [board[0 + j : 8 + j] for board in boards[0 + i : 8 + i]]
        result.append(check(sample))        # 반환값 저장

print(min(result))

사담

처음에 어떻게 해야 될 지 감이 안 와서 한참 생각하다 구글링을 했더랍니다..

브루트 포스가 가장 단순하고 빨리 짤 수 있다는데 왜 저한텐 브루트 포스가 더 어려울까요!! (가이드 보고도 스스로 코드 이해하는데 시간 많이 씀🤦‍♀️)

그래도 이렇게 포스팅하면서 제대로 이해하고 쓰는 만큼 실력도 얼른 향상되었으면 좋겠습니다🥰

+ Recent posts