链表

放在最前面

3.从尾到头打印链表

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Deque<Integer> stack = new ArrayDeque<>();
        while (listNode != null) {
            stack.addLast(listNode.val);
            listNode = listNode.next;
        }
 
        ArrayList<Integer> list = new ArrayList<>();
        while (!stack.isEmpty()) {
            list.add(stack.removeLast());
        }
        return list;       
    }
}

逆序打印链表 1->2->3(3,2,1),可以先逆序打印链表 2->3(3,2),最后再打印第一个节点 1。而链表 2->3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。

import java.util.*;
public class Solution {
    ArrayList<Integer> list;//先写在外面 因为需要递归
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
      list = new ArrayList<>();
      if(listNode != null){
          printListFromTailToHead(listNode.next);
          list.add(listNode.val);
      }
      return list;
    }
}
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        // 头插法构建逆序链表
        //建立头节点 辅助使用 head.next=null 默认构造
        ListNode head = new ListNode(-1);
        while (listNode != null) {
            //先记录下一节点
            ListNode pnext = listNode.next;
            //头插法第一步 插入listNode在head后
            listNode.next = head.next;
            //更新头节点head 指向插入的节点listNode
            head.next = listNode;
            //listNode 向后步进
            listNode = pnext;
        }
        // 构建 ArrayList
        ArrayList<Integer> res = new ArrayList<>();
        //跳过我们设置的头节点
        head = head.next;
        while (head != null) {
            res.add(head.val);
            head = head.next;
        }
        return res;
    }
}

14.链表中倒数第k个结点

1.两次遍历

public class Solution {
     public ListNode FindKthToTail(ListNode head,int k) {
         //k为0和head为null的情况返回null
        if(head == null || k == 0) return null;
        ListNode tmp = head;
        int len=0;
        while(tmp!=null){
            tmp=tmp.next;
            len++;
        }
        if(len<k){
            return null;
        }else{
            for(int i=0;i<len-k;i++){
                head=head.next;
            }
            return head;
        }//else end        
    }//method end
}

2.双指针

准备两个指针:left和 right

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
     public ListNode FindKthToTail(ListNode head,int k) {
         //k为0和head为null的情况返回null
        if(head == null || k == 0) return null;
        ListNode left = head,right = head;
        for (int i = 0; i < k; i++) {
            if(right == null){
                return null;
            }//快指针行进不到k步就为null证明 链表长度不够k 即返回null
            right = right.next;
        }//fori end

        while(true){
            //right为null时,left到达了位置,直接返回left
            if(right == null){
                return left;
            }
            right = right.next;
            left = left.next;
        }//while end
    }
}

15.反转链表

1.头插法

头结点和第一个节点的区别:

头结点是在头插法中使用的一个额外节点,这个节点不存储值;

第一个节点就是链表的第一个真正存储值的节点。

下文的head与prehead的区别
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
import java.util.*;
public class Solution {
    public ListNode ReverseList(ListNode head) {//head 为第一个节点 是原链表真正存储值的节点

        // 头插法构建逆序链表
        //建立头节点 辅助使用 prehead.next=null 默认构造 prehead 是额外节点
        ListNode prehead = new ListNode(-1);
        while (head != null) {
            //先记录下一节点
            ListNode pnext = head.next;
            //头插法第一步 插入listNode在prehead后
            head.next = prehead.next;
            //更新头节点prehead 指向插入的节点listNode
            prehead.next = head;
            //listNode 向后步进
            head = pnext;
        }

        //跳过我们设置的额外头节点
        return prehead.next;
    }
}

2.双指针就地逆序

题目所给的是单链表,先想一下反转后的样子:最后一个结点指向倒数第二个,倒数第二个指向倒数第三个,……,第二个指向第一个,第一个指向null;

知道了反转后各个结点指向哪之后,就需要开始调整每个结点的next指针。

这就需要把结点挨个从链表上摘下来,做调整;

这个调整过程需要两个指针辅助:pre记录其前一个结点位置,好让该结点的next指针指向前一个结点,但是在指向前一个结点前需要用一个指针p记录后一个结点地址,避免结点丢失。

例子:

以head结点为例步骤如下:

重复这四个操作直到head走完原链表,指向null时,循环结束,返回pre。

public class Solution {
    public ListNode ReverseList(ListNode head) {
        //初始化pre指针,用于记录当前结点的前一个结点地址
        ListNode pre = null;
        //初始化p指针,用于记录当前结点的下一个结点地址
        ListNode pnext = null;
        //head指向null时,循环终止。
        while(head != null){
            //先用p指针记录当前结点的下一个结点地址。
            pnext = head.next;
            //让被当前结点与链表断开并指向前一个结点pre。
            head.next = pre;
            //pre指针指向当前结点
            pre = head;
            //head指向p(保存着原链表中head的下一个结点地址)
            head = pnext;
        }
        return pre;//当循环结束时,pre所指的就是反转链表的头结点
    }
}

16.合并两个排序的链表

常规版本

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        if(list2==null){
            return list1;
        }
        ListNode head;
        if(list1.val>list2.val){
            head=list2;
            list2=list2.next;
        }else{
            head=list1;
            list1=list1.next;
        }
               
        ListNode tmp=head;
        while(list1!=null && list2!=null){
            if(list1.val>list2.val){
                tmp.next=list2;
                tmp=tmp.next;
                list2=list2.next;
            }else{
                tmp.next=list1;
                tmp=tmp.next;
                list1=list1.next;
            }
        }
        if(list1!=null){
            tmp.next=list1;
        }
        if(list2!=null){
            tmp.next=list2;
        }
        return head;
    }
}

适当优化

确定head那里用得太多 而且if那部分代码其实和后面重合了 可以考虑引入额外的头节点 节省代码

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null) return list2;
        if(list2 == null) return list1;
       ListNode node = new ListNode(-1),prehead = node;
       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;
        }//while end
           node.next = list1 == null? list2 : list1;
           return prehead.next;
       }
}

其实 也可以递归实现

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        if(list2==null){
            return list1;
        }
        if(list1.val < list2.val) {
            list1.next = Merge(list1.next, list2);
            return list1;
        }else {
            list2.next = Merge(list1, list2.next);
            return list2;
        }
    }//method end
}

25.复杂链表的复制

map的key存储链表的元素。

map的value是新new的元素,label已经确定,next和radom要在p2循环链表的时候进行指定。

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.*;
public class Solution {
        public RandomListNode Clone(RandomListNode pHead)
        {
            if(pHead == null) return null;
            Map<RandomListNode,RandomListNode> map = new HashMap<>();
            RandomListNode p1 = pHead;
            RandomListNode p2 = pHead;
            while(p1!=null){
                map.put(p1,new RandomListNode(p1.label));
                p1 = p1.next;
            }

            while(p2!=null){
                if(p2.next != null){
                    map.get(p2).next = map.get(p2.next);
                }else{
                    map.get(p2).next = null;
                }

                map.get(p2).random = map.get(p2.random);
                p2 = p2.next;

            }
            return map.get(pHead);
        }
}

思路

第一步,在每个节点的后面插入复制的节点。

第二步,对复制节点的 random 链接进行赋值。

第三步,拆分。

public RandomListNode Clone(RandomListNode pHead) {
    if (pHead == null)
        return null;
    // 插入新节点
    RandomListNode cur = pHead;
    while (cur != null) {
        RandomListNode clone = new RandomListNode(cur.label);
        clone.next = cur.next;
        cur.next = clone;
        cur = clone.next;
    }
    // 建立 random 链接
    cur = pHead;
    while (cur != null) {
        RandomListNode clone = cur.next;
        if (cur.random != null)
            clone.random = cur.random.next;//指向新复制的那个 所以有个next
        cur = clone.next;
    }
    // 拆分
    cur = pHead;
    RandomListNode pCloneHead = pHead.next;
    while (cur.next != null) {
        RandomListNode next = cur.next;
        cur.next = next.next;
        cur = next;
    }
    return pCloneHead;
}

36.两个链表的第一个公共结点

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        int len1=length(pHead1);
        int len2=length(pHead2);
        if(len1>len2){
            ListNode res=FindFirstCommonNode(pHead2,pHead1);
            return res;
        }else{
            int k=len2-len1;
            for(int i=0;i<k;i++){
                pHead2=pHead2.next;
            }
            while(pHead2!=null&&pHead1!=null){
                if(pHead1==pHead2){
                    return pHead1;
                }
                pHead1=pHead1.next;
                pHead2=pHead2.next;
            }
        }
         return null;
    }
    public int length(ListNode p) {
        int res=0;
        while(p!=null){
            res++;
            p=p.next;
        }
        return res;
    }
}

a1 a2 c1 c2 c3 /b1 b2 b3 c1 c2 c3

b1 b2 b3 c1 c2 c3 /a1 a2 c1 c2 c3

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       if(pHead1 == null || pHead2 == null) return null;
       ListNode p1 = pHead1;
       ListNode p2 = pHead2;
       while(p1.val != p2.val){
           p1 = p1.next;
           p2 = p2.next;
           if(p1 == null && p2 == null) return null;
            if(p1 == null) p1 = pHead2;
            if(p2 == null) p2 = pHead1;
       }
       return p1;
    }
}

55.链表中环的入口结点

fast 步进为2,slow 步进为1,有环时,fast与slow一定会在环内相遇

当fast与slow相遇时,fast走过的距离为a + b + c + b,而slow走过的距离为a + b,因为fast是slow速度的两倍,则有a+b+c+b = 2*(a+b),登出a=c;

相遇后将fast移到头指针,fast和slow同时步进1移动,找到根据a=c找到入口。

代码

public class Solution {
   public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead == null || pHead.next == null) return null;
        ListNode slow = pHead, fast = pHead;
        do{
            slow = slow.next;
            fast = fast.next.next;
        }while(slow != fast);

        fast = pHead;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

56.删除链表中重复的结点

非递归写法

我们在考虑的情况中,如果头节点开始就重复,我们就处理很起来多了一种情况就需要额外处理,所以我们添加一个头节点,变成带头节点,保证了头节点开始不会重复。

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        ListNode head = new ListNode(-1); // 新建一个头结点,防止链表中头结点是重复节点被删除。
        ListNode trail = head;
 
        while (pHead != null) {
            ListNode pnext = pHead.next;
            boolean flag = false;
            while (pnext != null && pHead.val == pnext.val) {
                pnext = pnext.next;
                flag = true;
            }//pnext重复性验证
            if (!flag) {
                trail.next = pHead;
                trail = trail.next;
            }
            pHead = pnext;
        }
        trail.next = null; // 1->2->3->3->3
        return head.next;
    }//method end
}

递归写法

要点是对重复元素和不重复元素的分别处理:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
    if(pHead == null || pHead.next == null) return pHead;

    //对重复元素进行处理,将node一直指向此所有重复元素后一位,然后递归继续进行判断,
    if(pHead.val == pHead.next.val){
        ListNode node = pHead.next;
        while(node != null && node.val == pHead.val){
            node = node.next;
        }
        //排除了前面的重复元素,重新递归
        return deleteDuplication(node);

    }else{//对不重复元素,直接进行next指向处理
        pHead.next = deleteDuplication(pHead.next);
    }

    return pHead;

    }
}