数据结构之链表面试题

  1. 移除链表元素
/** * 删除链表中等于给定值 val 的所有节点。 * * 示例: * * 输入: 1->2->6->3->4->5->6, val = 6 * 输出: 1->2->3->4->5 * * 基本思路:建立新链表,遍历筛选非val值尾插进新链表中 */

public class RemoveEle {
    public class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
        }
    }

    public ListNode removeElements(ListNode head, int val) {
        ListNode result = null;  //创建结果链表,初始为null
        ListNode last = null;   //结果链表的尾结点,初始为null

        ListNode cur = head;
        while(cur.next != null){
            ListNode next = cur.next;   //此处为cur存档

            if(cur.val != 6){
                //尾插
                if(result == null){
                    result = cur;
                }else{
                    last.next = cur;
                }

                last = cur;  //更新链表的最后一个元素
            }

            cur = next;    //回档,继续读原链表的下一个元素
        }
        return result;
    }
}
  1. 反转链表
/** * 反转一个单链表。 * * 示例: * * 输入: 1->2->3->4->5->NULL * 输出: 5->4->3->2->1->NULL * * 基本思路:创建新链表,将原表的元素头插进新表中 */

public class ReverseListSolution {

    public class ListNode {
      int val;
      ListNode next;
      ListNode(int x) { val = x; }
  }

    public ListNode reverseList1(ListNode head) {
        ListNode result = null;

        ListNode cur = head;
        while(cur != null){
            ListNode next = cur.next;   //存档

            //头插
            cur.next = result;
            result = cur;

            cur = next;   //回档
        }
        return result;
    }


    /** * 进阶: * 三引用遍历反转链表 * * 基本思路:定义三个结点: * prev--指向前一个结点, * cur--指向当前结点, * next--指向下一结点 * 当cur == null时停止遍历 */

    public ListNode reverseList2(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode prev = null;
        ListNode cur = head;

        //prev-->cur-->next
        while(cur != null){
            ListNode next = cur.next;

            cur.next = prev;    //反转 next-->cur-->prev

            prev = cur;
            cur = next;

        }
        // 返回反转后的链表 此时prev已经指向最后一个结点,cur和next都指向空
        return prev;
    }
}
  1. 合并两个有序链表
/** * 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 * * 示例: * * 输入:1->2->4, 1->3->4 * 输出:1->1->2->3->4->4 * * 基本思路:两个有序表合并 * 判空 * cur1和cur2同时不为空:尾插 * cur1和cur2有一个为空(在合并过程中,一个表的元素全被转移到新表中),将另一个表直接尾插进新表 */

public class MergeTwoLists {

    public class ListNode {
        int val;
        ListNode next;
        ListNode(int x) { val = x; }
    }

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }

        ListNode cur1 = l1;
        ListNode cur2 = l2;
        ListNode result = null;   //创建新链表,初始为空
        ListNode last = null;   //创建最后一个结点,初始为null

        while(cur1 != null && cur2 != null){
            if(cur1.val <= cur2.val){
                ListNode next = cur1.next;   //存档

                //cur1尾插
                if(result == null){
                    result = cur1;
                }else{
                    last.next = cur1;
                }
                    last = cur1;   //更新最后一个结点

                cur1 = next;   //回档
            }else{
                ListNode next = cur2.next;

                //cur2尾插
                if(result == null){
                    result = cur2;
                }else{
                    last.next = cur2;
                }

                last = cur2;   //更新最后一个结点

                cur2 = next;  //回档
            }

        }

        //当cur1或cur2两个表在合并过程中,一个表的元素全被转移到新表中,剩下的另一个表直接尾插
        if(cur1 == null){
            last.next =  cur2;
        }
        if(cur2 == null){
            last.next = cur1;
        }
        return result;
    }
}
  1. 链表分割
/** * 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 * * 给定一个链表的头指针 ListNode pHead,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。 * * 基本思路:创建新的链表,以x为界限,小于x的结点尾插存放在small,大于或等于x的结点尾插存放在big,最终合并两个表 */

public class Partition {

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

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode partition(ListNode pHead, int x) {
        ListNode small = null;  //小于x的存放链表
        ListNode smallLast = null;  //小于x的链表的最后一个结点

        ListNode big = null;  //大于或等于x的存放链表
        ListNode bigLast = null;  //大于或等于x的链表的最后一个结点

        ListNode cur = pHead;   //定义链表元素变量,初始为pHead

        while(cur != null){

            ListNode next = cur.next; //存档

            //小于x,尾插至small
            if(cur.val < x){

                if(small == null){
                    small = cur;
                } else {
                    smallLast.next = cur;
                }
                smallLast = cur;   //更新最后一个结点

            } else {
                //大于或等于x,尾插至big
                if(big == null){
                    big = cur;
                }else{
                    bigLast.next = cur;
                }
                bigLast = cur;   //更新最后一个结点
                bigLast.next =null;
            }
            cur = next;  //回档
        }

        //当一个表为空
        if(small == null){
            return  big;
        } else {
            smallLast.next = big;
            return small;
        }
    }
}
  1. 链表的中间结点
    在这里插入图片描述
/** * 链表的中间结点 * * 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 * * 如果有两个中间结点,则返回第二个中间结点。 * * 示例 1: * * 输入:[1,2,3,4,5] * 输出:此列表中的结点 3 * 返回的结点值为 3 。 * * 示例 2: * * 输入:[1,2,3,4,5,6] * 输出:此列表中的结点 4 * * 基本思路:双指针遍历---->快慢指针:快2慢1 * 返回慢指针 */

public class MiddleNode {

    public class ListNode {
      int val;
      ListNode next;
      ListNode(int x) { val = x; }
    }

    public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        //快2慢1
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }

    /* while(fast != null){ fast = fast.next; if(fast == null){ break; } slow = slow.next; fast = fast.next; } */
        return slow;
    }
}
  1. 输出该链表中倒数第k个结点
/** * 链表中倒数第k个结点 * * 输入一个链表,输出该链表中倒数第k个结点。 * * 基本思路:定义两个结点变量(front,back) 前后引用 * 让front先走k步,back再开始走 * 当front为空的时候代表走到了链表的尽头,此时的back所指向的就是倒数第k个数 * * 判断两个状况:若front为空并且k大于结点数时,意味着找不到这个元素,直接返回null [1,6) i=6,k=7 * 当front为null,而i>=k时,直接返回head [1,6] i=6,k=6 * */

public class FindKthToTail {

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

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode FindLastKth(ListNode head,int k) {

        ListNode front = head;
        ListNode back = head;
        int i;

        for (i = 0; front != null && i < k; i++) {
            front = front.next;            //front先走到正数第k个元素
        }

        if (front == null && i < k) {
            // k 大于 结点个数
            return null;
        } else if (front == null) {    //[1,6]
            return head;
        }

        //front和back一前一后对链表进行遍历,直到front == null
        while (front != null) {
            front = front.next;
            back = back.next;
        }

        return back;
    }
}
  1. 判断链表回文
    在这里插入图片描述
/** * 链表的回文结构 * * 对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。 * * 给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。 * * 测试样例: * 1->2->2->1 * 返回: * true * * 基本思路:找中间结点+反转后半部分链表与前半部分进行比较 * * 先求出链表的长度-->求链表的一半长度---->找中间结点 * 再反转后半部分链表 * 最后判断回文 */

public class PalindromeList {

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

        ListNode(int val) {
            this.val = val;
        }
    }

    //获取链表长度
    public int getLength(ListNode head){
        int len = 0;
        ListNode cur = head;

        while(cur != null){
            len ++;
            cur = cur.next;
        }
        return len;
    }

    //反转函数
    public ListNode reverse(ListNode head){
        ListNode result = null;
        ListNode cur = head;

        while(cur != null){
            ListNode next = cur.next;

            //头插
            cur.next = result;
            result = cur;  

            cur = next;
        }
        return  result;
    }

    //判断回文
    public boolean chkPalindrome(ListNode A) {
        ListNode middle = A;
        int len = getLength(A);
        int halfLen = len/2;

        //找到中间结点
        for(int i = 0; i < halfLen; i++){
            middle = middle.next;
        }

        //从中间结点middle开始到尾结点进行链表反转
        ListNode r = reverse(middle);

        ListNode c1 = A;  //c1初始化为原链表头结点
        ListNode c2 = r;  //c2初始化为反转后的链表头结点

        //遍历进行值比对
        while(c1 != null && c2 != null){
            if(c1.val != c2 .val){
                return false;
            } else {
            c1 = c1.next;
            c2 = c2.next;
            }
        }
        return true;
    }

}
  1. 删除链表中重复的结点
    在这里插入图片描述
    在这里插入图片描述
/** * 删除链表中重复的结点 * * 在一个有序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 * * 例如,链表1->2->3->3->4->4->5 * 处理后为 1->2->5 * * 基本思路: * 1.创建结点,假结点dummy,指针结点prev,p1,p2 * 2.令dummy.next->head prev = dummy p1->head p2->head.next * 3.在p2不为空的条件下对p1,p2的值进行比较 * 3.1 当P1和P2值不等,指针统一后移 * 3.2 当P1和P2值相等且P2不为空,删除当前结点,仅将P2后移一位 */

public class DeleteDuplication {

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

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead == null){
            return null;
        }
        
        ListNode dummy = new ListNode(0);  //为前驱结点变量赋值,消除第一个结点没有前驱的特殊性
        dummy.next = pHead;

        ListNode prev = dummy;  // prev 永远是 p1 的前驱结点,用来删除结点
        ListNode p1 = pHead;
        ListNode p2 = pHead.next;
        
        //比较p1和p2的值
        while (p2 != null) {
            if (p1.val != p2.val) {
                //值不同结点变量统一后移
                prev = prev.next;
                p1 = p1.next;
                p2 = p2.next;
            } else {
                //值相同p2后移,其余两个结点不动
                while (p2 != null && p1.val == p2.val) {
                    p2 = p2.next;
                }

                prev.next = p2; //将两个相同的p1和p2所指的结点同时删除

                p1 = p2;   //p1后移
                if (p2 != null) {
                    p2 = p2.next;  //p2后移
                }

            }
        }
        return dummy.next;
    }
}

小结:

  1. 删除链表中的所有value

基本思路:遍历链表中的每个结点,和value值比对,同则删不同则将结点尾插到新链表中

  1. 反转单链表

基本思想:遍历每个结点,头插到result链表

  1. 合并两个有序链表

基本思路:遍历两个链表,比对两个链表每个结点的val,小的优先尾插到新链表中,当一个表被遍历空后,将另一个表剩余结点直接拼到新链表后

  1. 利用x把链表分割成<x和>=x两部分

基本思路:遍历链表的每个结点,若<x,尾插到small表,否则,尾插到big表中,最后将small和big拼接起来(注意判空)

  1. 双引用遍历–求中间结点

基本思路:快慢引用,快2慢1.在一个周期内,快的走两步慢的走一步(注意快的每走一步都要判空),快的走到尽头慢的即为所求

  1. 双引用遍历–求倒数第k个结点

基本思路:前后引用,front先走k步back再走,这样保证走到最后front走到链表尽头,此时的back就是倒数第k个数(注意K与链表长度的对比)

  1. 判断回文

基本思路:先找中间结点,然后反转后半部分链表,最后将后半部分与前半部链表的val进行遍历值比对

  1. 删除重复结点

基本思路:创建假结点dummy,前驱结点prev,值对比前后两个结点p1,p2。当p2不为空且p1,p2值不相等时,全部结点指针变量后移一位,相等则让p2后移一位,prev删除相同结点,集体后移一位。

完整代码:https://github.com/Loinbo/Data-Structure/tree/master/LinkedListTest/src/com/lamb