链表经典十八问题-解析(上篇)

164 阅读4分钟

链表经典十八问题-解析 (Linked List Problems)

尽管现代语言和工具已经使链表在日常编程中变得非常不重要,但复杂指针算法的技能是非常重要的,而链表是开发这些技能的极好方法。

回顾基础的链表知识

链表问题经常被用作面试和考试的问题。 他们是短的状态,和有复杂的,指针密集的解决方案。没有人真正关心你是否能建立链接列表,但他们想看看你是否有复杂算法的编程敏捷性指针操作。链表是这些问题的完美来源。

链表基本规则

一个头指针指向列表中的第一个节点。每个节点包含一个.next指针指向下一个节点。最后一个节点的.next指针为NULL。空列表由一个NULL头指针表示。所有节点都是在堆中分配的,例如:

function ListNode(val, next) {
    this.val = (val===undefined ? 0 : val)
    this.next = (next===undefined ? null : next)
}

可以通过设置虚拟头结点(dummyHead)的方式,来避免头结点和中间节点不一致的问题,(除了虚拟头结点,每个节点都是上一个节点的next节点),节省不必要的判断。

Linked List Problems

以下是按难度顺序排列的18个链表问题

  1. 编写一个Count()函数,计算一个给定的整数在链表中出现的次数
/**
    
*/
function count(head,val) {
    let cur = head, count = 0;
    // 遍历整个链表
    while(cur) {
        // 如果存在相等的值,则计数+1
        if(cur.val === val){
            count++;
        }
        cur = cur.next;
    }
    return count;
}

2.编写一个getNth()函数,它接受一个链表和一个整数索引(0-N)并返回数据

function getNth(head,index) {
    let cur = head, count = 0;
    // 遍历整个链表
    while(cur) {
        // 找到索引值对应的节点,直接返回
        if(count === index){
            return cur;
        }
        cur = cur.next;
        count++;
    }
    return null;
}

3.写一个函数DeleteList(),它接受一个列表,释放它的所有内存。

function deleteList(head) {
    let cur = head;
    // 遍历整个链表
    while(cur) {
        // 保存上一个值,cur向后移动,删除next指向
        const prev = cur;
        cur = cur.next;
        prev.next = null;
    }
    head = null;
}

4.编写一个与Push()相反的Pop()函数。Pop()接受一个非空列表,并进行删除head节点,并返回head节点的数据。

function pop(head) {
    // 返回头结点数据,并且将头结点后移
    let cur = head;
    head = head.next;
    cur.next = null;
    return cur.val;
    
}
  1. 编写insertNth()方法,实现在指定索引位置插入节点
function insertNth(head,index,node) {
    // 设置虚拟头结点
    const dummyHead = new Node(-1,head);
    let prev = dummyHead, count = 0;
    while(prev) {
        // 找到索引值对应的节点,进行插入操作
        if(count === index){
            const cur = prev.next;
            const newNode = new Node(node,cur);
            prev.next = newNode;
            head = dummyHead.next;
            break;
        }
        prev = prev.next;
        count++;
    }
    return dummyHead.next;
}
  1. 编写一个SortedInsert()函数,该函数给出了一个按递增顺序插入排序的列表
function sortedInsert(head,node) {
    // 设置虚拟头结点
    const dummyHead = new Node(-1,head);
    let prev = dummyHead;
    while(prev) {
        // 如果下一个节点大于node的值,或者走到链表尽头,执行node插入
        if(prev.next == null || prev.next.val >= node){
            const cur = prev.next;
            const newNode = new Node(node,cur);
            prev.next = newNode;
            head = dummyHead.next;
            break;
        }
        prev = prev.next;
    }
    return dummyHead.next;
}
  1. 写一个InsertSort()函数,给出一个列表,重新排列它的节点,以便它们被排序
    --对应 leetcode 148.排序链表
var sortList = function(head) {
    // 使用自上而下的归并排序
    // 首先将链表不断执行一分为二的操作,当分割到1-1的时候,向上执行合并操作
    function sortList(head,tail) {
        // 如果链表为null,直接返回null
        if(head === null) {
            return null;
        }
        // 如果当前链表只有两个节点,则直接返回单个头结点
        if(head.next === tail) {
            head.next = null;
            return head;
        }
        // 通过设置fast(一次+2)和slow(一次+1)指针找到链表的中间位置
        let fast = head, slow = head;
        // 条件判断,只要fast没结束,就可以一直向下走,直到==尽头tail
        while(fast !== tail) {
            fast = fast.next;
            slow = slow.next;
            if(fast !== tail) {
                fast = fast.next;
            }
        }
        // 提高代码的可读性,slow就是mid
        const mid = slow;
        const list1 = sortList(head,mid);
        const list2 = sortList(mid,tail);
        // 合并两个链表
        const merged = merge(list1,list2);
        return merged;
    }
    // 对于两个链表进行合并的操作
    function merge(list1, list2) {
        const dummyHead = new ListNode(-1,null);
        let temp = dummyHead;
        let temp1 = list1, temp2 = list2;
        // 只要有一个指针到达,两个指针就不能进行比较了,所以停止循环
        while(temp1 !== null && temp2 !== null) {
            if(temp1.val <= temp2.val){
                temp.next = temp1;
                temp1 = temp1.next;
            }else{
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        // 对于任意一个剩下的链表来说,直接拼接到当前结果的末尾即可。
        if(temp1 !== null) {
            temp.next = temp1;
        }else if(temp2 !== null) {
            temp.next = temp2;
        }
        return dummyHead.next;
    }
    return sortList(head,null);
};