算法通关村第一关 | 链表经典问题白银挑战 | Java

77 阅读4分钟

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

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

图片.png 链表节点

static class ListNode {  
    public int val;  
    public ListNode next;  
    ListNode(int x) {  
        val = x;  
        next = null;  
    }  
    //初始化两个链表
    //la 为 1 2 3 4 5  
    //lb 为 11 22 4 5
}

1.1Hash和集合

首先将第一个链表存放在Map里,然后在遍历第二个链表的同时检测当前元素是否在Map里。集合同理

1.1.1通过Hash辅助查找

/**  
* 方法1:通过Hash辅助查找  
*  
* @param pHead1  
* @param pHead2  
* @return  
*/  
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {  
    if (pHead1 == null || pHead2 == null) return null;  
    HashMap<ListNode, Integer> map = new HashMap<>();  
    ListNode node1 = pHead1;  
    ListNode node2 = pHead2;  
    while (node1 != null) {  //将第一个链表存入map
        map.put(node1,null);  
        node1 = node1.next;  
    }  
    while (node2 != null) {  //比较
        if (map.containsKey(node2)) {  
            return node2;  
        }  
        node2 = node2.next;  
    }  
    return null;  
}

1.1.2通过集合辅助查找

基本与Hash一致

public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {  
    if (headA == null || headB == null) return null;  
    Set<ListNode> set = new HashSet<>();  
    ListNode node1=headA;  
    ListNode node2=headB;  
    while (node1!=null){  
        set.add(node1);  
        node1=node1.next;  
    }  
    while(node2!=null){  
        if(set.contains(node2)){  
            return node2;  
        }  
    node2=node2.next;  
    }  
    return null;  
}

1.2使用栈

将两个链表同时压栈,若两链表相交之后成为单链表,则必有相交部分的元素先出栈,若出栈的元素不相同了,则公共节点是当前出栈元素的前节点

图片.png

public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {  
    if (headA == null || headB == null) return null;  
    ListNode node1=headA;  
    ListNode node2=headB;  
    ListNode preNode = null;  //前节点
    Stack<ListNode> stack1 = new Stack<>();  
    Stack<ListNode> stack2 = new Stack<>();  
    while (node1!=null){  //压栈
        stack1.push(node1);  
        node1=node1.next;  
    }  
    while (node2!=null){  
        stack2.push(node2);  
        node2=node2.next;  
    }  
    while (stack1!=null||stack2!=null){  
        if(stack1.peek()!=stack2.peek()){  //若栈顶元素不相同,返回前节点
            return preNode;  
        }  
        preNode=stack1.pop();  
        stack2.pop();  
    }  
    return null;  
}

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

图片.png

2.1使用栈

2.1.1全部压栈

将链表全部压栈,然后一边出栈一边遍历链表,若有一个不相同,则不是回文序列

 public boolean isPalindrome(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        ListNode node = head;
        while(node!=null){//压栈
            stack.push(node);
            node=node.next;
        }
        while(head!=null){
            if(stack.pop().val!=head.val){//比较
                return false;  
            }
             head=head.next;
        }
        return true;
    }

2.1.2优化

获取链表长度并全部压栈,比较的时候只遍历一半的元素

 public boolean isPalindrome(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        ListNode node = head;
        int len=0;
        while(node!=null){
            stack.push(node);
            node=node.next;
            len++; //记录链表长度
        }
        for(int i=0;i<len/2;i++){//遍历一半
            if(stack.pop().val!=head.val){
                return false;
            }
            head=head.next;
        }
        return true;
    }

3.合并有序链表

3.1合并两个有序链表

图片.png 新建一个链表,分别遍历两个链表,每次都选最小的节点链接到新链表

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummyNode = new ListNode(-1);//虚拟节点
        ListNode node = dummyNode;
        while(list1!=null&&list2!=null){
            if(list1.val<=list2.val){
                node.next=list1;
                list1=list1.next;
            }else{
                node.next=list2;
                list2=list2.next;
            }
            node=node.next;
        }
        node.next=list1==null ? list2:list1;
        return dummyNode.next;
    }

3.2合并多个链表

两两合并,之后再逐步合并

public ListNode mergeLists(ListNode[] lists) {
        ListNode res = null;
        for(ListNode list : lists){
            res = mergeTwoLists(list,res);
        }
        return res;
    }

3.3举一反三

图片.png 遍历list1找到替换区间的前后两个节点,再找到list2的尾节点,接着将链表连接起来可以了

public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode node1 = list1;
        ListNode node2 = list2;
        while(node2.next!=null){
            node2=node2.next;
        }
        for(int i=1;i<a;i++){
            node1=node1.next;
        }
        ListNode pre = node1; //替换区间的前节点
        for(int i=0;i<=b-a+1;i++){
            node1=node1.next;
        }
        ListNode end = node1;//替换区间的后节点
        pre.next=list2;
        node2.next=end;
        return list1;
    }

4.双指针专题

4.1寻找中间节点

图片.png 普通解法:遍历一遍求链表长度,再将链表遍历一半(即:len/2)可解

public ListNode middleNode(ListNode head) {
        ListNode node = head;
        int len = 0;
        while(node!=null){
            node=node.next;
            len++;
        }
        for(int i =0;i<len/2;i++){
            head = head.next;
        }
        return head;
    }

运用快慢指针解法

用两个指针slowfast同时遍历链表,slow一次走一步,fast一次走两步,当fast走到头时,slow一定在链表中间。

public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null&&fast.next!=null){//注意判空
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }

当给定元素个数为偶数时,在链表问题中,使用双指针解法会返回中间偏后的节点(上述示例的4),而在数组问题中会返回中间偏前的节点(上述示例的3),原因如下图所示:

图片.png

4.2寻找倒数第k个元素

图片.png 我们先将fast遍历到k+1个节点,slow在头节点,之后同时走,当fast指向null时,slow正好在倒数第k个节点上

public ListNode getKthFromEnd(ListNode head,int k){
    ListNode fast = head;
    ListNode slow = head;
    while(fast != null && k > 0){
        fast = fast.next;
        k--;
    }
    while(fast!=null){
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

4.3旋转链表

图片.png

public ListNode rotateRight(ListNode head, int k) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode temp = head;
        int len = 0;
        while(temp!=null){
            temp = temp.next;
            len++;
        }
        if(len<2||k%len==0){
            return head;
        }
        while( k%len > 0){//快指针指向第k%len节点
            fast = fast.next;
            k--;
        }
        while(fast.next!=null){
            fast = fast.next; //链表尾节点
            slow = slow.next; //倒数第k%len+1个节点
        }
        ListNode res = slow.next;
        slow.next = null;//防止链表循环
        fast.next = head;//尾连接头(示例一 5连接1)
        return res;
    }