リンクリストの基本操作:要素削除・設計・反転をC言語で徹底解説

1. 問題203:リンクリストの特定要素削除

// ヘッダノードを使用しない実装(移動操作で削除)
struct ListNode* removeElements(struct ListNode* head, int val) {
    // 先頭ノードが削除対象の場合
    while (head != NULL && head->val == val) {
        head = head->next;
    }
    // 先頭以外のノードを削除
    struct ListNode* current = head;
    while (current != NULL && current->next != NULL) {
        if (current->next->val == val) {
            current->next = current->next->next;
        } else {
            current = current->next;
        }
    }
    return head;

    // ダミーヘッダノード版(free忘れあり)
    struct ListNode* dhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dhead->next = head;
    current = dhead;
    while (current->next != NULL) {
        if (current->next->val == val) {
            current->next = current->next->next;
        } else {
            current = current->next;
        }
    }
    return dhead->next;
}

// ダミーヘッダ+メモリ解放を実装した完全版
struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode newHead;
    newHead.next = head;

    for (struct ListNode* p = &newHead; p->next != NULL; ) {
        if (p->next->val == val) {
            struct ListNode* target = p->next;
            p->next = target->next;
            free(target);
        } else {
            p = p->next;
        }
    }
    return newHead.next;
}

上記のコードでは、最初と最後が正しい実装です。真ん中のコードは、メモリ解放を省略しているものの動作はする例として提示しています。

2. 問題707:独自リンクリストの設計

typedef struct Node {
    int val;
    struct Node* next;
} Node;

typedef struct {
    int size;        // 有効要素数
    Node* data;      // ダミーヘッダノードへのポインタ
} MyLinkedList;

MyLinkedList* myLinkedListCreate() {
    MyLinkedList* obj = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    Node* dummy = (Node*)malloc(sizeof(Node));
    dummy->next = NULL;
    obj->data = dummy;
    obj->size = 0;
    return obj;
}

int myLinkedListGet(MyLinkedList* obj, int index) {
    if (index < 0 || index >= obj->size || obj->data == NULL)
        return -1;
    Node* p = obj->data->next; // ダミーヘッダの次から開始
    for (int i = 0; p != NULL; i++) {
        if (i == index)
            return p->val;
        p = p->next;
    }
    return -1;
}

void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->val = val;
    newNode->next = obj->data->next;
    obj->data->next = newNode;
    obj->size++;
}

void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    Node* p = obj->data;
    while (p->next != NULL) p = p->next;
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->val = val;
    newNode->next = NULL;
    p->next = newNode;
    obj->size++;
}

void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    if (index < 0 || index > obj->size) return;
    // インデックス位置の1つ前に挿入
    Node* prev = obj->data;
    for (int i = 0; i < index; i++) prev = prev->next;
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->val = val;
    newNode->next = prev->next;
    prev->next = newNode;
    obj->size++;
}

void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    if (index < 0 || index >= obj->size) return;
    Node* prev = obj->data;
    for (int i = 0; i < index; i++) prev = prev->next;
    Node* target = prev->next;
    prev->next = target->next;
    free(target);
    obj->size--;
}

void myLinkedListFree(MyLinkedList* obj) {
    Node* cur = obj->data;
    while (cur != NULL) {
        Node* tmp = cur;
        cur = cur->next;
        free(tmp);
    }
    free(obj);
}

このコードは一部に論理上の問題があります。特にmyLinkedListGetがダミーヘッダの値(無効な値)を返す場合があるため、注意が必要です。正しい実装では、最初の有効ノード(obj->data->next)から探索を開始すべきですが、上記コードでは obj->data 自体から始めています。これがバグの原因です。

3. 問題206:リンクリストの反転

// 2ポインタ方式
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* current = head;
    struct ListNode* prev = NULL;
    while (current != NULL) {
        struct ListNode* nextTemp = current->next;
        current->next = prev;
        prev = current;
        current = nextTemp;
    }
    return prev;
}

// 再帰方式
struct ListNode* reverseRecur(struct ListNode* cur, struct ListNode* pre) {
    if (cur == NULL) return pre;
    struct ListNode* next = cur->next;
    cur->next = pre;
    return reverseRecur(next, cur);
}

struct ListNode* reverseList(struct ListNode* head) {
    return reverseRecur(head, NULL);
}

まず2ポインタ法を理解し、その後に再帰版を学習するのが効率的です。再帰は2ポインタ法の考え方を、末尾再帰で実現しています。

タグ: リンクリスト C言語 アルゴリズム LeetCode メモリ管理

8月9日 12:37 投稿