刷完这篇文章,再也不怕链表啦!

刷完这篇文章,再也不怕链表啦!

      • 寻找单链表的倒数第 `k` 个节点
      • 删除链表的倒数第 N 个结点
      • 旋转链表
      • 寻找单链表的中点
      • 删除链表中的节点
      • 删除链表的中间节点
      • 判断单链表是否包含环
      • 回文链表
      • 合并两个有序链表
      • 合并 `k` 个有序链表
      • 链表的分解
      • 反转链表
      • 反转链表 II
      • K 个一组翻转链表

寻找单链表的倒数第 k 个节点

双指针,先让两个指针之间的距离保持在k。 当右边指针到达最后一个节点,左边指针所指向的就是倒数第k+1个节点。

var getKthFromEnd = function(head, k) {

    let left = head;
    let right = head;
    for(let i = 0;i<k;i++){
        right = right.next;
    }

    while(right!=null){
        right = right.next;
        left = left.next;
    }
    return left;
}

删除链表的倒数第 N 个结点

根据上一题的解法,寻找倒数第N个结点
假设该节点为x节点,则将x-1节点 指向 x+1 节点。因此还需要维护一个 pre 节点。
如果链表只有一个节点,则x-1节点不存在,要另外讨论。
为了不另外讨论,可以在链表前添加一个哨兵结点。

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    // 用一个哨兵节点,防止需要另外处理只有一个节点的特殊链表。
    let dummy = new ListNode(-1);
    dummy.next = head;

    let left = head;
    let pre = dummy;
    let right = head;
    for(let i = 0;i<n;i++){
        right = right.next
    }
    while(right!=null){
        right = right.next;
        left = left.next;
        pre = pre.next;
    }

    pre.next = left.next;
    return dummy.next
};

旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

思路:利用指针的移动,形成闭环。并且记录长度,找到该断开的地方断开。

向右移动k位,则说明前面增加k个结点,说明最后k个结点需要在首位。因此在第(len-k)个结点处需要断裂。

其实就是找到倒数第k个结点,让它与倒数第k+1个结点之间断开。由于k 有可能大于长度,因此需要提前计算出链表的长度。

var rotateRight = function(head, k) {
    
    if(head==null)
    return null
    
    let left = head;
    let right = head;
    let len = 0;
    while(right!=null){
        len++;
        right = right.next;
    }

    k = k%len;
    
    
    right = head;

    for(let i = 0;i<k;i++){
        right = right.next;
    }

    while(right.next!=null){
        right = right.next;
        left = left.next;
    }
    right.next = head;
    let res = left.next;
    left.next = null;
    return res;

};
public ListNode rotateRight(ListNode head, int k) {

        ListNode tail = head;
        ListNode pre = head;
        if(head==null)
            return null;
        int len=1;
        while(tail.next!=null){
            tail = tail.next;
            len++;
            // System.out.println(len);
        }
        tail.next=head;

        k=k%len;
        for (int i = 0; i < len-k-1; i++) {
            pre=pre.next;
        }
        ListNode ans = pre.next;
        pre.next=null;
        return ans;

    }

寻找单链表的中点

思路:有两个指针,一个一次走2步,一个一次走1步。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var middleNode = function(head) {
    let left = head;
    let right =head;
    while(right!=null&&right.next!=null){
        right = right.next.next;
        left = left.next;
    }
    return left
};

删除链表中的节点

给一个链表,删除node节点。给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。正常删除节点的方式是 找到节点node ,找到节点的前一个节点 node -1,然后node -1 指向 node+1.

但是这道题,将该节点变成下一个节点的样子,然后去除下一节点(题目给的提示是保证给定的节点 node 不是链表中的最后一个节点。)

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    node.val = node.next.val;
    node.next = node.next.next;
    
};

删除链表的中间节点

利用前两道题的寻找中间结点的时候 +删除结点

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
//错误案例
var deleteMiddle = function(head) {
    let left = head;
    let right =head;
    while(right!=null&&right.next!=null){
        right = right.next.next;
        left = left.next;
    }
    // 删除某个结点,除了记录前一个结点,还有一种方式就是改头换面,但是这种方法只适用于删除的结点不是最后一个结点
    left.val = left.next.val;
    left.next = left.next.next;
    return head
};
// 删除某个结点:一般的方法要记录前一个结点
var deleteMiddle = function(head) {
    let left = head;
    let right =head;
    let pre = new ListNode(-1);
    pre.next = head;
    while(right!=null&&right.next!=null){
        right = right.next.next;
        left = left.next;
        pre = pre.next;
    }
    pre.next = left.next;
    return head
};

判断单链表是否包含环

每当慢指针 slow 前进一步,快指针 fast 就前进两步。

如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var hasCycle = function(head) {
    let left = head;
    let right =head;
    while(right!=null&&right.next!=null){
        right = right.next.next;
        left = left.next;
        if(right === left)
            return true;
    }
    return false
};

回文链表

单链表无法倒着遍历,把原始链表反转存入一条新的链表,然后比较这两条链表是否相同。

借助二叉树后序遍历的思路,不需要显式反转原始链表也可以倒序遍历链表

    /**
     * 倒序打印单链表中的元素值
     */
    function traverse(head) {
      if (head === null) {
        return;
      }
      traverse(head.next);
      // 后序遍历代码
      console.log(head.val);
    }
    ~~~

~~~js
/**
* @param {ListNode} head
* @return {boolean}
*/

var isPalindrome = function(head) {
   let left = head;
   const traverse=(head)=>{
       if(head===null) return true;
       let res = traverse(head.next);
       res = res && (head.val===left.val)
       left = left.next;
       return res;
   }
   return traverse(head);

};

合并两个有序链表

思路一:新建,遍历两个链表,谁小就存谁。

var mergeTwoLists = function(l1, l2) {

    // function body goes here
    // dummy 虚拟头结点。
    let list = new ListNode();
    let head = list;

    while(l1!==null&&l2!==null){
        if(l1.val<l2.val){
            list.next = l1;
            l1=l1.next;
        }else{
            list.next = l2;
            l2= l2.next;
        }
        list = list.next;
    }

    // 如果没有比较完则将剩下的接起来。
    list.next = l1===null?l2:l1;
    return head.next;
};

合并 k 个有序链表

如何快速得到 k 个节点中的最小节点,接到结果链表上。

链表的分解

就是小于x的放在x前面。大于的放在x后面,不用排序。

思路就是遍历原有的链表,创建两个新的链表,小的链表在大的链表前面

【注意点】:要断开原链表之间的连接。

var partition = function(head, x) {
    let smallDummy = new ListNode(-1);
    let smallHead = smallDummy;
    let bigDummy = new ListNode(-1);
    let bigHead = bigDummy;
    
    while(head!=null){
        if(head.val<x){
            smallDummy.next = head;
            smallDummy = smallDummy.next;
        }else{
            bigDummy.next = head;
            bigDummy = bigDummy.next;
        }
        // 断开原链表中的每个节点的 next 指针
        let temp = head.next;
        head.next = null
        head = temp;
    }

    smallDummy.next = bigHead.next;
    return smallHead.next

};

反转链表

指针+移动

虚拟结点

两个指针:pre ,head。将head 指向pre。pre从null开始。head 由第一个节点到达最后一个节点。由于head 指向了pre ,因此需要一个next 记录head 的下一个结点。

/**
 * 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;
    while(head!=null){
        let next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    
    return pre;
   
};
public ListNode reverseList(ListNode head){
  
    //一共三个指针,cur 指向前面pre,
    //还要有cur的next也要记录
    //移动的边界。null
        ListNode pre = null;

        while(head!= null){
            ListNode next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function(head, left, right) {

    let dummy = new ListNode(-1);
    dummy.next = head;

    let pre = dummy;
    let cur = head;
    let leftNode = head;
    for(let i =0;i<left-1;i++){
        cur = cur.next;
        pre = pre.next;
        leftNode = leftNode.next;
    }
    
    let next = cur.next;
    for(let i = left;i<right;i++){
        
        let temp = next.next;
        next.next = cur;
        cur = next;
        next = temp;
    }

    pre.next = cur;
    leftNode.next = next;


    return dummy.next

};

K 个一组翻转链表

算到第k个执行一个翻转链表。

翻转链表的输入参数有头尾节点,因此结束循环的条件不再是head 不指向null。而是头节点!=尾节点

返回节点也是一个数组,存储着新节点。

虚拟结点,pre 不再是null,因为pre 有可能是结点。因此增加哨兵结点以统一处理。

public ListNode reverseKGroup(ListNode head, int k) {
    //思路:就是两个指针
        
        ListNode dummyNode = new ListNode();
        dummyNode.next = head;
        ListNode pre = dummyNode;

  
        while(head!=null){
            //定位首尾指针
            ListNode tail = pre;
            for (int i = 0; i < k; i++) {
                tail=tail.next;
                if(tail==null){
                    return dummyNode.next;
                }
            }
            //除了首尾指针,还要有首指针的前一个,尾指针的后一个,才能将反装后的链表与原来的链表连接起来。
            ListNode next = tail.next;
            ListNode[] res=reverseList(head,tail);
            head = res[0];
            tail = res[1];

            pre.next=head;
            tail.next = next;
            
            head = next;
            pre = tail;

        }
    //结果就是返回哨兵结点的下一个结点。
        return dummyNode.next;

    }

    public ListNode[] reverseList(ListNode head,ListNode tail){
        ListNode pre = null;
        ListNode cur = head;
        //已知头尾反转链表,边界不再是null
        
        while(pre!=tail){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur=next;
        }
        //返回首尾
        return new ListNode[]{tail,head};
    }

你可能感兴趣的:(LeetCode刻意练习,leetcode,链表,数据结构)