链表刷题总结

  

  链表是面试的时候最为基本的一个题目,而且关于链表的题目也相对来说比较简单,相关的题目:

    1.逆序打印链表

    2.链表反转,反转链表的指定部分,交换链表的相邻节点,交换相邻的K个节点

    3.查找链表的最大值,查找并删除链表倒数第N个节点,查询节点并删除,删除链表的重复节点,删除链表的所有重复节点

    4.判断旋转链表,回文链表

    5.链表划分,链表重新洗牌

    6.链表的排序(归并排序和插入排序),几个有序链表合并成为一个有序链表

    7.利用循环链表实现约瑟夫环,得到链表的交点,判断链表是否有环以及找到链表环的入口,利用链表环的思路解决数组环

1.逆序打印链表

  题目要求:给定单链表,从尾到头打印每个节点的值,不同的值之间用空格隔开,比如1->2->3->4->5,输出5 4 3 2 1。

  这道题有递归和非递归两种实现方式,非递归算法如下:

利用栈,从头到尾依次入栈,然后出栈并打印,代码如下:

  

    public void printInverse(ListNode head) {
        if (head.next == null) {
            return;
        }
        Stack<T> stack = new Stack<T>();
        ListNode<T> p = head.next;
        while (p != null) {
            stack.push(p.val);
            p = p.next;
        }
        while (!stack.isEmpty()) {
            System.out.println(stack.pop() + " ");
        }
        System.out.println();
    }

 

递归算法如下:

    public void printrecursive(ListNode<T> head) {
        if (head.next == null) {
            return;
        }
        recursive(head.next);
        System.out.println();
    }
    public void recursive(ListNode<T> p) {
        if (p != null) {
            recursive(p.next);
            System.out.print(p.val + " ");
        }
    }

 

2.链表反转,反转链表的指定部分,交换链表的相邻节点,交换相邻的K个节点

 

  首先是链表反转,有递归和非递归两种实现方式,对于非递归方式,首先要定要三个指针,pre表示前驱节点,p表示当前节点,next表示下一个节点,非递归的时候有非常固定的模式,next=p.next,p.next=pre,pre=p,p=next;具体有leetcode206. Reverse Linked List,具体的代码如下:

    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            ListNode pre = head;
            ListNode p = head.next;
            ListNode next = null;
            while (p != null) {
                next = p.next;
                p.next = pre;
                pre = p;
                p = next;
            }
            head.next = null;
            return pre;
        }
    }

 

递归算法如下:

    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            ListNode tail = recursive(head);
            head.next = null;
            return tail;
        }
    }
    
    public ListNode recursive(ListNode p) {
        if (p.next == null) {
            return p;
        } else {
            ListNode next = p.next;
            ListNode tail = recursive(next);
            next.next = p;
            return tail;
        }
    }

 ------------------------------------------------------------------------------------------------

 

  然后是反转链表的指定部分,具体的题目leetcode92. Reverse Linked List II,题目要求如下:

Reverse a linked list from position m to n. Do it in-place and in one-pass.

For example:
Given 1->2->3->4->5->NULL, m = 2 and n = 4,

return 1->4->3->2->5->NULL.

Note:
Given m, n satisfy the following condition:
1 ≤ m ≤ n ≤ length of list.
View Code

  这道题跟上一道的解法类似,不过需要多两个指针,首先是一个指向要反转那一部分链表第一个节点的前一个节点的指针,然后是指向要反转的那一部分的第一个节点的指针,所以总共有五个指针,pre,p,next,top(要反转链表的第一个节点),first(要反转链表的第一个指针的上一个节点的指针),图示如下:

  最后需要让top.next = p;first.next = pre这样这部分链表就翻转了,具体代码如下:

public ListNode reverseBetween(ListNode head, int m, int n) {
        if (head == null || head.next == null) {
            return head;
        } else if (m == n) {
            return head;
        } else {
            //新建新的头结点,因为head可能也参与反转
            ListNode newHead = new ListNode(0);
            newHead.next = head;
            ListNode first = newHead;
            int k = 1;
            while (k < m) {
                //要反转链表部分的上一个节点
                first = first.next;
                k++;
            }
            ListNode pre = first.next;
            ListNode p = pre.next;
            ListNode next = null;
            //要反转部分的第一个节点,这个指针的值保持不变
            final ListNode top = pre; 
            while (k < n) {
                next = p.next;
                p.next = pre;
                pre = p;
                p = next;
                k++;
            }
            //第一个节点的下一个节点指向p,前一个节点的下一个节点指向pre
            top.next = p;
            first.next = pre;
            return newHead.next;
        }
    }

 -----------------------------------------------------------------------------------------

 

  然后是交换链表的相邻节点,相关题目时leetcode24. Swap Nodes in Pairs,具体的题目如下:

 

Given a linked list, swap every two adjacent nodes and return its head.

For example,
Given 1->2->3->4, you should return the list as 2->1->4->3.

Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.
View Code

 

  这道题需要五个指针,首先是指向头结点的新的头结点指针,定义为newHead,然后是指向要反转的两个节点的pre,p指针,指向要反转的部分的前一个节点的指针zero,指向要反转的部分的后一个节点的指针next,如下图:

 

  具体的代码如下:

    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            //定义新的头结点,它的next指向原来的头结点
            ListNode newHead = new ListNode(0);
            newHead.next = head;
            ListNode zero = newHead;
            ListNode pre = head;
            ListNode p = pre.next;
            ListNode next = null;
            while (pre != null && p != null) {
                next = p.next;
                p.next = pre;
                pre.next = next;
                zero.next = p;
                if (next == null) {
                    break;
                } else {
                    zero = pre;
                    pre = next;
                    p = pre.next;
                }
            }
            return newHead.next;
        }
    }

 

  这个题目还有另一种递归的解法,具体的代码如下:

 

public class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            ListNode second = head.next;
            ListNode third = second.next;
            second.next = head;
            head.next = swapPairs(third);
            return second;
        }
    }
}

 -----------------------------------------------------------------------------------------

 

  另外一个比较难的题目是交换链表的相邻K个节点,具体的题目有25. Reverse Nodes in k-Group,具体的题目如下:

 

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

You may not alter the values in the nodes, only nodes itself may be changed.

Only constant memory is allowed.

For example,
Given this linked list: 1->2->3->4->5

For k = 2, you should return: 2->1->4->3->5

For k = 3, you should return: 3->2->1->4->5
View Code

 

  这道题的思路如下:

    先找到要交换的这K个节点的第一个节点的上一个节点和最后一个节点的下一个节点,比如1->2->3->4->5->6,假如k=3的话,那么首先就要将

    1->2->3进行反转,先添加一个节点让其指向头结点,假设这个节点为0,那么新的链表是0->1->2->3->4->5->6,那么要反转部分就在0和4之间,

    现在让新建一个指针pre,让其指向0,然后新建指针next,通过循环让其指向4,在循环的过程中进行计数,如果计数值count = k,那么就对这一部                   分进行反转,单独的写一个反转函数,让其返回反转后链表的最后一个节点,这样就可以作为新的要反转的链表的第一个节点的前一个节点进行使用。

         反转函数中声明两个变量,分别为last和cur,其中last = pre.next,cur = last.next,然后进行循环只要cur != next,那么进行反转,具体的操作是:

          last.next = cur.next , cur.next = pre.next , pre.next = cur , cur = last.next,这其中的原理可以自己画图进行理解,画图很简单,比较容易理解,最后的结                  果就是last指针指向反转完成之后的最后一个节点,将其返回,并且赋给pre,进行下一轮的循环。具体的代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
     /**
     * Reverse a link list between pre and next exclusively
     * an example:
     * a linked list:
     * 0->1->2->3->4->5->6
     * |           |   
     * pre        next
     * after call pre = reverse(pre, next)
     * 
     * 0->3->2->1->4->5->6
     *          |  |
     *          pre next
     * @param pre 
     * @param next
     * @return the reversed list's last node, which is the precedence of parameter next
     */
public class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || k == 1) {
            return head;
        }
        ListNode phead = new ListNode(0);
        phead.next = head;
        int count = 0;
        ListNode pre = phead;
        ListNode cur = head;
        ListNode next = null;
        while (cur != null) {
            count++;
            next = cur.next;
            if (count == k) {
                pre = reverse(pre, next);
                count = 0;
            }
            cur = next;
        }
        return phead.next;
    }
    public ListNode reverse(ListNode pre, ListNode next) {
        ListNode last = pre.next;
        ListNode cur = last.next;
        while (cur != next) {
            last.next = cur.next;
            cur.next = pre.next;
            pre.next = cur;
            cur = last.next;
        }
        return last;
    }
}

 

 

3.查找链表的最大值,查找并删除链表倒数第N个节点,查询节点并删除

  首先是查找链表的最大节点,这道题比较简单,就是逐个比较一下节点的值,保存比较大的节点,有点类似于冒泡排序,可以利用comparator的compare函数以及comparable的compareTo函数,具体的代码如下;

    public Comparator<T> comp;
    public int compare(T a, T b) {
        if (comp != null) {
            return comp.compare(a, b);
        } else {
            Comparable<T> c = (Comparable<T>)a;
            return c.compareTo(b);
        }
    }
    public T getMax(ListNode head) {
        if (head.next == null) {
            return null;
        }
        ListNode<T> p = head.next;
        T max = p.val;
        p = p.next;
        while (p != null) {
            if (compare(p.val, max) > 0) {
                max = p.val;
            }
            p = p.next;
        }
        return max;
    }

 ----------------------------------------------------------------------------------------------

  另一个是查找并删除链表的倒数第N个节点,有leetcode19. Remove Nth Node From End of List,题目如下:

 

Given a linked list, remove the nth node from the end of list and return its head.

For example,

   Given linked list: 1->2->3->4->5, and n = 2.

   After removing the second node from the end, the linked list becomes 1->2->3->5.
Note:
Given n will always be valid.
Try to do this in one pass.
View Code

 

  题目要求用onepass,也就是遍历一次链表,可以创建两个指针p,pre,初始化都指向头结点,其中的p指针往后移动n+1个节点,然后两个指针

  同时往后移动,直到链表尾,此时后移动的指针pre就指向要删除节点的前一个节点,然后执行pre.next = pre.next.next进行删除操作就行,要注意的是

  可能要删除的节点是头结点,这样在第一个指针p一动的时候要判断是否为空也就是到链表尾部了,如果是直接返回head.next就行,具体代码如下:

public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head == null) {
            return head;
        } else {
            n++;
            ListNode pre = head;
            ListNode p = head;
            for (int i = 1; i <= n; i++) {
                if (p == null) {
                    return head.next;
                }
                p = p.next;
            }
            while (p != null) {
                pre = pre.next;
                p = p.next;
            }
            pre.next = pre.next.next;
            return head;
        }
    }
}

 

------------------------------------------------

   还有一种题目是查找元素并删除,leetcode203. Remove Linked List Elements,题目如下:

Remove all elements from a linked list of integers that have value val.

Example
Given: 1 --> 2 --> 6 --> 3 --> 4 --> 5 --> 6, val = 6
Return: 1 --> 2 --> 3 --> 4 --> 5
View Code

  跟上一个题目类似,不过也要考虑删除的元素可能是头结点,所以创建一个新的头结点,指向原来的头结点,然后创建两个指针pre,p,pre初始化为

  新的头结点,p指向头结点,然后同时往后移动,判断p.val是否等于val,是的话执行删除操作,具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        } else {
            ListNode newHead = new ListNode(0);
            newHead.next = head;
            ListNode pre = newHead;
            ListNode p = head;
            while (p != null) {
                if (p.val == val) {
                    pre.next = p.next;
                    p = p.next;
                } else {
                    pre = p;
                    p = p.next;
                }
            }
            return newHead.next;
        }
    }
}

 

----------------------------------------------------------------

   leetcode83. Remove Duplicates from Sorted List删除排序链表的重复元素,题目要求如下:

Given a sorted linked list, delete all duplicates such that each element appear only once.

For example,
Given 1->1->2, return 1->2.
Given 1->1->2->3->3, return 1->2->3.

  同样的方法,声明两个指针pre,p,初始时指向头结点和头结点的下一个节点,判断pre.val是否等于p.val,如果相等的话直接将p指针后移,直到

  两者不相等,然后将pre.next=p,否则的话两者都往后移动,具体的代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            ListNode pre = head;
            ListNode p = head.next;
            while (p != null) {
                if (p.val == pre.val) {
                    while (p != null && p.val == pre.val) {
                        p = p.next;
                    }
                    pre.next = p;
                } else {
                    pre = p;
                    p = p.next;
                }
            }
            return head;
        }
    }
}

 

---------------------------------------------------------

   leetcode82. Remove Duplicates from Sorted List II删除排序链表中的所有重复节点,题目如下:

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.

For example,
Given 1->2->3->3->4->4->5, return 1->2->5.
Given 1->1->1->2->3, return 2->3.

  跟上一道题不一样在于把所有重复的元素都要删除,因为头结点可能也是重复元素,所以要声明新的头结点,同样这次要声明三个指针pre,p,next;

  pre初始化指向新生命的头结点,p初始化为头结点,要判断p和next的值是否相等,所以这两者都不能为空。直接判断p.val是否等于next.val,如果相等

  将next后移直至不相等,然后pre.next = next,p = next,这样就将重复的元素都删除了,如果不想等的话直接将pre = p;p = p.next;然后继续执行循环,

  具体代码如下;

 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        } else {
            ListNode newHead = new ListNode(0);
            newHead.next = head;
            ListNode pre = newHead;
            ListNode p = head;
            ListNode next = null;
            while (p != null && p.next != null) {
                next = p.next;
                if (p.val == next.val) {
                    while (next != null && next.val == p.val) {
                        next = next.next;
                    }
                    pre.next = next;
                    p = next;
                } else {
                    pre = p;
                    p = p.next;
                }
            }
            return newHead.next;
        }
    }
}

 

 

----------------------------------------------------------------------------------------------------------------

4.判断旋转链表,回文链表

   首先时旋转链表,leetcode 61. Rotate List,具体的题目如下:

Given a list, rotate the list to the right by k places, where k is non-negative.

For example:
Given 1->2->3->4->5->NULL and k = 2,
return 4->5->1->2->3->NULL.

  意思是从倒数第n个数字前面开始旋转链表,这道题的思路很简单,比如题目的例子,直接将5的next指针指向头结点,然后将3的next指针断开

  所以要做的就是找到倒数第n-1个节点,然后断开next指针,然后从倒数第n个节点遍历到最后一个节点,然后将最后一个的节点的next指针指向

  头结点即可,需要注意的是k有可能是大于等于链表的长度的,因为他是正整数,所以要进行判断,当大于链表长度是,对链表长度进行取余操作,

  如果整除,直接返回头结点,否则按照上面的计算,具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) {
            return head;
        } else {
            int m = lengthOfList(head); 
            if (k >= m) {
                k = k % m;
            }
            if (k == 0) {
                return head;
            }
            //1 2 3 | 4 5 k=2
            ListNode pre = head;
            int index = 1;
            while (index < m - k) {
                index++;
                pre = pre.next;
            }
            ListNode newHead = pre.next;
            ListNode last = newHead;
            while (last.next != null) {
                last = last.next;
            }
            pre.next = null;
            last.next = head;
            return newHead;
        }
    }
    public int lengthOfList(ListNode head) {
        int m = 0;
        ListNode p = head;
        while (p != null) {
            m++;
            p = p.next;
        }
        return m;
    }
}

 

--------------------------------

  另一道题目是判断回文链表,leetcode234. Palindrome Linked List,具体题目如下:

Given a singly linked list, determine if it is a palindrome.

Follow up:
Could you do it in O(n) time and O(1) space?

  加入要判断的链表是 1 2 3 4 3 2 1,首先要做的是找到链表的中间节点,然后翻转链表的前一部分或者是后一部分,然后再从头逐个比较两个部分

  的值,如果有不相等的,直接返回false,否则返回true,注意在找中间节点的时候要判断链表长度是奇数还是偶数。具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        int n = lengthOfList(head);
        int half = n / 2;
        //1 2 3 4 3 2 1
        ListNode leftEnd = head;
        for (int i = 1; i < half; i++) {
            leftEnd = leftEnd.next;
        }
        ListNode rightStart = leftEnd.next;
        if (n % 2 != 0) {
            rightStart = rightStart.next;
        }
        rightStart = reverse(rightStart);
        ListNode leftStart = head;
        for (int i = 1; i <= half; i++) {
            if (leftStart.val != rightStart.val) {
                return false;
            }
            leftStart = leftStart.next;
            rightStart = rightStart.next;
        }
        return true;
    }
    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode p = pre.next;
        ListNode next = null;
        while (p != null) {
            next = p.next;
            p.next = pre;
            pre = p;
            p = next;
        }
        head.next = null;
        return pre;
    }
    public int lengthOfList(ListNode head) {
        ListNode p = head;
        int m = 0;
        while (p != null) {
            p = p.next;
            m++;
        }
        return m;
    }
}

 

  在寻找中间节点的时候也可以利用快慢指针,思路是一样的,具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        //如果数组的节点个数是奇数
        if (fast != null) {
            slow = slow.next;
        }
        slow = reverse(slow);
        fast = head;
        while (slow != null) {
            if (slow.val != fast.val) {
                return false;
            }
            slow = slow.next;
            fast = fast.next;
        }
        return true;
    }
    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode p = pre.next;
        ListNode next = null;
        while (p != null) {
            next = p.next;
            p.next = pre;
            pre = p;
            p = next;
        }
        head.next = null;
        return pre;
    }
}

 

-------------------------------------------------------------------------------------------

5.链表划分,链表重新洗牌

  首先是链表的划分,leetcode86. Partition List,题目如下:

 

Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.

You should preserve the original relative order of the nodes in each of the two partitions.

For example,
Given 1->4->3->2->5->2 and x = 3,
return 1->2->2->4->3->5.

 

  要注意的是不能改变数字原来的顺序,比如说后面的4->3->5不能变为3->4->5,具体的解题思路如下;

 

  代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode partition(ListNode head, int x) {
        if (head == null || head.next == null) {
            return head;
        }
        //建立左部链表,该链表中所有的节点值都小于x
        ListNode leftHead = new ListNode(0);
        ListNode leftTail = leftHead;
        //建立右部链表,该链表中所有的节点值都大于x
        ListNode rightHead = new ListNode(0);
        ListNode rightTail = rightHead;
        //遍历原来的链表
        ListNode p = head;
        while (p != null) {
            if (p.val < x) {
                leftTail.next = p;
                leftTail = p;
            } else {
                rightTail.next = p;
                rightTail = p;
            }
            p = p.next;
        }
        //将左右两个链表首尾相连,并将右链表尾部节点的next置为空
        p = leftTail;
        p.next = rightHead.next;
        rightTail.next = null;
        return leftHead.next;
    }
}

 

 --------------------------------------------------------------------

  另一个题目是链表重新洗牌,leetcode143. Reorder List,题目如下:

Given a singly linked list L: L0?L1?…?Ln-1?Ln,
reorder it to: L0?Ln?L1?Ln-1?L2?Ln-2?…

You must do this in-place without altering the nodes' values.

For example,
Given {1,2,3,4}, reorder it to {1,4,2,3}.

  其实就是将链表分为两部分,然后将后半部分反转,然后将前半部分和后半部分进行交替的连接,假如链表是1 2 3 4 5 6,那么先将4 5 6反转变成

  6 5 4,然后将1 2 3和6 5 4进行交替的连接,首先是1 6,然后是2 5,然后是3 4,此时需要两个指针left和right分别指向两个链表的头部,定义一个

  flag来判断是连接哪一个链表,定义一个next指针存储下一个节点,首先就是next = left.next, left.next = right, left = next,也就是将1的next指向6,

        时next指针变为2,left也变为2;下一步就是对右边的链表进行操作,将6的next指针指向2,然后将right指向5,然后继续进行操作,具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }
        int n = lengthOfList(head);
        int half = n / 2;
        if (n % 2 != 0) {
            half++;
        }
        ListNode leftEnd = head;
        for (int i = 1; i < half; i++) {
            leftEnd = leftEnd.next;
        }
        ListNode rightStart = leftEnd.next;
        rightStart = reverse(rightStart);
        leftEnd.next = null;
        ListNode left = head;
        ListNode right = rightStart;
        boolean flag = true;
        ListNode next = null;
        while (right != null) {
            if (flag) {
                next = left.next;
                left.next = right;
                left = next;
            } else {
                next = right.next;
                right.next = left;
                right = next;
            }
            flag = !flag;
        }
    }
    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode p = pre.next;
        ListNode next = null;
        while (p != null) {
            next = p.next;
            p.next = pre;
            pre = p;
            p = next;
        }
        head.next = null;
        return pre;
    }
    public int lengthOfList(ListNode head) {
        ListNode p = head;
        int m = 0;
        while (p != null) {
            p = p.next;
            m++;
        }
        return m;
    }
}

   这道题还可以用快慢指针寻找中间节点,要注意链表节点是奇偶个数的情况还有左边链表的尾部节点的next指针应该置为null,具体代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }
        ListNode slow = head;
        ListNode fast = head;
        ListNode pre = null;
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        if (fast != null) {
            pre = slow;
            slow = slow.next;
        }
        pre.next = null;
        ListNode right = reverse(slow);
        ListNode left = head;
        ListNode next = null;
        boolean flag = true;
        while (right != null) {
            if (flag) {
                next = left.next;
                left.next = right;
                left = next;
            } else {
                next = right.next;
                right.next = left;
                right = next;
            }
            flag = !flag;
        }
    }
    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode p = pre.next;
        ListNode next = null;
        while (p != null) {
            next = p.next;
            p.next = pre;
            pre = p;
            p = next;
        }
        head.next = null;
        return pre;
    }
}

 

-----------------------------------------------------------------------------------------------------

6.链表的排序(归并排序和插入排序),几个有序链表合并成为一个有序链表

  首先是链表的排序,链表的排序有插入排序和归并排序两种,插入排序的时间复杂度是O(n*n),归并排序的时间复杂度是O(nlog(n))。

  首先看leetcode 147. Insertion Sort List       Sort a linked list using insertion sort. 主要就是利用插入排序对链表进行排序,具体思路是:

  新建一个链表,头结点是dummy,让一个指针p指向头结点,每次插入的时候,判断要插入的节点应该插入新链表的什么位置,

  找到要插入的位置p和p.next之间,然后插入,最后将p重新指向新链表的头结点dummy,重新循环。具体代码如下:

 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode insertionSortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        //新建一个链表,用于存储排序后的节点,这是头结点
        ListNode dummy = new ListNode(0);
        //指向新链表的头结点,每次插入新节点后,再重新变为头结点
        ListNode pre = dummy;
        ListNode cur = head;
        ListNode next = null;
        while (cur != null) {
            next = cur.next;
            //寻找到在新链表中的插入位置,在pre,pre.next之间
            while (pre.next != null && pre.next.val < cur.val) {
                pre = pre.next;
            }
            cur.next = pre.next;
            pre.next = cur;
            //重新指向新链表的头结点
            pre = dummy;
            cur = next;
        }
        return dummy.next;
    }
}

 

 

-------------------------------------------------------------------------

  另一种排序是利用归并排序进行链表的重排序,leetcode 148. Sort List   Sort a linked list in O(n log n) time using constant space complexity.

  题目有求时间复杂度为O(nlogn),所以利用归并排序进行排序,首先利用快慢指针得到链表的中间节点,将原始的链表分为两个,递归执行,

  然后递归将两个有序链表合并成一个有序链表,返回最后的链表即可,具体代码如下:

 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head;
        ListNode pre = null;
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        pre.next = null;
        ListNode l1 = sortList(head);
        ListNode l2 = sortList(slow);
        return merge(l1, l2);
    }
    public ListNode merge(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
        p.next = (l1 == null) ? l2 : l1;
        return dummy.next;
    }
}

 

 

 

 

------------------------------------------------------------------------------

  leetcode 21. Merge Two Sorted Lists

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

  将两个有序链表合并成一个有序链表,首先判断一下是否有一个为空,如果是的话返回另外一个,然后从头结点开始判断哪一个链表的节点值小,

  将小的一个节点插入到新建的链表中,同时指针向后移动一个,最后知道有一个链表为空结束。最后还要判断哪一个链表没有结束,直接将新链表

  的next指向没有结束的链表即可,具体的代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
        p.next = (l1 == null) ? l2 : l1;
        return dummy.next;
    }
}

  另外还有一种递归的算法,直接比较两个链表的头结点大小,将小的一个作为新的头结点,然后递归的调用函数,代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

 

-------------------------------------------------------------------------

  最后一个比较难的算法是将k个有序的链表合并成一个新的有序的链表,leetcode 23. Merge k Sorted Lists,题目如下:

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

  这道题可以有两种解法,一种是利用二分法,一种是利用最小堆,思路如下:

  解法一:基于“二分”思想的归并排序

       初见之下,最容易想到的方法是“归并排序”(Merging Sort):将两个或两个以上的有序表组合成一个新的有序表,无论是顺序存储结构还是链式存储结构,对于任何两个长度分别为m和n的有序表,其组合都可在O(m+n)的时间复杂度量级上完成。对于K个有序表,假设共有N个元素,且这些有序表初始状态都不为空,每个有序表平均拥有N/K个元素。最常用的方法是采用“二分”的思想进行两两合并:第一轮循环,有序表lists[0]与lists[(K+1)/2],lists[1]与lists[(K+1)/2+1],lists[2]与lists[(K+1)/2+2]....,lists[K/2-1]与lists[K-1]。这样K个有序表就被组合成了K/2个有序表;第二轮循环后将减少为K/4个有序表;直到组合成一个具有N个元素的有序表。总的时间复杂度:O(NKlogK)

 

  解法二:基于优先级队列的“堆排序”

     堆(Heap)的定义如下:n个元素的序列{k1,k2,k3,...,kn}当且仅当满足下列关系时,称为堆,

     其中前者称为最小堆,后者称为最大堆。在Java中,堆是一种可以自我调整的二叉树,对于最小堆来说,对树执行删除和添加操作,可以让最小元素移动到根节点。在Java中,优先级队列(Priority Queue)便采用了“堆”这种数据结构,PriorityQueue是一个泛型类,它可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供比较器的对象(优先级队列实现排序中也需要比较)。

       基于优先级队列,我们可以将K个链表的头结点全部添加到队列,由于优先级队列采用了最小堆数据结构,堆顶为队列的最小元素,我们将其取出添加到结果链表中,取出元素对应的链表下移一个节点,并将这个节点添加到优先级队列中;然后我们继续取出堆顶元素,...,直到优先级队列为空,那么其中所有元素取尽,K个链表的元素已经全部排序到结果链表。

  二分的代码如下:

 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
public ListNode mergeKLists(ListNode[] lists) { if (lists == null || lists.length == 0) { return null; } return sort(lists, 0, lists.length - 1); } public ListNode sort(ListNode[] lists, int lo, int hi) { if (lo >= hi) { return lists[lo]; } int mid = lo + (hi - lo) / 2; ListNode l1 = sort(lists, lo, mid); ListNode l2 = sort(lists, mid + 1, hi); return merge(l1, l2); } public ListNode merge(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } if (l2 == null) { return l1; } ListNode dummy = new ListNode(0); ListNode p = dummy; while (l1 != null && l2 != null) { if (l1.val < l2.val) { p.next = l1; l1 = l1.next; } else { p.next = l2; l2 = l2.next; } p = p.next; } p.next = (l1 == null) ? l2 : l1; return dummy.next; } }

 

 

 

  基于最小堆的算法如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        int len = lists.length;
        if (len == 0 || lists == null) {
            return null;
        }
        if (len == 1) {
            return lists[0];
        }
        PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(11, new Comparator<ListNode>(){
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val -o2.val;
            }
        });
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        for (int i = 0; i < lists.length; i++) {
            if (lists[i] != null) {
                queue.offer(lists[i]);
            }
        }
        while (!queue.isEmpty()) {
            ListNode cur = queue.poll();
            p.next = cur;
            p = p.next;
            if (cur.next != null) {
                queue.offer(cur.next);
            }
        }
        return dummy.next;
    }
}

 

---------------------------------------------------------------------------------------------

 

 

 7.利用循环链表实现约瑟夫环,得到链表的交点,判断链表是否有环以及找到链表环的入口

  首先是利用循环链表实现约瑟夫环,问题的描述如下;

 

  

  假如总共有8个人,即n=8,并且start=3,step=4,具体的执行过程如下:

 

 

  首先要创建一个链表节点类,具体的代码如下:

package com.bupt.suanfa;

public class ListNode {
    public int val;
    public ListNode next;
    public ListNode(int val, ListNode next) {
        super();
        this.val = val;
        this.next = next;
    }
    public ListNode(int val) {
        super();
        this.val = val;
    }
    @Override
    public String toString() {
        return "ListNode [val=" + val + "]";
    }
    public static void printList(ListNode head) {
        ListNode p = head;
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }
    //利用数组创建循环链表
    public static ListNode arrayToCircle(int[] array) {
        ListNode head = new ListNode(0);
        ListNode p = head;
        for (int value : array) {
            p.next = new ListNode(value);
            p = p.next;
        }
        p.next = head.next;
        return head.next;
    }
}
View Code

  然后创建一个约瑟夫的类,在里面执行相关的操作,具体代码如下:

package com.bupt.suanfa;

import org.junit.Test;

public class Josephus {
//n=8,start=3,step=4
    //获得开始的节点
    public ListNode getStart(ListNode head, int start) {
        ListNode p = head;
        for (int i = 1; i < start; i++) {
            p = p.next;
        }
        return p;
    }
    //执行一次操作,计数并删除
    public ListNode countAndRemove(ListNode node, int step) {
        ListNode pre = node;
        for (int i = 1; i < step - 1; i++) {
            pre = pre.next;
        }
        System.out.println(pre.next.val);
        pre.next = pre.next.next;
        return pre.next;
    }
    public void josephusCircle(ListNode head, int n, int start, int step) {
        ListNode startNode = getStart(head, start);
        for (int i = 1; i <= n; i++) {
            startNode = countAndRemove(startNode, step);
        }
        startNode.next = null;
    }
    //测试
    @Test
    public void test() {
        int[] array = {1,2,3,4,5,6,7,8};
        ListNode head = ListNode.arrayToCircle(array);
        josephusCircle(head, array.length, 3, 4);
    }
}
View Code

  通过上面的代码可以模拟并实现约瑟夫环。

---------------------------------------------------------------------

  另一个题目是得到两个链表的交点,具体的问题描述如下:

 

  

  160. Intersection of Two Linked Lists 就是关于求解两个链表的交点的,具体的题目如下:

Write a program to find the node at which the intersection of two singly linked lists begins.


For example, the following two linked lists:

A:          a1 → a2
                   ↘
                     c1 → c2 → c3
                   ↗            
B:     b1 → b2 → b3
begin to intersect at node c1.


Notes:

If the two linked lists have no intersection at all, return null.
The linked lists must retain their original structure after the function returns.
You may assume there are no cycles anywhere in the entire linked structure.
Your code should preferably run in O(n) time and use only O(1) memory.
View Code

  这道题要求O(N)的时间复杂度和O(1)的空间复杂度,有一种简单的方法是利用HashSet,先将其中的一个链表存到里面,然后遍历另一个链表,

  判断每一个节点是否包含在HashSet中,假设两个链表的长度分别为m和n,那么时间复杂度界于O(m+n)~O(m*sqrt(n)),空间复杂度是O(n),

  虽然不满足题目要求,但是提交也能通过,具体的代码如下:

  

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        HashSet<ListNode> set = new HashSet<ListNode>();
        for (ListNode p = headB; p != null; p = p.next) {
            set.add(p);
        }
        for (ListNode p = headA; p != null; p = p.next) {
            if (set.contains(p)) {
                return p;
            }
        }
        return null;
    }
}

 

  另一种好的解法是,利用两个指针,分别指向两个链表的头结点,如果两个链表的长度相等,那么这两个指针同时往后移动直到遇到第一个

  相等的节点,这个就是交点,图示如下:

 

  

  如果两个链表长度不相等的话,先将长度较长的那个链表的指针向后移动m-n个,然后两个指针再同时往后移动,图示如下:

 

  

  具体的代码如下:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int m = lengthOfList(headA);
        int n = lengthOfList(headB);
        if (m == 0 || n == 0) {
            return null;
        } else {
            int k;
            ListNode p = headA;
            ListNode q = headB;
            if (m > n) {
                k = m - n;
                for (int i = 1; i <= k; i++) {
                    p = p.next;
                }
            } else if (m < n) {
                k = n - m;
                for (int i = 1; i <= k; i++) {
                    q = q.next;
                }
            }
            while (p != null && q != null) {
                if (p == q) {
                    return p;
                } else {
                    p = p.next;
                    q = q.next;
                }
            }
            return null;
        }
    }
    public int lengthOfList(ListNode head) {
        ListNode p = head;
        int n = 0;
        while (p != null) {
            p = p.next;
            n++;
        }
        return n;
    }
}

 

----------------------------------------------------------------

   另一个题目是判断链表是否有环,要判断的链表主要有以下三种情况;

 

 

  单链表无环,循环链表有环,部分循环链表有环。leetcode 141. Linked List Cycle 就是关于判断链表中是否存在环的,题目描述如下:

Given a linked list, determine if it has a cycle in it.

Follow up:
Can you solve it without using extra space?

 

  这道题主要用快慢指针来做,定义一个快指针fast和慢指针slow,当fast!=null&&fast.next!=null时,fast = fast.next.next,slow = slow.next,判断fast是否等于slow,如果相等,则存在环。具体的代码如下:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        } else {
            ListNode fast = head;
            ListNode slow = head;
            while (fast != null && fast.next != null) {
                slow = slow.next;
                fast = fast.next.next;
                if (slow == fast) {
                    return true;
                }
            }
            return false;
        }
    }
}

 

  跟上面类似,当求解链表环的起始节点时,解法相似,也是利用快慢指针,leetcode 142. Linked List Cycle II 题目描述如下:

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

Note: Do not modify the linked list.

Follow up:
Can you solve it without using extra space?

  也是利用快慢指针做,判断时主要有以下依据:

 

 

  具体的代码如下:

 

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    //二刷,利用快慢指针20180316
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                slow = head;
                while (slow != fast) {
                    slow = slow.next;
                    fast = fast.next;
                }
                return fast;
            }
        }
        return null;
    }
}

 

 

 

 

-------------------------------------------------------

 

  另外我们也可以利用链表环的思路解决数组环,LeetCode 287. Find the Duplicate Number 题目要求如下:

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Note:
You must not modify the array (assume the array is read only).
You must use only constant, O(1) extra space.
Your runtime complexity should be less than O(n2).
There is only one duplicate number in the array, but it could be repeated more than once.

  具体的思路如下:

 

 

  具体的代码如下:

public class Solution {
    public int findDuplicate(int[] nums) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int n = nums.length;
        int slow = n;
        int fast = n;
        while (true) {
            //slow,fast在1~n之间,所以需要-1
            slow = nums[slow - 1];
            fast = nums[fast - 1];
            fast = nums[fast - 1];
            if (slow == fast) {
                break;
            }
        }
        slow = n;
        while (slow != fast) {
            slow = nums[slow - 1];
            fast = nums[fast - 1];
        }
        return slow;
    }
}

 

 

-------------------------------------------------------------

 

posted @ 2017-07-26 21:26  温暖的向阳花  阅读(1079)  评论(0编辑  收藏  举报