연결 리스트 알고리즘 문제 풀이

연결 리스트 요소 제거

문제 설명:

주어진 연결 리스트에서 특정 값을 가진 모든 노드를 제거하는 문제이다.

解题 전략:

노드를 삭제할 때 현재 노드의 next 포인터를 다음 노드의 next로 변경하면 된다. C++을 사용하므로 메모리 해제도 반드시 처리해야 한다. 더미 노드를 사용하면 헤드 노드의特殊性한 경우를 처리할 필요가 없어져 코드가 간단해진다.

구현 코드:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 더미 노드를 생성하여 연결 리스트의 헤드 처리 복잡도 감소
        ListNode* sentinel = new ListNode(0);
        sentinel->next = head;
        
        ListNode* current = sentinel;
        while(current->next != nullptr) {
            // 목표 값과 일치하는 노드를 발견하면 해당 노드 삭제
            if(current->next->val == val) {
                ListNode* target = current->next;
                current->next = current->next->next;
                delete target;
            } else {
                // 값이 일치하지 않으면 다음 노드로 이동
                current = current->next;
            }
        }
        
        head = sentinel->next;
        delete sentinel;
        return head;
    }
};

핵심 포인트:

  • 더미 노드를 활용하면 헤드 노드 삭제 케이스를 일반화할 수 있다
  • 삭제된 노드의 메모리를 반드시 해제해야 한다

연결 리스트 구현

문제 설명:

연결 리스트를 직접 구현하는 문제로, 다음 연산들을 지원해야 한다: 특정 인덱스 값 조회, 헤드에 노드 추가, 테일에 노드 추가, 특정 위치에 노드 삽입, 특정 위치 노드 삭제.

解题 전략:

연결 리스트의 크기를 멤버 변수로 저장하면 경계 조건 체크를 단순화할 수 있다. 모든 연산에서 크기 정보를 활용하여 효율적인 구현이 가능하다.

구현 코드:

class MyLinkedList {
private:
    struct Node {
        int value;
        Node* next;
        Node(int val) : value(val), next(nullptr) {}
    };
    
    Node* dummyHead;
    int elementCount;
    
public:
    MyLinkedList() {
        dummyHead = new Node(0);
        elementCount = 0;
    }
    
    // 인덱스에 해당하는 값 조회
    int get(int index) {
        if(index < 0 || index >= elementCount) {
            return -1;
        }
        
        Node* cursor = dummyHead->next;
        for(int i = 0; i < index; i++) {
            cursor = cursor->next;
        }
        return cursor->value;
    }
    
    // 헤드에 노드 추가
    void addAtHead(int val) {
        Node* newNode = new Node(val);
        newNode->next = dummyHead->next;
        dummyHead->next = newNode;
        elementCount++;
    }
    
    // 테일에 노드 추가
    void addAtTail(int val) {
        Node* cursor = dummyHead;
        while(cursor->next != nullptr) {
            cursor = cursor->next;
        }
        Node* newNode = new Node(val);
        cursor->next = newNode;
        elementCount++;
    }
    
    // 특정 인덱스에 노드 삽입
    void addAtIndex(int index, int val) {
        if(index > elementCount) {
            return;
        }
        if(index < 0) {
            index = 0;
        }
        
        Node* cursor = dummyHead;
        for(int i = 0; i < index; i++) {
            cursor = cursor->next;
        }
        
        Node* newNode = new Node(val);
        Node* temp = cursor->next;
        cursor->next = newNode;
        newNode->next = temp;
        elementCount++;
    }
    
    // 특정 인덱스의 노드 삭제
    void deleteAtIndex(int index) {
        if(index < 0 || index >= elementCount) {
            return;
        }
        
        Node* cursor = dummyHead;
        for(int i = 0; i < index; i++) {
            cursor = cursor->next;
        }
        
        Node* target = cursor->next;
        cursor->next = cursor->next->next;
        delete target;
        elementCount--;
    }
};

핵심 포인트:

  • delete 연산 후 포인터를 nullptr로 설정하여 와일드 포인터 방지
  • 크기 정보를 관리하면 경계 조건 처리가 용이

연결 리스트 뒤집기

문제 설명:

주어진 연결 리스트의 노드 순서를 반대로 뒤집는 문제이다.

解题 전략:

뒤집기 과정에서 핵심은 현재 노드의 다음 노드를 미리 저장하는 것이다. 순차적으로 진행하면서 각 노드의 next 포인터를 이전 노드로 변경하면 된다.

구현 코드:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* previous = nullptr;
        ListNode* current = head;
        ListNode* nextNode;
        
        while(current != nullptr) {
            // 현재 노드의 다음 노드를 미리 저장
            nextNode = current->next;
            
            // 현재 노드의 next를 이전 노드로 변경
            current->next = previous;
            
            // 이전 노드와 현재 노드를 한 칸씩 이동
            previous = current;
            current = nextNode;
        }
        
        return previous;
    }
};

핵심 포인트:

  • nextNode 변수로 다음 위치를 저장하지 않으면链表 구조가 끊어짐
  • 반복 종료 후 previous가新的 헤드가 됨

태그: C++ linked-list data-structures algorithm LeetCode

6월 17일 19:49에 게시됨