题目链接

设计链表

题目描述

你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性: v a l val val n e x t next next v a l val val是当前节点的值, n e x t next next是指向下一个节点的指针 / / /引用。
如果是双向链表,则还需要属性 p r e v prev prev以指示链表中的上一个节点。假设链表中的所有节点下标从 0 0 0开始。

实现 M y L i n k e d L i s t MyLinkedList MyLinkedList类:

  • M y L i n k e d L i s t ( ) MyLinkedList() MyLinkedList()初始化 M y L i n k e d L i s t MyLinkedList MyLinkedList对象。
  • i n t   g e t ( i n t   i n d e x ) int\ get(int\ index) int get(int index)获取链表中下标为 i n d e x index index的节点的值。如果下标无效,则返回 − 1 -1 1
  • v o i d   a d d A t H e a d ( i n t   v a l ) void\ addAtHead(int\ val) void addAtHead(int val)将一个值为 v a l val val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • v o i d   a d d A t T a i l ( i n t   v a l ) void\ addAtTail(int\ val) void addAtTail(int val)将一个值为 v a l val val的节点追加到链表中作为链表的最后一个元素。
  • v o i d   a d d A t I n d e x ( i n t   i n d e x ,   i n t   v a l ) void\ addAtIndex(int\ index,\ int\ val) void addAtIndex(int index, int val)将一个值为 v a l val val的节点插入到链表中下标为 i n d e x index index的节点之前。如果 i n d e x index index等于链表的长度,那么该节点会被追加到链表的末尾。如果 i n d e x index index比长度更大,该节点将 不会插入到链表中。
  • v o i d   d e l e t e A t I n d e x ( i n t   i n d e x ) void\ deleteAtIndex(int\ index) void deleteAtIndex(int index)如果下标有效,则删除链表中下标为 i n d e x index index的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

提示:

  • 0 < = i n d e x , v a l < = 1000 0 <= index, val <= 1000 0<=index,val<=1000
  • 请不要使用内置的 L i n k e d L i s t LinkedList LinkedList库。
  • 调用 g e t get get a d d A t H e a d addAtHead addAtHead a d d A t T a i l addAtTail addAtTail a d d A t I n d e x addAtIndex addAtIndex d e l e t e A t I n d e x deleteAtIndex deleteAtIndex的次数不超过 2000 2000 2000

解题思路

这个题目要求我们实现设计一个链表(链表的下标从0开始),实现链表的 5 5 5个接口:

  1. 获取链表下表为 i n d e x index index的结点的值
  2. 在头结点之前插入一个结点
  3. 在链表的最后插入一个结点
  4. 在指定位置插入一个结点
  5. 删除下表为 i n d e x index index的结点
    对于这个题,可以选择设计一个单链表,也可以选择实现一个双链表。

单链表的设计

单链表的每个结点除了数据区域外,还有有一个指针区域用来指向下一个结点。

单链表结构
结点结构:

struct ListNode {
	int val;
	ListNode* next;
	ListNode() : val(0), next(nullptr) {}
	ListNode(int val) : val(val), next(nullptr) {}
	ListNode(int val, ListNode* next) : val(val), next(next) {}
};

插入结点:

// 假设要在p节点的后面插入一个q结点
q->next = p->next;
p->next = q;

插入结点

删除结点:

// 假设p是要删除的结点的前一个结点
p->next = p->next->next;

删除结点

不用虚拟头结点:

class MyLinkedList {
public:
	// 构造函数,初始化链表
	MyLinkedList() {
		head = nullptr;
		length = 0;
	}
	
	int get(int index) {
		// 下标无效,返回-1
		if (index < 0 || index >= length)
			return -1;
		ListNode* now = head;
		// 搜索链表中下标为index的结点
		while (index) {
			now = now->next;
			index--;
		}
		// 返回结点的值
		return now->val;
	}

	void addAtHead(int val) {
		ListNode* p = new ListNode(val, head);
		// 在头结点之前插入新节点后,新节点将作为新的头结点
		head = p;
		// 链表中每插入一个结点,链表的长度就增加1
		length++;
	}

	void addAtTail(int val) {
		// 链表为空时,在链表的最后插入结点的操作和在头部插入结点的操作一样的
		if (!head)
            addAtHead(val);
        else {
            ListNode* p = head;
            // 定位到最后一个结点
            while (p->next) {
                p = p->next;
            }
            ListNode* adder = new ListNode(val);
            // 直接让最后一个结点的next指针指向新结点
            p->next = adder;
            // 链表中每插入一个结点,链表的长度就增加1
            length++;
        }
	}

	void addAtIndex(int index, int val) {
		// 无效下标,直接结束
		if (index < 0 || index > length)
            return;
        // index为0的时候,直接在头部插入
        if (index == 0)
            addAtHead(val);
        // index和链表的长度相等时,在链表的最后插入结点
        else if (index == length)
            addAtTail(val);
        else {
        	// 搜索到下标为index结点的前一个结点
            ListNode* now = head;
            while (index - 1) {
                now = now->next;
                index--;
            }
            // 执行插入操作
            ListNode* p = new ListNode(val, now->next);
            now->next = p;
            length++;
        }
	}

	void deleteAtIndex(int index) {
		// 链表为空
		if (!head)
            return;
		// 无效下标
        if (index < 0 || index >= length)
            return;
        // 删除头结点
        if (index == 0) {
            head = head->next;
        }
        // 删除非头结点
        else {
            ListNode* p = head;
            // 搜索到要删除的结点的前一个结点
            while (index - 1) {
                p = p->next;
                index--;
            }
            p->next = p->next->next;
        }
        // 从链表中删除一个结点,链表的长度减1
        length--;
	}

private:
	int length; // 链表的长度
	ListNode* head; // 头结点
};

使用虚拟头结点:

class MyLinkedList {
public:
    MyLinkedList() {
        virtualHead = new ListNode(0);
        length = 0;
    }
    
    int get(int index) {
        if (index < 0 || index >= length)
            return -1;
        // 从虚拟头结点下面一个结点(实际地=的头结点)开始搜索
        ListNode* p = virtualHead->next;
        while (index) {
            p = p->next;
            index--;
        }
        return p->val;
    }
    
    // 可以直接插入到虚拟头结点的后面
    void addAtHead(int val) {
        ListNode* p = new ListNode(val, virtualHead->next);
        virtualHead->next = p;
        length++;
    }
    
    void addAtTail(int val) {
        ListNode* now = virtualHead;
        while (now->next) {
            now = now->next;
        }
        ListNode* p = new ListNode(val);
        now->next = p;
        length++;
    }
    
    void addAtIndex(int index, int val) {
        if (index < 0 || index > length)
            return;
        if (index == length)
            addAtTail(val);
        else {
            ListNode* p = virtualHead;
            while (index) {
                p = p->next;
                index--;
            }
            p->next = new ListNode(val, p->next);
            length++;
        }
    }
    
    void deleteAtIndex(int index) {
        if (index < 0 || index >= length)
            return;
        ListNode* p = virtualHead;
        while (index) {
            p = p->next;
            index--;
        }
        p->next = p->next->next;
        length--;
    }
private:
    int length; // 链表的长度
    ListNode* virtualHead; // 虚拟头结点,真正的头结点是虚拟头结点的下一个结点
};

双链表的设计

双链表的结构:
双链表结构
关于双链表的插入操作:

// 假设在结点now的后面插入结点为p
p->next = now->next;
p->next->pre = p;
now->next = p;
p->pre = now;

插入结点
关于双链表的删除操作:

// 假设要删除的前一个结点为p
p->next = p->next->next;
p->next->pre = p;

删除结点
代码展示:

// 定义双链表的结点
struct ListNode {
	int val;
	ListNode* pre;
	ListNode* next;
	ListNode() : val(0), pre(nullptr), next(nullptr) {}
	ListNode(int val) : val(val), pre(nullptr), next(nullptr) {}
	ListNode(int val, ListNode* pre, ListNode* next) : val(val), pre(pre), next(next) {}
};

class MyLinkedList {
public:
	// 构造函数,初始化对象
    MyLinkedList() {
        virtualHead = new ListNode1(0);
        length = 0;
    }
    
    // 通过下标获取结点的值
    int get(int index) {
        if (index < 0 || index >= length)
            return -1;
        ListNode1* now = virtualHead;
        while (index) {
            now = now->next;
            index--;
        }
        return now->next->val;
    }
    
    // 在第一个元素之前插入一个结点
    void addAtHead(int val) {
        ListNode1* p = new ListNode1(val);
        if (!virtualHead->next) {
            virtualHead->next = p;
            p->pre = virtualHead;
        }
        else {
            p->next = virtualHead->next;
            virtualHead->next->pre = p;
            p->pre = virtualHead;
            virtualHead->next = p;
        }
        length++;
    }
    
    // 在链表末尾插入一个结点
    void addAtTail(int val) {
        ListNode1* p = new ListNode1(val);
        ListNode1* now = virtualHead;
        while (now->next) {
            now = now->next;
        }
        now->next = p;
        p->pre = now;
        length++;
    }
    
    // 在指定位置插入一个结点
    void addAtIndex(int index, int val) {
        if (index < 0 || index > length)
            return;
        if (index == 0) {
            addAtHead(val);
            return;
        }
        if (index == length) {
            addAtTail(val);
            return;
        }
        ListNode1* now = virtualHead;
        while (index) {
            now = now->next;
            index--;
        }
        ListNode1* p = new ListNode1(val);
        p->next = now->next;
        now->next->pre = p;
        now->next = p;
        p->pre = now;
        length++;
    }
    
    // 删除指定下标的结点
    void deleteAtIndex(int index) {
        if (index < 0 || index >= length)
            return;
        ListNode1* now = virtualHead;
        while (index) {
            now = now->next;
            index--;
        }
        ListNode1* p = now->next;
        if (!p->next) {
            now->next = nullptr;
            delete p;
        }
        else {
            p->next->pre = now;
            now->next = p->next;
            delete p;
        }
        length--;
    }
private:
    int length; // 链表的长度
    ListNode1* virtualHead; // 虚拟头结点
};

总结

链表是一种常见的数据结构,在计算机科学中用于以线性方式存储数据,但它在物理存储单元上是非连续、非顺序的,关于链表常见的基本操作包括创建、插入、删除、查找和遍历等。

本文参考视频:帮你把链表操作学个通透!LeetCode:707.设计链表
本文参考文章:代码随想录——设计链表

Logo

2万人民币佣金等你来拿,中德社区发起者X.Lab,联合德国优秀企业对接开发项目,领取项目得佣金!!!

更多推荐