【算法训练营Day04】链表part2

文章目录

  • 两两交换链表中的节点
  • 删除链表的倒数第 N 个结点
  • 链表相交
  • 环形链表 II
  • 链表总结

两两交换链表中的节点

题目链接:24. 两两交换链表中的节点

算法逻辑:

  • 添加一个虚拟头节点
  • 初始化一个交换指针,代表每次交换指针的后两个节点,该指针从虚拟头节点开始移动
  • 接下来就是交换步骤
    • 先将交换指针的后面第一个节点(简称后1节点)以及交换指针的后3节点存起来
    • 将交换指针的后2节点与交换指针节点进行缝合
    • 将存起来的后1节点与后2节点进行缝合
    • 在将存起来的后3节点与后1节点进行缝合
  • 如此交换直到交换指针节点为null,或者后1节点为null,或者后2节点为null,因为必须要有两个节点才能交换
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode virtualHead = new ListNode(-1,head);
        ListNode exchangePointer = virtualHead;
        while(exchangePointer != null && exchangePointer.next != null && exchangePointer.next.next != null) {
            ListNode temp1 = exchangePointer.next;
            ListNode temp3 = temp1.next.next;
            exchangePointer.next = exchangePointer.next.next;
            exchangePointer.next.next = temp1;
            exchangePointer.next.next.next = temp3;
            exchangePointer = exchangePointer.next.next;
        }
        return virtualHead.next;
    }
}

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

题目链接:19. 删除链表的倒数第 N 个结点

逻辑思路:
这道题既然是删除倒数的第n个节点,那么我们可以将链表反转,然后再删除第n个,最后再把链表反转回去即可。我们前几天的训练营中做过移除链表元素、翻转链表等算法题,所以这里直接把相关方法移植过来即可。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode reverseList = reverseList(head);
        ListNode deleteList = deleteAtCount(reverseList,n);
        return reverseList(deleteList);
    }

    public ListNode reverseList(ListNode head) {
        ListNode reverseResultVirtualHead = new ListNode();
        ListNode pointer = head;
        while(pointer != null) {
            ListNode currentNode = new ListNode(pointer.val,null);
            currentNode.next = reverseResultVirtualHead.next;
            reverseResultVirtualHead.next = currentNode;
            pointer = pointer.next;
        }
        return reverseResultVirtualHead.next;
    }

    public ListNode deleteAtCount(ListNode head,int count) {
        ListNode virtualHead = new ListNode(-1,head);
        ListNode pointer = virtualHead;
        while(count > 0 && pointer.next != null) {
            if(count == 1) {
                pointer.next = pointer.next.next;
            }
            count -= 1;
            pointer = pointer.next;
        }
        return virtualHead.next;
    }
}

链表相交

题目链接:面试题 02.07. 链表相交

算法逻辑:
读完题目之后可以发现一个特点那就是若链表相交,那么必有公共后缀!我们若想找到两个链表的公共后缀那么第一想法肯定就是反向遍历。但是这是一个单向链表,直接反向遍历无法实现,这个时候我们就可以借助于栈结构来完成公共后缀的比对。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) return null;
        ListNode pointerA = headA;
        ListNode pointerB = headB;
        Stack<ListNode> a = new Stack<ListNode>();
        Stack<ListNode> b = new Stack<ListNode>();
        while(pointerA != null) {
            a.push(pointerA);
            pointerA = pointerA.next;
        }
        while(pointerB != null) {
            b.push(pointerB);
            pointerB = pointerB.next;
        }
        ListNode nodeA = a.pop();
        ListNode nodeB = b.pop();
        ListNode result = null;
        while(nodeA == nodeB) {
            result = nodeA;
            if (a.empty() || b.empty()) break;
            nodeA = a.pop();
            nodeB = b.pop();
        }
        return result;
    }
}

环形链表 II

题目链接: 142. 环形链表 II

解题思路:

判断是否成环,换一句话说也就是在遍历链表的时候看是否有重复的节点,第一个重复的节点就是成环的起点。我们可以利用set数据结构,每遍历到一个新节点就看set中是否有相同节点,如果没有就添加到set中,如果有则说明该节点就是成环节点。遍历完了都没有重复节点,则说明不成环。

代码:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> nodeSet = new HashSet();
        ListNode pointer = head;
        while(pointer != null) {
            if(nodeSet.contains(pointer)) return pointer;
            nodeSet.add(pointer);
            pointer = pointer.next;
        }
        return null;
    }
}

链表总结

链表的关键点就在于:

  • 增加、删除的时候可以引入虚拟头节点,让逻辑更加统一,不用单独考虑一些边界情况
  • 如果是遍历等不会修改链表结构的操作则无需引入虚拟头节点
  • 增加删除的重点就在于增加、删除元素的前后链要是可见的,才能完成相关的节点缝合工作。这就需要我们引入一些中间变量来存储这些前后链.

你可能感兴趣的:(算法训练营,算法,链表,数据结构)