力扣链表专题 -2 【单链表 java js c++】 leetcode707 设计链表 通过设计一个自己的链表来强化对(单/双)链表的理解

欢迎来到力扣刷题笔记的第三部分 —— 超极高频的链表!
总路线戳这里~

707.设计链表【双链表专题记得再来写双链表方法】

练习设计自己的单链表~

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnext

val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。
    • 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
    • 如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

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

解题思路

就挨个功能实现咯

设计题考察对数据结构基本操作的熟练哈~

官方题解中提示的面试要点如下:
请添加图片描述
注意两个要点

  • 哨兵节点 sentinel node

哨兵节点在树和链表中被广泛用作伪头(pseudo-head)、伪尾等,通常不保存任何数据。

我们将使用伪头来简化我们的插入和删除。我们将在在单链表、双链表中应用此方法。

  • 双链表的双向搜索

我们可以从头部或尾部进行搜索。

另外需要注意本题的index是从0开始算起的(从示例可以看出 第0个节点 第1个节点…)

Java代码

单链表

public class ListNode{
    //以下为单链表中结点的典型定义
    int val;
    ListNode next;
    ListNode(int x){
        val = x;
    }
}
class MyLinkedList {
    /** Initialize your data structure here. */
    //以下是对单链表的实现
    int size;//size存储链表中元素的个数 进行插入操作size++ 进行删除操作size--
    ListNode head;//哨兵节点
    //哨兵节点被用作伪头始终存在,这样结构中永远不为空,它将至少包含一个伪头。
    public MyLinkedList(){
        //MyLinkedList 中所有节点均包含:值 + 链接到下一个元素的指针。
        size = 0;
        head = new ListNode(0);
    }
        /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    //获取链表中第 index 个节点的值(从0算起)
    public int get(int index) {
        if(index < 0 || index >= size) return -1;
        ListNode ans = head;
        //接下来遍历寻找要get的节点
        for(int i = 0; i < index + 1; i++){
            ans = ans.next;
        }
        return ans.val;
    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
        addAtIndex(-666, val);//传入的index值只要<=0 即可插入到头节点
    }
    
    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
        addAtIndex(size, val);//传入的index值 >= size 即可插入到尾节点
    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    //单链表的插入操作
    public void addAtIndex(int index, int val) {
        //如果index大于链表长度 返回空 index小于0 添加到头节点
        if (index > size) return;
        if(index < 0) index = 0;
        size++;
        //先设置 然后找到插入位置节点的前驱节点prev 注意这里是一个模板!
        ListNode prev = head;//初始化prev为虚拟头节点head 在链表为空时进行插入删除/插入头节点操作时将更好理解
        for(int i = 0; i < index; i++){
            prev = prev.next;
        }
        ListNode toAdd = new ListNode(val);
        //01 先让要插入的节点指向prev的下一个节点 也就是插入过后toAdd的下一个节点
        toAdd.next = prev.next;
        //02 然后prev再指向toAdd
        prev.next = toAdd;
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    //单链表的删除操作
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= size) return;
        size--;
        //和上面添加的操作类似 找到要删除的那个节点的前一个 之后prev跨过要删除的节点直接指向.next.next即可
        ListNode prev = head;
        for(int i = 0; i < index; i++){
            prev = prev.next;
        }
        prev.next = prev.next.next;
    }
}

双链表

双链表是最常用的一种方法

因为它提供了——

  • 常数时间内的addAtHeadaddAtTail操作
  • 而单链表的addAtHead是常数时间的 但是由于没有设置虚拟尾节点 所以 addAtTail方法是线性时间的( O(n) )
public class ListNode{
    //以下为双链表中结点的典型定义
    int val;
    ListNode next;
    ListNode prev;//下一个节点 的意思
    ListNode(int x){
        val = x;
    }
}
class MyLinkedList {
    /** Initialize your data structure here. */
    //以下是对单链表的实现
    int size;//size存储链表中元素的个数 进行插入操作size++ 进行删除操作size--
    ListNode head, tail;//哨兵节点 用作虚拟头节点head 和 虚拟尾节点tail
    //哨兵节点被用作伪头始终存在,这样结构中永远不为空,它将至少包含一个伪头。
    public MyLinkedList(){
        //MyLinkedList 中所有节点均包含:值 + 链接到下一个元素的指针。
        size = 0;
        head = new ListNode(0);
        tail = new ListNode(0);//虚拟尾节点
        head.next = tail;
        tail.prev = head;
    }
        /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    //获取链表中第 index 个节点的值(从0算起)
    public int get(int index) {
        // if index is invalid
        if (index < 0 || index >= size) return -1;

        // choose the fastest way: to move from the head
        // or to move from the tail
        ListNode curr = head;
        if (index + 1 < size - index)
        for(int i = 0; i < index + 1; ++i) curr = curr.next;
        else {
            curr = tail;
            for(int i = 0; i < size - index; ++i) curr = curr.prev;
        }

        return curr.val;
    }
    
    
    public void addAtHead(int val) {
        ListNode pred = head, succ = head.next;

        ++size;
        ListNode toAdd = new ListNode(val);
        toAdd.prev = pred;
        toAdd.next = succ;
        pred.next = toAdd;
        succ.prev = toAdd;
    }
    
    public void addAtTail(int val) {
        ListNode succ = tail, pred = tail.prev;

        ++size;
        ListNode toAdd = new ListNode(val);
        toAdd.prev = pred;
        toAdd.next = succ;
        pred.next = toAdd;
        succ.prev = toAdd;
    }
    
    //单链表的插入操作
    public void addAtIndex(int index, int val) {
        //如果index大于链表长度 返回空 index小于0 添加到头节点
        if (index > size) return;
        if(index < 0) index = 0;
        
        //先设置 然后找到插入位置节点的前驱节点pred 注意这里是一个模板!
        ListNode pred, succ;
        if(index < size - index){
            //插入点在前半部分
            pred = head;
            for(int i = 0; i < index; i++){
                pred = pred.next;
            }
            succ = pred.next;
        }
        else{
            //插入点在后半部分 速度加快了!
            succ = tail;
            for(int i = 0; i < size - index; i++){
                //往前倒 找插入位置的上一个节点
                succ = succ.prev;
            }
            pred = succ.prev;
        }
        size++;
        ListNode toAdd = new ListNode(val);
        //双链表的插入方式
        toAdd.prev = pred;
        toAdd.next = succ;
        pred.next = toAdd;
        succ.prev = toAdd;
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    //单链表的删除操作
    public void deleteAtIndex(int index) {
        // if the index is invalid, do nothing
        if (index < 0 || index >= size) return;

        // find predecessor and successor of the node to be deleted
        ListNode pred, succ;
        if (index < size - index) {
        pred = head;
        for(int i = 0; i < index; ++i) pred = pred.next;
        succ = pred.next.next;
        }
        else {
            succ = tail;
            for (int i = 0; i < size - index - 1; ++i) succ = succ.prev;
            pred = succ.prev.prev;
        }

        // delete pred.next 
        --size;
        pred.next = succ;
        succ.prev = pred;
    }   
}

JS代码

这里参考的 「代码随想录」带你搞定链表!707. 设计链表:【链表基础题目】详解

整体的思路很清晰 还附带了链表基础知识、虚拟头节点的总结

class LinkNode {
    constructor(val, next) {
        this.val = val;
        this.next = next;
    }
}

/**
 * Initialize your data structure here.
 * 单链表 储存头尾节点 和 节点数量
 */
var MyLinkedList = function() {
    this._size = 0;
    this._tail = null;
    this._head = null;
};

/**
 * Get the value of the index-th node in the linked list. If the index is invalid, return -1. 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.getNode = function(index) {
    if(index < 0 || index >= this._size) return null;
    // 创建虚拟头节点
    let cur = new LinkNode(0, this._head);
    // 0 -> head
    while(index-- >= 0) {
        cur = cur.next;
    }
    return cur;
};
MyLinkedList.prototype.get = function(index) {
    if(index < 0 || index >= this._size) return -1;
    // 获取当前节点
    return this.getNode(index).val;
};

/**
 * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function(val) {
    const node = new LinkNode(val, this._head);
    this._head = node;
    this._size++;
    if(!this._tail) {
        this._tail = node;
    }
};

/**
 * Append a node of value val to the last element of the linked list. 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function(val) {
    const node = new LinkNode(val, null);
    this._size++;
    if(this._tail) {
        this._tail.next = node;
        this._tail = node;
        return;
    }
    this._tail = node;
    this._head = node;
};

/**
 * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function(index, val) {
    if(index > this._size) return;
    if(index <= 0) {
        this.addAtHead(val);
        return;
    }
    if(index === this._size) {
        this.addAtTail(val);
        return;
    }
    // 获取目标节点的上一个的节点
    const node = this.getNode(index - 1);
    node.next = new LinkNode(val, node.next);
    this._size++;
};

/**
 * Delete the index-th node in the linked list, if the index is valid. 
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function(index) {
    if(index < 0 || index >= this._size) return;
    if(index === 0) {
        this._head = this._head.next;
        this._size--;
        return;
    }
    // 获取目标节点的上一个的节点
    const node = this.getNode(index - 1);    
    node.next = node.next.next;
    // 处理尾节点
    if(index === this._size - 1) {
        this._tail = node;
    }
    this._size--;
};

双链表
法一

参考了 基础易懂的链表 官方题解

双链表比单链表快得多 但是它更加复杂,它包含了 size,记录链表元素个数,和伪头伪尾。

这个效率较低。。。

// 定义数据结构
var MyLinkedList = function() {
    this.ListNode = function(val, prev, next){
        this.val = val;
        this.prev = prev;
        this.next = next;
    }
    // 添加了 size 记录 结点个数
    this.size = 0;
    // 创建虚拟节点
    this.dummyHead = new this.ListNode()
};

// 获取链表结点的值
MyLinkedList.prototype.get = function(index) {
    if(index > this.size - 1 || index < 0){//这里注意要遍历到目标结点的前一个元素且index从0算起(题目要求) 所以这里要>this.size-1 下面要用虚拟头结点
        return -1;
    }
    let node = this.dummyHead;//从虚拟头结点开始往下找结点的值
    for(var i = 0; i < index; i++){
        node = node.next;
    }
    return node.next.val;
};

// 在头部加入元素 (如果index小于0,则在头部插入节点
MyLinkedList.prototype.addAtHead = function(val) {
    this.addAtIndex(0, val);//直接用下面写好的函数
};

// 在尾部加入元素 (如果 index 等于链表的长度,则该节点将附加到链表的末尾
MyLinkedList.prototype.addAtTail = function(val) {
    this.addAtIndex(this.size, val);
};

// 插入结点
MyLinkedList.prototype.addAtIndex = function(index, val) {
    if(index > this.size){
        return;//如果 index 大于链表长度,则不会插入节点
    }
    let node = this.dummyHead;
    for(var i = 0; i < index; i++){//要遍历到目标结点的前一个元素 然后执行插入操作
        node = node.next;
    }
    let next = node.next;//这个就是要插入的那个结点
    node.next = new this.ListNode(val, node, next);// 使用上面的函数 + 输入的数据(val node(前一个结点) next)创造当前结点
    if (next){
        next.prev = node.next;//把双链表连上
    }
    this.size++;   
};

// 删除结点
MyLinkedList.prototype.deleteAtIndex = function(index) {
    if(index > this.size - 1 || index < 0){
        return;
    }
    let node= this.dummyHead;
    for(var i = 0; i < index + 1; i++){//这里因为要删除当前结点 所以要遍历到当前节点的位置
        node = node.next;
    }
    node.prev.next = node.next;//双链表的prev字段还是很方便的!
    if(node.next){
        node.next.prev = node.prev;
    }
    this.size--;
};


法二

这个方法效率较高
但是解题的方法比较个性化——自己创建了一个功能类 用于实现链表的这些功能

双向链表+测试方法

function ListNode(val, next = null, prev = null) {
  this.val = val
  this.next = next
  this.prev = prev
}

class MyLinkedList {
  constructor () {
    this.data = new ListNode('head', new ListNode('tail'))
    this.data.next.prev = this.data
    this.len = 0
  }
  operate (index) { // 获得操作节点,主要定位到所需要修改的节点,的前面的节点!!
    let cur = this.data
    while (index-- > 0) {
      cur = cur.next
    }
    return cur
  }
  get (index) {
    if (index < 0 || index >= this.len) {
      return -1
    }
    const operateNode = this.operate(index)
    return operateNode.next.val
  }
  addAtHead (val) {
    const cur = new ListNode(val, this.data.next, this.data) // 建立头部节点
    this.data.next = cur // 正向链接
    this.data.next.next.prev = cur // 反向链接,因为有tail,不需要判断
    this.len++
  }
  addAtTail (val) {
    const tail = this.operate(this.len + 1) // 指向尾部‘tail'节点
    const cur = new ListNode(val, tail, tail.prev) // 建立尾部节点
    tail.prev = cur // 反向链接
    tail.prev.prev.next = cur // 正向链接
    this.len++
  }
  addAtIndex (index, val) {
    if (index < 0 || index > this.len) { //不在范围之内,包括this.len,因为有可能在尾部添加
      return -1
    }
    if (index === 0) return this.addAtHead(val)
    if (index === this.len) return this.addAtTail(val)
    const operateNode = this.operate(index)
    const cur = new ListNode(val, operateNode.next, operateNode)
    operateNode.next = cur
    operateNode.next.next.prev = cur
    this.len++
  }
  deleteAtIndex (index) {
    if (index < 0 || index >= this.len) {
      return -1
    }
    const operateNode = this.operate(index)
    operateNode.next = operateNode.next.next
    operateNode.next.prev = operateNode
    this.len--
  }
}

C++代码

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }

    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就会陷入死循环
            cur = cur->next;
        }
        return cur->val;
    }

    // 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }

    // 在链表最后面添加一个节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }

    // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    void addAtIndex(int index, int val) {
        if (index > _size) {
            return;
        }
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

    // 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }

    // 打印链表
    void printLinkedList() {
        LinkedNode* cur = _dummyHead;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }
private:
    int _size;
    LinkedNode* _dummyHead;

};


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值