链表相关算法


1 带环的链表

1-1 判断链表是否有环

🚀 题目链接https://leetcode-cn.com/problems/linked-list-cycle/
🚀 代码

public boolean hasCycle(ListNode head) {
    if (null == head) {
        return false;
    }
    
    // 快慢指针
    ListNode fast = head, slow = head;
    while (null != fast && null != fast.next && null != slow) {
        // 快指针移动两步
        fast = fast.next.next;
        // 慢指针移动一步
        slow = slow.next;
        
        if (fast == slow) {
            // 两个指针相遇,说明有环
            return true;
        }
    }
    return false;
}

1-2 求带环链表的环入口

🚀 题目链接https://www.acwing.com/problem/content/86/
🚀 代码

public ListNode entryNodeOfLoop(ListNode head) {
    if (null == head) {
        return null;
    }

    // 快慢指针
    ListNode fast = head, slow = head;
    while (null != fast && null != fast.next && null != slow) {
        fast = fast.next.next;
        slow = slow.next;

        if (fast == slow) {
            // 快慢指针相遇,再从头移动
            ListNode temp = head;
            while (null != temp && null != slow) {
                // 两个同步指针一起移动
                temp = temp.next;
                slow = slow.next;

                if (temp == slow) {
                    // 同步指针相遇的位置就是环的起点
                    return slow;
                }
            }
        }
    }
    // 找不到环,返回null
    return null;
}

2 删除指定节点/元素

2-1 单链表删除,给出头节点

public void deleteNode(ListNode head, ListNode target) {
    if (null == head) {
        return ;
    }
    // 如果目标值就是头节点,则直接返回下一个节点即可
    if (head == target) {
        return head.next;
    }

    ListNode p = head;
    // 头节点是目标值已排除,直接从next开始,方便删除
    while (null != p.next) {
        if (p.next == target) {
            p.next = p.next.next;
            return ;
        }
        p = p.next;
    }
}

2-2 单链表删除,只给出待删除的节点

🚀 题目链接https://www.acwing.com/problem/content/85/
🚀 代码

// 前提:不知道前一个节点、不需要删除整个节点对象(地址)、待删除节点不是尾节点
public void deleteNode(ListNode node) {
    // 记住下一个节点
    ListNode nextNode = node.next;

    // 将后面的节点值复制给当前节点
    node.val = nextNode.val;

    // 再删除下一个节点即可(当前节点指向下下一个节点)
    node.next = nextNode.next;
}

2-3 双向链表删除

public void deleteNode(DoubleListNode node) {
    // 前节点的后节点指向当前节点的后节点
    node.pre.next = node.next;
    // 后节点的前节点指向当前节点的前节点
    node.next.pre = node.pre;
}

2-4 删除重复元素,保留1个重复元素

🚀 题目链接https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
🚀 代码

public ListNode deleteDuplicates(ListNode head) {
    if (null == head) {
        return null;
    }

    ListNode p = head;
    while (null != p && null != p.next) {
        if (p.val == p.next.val) {
            // 当前节点与下一个节点值相同,直接删除下一个节点,不移动p
            p.next = p.next.next;
        } else {
            // 不相同,正常移动p
            p = p.next;
        }
    }
    return head;
}

2-5 删除重复元素,不保留重复元素

🚀 题目链接https://www.acwing.com/problem/content/27/
🚀 代码

public ListNode deleteDuplicates(ListNode head) {
    if (null == head) {
        return null;
    }

    // 创建一个虚拟头节点,这里初始化的val值不能与原链表的头节点值相等
    ListNode headPre = new ListNode(-1);
    headPre.next = head;

    ListNode p = headPre;
    while (null != p.next) {
        // 遍历重复元素
        ListNode q = p.next;
        while (null != q && p.next.val == q.val) {
            // 依次与p.next比较,有重复元素就继续移动
            q = q.next;
        }

        if (p.next.next == q) {
            // 如果q只移动了一格,说明没有与p.next重复的元素
            // p正常移动
            p = p.next;
        } else {
            // 如果q移动了不止一格,则存在重复元素,直接删除
            p.next = q;
        }
    }

    // 返回虚拟头节点的下一个节点
    return headPre.next;
}

3 反转链表

3-1 整条链反转

🚀 题目链接https://www.acwing.com/problem/content/33/
🚀 代码

  • 迭代版本

    public ListNode reverseList(ListNode head) {
        if (null == head) {
            return null;
        }
    
        // 前后指针
        ListNode pre = null, curr = head;
        while (null != curr) {
            // 头指针先移动
            head = head.next;
            // 当前指针下一个节点指向pre
            curr.next = pre;
            // 修改pre和curr
            pre = curr;
            curr = head;
        }
    
        // 最后返回pre,不是curr/head
        return pre;
    }
    
  • 递归版本

    public ListNode reverseList(ListNode head) {
        if (null == head) {
            return null;
        }
    
        // 递归
        return dfs(null, head);
    }
    
    // 递归反转链表
    private ListNode dfs(ListNode pre, ListNode curr) {
        if (null == curr) {
            return pre;
        }
    
        // 记住下一个节点
        ListNode nextNode = curr.next;
        // 修改当前节点下一个节点为前节点
        curr.next = pre;
    
        // 递归继续修改下一个节点
        return dfs(curr, nextNode);
    }
    

3-2 在指定区间内反转

🚀 题目链接https://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c
🚀 代码

// 只反转[m,n]区间的链表,链表节点索引从1开始
public ListNode reverseBetween(ListNode head, int m, int n) {
    if (null == head) {
        return null;
    }
    
    // 因为反转的可能是head,所以需要虚拟头节点
    ListNode headPre = new ListNode(-1, head);
    // 辅助指针,pre指向m-1位置的节点,p指向要反转的链表头节点
	ListNode pre = headPre, p = head;
    // 记住(m,n)之间的节点数(不包括m和n)
    int diff = n - m;
    
    // 移动到第m个节点,如果m=1就不用移了
    while (--m > 0) {
        pre = p;
        p = p.next;
    }
    
    // 依次反转[m,n]之间的节点
    while (diff-- > 0) {
        // 辅助指针,记住p.next,将p和temp两个节点反转
        ListNode temp = p.next;
        // p下一个节点指向temp的下一个节点
        p.next = temp.next;
        // temp下一个节点再指向pre的下一个节点(pre始终在m-1的位置)
        // 注意这里不能写temp.next = p,因为p是一直往后移动,pre始终在m-1的位置,故pre后面不一定是p
        temp.next = pre.next;
        // pre的下一个节点指向temp,完成p和temp的反转
        pre.next = temp;
    }
    
    return headPre.next;
}

4 链表排序

相关:常见排序算法

4-1 合并两个有序链表

🚀 题目链接https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/comments/
🚀 代码

public ListNode merge(ListNode l1, ListNode l2) {
    if (null == l1 && null == l2) {
        return null;
    }

    // 分别指向两个链表的指针
    ListNode p1 = l1, p2 = l2;
    // 创建新的链表
    ListNode newHead = new ListNode(-1), p = newHead;

    // 一次归并排序
    while (null != p1 && null != p2) {
        if (p1.val < p2.val) {
            p.next = p1;
            p1 = p1.next;
        } else {
            p.next = p2;
            p2 = p2.next;
        }

        p = p.next;
    }

    if (null != p1) {
        p.next = p1;
    } else {
        p.next = p2;
    }

    return newHead.next;
}

4-2 排序链表

🚀 题目链接https://leetcode-cn.com/problems/sort-list/
🚀 代码

PS:这里涉及的归并排序merge()用上面👆的就行

  • 迭代版本
    public ListNode sortList(ListNode head) {
        if (null == head) {
            return null;
        }
        
        // 先求出链表长度
        int len = 0;
        ListNode h = head;
        while (null != h) {
            h = h.next;
            len++;
        }
        
        // 虚拟头节点,指向head
        ListNode headPre = new ListNode(-1, head);
        
        // 外循环subLen逐渐倍增,直到等于len
        for (int subLen = 1;subLen < len;subLen <<= 1) {
            // 记住此时虚拟节点,并初始化curr(初始化指向第一个节点)
            ListNode pre = headPre, curr = headPre.next;
            while (null != curr) {
                // 记住当前curr为子链表①的头节点
                ListNode head1 = curr;
                
                // 移动subLen-1个位置
                for (int i = 1;i < subLen && null != curr.next;i++) {
                    curr = curr.next;
                }
                
                // 从此时的curr断开,curr下一个节点作为子链表②的头节点
                // 前面循环条件确保了curr不为null,所以这里可以直接写head2 = curr.next
                ListNode head2 = curr.next;
                curr.next = null;
                curr = head2;
                
                // 再移动subLen-1个位置
                for (int i = 1;i < subLen && null != curr && null != curr.next;i++) {
                    curr = curr.next;
                }
                
                // 再从此时的curr断开,curr下一个节点作为新头节点
                // 这里不能直接将curr.next赋值给newHead,因为curr可能为null
                ListNode newHead = null;
                if (null != curr) {
                    newHead = curr.next;
                    curr.next = null;
                }
                
                // 合并排序两个子链表,合并后的链表拼接到pre后面
                pre.next = merge(head1, head2);
               	// pre再移动到最后一个节点
                while (null != pre.next) {
                    pre = pre.next;
                }
           
                // 更新下一个curr
                curr = newHead;
            }
        }
        
        return headPre.next;
    }
    
  • 递归版本
    public ListNode sortList(ListNode head) {
        if (null == head || null == head.next) {
            // 如果头节点为null或只有一个节点,要返回这个head,不能返回null
            return head;
        }
    
        // 快慢指针将链表等分
        ListNode fast = head, slow = head;
        // 注意这里结束条件是fast.next和fast.next.next
        /// 保证fast不会移动到null,也就是保证fast不会移动到链表外面
        // 如果fast移动到链表外,slow指向的节点就不是中间那个节点
        while (null != fast.next && null != fast.next.next) {
            fast = fast.next.next;
            slow = slow.next;
        }
    
        // 断开两条长度一样的链
        ListNode head2 = slow.next;
        slow.next = null;
    
        // 递归返回排序后的链表,然后再合并
        return merge(sortList(head), sortList(head2));
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值