블로그

홍정모

최적의 프로그래밍 공부 방법

*같은 글의 브런치 링크MZ 세대를 위한 가장 효율적인 프로그래밍 공부 순서 요약- 문법보다 활용- 뭘 하든 생각하는 방법부터지금 프로그래밍 공부를 시작하거나 하고 있는 분들은 정말로 한 분 한 분이 소중합니다. 누구나 얼마든지 무럭무럭 자라나서 창조성을 꽃피울 수 있는 잠재력을 가지고 있음에도 불구하고 C언어 문법 공부로 대표되는 옛날 방식으로 지쳐버리는 것을 보는 것은 안타깝습니다.​오프라인/온라인에서 오랜시간 동안 다양한 과목들을 다양한 학생들에게 가르치면서 프로그래밍 교육에서 고려해야할 점들을 정리해봤습니다.​1. 흥미를 끌고 매 단계마다 재미를 느낄 수 있어야 합니다. 지루함을 억지로 참게하면 안되고 짧은 템포로 강한 집중력을 유도해야 합니다.​2. 왜 필요한지를 먼저 알려줘야 합니다. 예전에는 그냥 알아두면 나중에 도움된다며 일단 압박하며 가르쳐야했던 내용들도 다양한 응용 분야가 내 미래에 직결된다는 것을 미리 알게 되면 강한 의지를 보입니다.​3. 클릭만 유도하는 미디어에 지쳐가는 새싹들에게 수명이 짧고 단편적인 기술이 아니라 생각하는 방법 자체를 배울 수 있는 기회를 제공해야 합니다.​여기에 맞춰 가장 효율적인 프로그래밍 공부 방법을 정리해봤습니다.​1. 안정적인 초중고 교육은 인생 자체를 지탱하는 뿌리가 됩니다. 이 시기에는 컴퓨터로 인해 바뀌어가는 인류의 미래에 대해 호기심을 갖는 정도면 충분합니다. 타의에 의한 강압적인 선행학습은 권장하지 않습니다.​2. 프로그래밍 입문 초반부터 다양한 응용 분야에 대해 가볍게 체험을 해보는 것을 권장합니다. 자신의 가능성에 미리부터 선을 긋고 특정 분야에 대해 단편적인 지식만을 습득하는 방식은 미래의 나에게 족쇄를 채우는 행위일 뿐입니다. 프로그래밍 언어로는 파이썬을 추천합니다. C언어와 달리 소소한 불편함이 없고 분야별로 사용하기 쉬운 패키지들이 미리 준비되어 있어서 빠르게 다양한 체험을 할 수 있습니다. 이 단계에서 주의해야할 점은 쉬운 것일수록 스스로 해결할 수 있도록 올바르게 지도받아야 합니다. 나의 두뇌가 크게 성장할 수 있는 기회를 강탈당하지 않도록 주의하세요.​3. C/C++언어를 최소한으로 공부합니다. 딱 자료구조 공부에 필요한 만큼만 공부하시면 됩니다. C/C++ 문법을 깊게 들어가는 것은 알고리즘 공부 뒤에 자신이 선택한 응용 분야에서 필요하다면 그때 하시면 됩니다. 한 언어를 공부한 후에 다른 언어를 공부하는 것은 쉽구나 하는 체험도 이 단계에서 거쳐가야겠지요.​4. 프로그래밍 연습으로써의 자료구조 공부를 진행합니다. 우리의 목표는 "생각하는 방법 = 알고리즘"이지만 프로그래밍 연습이 부족한 상태에서는 반쪽짜리 공부가 되어버릴 수도 있습니다. 생각하는 방법을 터득하면서 동시에 구현까지 할 수 있으려면 약간의 프로그래밍 연습이 필요합니다. 그렇다고 해서 "C/C++ 문법 공부"를 "프로그래밍 연습"으로 착각하면 안되기 때문에 문법은 최소로 줄이고 자료구조를 공부하는 것을 추천합니다. 파이썬으로 다양한 응용 분야를 체험해봤기 때문에 자료구조와 알고리즘의 추상적인 개념들도 필요성을 느끼고 하나씩 내것으로 만들어나갈 수 있습니다. 파이썬으로 아주 기초적인 문제풀이를 해봤다면 더 좋습니다.​5. 알고리즘 공부를 시작합니다. 보다 구체적으로는 인터넷에 답이 없는 문제도 해결할 수 있는 능력을 갖추는 것입니다. 어떻게 공부해야할까요? 이미 앞에서 쉬운 것부터 스스로 해결하는 습관을 갖추셨다면 약간의 훈련만으로 빠르게 성장하실 수 있습니다. 이 단계에서 주의해야할 점은 코딩테스트 통과를 위한 문제 풀이와 알고리즘 공부를 헷갈리면 안됩니다. 쉬운 것부터 스스로 해결해온 순간들이 모여서 이때부터 화려하게 꽃 피우기 시작합니다. 알고리즘 공부는 Java로 하는 것도 괜찮습니다. 저는 C->C++로 흐름이 자연스럽게 연결될 수 있다는 점에서 C++을 추천합니다. 앞에서 이미 한 번 경험해봤듯이 C++ 후에 자바를 공부하는 것은 빠르게 하실 수 있습니다. 다만, 전문 영역으로써 깊게 언어를 파고드는 것은 추가적인 노력이 필요합니다.​6. 코딩 테스트 문제 풀이를 천천히 시작합니다. 알고리즘에서 터들한 내용들로부터 직결되는 문제들부터 몇 개만 스스로 풀어보며 감을 잡으면 문법 공부 직후에 바로 문제풀이 시작한 사람들보다 오히려 진행 속도가 훨씬 빠를겁니다. 문제풀이는 일찍 시작하고 꾸준히 하는 것을 추천합니다. 대신에 이제부터는 전문/응용 분야와 병행해야 합니다.​7. 전문/응용 분야 공부를 시작하면서 자유와 창의성을 발휘해보세요. 웹을 HTML 사용법 익히는 정도로 생각하는 경우도 있는데 저는 웹 프로그래밍도 고도로 전문화된 응용 분야로 봅니다. 웹, 앱, 서버, 게임, AI, 비전 등의 다양한 분야가 여러분들을 애타게 기다리고 있습니다. 특정 언어의 문법을 깊이 파고 들어가는 공부도 이때 시작하시면 되는데 복잡한 문법도 이런 문법이 왜 필요한지를 깨달으면 쉽게 이해가 되기 때문에 훨씬 수월할겁니다.​당연히 저도 제한된 경험을 가지고 있는 개인이며 제가 제시한 로드맵이 모두에게 정답은 아닐 수도 있습니다. 그러나 앞으로 고도화된 현대사회에서 평생 직장이란 개념 없이 오랜 기간동안 경력을 이어나가야 하는 MZ 세대가 효율적이면서도 미래지향적인 공부를 해나가는 데에 약간의 도움이라도 되기를 바랍니다.​2023년 1월 17일​홍정모 드림

프로그래밍 언어프로그래밍공부순서프로그래머알고리즘문제풀이자료구조코딩테스트웹프로그래밍프로그래밍공부홍정모

dremdeveloper

[코딩 테스트 합격자 되기]동적계획법으로 접근해야 하는 문제를 판단하는 방법

동적 계획법으로 해결 가능한 문제를 판단하는 것은 때로는 경험이나 직감에 기반하기도 합니다. 그러나 일반적으로 다음 두 가지 주요 특성을 가진 문제들이 동적 계획법을 사용하여 해결될 가능성이 높습니다. 중복되는 부분 문제 (Overlapping Subproblems)동적 계획법은 중복되는 부분 문제들을 효과적으로 해결하기 위해 설계되었습니다.주어진 문제를 여러 작은 문제로 나누었을 때, 그 작은 문제들이 반복적으로 나타나는 경우 동적 계획법이 효과적입니다. 최적 부분 구조 (Optimal Substructure)주어진 문제의 최적 해결 방법이 해당 문제의 부분 문제들의 최적 해결 방법을 포함하는 경우입니다.즉, 큰 문제의 최적의 해결책을 찾기 위해 작은 문제들의 최적의 해결책을 사용할 수 있어야 합니다.  동적계획법으로 접근하는게 효율적인 경우동적계획법의 대표적인 예시는 피보나치수를 구하는 문제가 있습니다. 피보나치를 구하는 과정은 아래와 같습니다.F(0) = 0 F(1) = 1 F(n) = F(n-1) + F(n-2) (n >= 2) 이를 코드로 그대로 옮기면 아래와 같습니다.def fibonacci_dp(n): if n <= 1: return n dp = [0] * (n + 1) dp[1] = 1 for i in range(2, n + 1): dp[i] = dp[i-1] + dp[i-2] return dp[n] 위의 코드에서 dp 배열은 피보나치 수열의 값을 저장합니다. 이 방법은 중복 계산을 피하면서 각 값들을 한 번씩만 계산하므로 효율적입니다. 코드를 분석해보면, fibonacci_recursive(3) 같은 값을 여러 번 계산하게 됩니다. 이러한 중복된 계산은 많은 시간을 낭비하게 됩니다.(중복부분문제 만족) 추가적으로, 피보나치 수열에서 F(n)의 최적의 해는 F(n-1)과 F(n-2)의 최적의 해를 바탕으로 구해집니다. 즉, 큰 문제의 최적의 해결책을 작은 문제들의 최적의 해결책을 통해 얻을 수 있습니다.(최적 부분 구조 만족) 중복부분문제와 최적 부분 구조 모두 만족하므로, 피보나치수는 동적계획법을 통해 효율적으로 문제를 풀 수 있습니다. 동적계획법으로 접근하는게 효율적이지 않은 경우이번에는 팩토리얼을 구하는 과정을 봅시다.n! = n × (n-1) × (n-2) × ... × 2 × 1 이를 코드로 구현하면 아래와 같습니다.def factorial_recursive(n): if n == 0 or n == 1: return 1 else: return n * factorial_recursive(n-1) 이 코드에서, factorial_recursive(n)을 호출하면 factorial_recursive(n-1)을 호출합니다. 그러나 이전에 계산한 값이 다시 사용되지 않습니다.(중복부분 문제가 아님) 즉, 어떤 값에 대한 팩토리얼을 계산하면 그 값에 대한 계산은 한 번만 이루어집니다. 따라서 팩토리얼은 동적계획법으로 접근하면 효율적이지 않습니다. 부분문제가 중복되지 않기 때문에, 메모이제이션으로 해도 효율이 없기 때문입니다.

알고리즘 · 자료구조알고리즘자료구조코딩테스트

dremdeveloper

[코딩 테스트 합격자 되기]시간복잡도

해당 블로그는 아래 강의 내용을 중 "시간 복잡도"부분을 요약한 내용 입니다.실제 강의영상에는 별도로 강의자료를 제공 합니다.강의 영상 : https://inf.run/H9yxm  시간 복잡도란?시간 복잡도(Time Complexity)는 알고리즘의 효율성을 평가하는 데 사용되는 개념으로, 입력 크기에 따라 알고리즘의 실행 시간이 어떻게 변하는지를 나타냅니다. 이는 코딩 테스트뿐만 아니라 실제 소프트웨어 개발에서도 매우 중요한 개념입니다. 알고리즘 정의 및 특성먼저, 알고리즘이란 문제를 해결하기 위한 일련의 규칙이나 절차를 의미합니다. 알고리즘은 다음과 같은 주요 특성을 가져야 합니다:1. 정확성: 알고리즘의 각 단계는 명확하고 변하지 않아야 합니다.2. 유일성: 각 단계마다 다음에 수행할 작업이 명확해야 합니다.3. 실현 가능성: 알고리즘은 실제로 구현 가능하고 실용적이어야 합니다.4. 입력과 출력: 알고리즘은 명확한 입력과 출력을 가져야 합니다.5. 유한성: 알고리즘은 반드시 종료되어야 합니다. 무한 루프에 빠지면 안 됩니다. 알고리즘 성능 측정 방법알고리즘의 성능을 측정하는 방법에는 여러 가지가 있지만, 대표적인 두 가지 방법은 다음과 같습니다:- 절대 시간 측정: 특정 하드웨어에서 알고리즘을 실행하는 데 걸리는 시간을 측정합니다. 하지만 하드웨어 성능에 따라 결과가 달라질 수 있어 일반적으로 사용하기에는 한계가 있습니다.- 연산 횟수 측정: 하드웨어와 무관하게 알고리즘이 수행하는 연산의 횟수를 측정합니다. 이는 보다 신뢰할 수 있는 방법입니다. 빅오 표기법 (Big-O Notation)빅오 표기법은 알고리즘의 최악의 경우 시간 복잡도를 나타내는 데 사용됩니다. 이는 상수와 낮은 차수 항을 무시하고, 가장 큰 성장률을 가진 항을 중심으로 표현합니다. 예를 들어, O(n^2), O(n), O(log n), O(n log n) 등이 있습니다.### 일반적인 시간 복잡도다음은 코딩 테스트에서 자주 접할 수 있는 시간 복잡도 예시입니다:- O(1): 입력 크기에 상관없이 항상 일정한 시간이 걸리는 경우.- O(n): 입력 크기에 비례하여 시간이 증가하는 경우.- O(n^2): 중첩된 반복문처럼 입력 크기의 제곱에 비례하여 시간이 증가하는 경우.- O(log n): 이진 탐색처럼 로그 스케일로 시간이 증가하는 경우.- O(n log n): 합병 정렬과 같은 알고리즘의 경우. 시간 복잡도의 실제 적용코딩 테스트에서 시간 복잡도를 이해하는 것은 매우 중요합니다. 예를 들어, 중첩된 반복문을 사용하면 O(n^2) 복잡도가 발생하며, 이는 큰 입력에 대해 비효율적일 수 있습니다. 따라서 입력 크기 제한을 고려하여 적절한 자료 구조와 알고리즘을 선택해야 합니다. 코딩 테스트에서의 시간 복잡도 활용코딩 테스트 문제를 풀 때, 문제에서 주어진 입력값의 크기를 바탕으로 우리가 사용할 수 있는 알고리즘의 시간 복잡도를 추측할 수 있습니다. 예를 들어, 입력값의 크기가 100만이라면, O(n^2) 알고리즘은 시간 초과가 날 가능성이 높습니다. 이때는 O(n log n) 또는 O(n) 알고리즘을 사용하는 것이 바람직합니다. 결론시간 복잡도는 코딩 테스트에서 알고리즘의 효율성을 평가하는 중요한 도구입니다. 이를 통해 우리는 더 나은 성능을 가진 알고리즘을 선택하고, 시간 초과를 방지할 수 있습니다. 알고리즘의 정확한 연산 횟수를 측정하는 것도 중요하지만, 빅오 표기법을 사용하여 대략적인 성능을 파악하는 것이 더 중요합니다. 이를 통해 문제의 요구 사항을 충족하는 최적의 알고리즘을 선택할 수 있습니다.이상으로 시간 복잡도에 대한 블로그 포스트를 마치겠습니다. 읽어주셔서 감사합니다! 다음 포스트에서는 더 흥미로운 알고리즘 주제로 찾아뵙겠습니다.

알고리즘 · 자료구조자료구조알고리즘코딩테스트시간복잡도

dremdeveloper

[코딩 테스트 합격자 되기]그리디로 접근해야 하는 문제를 판단하는 방법

그리디 알고리즘으로 해결 가능한 문제를 판단하는 것은 때로는 경험이나 직감에 기반하기도 합니다. 그러나 일반적으로 다음 두 가지 주요 특성을 가진 문제들이 그리디 알고리즘을 사용하여 해결될 가능성이 높습니다.탐욕적 선택 속성 (Greedy Choice Property)그리디 알고리즘은 매 단계에서 가장 최적이라고 생각되는 선택을 합니다. 즉, 현재 상황에서 가장 좋은 선택을 함으로써 문제를 해결해 나가는 방법입니다. 따라서, 매 단계의 탐욕적 선택이 최종 해답으로 이어지는 경우에 그리디 알고리즘이 효과적입니다.최적 부분 구조 (Optimal Substructure)주어진 문제의 최적 해결 방법이 해당 문제의 부분 문제들의 최적 해결 방법을 포함하는 경우입니다. 즉, 큰 문제의 최적의 해결책을 찾기 위해 작은 문제들의 최적의 해결책을 사용할 수 있어야 합니다.그리디 알고리즘으로 접근하는 게 효율적인 경우그리디 알고리즘의 대표적인 예시는 거스름돈 문제입니다.거스름돈을 구하는 과정은 아래와 같습니다. 예를 들어, 1, 5, 10, 25원의 동전이 있을 때, 32원을 거슬러주기 위해 다음과 같이 합니다.25원 동전 1개5원 동전 1개1원 동전 2개이를 코드로 그대로 옮기면 아래와 같습니다.python코드 복사def get_change(amount, coins): change = [] for coin in sorted(coins, reverse=True): while amount >= coin: amount -= coin change.append(coin) return change 위의 코드에서, 매 단계에서 가능한 가장 큰 동전을 선택합니다. 이 방법은 각 단계에서 가장 최적의 선택을 함으로써 최종적으로도 최적의 해결책을 찾을 수 있습니다.그리디 알고리즘으로 접근하는 게 효율적이지 않은 경우반대로, 그리디 알고리즘이 비효율적인 경우도 있습니다. 대표적인 예로는 배낭 문제가 있습니다.배낭 문제는 다음과 같습니다.배낭에 담을 수 있는 최대 무게가 정해져 있고, 여러 물건들이 각각의 무게와 가치가 주어집니다.물건들을 선택하여 배낭에 담을 때, 가치의 합이 최대가 되도록 해야 합니다.그리디 알고리즘을 사용하여 가치가 가장 높은 물건부터 선택하면 항상 최적의 해결책을 찾을 수 없습니다. 예를 들어, 다음과 같은 물건들이 있다고 합시다.물건 A: 무게 3, 가치 4물건 B: 무게 2, 가치 2물건 C: 무게 1, 가치 1배낭의 최대 무게가 3이라면, 그리디 알고리즘은 물건 A를 선택할 것입니다. 그러나, 최적의 해결책은 물건 B와 물건 C를 선택하여 가치를 3으로 만드는 것입니다. 이처럼, 그리디 알고리즘이 항상 최적의 해결책을 보장하지 않는 경우도 있습니다.이러한 이유로 배낭 문제는 동적 계획법을 사용하여 해결하는 것이 더 적합합니다.

알고리즘 · 자료구조자료구조알고리즘코딩테스트

dremdeveloper

[코딩 테스트 합격자 되기]스택으로 접근하는게 효율적인 경우

스택을 사용하여 문제를 해결하는 것은 때로는 경험이나 직감에 기반하기도 합니다. 그러나 일반적으로 다음 두 가지 주요 특성을 가진 문제들이 스택을 사용하여 해결될 가능성이 높습니다.후입선출 특성 (Last In, First Out, LIFO)스택은 후입선출 방식으로 동작합니다. 즉, 나중에 삽입된 요소가 먼저 제거됩니다. 따라서, 후입선출 특성을 이용해야 하는 문제에서 스택이 효과적입니다.재귀적 구조 (Recursive Structure)재귀적으로 정의된 문제를 반복문을 사용하여 해결할 때 스택이 유용합니다. 함수 호출의 재귀적인 구조를 명시적으로 스택을 사용하여 구현할 수 있습니다.스택으로 접근하는 게 효율적인 경우스택의 대표적인 예시는 괄호의 유효성을 검사하는 문제입니다.괄호의 유효성을 검사하는 과정은 아래와 같습니다. 예를 들어, 문자열 "(([]))"이 주어졌을 때, 괄호가 유효한지 검사합니다.이를 코드로 구현하면 아래와 같습니다.python코드 복사def is_valid_parentheses(s): stack = [] mapping = {")": "(", "]": "[", "}": "{"} for char in s: if char in mapping: top_element = stack.pop() if stack else '#' if mapping[char] != top_element: return False else: stack.append(char) return not stack 위의 코드에서 스택은 열린 괄호를 저장하고, 닫힌 괄호를 만날 때마다 스택의 최상단 요소와 비교합니다. 이 방법은 후입선출 특성을 이용하여 괄호의 짝을 맞추므로 효율적입니다.스택으로 접근하는 게 효율적이지 않은 경우반대로, 스택이 비효율적인 경우도 있습니다. 대표적인 예로는 배열의 합을 계산하는 문제가 있습니다.배열의 합을 계산하는 과정은 아래와 같습니다.주어진 배열의 모든 요소를 더합니다.이를 코드로 구현하면 아래와 같습니다.python코드 복사def sum_array(arr): total = 0 for num in arr: total += num return total 이 코드에서, 스택을 사용하여 각 요소를 저장하고 꺼내어 더할 수도 있습니다. 그러나 이는 불필요한 메모리 사용과 연산을 증가시킵니다. 단순히 반복문을 사용하여 배열의 합을 구하는 것이 더 효율적입니다.python코드 복사def sum_array_with_stack(arr): stack = [] for num in arr: stack.append(num) total = 0 while stack: total += stack.pop() return total 이와 같이, 배열의 합을 계산하는 문제는 스택을 사용하지 않는 것이 더 효율적입니다. 스택의 후입선출 특성이 불필요하게 사용되기 때문입니다.따라서, 스택을 사용해야 하는 문제는 주로 후입선출 특성을 활용해야 하는 문제나 재귀적인 구조를 반복문으로 해결해야 하는 문제입니다.

알고리즘 · 자료구조자료구조알고리즘코딩테스트

채널톡 아이콘