day3.链表理论基础 203.移除链表元素 707.设计链表 206.反转链表

今天主要考察链表的理论基与基本操作。

数组与链表区别

JS 中定义链表节点

class ListNode {
  val;
  next = null;
  constructor(value) {
    this.val = value;
    this.next = null;
  }
}

203.移除链表元素

思路:为了统一逻辑处理,创建虚拟头节点来删除第一个元素。链表删除当前节点下一个节点操作:cur.next = cur.next.next。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    const temp = new ListNode(0, head)
    let cur = temp
    // console.log(cur.next)   
    while(cur.next!==null){
        if(cur.next.val === val){
            cur.next = cur.next.next
            continue
        }
        cur = cur.next
    }
    return temp.next

};

707.设计链表

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

思路 :这道题没思路,较难,主要考察链表的增删改查操作。要考虑插入,删除时 链表中首尾节点的变化。
关键点:1)根据index获取对应节点可单独封装成一个函数,方便复用。
2)根据index删除元素时,考虑index为0时,链表首节点和尾节点的影响。考虑index为除0外的元素时,对链表尾节点的影响。
3)链表构造函数在定义时初始化了首尾指针和链表大小。

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

var MyLinkedList = function() {
    this._size = 0
    this._tail = null
    this._head = null

};

//获取index对应节点
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;
};

/** 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function(index) {
    if(index < 0 || index >= this._size) return -1
  
    return this.getNode(index).val;

};

/** 
 * @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;
    }
};

/** 
 * @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 //node为新尾节点
        return
    }
    //不存在尾结点
    this._tail = node
    this._head = node //该链表头尾节点都为node
};

/** 
 * @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;
    }

    //找到index-1 那个节点
    const node = this.getNode(index - 1);

    const cur = new LinkNode(val,node.next) //新节点指针指向前一个节点的next
    node.next = cur //插入新节点
    this._size++
};

/** 
 * @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;
        // 如果删除的这个节点同时是尾节点,要处理尾节点
        if(index === this._size - 1){
            this._tail = this._head
        }
        this._size--;
        return;
    }

      // 获取目标节点的上一个的节点
    const node = this.getNode(index - 1);
    node.next = node.next.next;
    // 处理尾节点
    if(index === this._size - 1) {
        this._tail = node;
    }
    this._size--;
};


/**
 * Your MyLinkedList object will be instantiated and called as such:
 * var obj = new MyLinkedList()
 * var param_1 = obj.get(index)
 * obj.addAtHead(val)
 * obj.addAtTail(val)
 * obj.addAtIndex(index,val)
 * obj.deleteAtIndex(index)
 */

206.反转链表

链表经典面试题:迭代法与递归法
思路:
双指针迭代,pre = null ,cur = head

  1. 用 temp 保存 cur.next 节点信息
  2. 将 cur.next 指向 pre ,pre始终为cur前的那个节点
  3. pre = cur , pre先向后移动,指向cur当前位置
  4. cur = temp ,cur再在后移动,指向cur的下一个为位置。

为什么pre先动,cur后动?

pre需要移动到cur的当前位置,cur先动了就无法记录当前cur的位置。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let pre = null,node = null
    let cur = head
    while(cur){
        node = cur.next //保存cur的下一个节点
        cur.next = pre //指针反向
        pre = cur //先移动pre
        cur = node //cur向下移动           
    }
    return pre
}

递归写法:

递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。

// 递归:
var reverse = function(pre, head) {
    if(!head) return pre;
    const temp = head.next;
    head.next = pre;
    pre = head
    return reverse(pre, temp);
}

var reverseList = function(head) {
    return reverse(null, head);
};

今日总结:

主要考察链表基础算法。
第一题ac,注意虚拟头节点的使用。
第二题设计链表较难,第一次见没思路,考察单链表的五种基本操作,注意首尾节点的细节处理以及函数复用(根据index获取某个节点)。
第三题,面试高频,迭代和递归都需掌握,左右指针思想,注意节点指向会带来的链表变化,改变节点指向时,需要提前保存当前指向的节点,防止丢失链表指向。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值