算法通关村第一关——链表白银挑战笔记

本文介绍了如何通过链表算法解决寻找两个链表的第一个公共节点、判断链表是否为回文序列、合并有序链表以及涉及双指针技巧的删除元素等问题,展示了编程中常见的链表操作实践。
摘要由CSDN通过智能技术生成

算法通关村第一关——链表白银挑战笔记

前言:

通过白银挑战的学习,我也了解到了很多知识和解题套路,并且进一步增加了自己对算法学习的兴趣。

以下代码均为自己后期手写,所以可能与教程不太一致。因此也可能会有一部分错误,大家在浏览时发现错误可以积极评论指出,我也会第一时间进行更正,谢谢大家。

以下代码通用初始化ListNode类

public class ListNode {
​
    public int val;
    public ListNode next;
​
    public ListNode(int val) {
        this.val = val;
    }
}

1. 两个链表第一个公共子节点

题目:输入两个链表,找出它们的第一个公共节点。例如下面的两个链表:

两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。

public ListNode findFirstCommonNode(ListNode headA, ListNode headB) {
        Stack<ListNode> stackA = new Stack();
        Stack<ListNode> stackB = new Stack();
        while(headA != null){
            stackA.push(headA);
            headA = headA.next;
        }
        while(headB != null){
            stackB.push(headB);
            headB = headB.next;
        }
​
        ListNode preNode = null;
        while(stackB.size() > 0 && stackA.size() > 0){
            if(stackA.peek() == stackB.peek()){
                preNode = stackA.pop();
                stackB.pop();
            }else{
                break;
            }
        }
        return preNode;
    }

注:本题用hashmap或者hashset或者集合也均可

2. 判断链表是否为回文序列

题目:判断一个链表是否为回文链表。

示例1:

输入: 1->2->2->1

输出: true

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

public static boolean IsPalindromic(ListNode headA) {
        Stack<Integer> stack = new Stack<>();
        ListNode node = headA;
        while (headA != null){
            stack.push(headA.val);
            headA = headA.next;
        }
        while (node != null){
            if (stack.pop() != node.val){
                return false;
            }
            node = node.next;
        }
        return true;
    }

上述代码可以进行改进:压栈完毕的时候再次遍历只需 遍历一般元素即可

还可以使用双指针的办法,slow 、fast fast每次走的步数是slow的二倍,当fast到尾部的时候slow正好到中间部分,然后继续遍历进行比较即可

3. 合并有序链表

3.1:合并两个有序链表

题目:将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的。

public static ListNode MergeList(ListNode list1, ListNode list2) {
        //充当哨兵结点的作用
        ListNode node = new ListNode(0);
​
        //扮演尾指针
        ListNode res = node;
​
        while(list1 != null || list2 != null){
            //情况1:都不为空的情况
            if(list1 != null && list2 != null){
                if(list1.val < list2.val){
                    res.next = list1;
                    list1 = list1.next;
                }else if(list1.val > list2.val){
                    res.next = list2;
                    list2 = list2.next;
                }else{ //相等的情况,分别接两个链
                    res.next = list2;
                    list2 = list2.next;
                    res = res.next;
                    res.next = list1;
                    list1 = list1.next;
                }
                res = res.next;
                //情况2:假如还有链表一个不为空
            }else if(list1 != null && list2 == null){
                //后面一次性全链接上
                res.next = list1;
            }else if(list1 == null && list2 != null){
                //后面一次性全链接上
                res.next = list2;
            }
        }
        return node.next;
    }

3.2:合并K个链表

题目:合并k个链表,有多种方式,例如堆、归并等等。

先将前两个合并,之后再将后面的逐步合并进来,这样的的好处是只要将两个合并的写清楚,合并K个就容易很多:

public ListNode mergeKLists(ListNode[] lists) {
    ListNode node = null;
    for (ListNode list: lists) {
        node = MergeList(node, list);
    }
    return node;
}

3.3:一道很无聊的好题

题目:给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。请你将 list1 中下标从a到b的节点删除,并将list2 接在被删除节点的位置。

public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
    ListNode pre1 = list1, post1 = list1, post2 = list2;
    int i = 0, j = 0;
    while(pre1 != null && post1 != null && j < b){
        if(i != a - 1){
            pre1 = pre1.next;
            i++;
        } 
        if(j != b){
            post1 = post1.next;
            j++;
        } 
    }
    post1 = post1.next;
    //寻找list2的尾节点
    while(post2.next != null){
        post2 = post2.next;
    }
    //链1尾接链2头,链2尾接链1后半部分的头
    pre1.next = list2;
    post2.next = post1;
    return list1;
}

4. 双指针专题

4.1:寻找中间结点

题目:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

示例1

输入:[1,2,3,4,5]

输出:此列表中的结点 3

示例2:

输入:[1,2,3,4,5,6]

输出:此列表中的结点 4

public ListNode middleNode(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

直接用双指针,标准的双指针会直接显示偶数个中间靠后面的那个,如果要显示前面的那个,可以具体该一下代码即可

4.2:寻找倒数第K个元素

题目:寻找倒数第K个元素

输入一个链表,输出该链表中倒数第k个节点。本题从1开始计数,即链表的尾节点是倒数第1个节点。

示例

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

public static ListNode GetKthFromEnd(ListNode node,int k) {
​
       ListNode fast = node;
       ListNode slow = node;
       ListNode head = node;
       int length = 0;
       while (node != null){
           node = node.next;
           length++;
       }
       if (length < k){
           return head;
       }
       while (fast != null && k > 0){
           fast = fast.next;
           k--;
       }
       while (fast != null){
           slow = slow.next;
           fast = fast.next;
       }
       return slow;
    }

4.3:旋转链表

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

示例1:

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

输出:[4,5,1,2,3]

public ListNode rotateRight(ListNode head, int k) {
    if(head == null || k == 0){
        return head;
    }
    //这里三个变量都指向链表头结点
    ListNode temp = head;
    ListNode fast = head;
    ListNode slow = head;
    int len = 0;
    //这里head先走一遍,统计出链表的元素个数,完成之后head就变成null了
    while(head != null){
        head = head.next;
        len++;
    }
    if(k % len == 0){
        return temp;
    }
    // 从这里开始fast从头结点开始向后走
    //这里使用取模,是为了防止k大于len的情况
    //例如,如果len=5,那么k=2和7,效果是一样的 
    while((k % len) > 0){
        k--;
        fast = fast.next;
    }
    // 快指针走了k步了,然后快慢指针一起向后执行
    // 当fast到尾结点的时候,slow刚好在倒数第K个位置上
    while(fast.next != null){
        fast = fast.next;
        slow = slow.next;
    }
    ListNode res = slow.next;
    slow.next = null;
    fast.next = temp;
    return res;
}

5. 删除链表元素专题

5.1:删除特定结点

题目:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。

示例1:

输入:head = [1,2,6,3,4,5,6], val = 6

输出:[1,2,3,4,5]

public static ListNode removeElements(ListNode node,int val) {

        ListNode head = node;
        while (node.next != null){
            if (node.next.val == val){
                node.next = node.next.next;
                node = node.next;
            }
            node = node.next;
        }
        return head;
    }

5.2:删除倒数第n个节点

题目:给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。进阶:你能尝试使用一趟扫描实现吗?

示例1:

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

输出:[1,2,3,5]

public static ListNode removeNthFromEnd(ListNode node, int n) {

        ListNode head = node;
        ListNode fast = node;
        ListNode slow = node;
        int length = 0;
        while (node != null){
            node = node.next;
            length++;
        }
        //避免n大于链表长度,导致发生错误
        int k = n % length;
        if(k == 0){
            return head;
        }
        while (k > 0){
            fast = fast.next;
            k--;
        }
        while (fast.next !=null){
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return head;
    }

其实这道题我觉得用栈比较好,将链表压入栈,直接弹栈,弹到n的时候,即可找到我们所需的结点(主要是双指针格调高,感觉比较装...)

5.3:删除重复元素
5.3.1:重复元素保留一个

题目:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素只出现一次 。返回同样按升序排列的结果链表。

示例1:

输入:head = [1,1,2,3,3]

输出:[1,2,3]

public ListNode deleteDuplicates(ListNode head) {
  if (head == null) {
    return head;
  }
  ListNode cur = head;
  while (cur.next != null) {
    if (cur.val == cur.next.val) {
      cur.next = cur.next.next;
    } else {
      cur = cur.next;
    }
  }
  return head;
}

5.3.2:重复元素都不要

例如:

示例1:

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

输出:[1,2,5]

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

        //直接对node.next 以及 node.next.next 两个node进行比较就行了,所以加个哨兵节点会好很多
        ListNode res = new ListNode(0);
        res.next = node;

        while (res.next != null && res.next.next != null) {

            if (res.next.val == res.next.next.val) {
                //标记值
                int a = res.next.val;
                while (res.next.val == a && res.next != null) {
                    res.next = res.next.next;
                }
            } else {
                res = res.next;
            }
        }
        return node;
    }

6. 再论第一个公共子节点问题

6.1:拼接两个字符串

先看下面的链表A和B: A: 0-1-2-3-4-5 B: a-b-4-5 如果分别拼接成AB和BA会怎么样呢? AB:0-1-2-3-4-5-a-b-4-5 BA:a-b-4-5-0-1-2-3-4-5

发现拼接后从最后的4开始,两个链表是一样的了,自然4就是要找的节点,所以可以通过拼接的方式来寻找交点。

6.2:差和双指针

假如公共子节点一定存在第一轮遍历,假设La长度为L1,Lb长度为L2.则|L2-L1|就是两个的差值。第二轮遍历,长的先走|L2-L1|,然后两个链表同时向前走,结点一样的时候就是公共结点了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值