알고리즘 풀 때 꼭 알아야 하는 Java 문법

예름·2026년 7월 27일

Java

목록 보기
10/10

개요

  • 자바 알고리즘/코테 문제를 풀 때 필요한 지식들을 실전에서 쓰는 것들만 정리했습니다.
  • 세부 개념을 자세하게 설명하지 않습니다. 필요한 개념은 따로 공부하시길 권장드립니다.

입출력

입출력은 가장 기본이지만 처음엔 가장 어렵습니다.

기본

class Main {
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 한 줄씩 입력받음
        String str = br.readLine(); //
        int i = Integer.parseInt(br.readLine()); // int는 Integer.parseInt()로 변환해줘야 함
    }
}

한 줄에 여러 입력

class Main {
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 한 줄씩 입력받음
        StringTokenizer st = new StringTokenizer(br.readLine()); // 토큰화해서 임시 저장
        String str = st.nextToken(); // 공백 기준으로 잘라서 반환
    }
}

공백 없는 문자열

가령 이렇게 입력이 주어지는 경우도 있습니다.

BBWB
BWBW
BBWB
WBBW

StringTokenizer를 떠올렸을 수도 있지만, 저렇게 공백없는 문자열로 주어졌을 때는 적용되지 않습니다. 이때는 간단하게 문자열로 받아서 charAt() 함수를 쓰면 됩니다.

class Main {
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 한 줄씩 입력받음
        int N = Integer.parseInt(br.readLine());
        int[][] board = new int[N][N];
        
        for (int i = 0; i < N; i++) {
        	String line = br.readLine(); // 문자열 한 줄 받고
            
        	for (int j = 0; j < N; j++) {
            	board[i][j] = line.charAt(j); // 한 글자씩 저장
            }
        }
    }
}

출력

출력은 기본적으로 System.out.println() 을 사용합니다.

  • System.out.print(): 출력 후 개행문자(\n) 없음
  • System.out.println(): 출력 후 개행문자(\n) 있음

StringBuilder

하지만 System.out.print()은 여러 번 출력할 경우 시간이 오래 걸려 간혹 시간초과가 발생하기도 합니다.

그땐 StringBuilder에 임시 저장한 후 나중에 System.out.println()으로 한 번에 출력합니다.

class Main {
	public static void main(String[] args) throws Exception {
		StringBuilder sb = new StringBuilder();
        int answer = 0;
        sb.append(answer).append('\n'); // 개행문자도 append 해줘야 개행이 됨
        
        System.out.println(sb.toString()); // .toString()은 생략해도 됨
    }
}

StringBuilder vs StringBuffer

StringBuilder 말고도 StringBuffer도 있습니다.

둘의 차이는 멀티스레드에서 thread-safe 하냐 안하냐 입니다.

StringBuilder는 멀티스레드에서 thread-safe 하지 않습니다. 즉, 동시성을 보장하지 않기 때문에 값이 공유되는 특징이 있습니다.

반면 StringBuffer는 멀티스레드에서 thread-safe 하기 때문에 그만큼 확인하는 시간이 늘어 StringBuilder보다 시간이 오래 걸립니다.

물론, 두 개 모두 System.out.println() 보다는 적게 걸리지만 어차피 알고리즘을 풀 때는 멀티스레드를 고려하지 않아도 되기 때문에 시간이 조금이라도 적게 걸리는 StringBuilder()를 쓰는게 이득입니다.


자료구조

Java의 대표 자료구조로는 List, Set, Map이 있습니다. 해당 자료구조들은 Collection을 상속받기 때문에 Collection의 함수들도 속성으로 갖습니다.

CollectionIterable을 상속받기 때문에 iterator()을 사용할 수 있지만, 저는 개인적으로는 자주 사용하지 않습니다. (이터레이터 어려워욥 ㅜ)

자주 사용하는 함수는 볼드체로 적었으니, 참고하세요.

Array

가장 기본이 되는 자료구조입니다.

class Main {
	public static void main(String[] args) throws Exception {
		int[] arr = new int[10];
	}
}

Arrays.fill()

class Main {
	public static void main(String[] args) throws Exception {
    	// 1차원 배열 채우기
    	int[] arr = new int[10];
    	Arrays.fill(arr, -1); // arr = { -1, -1, ... -1 }
        
        // 2차원 배열 채우기
        int[][] arr2 = new int[10][10];
        for (int i = 0; i < 10; i++) {
        	Arrays.fill(arr2[i], -1); // arr = { { -1, -1, ... -1 }, ... , { -1, -1, ... -1 } }
        }
    }
}

Arrays.copyOf()

class Main {
	public static void main(String[] args) throws Exception {
    	int[] arr = { 1, 2, 3 };
    	int[] copy = Arrays.copyOf(arr, arr.length);
    }
}

Collection

분류함수파라미터설명
추가add(E e)추가할 원소원소 하나 추가
전체 추가addAll(Collection<? extends E> c)추가할 컬렉션컬렉션 전체 추가
포함 여부contains(Object o)찾을 원소해당 원소가 있는지
전체 포함 여부containsAll(Collection<?> c)비교할 컬렉션모든 원소를 포함하는지
동일 여부equals(Object o)비교 대상컬렉션 내용이 같은지
비어있는지isEmpty()-원소가 하나도 없는지
크기size()-원소 개수
전체 삭제clear()-모든 원소 삭제
삭제remove(Object o)삭제할 원소해당 원소 삭제
전체 삭제removeAll(Collection<?> c)삭제할 컬렉션컬렉션에 포함된 원소 전부 삭제
배열 변환toArray()-배열로 변환

List

List의 제네릭 타입은 반드시 참조 타입(Reference Type) 이어야 합니다.

즉, int, long, double, char 같은 primitive type은 사용할 수 없습니다.

class Main {
	public static void main(String[] args) throws Exception {
    	List<Integer> intList = new ArrayList<>();
		List<Long> longList = new ArrayList<>();
		List<Double> doubleList = new ArrayList<>();
		List<Character> charList = new ArrayList<>();
		List<Boolean> booleanList = new ArrayList<>();
        List<String> stringList = new ArrayList<>();
        
		List<int> list = new ArrayList<>();      // ❌ 컴파일 에러
		List<long> list = new ArrayList<>();     // ❌
		List<double> list = new ArrayList<>();   // ❌
        
        list.add(1);
		list.get(0);
		list.set(0, 10);
		list.remove(0);
		list.size();
		list.contains(10);
		Collections.sort(list);
    }
}
분류함수시간복잡도파라미터설명
추가add(E e)평균 O(1)추가할 원소맨 뒤에 추가
위치 추가add(int index, E e)O(N)인덱스, 원소원하는 위치에 추가
전체 추가addAll(Collection<? extends E> c)O(M)추가할 컬렉션맨 뒤에 컬렉션 추가
위치 전체 추가addAll(int index, Collection<? extends E> c)O(N + M)인덱스, 컬렉션원하는 위치에 컬렉션 추가
조회get(int index)O(1)인덱스해당 위치 원소 반환
첫 번째 위치indexOf(Object o)O(N)찾을 원소처음 등장하는 인덱스 반환
마지막 위치lastIndexOf(Object o)O(N)찾을 원소마지막 등장하는 인덱스 반환
삭제(인덱스)remove(int index)O(N)인덱스해당 위치 원소 삭제 후 반환
삭제(원소)remove(Object o)O(N)삭제할 원소해당 원소 삭제
수정set(int index, E e)O(1)인덱스, 원소해당 위치 원소 변경

Set

class Main {
	public static void main(String[] args) throws Exception {
    	Set<Integer> set = new HashSet<>();
        
        set.add(1);
		set.remove(1);
		set.contains(1);
		set.size();
    }
}

HashSet은 순서를 보장하지 않기 때문에 인덱스로 접근할 수 없습니다.
만약 특정 값을 꺼내고 싶다면 순회하거나 배열/리스트로 변환해야 합니다.

class Main {
	public static void main(String[] args) throws Exception {
    	for (int num : set) {
    		System.out.println(num);
		}
    }
}
분류함수시간복잡도파라미터설명
추가add(E e)평균 O(1)추가할 원소원소 추가 (중복이면 추가되지 않음)
전체 추가addAll(Collection<? extends E> c)O(M)추가할 컬렉션컬렉션 전체 추가
포함 여부contains(Object o)평균 O(1)찾을 원소해당 원소가 있는지
전체 포함 여부containsAll(Collection<?> c)평균 O(M)비교할 컬렉션모든 원소를 포함하는지
삭제remove(Object o)평균 O(1)삭제할 원소해당 원소 삭제
전체 삭제removeAll(Collection<?> c)평균 O(M)삭제할 컬렉션컬렉션에 포함된 원소 전부 삭제
전체 삭제clear()O(N)-모든 원소 삭제
비어있는지isEmpty()O(1)-원소가 하나도 없는지
크기size()O(1)-원소 개수
배열 변환toArray()O(N)-배열로 변환

TreeSet

원소를 자동으로 정렬하여 저장하는 Set입니다.

HashSet은 순서를 보장하지 않지만, TreeSet은 항상 정렬된 순서를 유지합니다.

 class Main {
        public static void main(String[] args) throws Exception {
            TreeSet<Integer> set = new TreeSet<>();

            set.add(3);
            set.add(1);
            set.add(2);

            System.out.println(set); // [1, 2, 3]

            set.first(); // 최소값
            set.last(); // 최대값
            set.ceiling(4); // 4 이상인 최소 원소
            set.floor(4); // 4 이하인 최대 원소
            set.higher(4); // 4 초과인 최소 원소
            set.lower(4); // 4 미만인 최대 원소
        }
    }
함수설명시간복잡도
add(E e)원소 추가O(log N)
remove(Object o)원소 삭제O(log N)
contains(Object o)포함 여부 확인O(log N)
first()최소값 반환O(log N)
last()최대값 반환O(log N)
ceiling(E e)e 이상인 최소 원소 반환O(log N)
floor(E e)e 이하인 최대 원소 반환O(log N)
higher(E e)e 초과인 최소 원소 반환O(log N)
lower(E e)e 미만인 최대 원소 반환O(log N)
pollFirst()최소값 삭제 후 반환O(log N)
pollLast()최대값 삭제 후 반환O(log N)
isEmpty()원소가 비어있는지 확인O(1)
size()원소 개수O(1)
clear()모든 원소 삭제O(N)

Map

class Main {
	public static void main(String[] args) throws Exception {
        map.computeIfAbsent(2, k -> new ArrayList<>()).add("C");
        
        map.put(1, "A");
		map.get(1);
		map.getOrDefault(0, "B");
		map.remove(1);
		map.containsKey(1);
		map.containsValue("B");
        
        // key 순회
        for (String key : map.keySet()) {

		}
		// key-value 조합으로 순회
		for (Map.Entry<String, Integer> entry : map.entrySet()) {

		}
    }
}
class Main {
	public static void main(String[] args) throws Exception {
    	Map<K, List<V>> map = new HashMap<>();
        
    	if (!map.containsKey(key)) {
        	map.put(key, new ArrayList<>());
		}
		map.get(key).add(value);
        
        // 한 줄로 하면 아래 코드
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }
}
분류함수시간복잡도파라미터설명
추가/수정put(K key, V value)평균 O(1)키, 값키에 값 저장 (기존 키면 덮어쓰기)
전체 추가putAll(Map<? extends K, ? extends V> m)평균 O(M)추가할 MapMap 전체 추가
조회get(Object key)평균 O(1)해당 키의 값 반환
기본값 조회getOrDefault(Object key, V defaultValue)평균 O(1)키, 기본값키가 없으면 기본값 반환
포함 여부(Key)containsKey(Object key)평균 O(1)해당 키가 있는지
포함 여부(Value)containsValue(Object value)O(N)해당 값이 있는지
삭제remove(Object key)평균 O(1)해당 키 삭제
삭제(Key, Value)remove(Object key, Object value)평균 O(1)키, 값키와 값이 모두 일치하면 삭제
전체 삭제clear()O(N)-모든 데이터 삭제
비어있는지isEmpty()O(1)-데이터가 없는지
크기size()O(1)-Key-Value 개수
Key 조회keySet()O(1)-모든 Key를 Set(View)으로 반환
Value 조회values()O(1)-모든 Value를 Collection(View)으로 반환
Entry 조회entrySet()O(1)-모든 Entry(Key-Value)를 Set(View)으로 반환
없으면 생성computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)평균 O(1)키, 생성 함수키가 없으면 값을 생성 후 반환

Deque

양쪽에서 삽입과 삭제가 가능한 자료구조입니다.
QueueStack 모두 구현할 수 있어서, Deque을 가장 많이 씁니다.

class Main {
	public static void main(String[] args) throws Exception {
    	Deque<Integer> dq = new ArrayDeque<>();
        
        dq.offer(1); // 큐처럼 사용
        dq.poll(); // 큐처럼 사용
        dq.push(2); // 스택처럼 사용
        dq.pop(); // 스택처럼 사용
        dq.peek();
        dq.addFirst();
        dq.addLast();
        dq.pollFirst();
        dq.pollLast();
        dq.peekFirst();
        dq.peekLast();
    }
}
분류함수시간복잡도파라미터설명
앞 추가addFirst(E e)O(1)추가할 원소맨 앞에 원소 추가, 실패 시 예외
뒤 추가addLast(E e)O(1)추가할 원소맨 뒤에 원소 추가, 실패 시 예외
앞 추가offerFirst(E e)O(1)추가할 원소맨 앞에 원소 추가, 실패 시 false
뒤 추가offerLast(E e)O(1)추가할 원소맨 뒤에 원소 추가, 실패 시 false
앞 조회getFirst()O(1)-맨 앞 원소 반환, 비어 있으면 예외
뒤 조회getLast()O(1)-맨 뒤 원소 반환, 비어 있으면 예외
앞 조회peekFirst()O(1)-맨 앞 원소 반환, 비어 있으면 null
뒤 조회peekLast()O(1)-맨 뒤 원소 반환, 비어 있으면 null
앞 삭제removeFirst()O(1)-맨 앞 원소 삭제 후 반환, 비어 있으면 예외
뒤 삭제removeLast()O(1)-맨 뒤 원소 삭제 후 반환, 비어 있으면 예외
앞 삭제pollFirst()O(1)-맨 앞 원소 삭제 후 반환, 비어 있으면 null
뒤 삭제pollLast()O(1)-맨 뒤 원소 삭제 후 반환, 비어 있으면 null
원소 삭제removeFirstOccurrence(Object o)O(N)삭제할 원소앞에서부터 처음 일치하는 원소 삭제
원소 삭제removeLastOccurrence(Object o)O(N)삭제할 원소뒤에서부터 처음 일치하는 원소 삭제
포함 여부contains(Object o)O(N)찾을 원소해당 원소가 있는지 확인
전체 삭제clear()O(N)-모든 원소 삭제
비어 있는지isEmpty()O(1)-원소가 하나도 없는지
크기size()O(1)-원소 개수

PriorityQueue

우선순위큐는 자동으로 정렬되는 Queue입니다.
기본은 오름차순입니다.

class Main {
	public static void main(String[] args) throws Exception {
    	// 기본(오름차순)
    	PriorityQueue<Integer> pq = new PriorityQueue<>();
        
        // 내림차순
        PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
        PriorityQueue<Integer> pq = new PriorityQueue<>((o1, o2) -> o2 - o1); // 전 이렇게 자주 사용했는데, 오버플로우 위험이 있다네요 -_- 하지만 한 번도... 읍읍
        PriorityQueue<Integer> pq = new PriorityQueue<>((o1, o2) -> Integer.compare(o2,o1));
    }
}
분류함수시간복잡도파라미터설명
추가offer(E e)O(log N)추가할 원소원소를 우선순위에 맞게 추가, 실패 시 false
추가add(E e)O(log N)추가할 원소원소를 우선순위에 맞게 추가, 실패 시 예외
최우선 조회peek()O(1)-우선순위가 가장 높은 원소 반환, 비어 있으면 null
최우선 조회element()O(1)-우선순위가 가장 높은 원소 반환, 비어 있으면 예외
최우선 삭제poll()O(log N)-우선순위가 가장 높은 원소 삭제 후 반환, 비어 있으면 null
최우선 삭제remove()O(log N)-우선순위가 가장 높은 원소 삭제 후 반환, 비어 있으면 예외
특정 원소 삭제remove(Object o)O(N)삭제할 원소해당 원소를 찾아 삭제
포함 여부contains(Object o)O(N)찾을 원소해당 원소가 있는지 확인
전체 추가addAll(Collection<? extends E> c)O(M log(N + M))추가할 컬렉션컬렉션의 원소를 모두 추가
전체 삭제clear()O(N)-모든 원소 삭제
비어 있는지isEmpty()O(1)-원소가 하나도 없는지
크기size()O(1)-원소 개수
배열 변환toArray()O(N)-배열로 변환

Graph

Java에는 그래프라는 자료구조는 없습니다. 모든 언어가 그렇듯 직접 구현해야 합니다.
대표적인 구현 방법은 인접 리스트, 인접 행렬, 간선 리스트입니다.

인접리스트

각 정점마다 연결된 정점들의 목록을 저장하는 방식입니다.
저는 주로 그래프는 인접리스트를 이용해서 구현합니다.

class Main {
	public static void main(String[] args) throws Exception {
    	List<List<Integer>> graph = new ArrayList<>();

		for (int i = 0; i <= N; i++) {
    		graph.add(new ArrayList<>());
		}

		graph.get(1).add(2);
		graph.get(1).add(3);
		graph.get(2).add(4);
        
        // 가중치가 있으면 int[] { 다음 정점, 가중치 } 로 저장
        List<List<int[]>> graph = new ArrayList<>();

		for (int i = 0; i <= N; i++) {
    		graph.add(new ArrayList<>());
		}

		graph.get(1).add(new int[] { 2, 5 });
		graph.get(1).add(new int[] { 3, 2 });
        
        // 순회
        for (int[] edge : graph.get(now)) {
    		int next = edge[0];
    		int cost = edge[1];
		}
    }
}
  • 공간복잡도: O(V + E)
  • 특정 정점의 인접 정점 순회: O(degree(V))
  • 두 정점의 연결 여부 확인: O(degree(V))
  • 간선 추가: 평균 O(1)

인접행렬

graph[i][j]에 정점 i와 j의 연결 여부 또는 가중치를 저장하는 방식입니다.
정점 수가 작거나 모든 정점의 연결 여부를 자주 확인해야 할 때 사용합니다.

class Main {
	public static void main(String[] args) throws Exception {
    	int[][] graph = new int[N + 1][N + 1];

		graph[1][2] = 1;
		graph[1][3] = 1;
        
        // 가중치 그래프는 가중치 저장
        graph[1][2] = 5;
		graph[2][1] = 3;
    }
}
  • 공간복잡도: O(V²)
  • 특정 정점의 인접 정점 순회: O(V)
  • 두 정점의 연결 여부 확인: O(1)
  • 간선 추가: 평균 O(1)

간선리스트

그래프의 모든 간선을 하나의 리스트에 저장하는 방식입니다.

class Main {
	public static void main(String[] args) throws Exception {
    	List<int[]> edges = new ArrayList<>();

		// { 출발 정점, 도착 정점, 가중치 }
		edges.add(new int[]{ 1, 2, 5 });
		edges.add(new int[]{ 1, 3, 10 });
        
        // 순회
        for (int[] edge : edges) {
    		int from = edge[0];
    		int to = edge[1];
    		int cost = edge[2];
		}
    }
}
  • 공간복잡도: O(E)
  • 전체 간선 순회: O(E)
  • 특정 정점의 인접 정점 순회: O(E)
  • 두 정점의 연결 여부 확인: O(E)
  • 간선 추가: 평균 O(1)

ArrayList vs LinkedList vs int[]

구분ArrayList<Integer>LinkedList<Integer>int[]
구조동적 배열연결 리스트고정 배열
크기 변경
인덱스 조회O(1)O(N)O(1)
마지막 추가평균 O(1)O(1)
중간 삽입·삭제O(N)탐색 포함 O(N)직접 이동
primitive 저장
코테 사용 빈도⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

핵심 특징

ArrayList

  • 인덱스 조회가 빠름 (O(1))
  • 중간 삽입·삭제는 느림 (O(N))
  • 코테에서 가장 많이 사용하는 List

LinkedList

  • 삽입·삭제 자체는 빠르지만 위치 탐색이 O(N)
  • 코테에서는 거의 사용하지 않음
  • Queue/Deque는 ArrayDeque 사용 권장

int[]

  • 크기가 고정되어 있음
  • primitive를 직접 저장하여 메모리 효율과 성능이 가장 좋음
  • 크기를 미리 안다면 가장 추천

List.of() vs Arrays.asList()

여러 값을 이용해 List를 간단하게 생성할 수 있습니다.

class Main {
	public static void main(String[] args) throws Exception {
    	List<Integer> list1 = List.of(1, 2, 3);
        List<Integer> list2 = Arrays.asList(1, 2, 3);
    }
}

둘 다 크기가 고정되어 있어 add(), remove()는 사용할 수 없습니다.

구분List.of()Arrays.asList()
add(), remove()
set()
null 저장
원본 배열과 연결

수정 가능한 리스트 만들기

class Main {
	public static void main(String[] args) throws Exception {
    	List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
		List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
    }
}

정렬

정렬의 기본은 오름차순입니다.

Arrays.sort()

배열을 정렬할 때 사용합니다.

primitive 배열은 Comparator를 사용할 수 없고, 객체 배열(Integer[])은 Comparator를 사용할 수 있습니다.

class Main {
	public static void main(String[] args) throws Exception {
    	// 오름차순
    	int[] arr = new int[10];
		Arrays.sort(arr);
        
        // 내림차순(Wrapper 클래스여야 Comparator 사용 가능)
        Integer[] arr2 = { 1, 2, 3 };
        Arrays.sort(arr2, Comparator.reverseOrder());
    }
}

Collections.sort()

Collection을 정렬할 때 사용합니다.

class Main {
	public static void main(String[] args) throws Exception {
    	List<Integer> list = new ArrayList<>();

		Collections.sort(list);
    }
}

Comparable vs Comparator

Comparable

객체 내부에서 기본 정렬 기준을 정의합니다.

class Node implements Comparable<Node> {

    int cost;

    @Override
    public int compareTo(Node o) {
        return this.cost - o.cost;
        // 또는 return Integer.compare(this.cost, o.cost);
    }
}

Comparator

정렬할 때마다 기준을 바꾸고 싶을 때 사용합니다.

Collections.sort(list, (o1, o2) -> {
    if (o1.cost != o2.cost) return o1.cost - o2.cost;
    // 또는 return Integer.compare(o1.cost, o2.cost);
    return o1.idx - o2.idx;
});

기타

length vs length() vs size()

  • length: 배열의 길이
  • length(): String의 길이
  • size(): Collection의 길이
class Main {
	public static void main(String[] args) throws Exception {
    	int[] arr = new int[10];
    	System.out.println(arr.length); // 10
        
        String str = "안녕하세요";
		System.out.println(str.length()); // 5
        
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
		System.out.println(list.size()); // 3
        
        // 이하 동일
		set.size();
		map.size();
    }
}

Math

class Main {
	public static void main(String[] args) throws Exception {
    	Math.max(a, b); // 최대
        Math.min(a, b); // 최소
        Math.abs(x); // 절대값
        Math.sqrt(x); // 루트
        Math.pow(a, b); // 제곱수
        Math.ceil(x); // 올림
        Math.floor(x); // 내림
        Math.round(x); // 반올림
    }
}

문자열 ⇄ 숫자

class Main {
	public static void main(String[] args) throws Exception {
    	int n = Integer.parseInt(str);
		long l = Long.parseLong(str);
		String s = String.valueOf(n);
    }
}

자주 쓰는 import

이 두 개만 import하면 99%는 다 커버할 수 있습니다.
(사실 전 한 번도 이 이상 import 해본적이 없지만 혹시 몰라서 1% 남겨두겠습니다 ㅎㅎ)

import java.io.*;
import java.util.*;

마치며

중간중간 귀찮아서 AI한테 맡긴 것도 있지만, 내용 자체는 제 경험을 바탕으로 생각해낸거니 믿으셔도 됩니다.

필요한 내용은 추후 추가하겠습니다.

profile
안정적인 쳇바퀴를 돌리는 삶

2개의 댓글

comment-user-thumbnail
2026년 7월 27일

오랜만에 코테 풀 때마다 항상 까먹곤했는데 이렇게 정리해주셔서 감사합니다!

1개의 답글