리스트 구조 이해 및 기본 연산 실습

연결 리스트 기초 개념

배열과 연결 리스트의 차이점을 이해하는 것이 중요합니다. 배열은 연속된 메모리 공간에 저장되지만, 연결 리스트는 각 요소가 다음 요소를 가리키는 포인터로 연결됩니다. 이로 인해 삽입/삭제 시 시간 복잡도가 다르며, 특히 중간 위치에서의 조작이 유연합니다.

203. 연결 리스트 요소 제거

가상 헤드 노드를 사용하면 첫 번째 노드 삭제 시 처리가 간편해집니다. 실제 헤드가 삭제될 수 있으므로, 반환할 때는 가상 헤드의 다음 노드를 반환해야 합니다.

ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode current = dummy;

while (current.next != null) {
    if (current.next.val == targetValue) {
        current.next = current.next.next;
    } else {
        current = current.next;
    }
}
return dummy.next;

재귀적 접근

재귀는 끝에서부터 시작하여 역순으로 처리합니다. 현재 노드의 값이 삭제 대상이라면 다음 노드를 반환하고, 그렇지 않으면 자신을 반환합니다.

public ListNode removeElements(ListNode node, int value) {
    if (node == null) return null;
    
    node.next = removeElements(node.next, value);
    return node.val == value ? node.next : node;
}

707. 연결 리스트 설계

단일 연결 리스트와 양방향 연결 리스트 두 가지 방식으로 구현할 수 있습니다. 가상 헤드를 사용하면 삽입/삭제 로직이 일관되게 유지됩니다.

단일 연결 리스트 구현

class MyLinkedList {
    private int size;
    private ListNode dummyHead;

    public MyLinkedList() {
        this.size = 0;
        this.dummyHead = new ListNode(0);
    }

    public int get(int index) {
        if (index < 0 || index >= size) return -1;
        ListNode cur = dummyHead;
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = dummyHead.next;
        dummyHead.next = newNode;
        size++;
    }

    public void addAtTail(int val) {
        ListNode cur = dummyHead;
        while (cur.next != null) cur = cur.next;
        cur.next = new ListNode(val);
        size++;
    }

    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) return;
        ListNode prev = dummyHead;
        for (int i = 0; i < index; i++) prev = prev.next;
        ListNode newNode = new ListNode(val);
        newNode.next = prev.next;
        prev.next = newNode;
        size++;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) return;
        ListNode prev = dummyHead;
        for (int i = 0; i < index; i++) prev = prev.next;
        prev.next = prev.next.next;
        size--;
    }
}

양방향 연결 리스트 구현

양방향 연결 리스트는 앞뒤로 탐색이 가능하며, 인덱스가 중앙 근처인지 끝 근처인지 판단하여 효율적인 탐색을 수행할 수 있습니다.

class MyLinkedList {
    private int size;
    private ListNode head, tail;

    public MyLinkedList() {
        this.size = 0;
        this.head = new ListNode(0);
        this.tail = new ListNode(0);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int index) {
        if (index < 0 || index >= size) return -1;
        ListNode cur = index >= size / 2 ? tail : head;
        for (int i = 0; i < (index >= size / 2 ? size - index : index + 1); i++) {
            cur = index >= size / 2 ? cur.prev : cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) return;
        ListNode prev = head;
        for (int i = 0; i < index; i++) prev = prev.next;
        ListNode newNode = new ListNode(val);
        newNode.next = prev.next;
        prev.next.prev = newNode;
        newNode.prev = prev;
        prev.next = newNode;
        size++;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) return;
        ListNode prev = head;
        for (int i = 0; i < index; i++) prev = prev.next;
        prev.next.next.prev = prev;
        prev.next = prev.next.next;
        size--;
    }
}

206. 연결 리스트 반전

반전은 세 개의 포인터를 사용하여 순차적으로 연결을 뒤집는 방식입니다.

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode current = head;
    ListNode nextTemp = null;

    while (current != null) {
        nextTemp = current.next;
        current.next = prev;
        prev = current;
        current = nextTemp;
    }
    return prev;
}

재귀 방식 (후행 재귀)

끝까지 도달한 후, 돌아오면서 연결을 뒤집습니다.

public ListNode reverseList(ListNode node) {
    if (node == null || node.next == null) return node;
    
    ListNode last = reverseList(node.next);
    node.next.next = node;
    node.next = null;
    return last;
}

재귀의 구조를 명확히 이해하고, 매번 호출 스택의 상태를 추적하는 연습이 필요합니다.

태그: 연결 리스트 단일 연결 리스트 양방향 연결 리스트 재귀 가상 헤드 노드

8월 5일 03:48에 게시됨