【JAVA】链表面试题

链表面试题

  • 链表面试题
    • 1. 删除链表中等于给定值 val 的所有节点。
    • 2. 反转一个单链表。
    • 3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
    • 4. 输入一个链表,输出该链表中倒数第k个结点。
    • 5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
    • 6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
    • 7. 链表的回文结构。
    • 8. 输入两个链表,找出它们的第一个公共结点。
    • 9. 给定一个链表,判断链表中是否有环。
    • 10. 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。
    • 11. 其他练习。
  • THINK


链表面试题

加油学习!!冲冲冲呀!


努力努力在努力!

1. 删除链表中等于给定值 val 的所有节点。

删除链表元素

  1. 思路:
    即removeAllKey()的代码:但是注意head是不可以加this的,因为该类实现的对象是通过后台进行传入的(注意部分进行修改)

  2. 代码:

/**
 * 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 removeElements(ListNode head, int val) {
        // 其实就相当于实现removeAllKey
        // 加头结点or先处理后面的节点再处理头结点

        /* // 进来首先进行头结点是否为空的判断!!
        if(head == null) {
            return null;
        }
        // 需要一个循环:把整个链表都遍历完
        ListNode pre = head;
        ListNode cur = head.next; //自定义类型变量存储的是地址
        while (cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
                cur = cur.next;
            } else {
                pre = cur;
                cur = cur.next;
            }
        }
        // 单独处理头节点的问题,刚刚没有处理头节点问题!!
        if(head.val == val) { // 注意是if,不用while循环,因为上面已经处理了所有头结点之后的节点
            head = head.next;
        }
        // 注意把头结点的处理放在最后!!:因为如果是从头结点开始连续存在要删除的key值,头结点将会变换至下一个key,也就是会一直保留一个key在头结点位置
*/

        // 另一种方法是 创建一个新的头节点!!
        ListNode head1 = new ListNode(-1); // 随便存一个值就行
        head1.next = head;
        head = head1; // 注意此处的修改!
        // 然后进行遍历处理(不用单独考虑头结点)--上面方法
        ListNode pre = head;
        ListNode cur = head.next;
        while (cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
                cur = cur.next;
            } else {
                pre = cur;
                cur = cur.next;
            }
        }
        head = head.next;  // 注意要将头结点的位置进行变换!!

        return head;
    }
}

2. 反转一个单链表。

反转链表

  1. 思路:
  • (出现频率高)类似逆向进行存储,但是是原地反转:把头结点.next=null,然后将其他结点依次进行头插就行,需要再设置一个新的结点:Node curNext=cur.next; cur.next=head; head=cur; cur=curNext;在while(cur!=null)循环中进行实现
    (注意:要考虑只有一个结点和没有结点也就是null的情况!!)
  • head其实就是当前需要进行反转的节点的前驱
    (reverseList)
  1. 代码:
/**
 * 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 reverseList(ListNode head) {
        // 考虑只有一个结点和没有结点的情况
        if(head == null) {
            return null;
        }
        if(head.next == null) {
            return head;
        }

        /* 以下是错误代码:
        ListNode curNext = head.next; //指向第二个结点

        // 因为头结点要变成尾结点,所以next进行置空
        head.next = null;

        // 其余结点进行头插:需要进行循环遍历链表实现
        while(curNext != null) {
            // 头插
            ListNode cur = head;
            head = curNext;
            head.next = cur;   // 此处也有问题:头结点的定义重要的不是变量head,而是指向性问题!!
            curNext = cur.next;  // 思路有问题!!
        }
        */

        // 更正代码:
        ListNode cur = head.next;  // 当前需要反转的节点,head是当前需要反转的节点的前驱!
        head.next = null; // 要变成尾结点
        while(cur != null) { // 进行遍历
            ListNode curNext = cur.next;
            cur.next = head;  // 注意此处逻辑!!!
            head = cur;
            cur = curNext;
        }

        return head;

    }
}

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

链表的中间结点

  1. 思路:

方法1:遍历一遍求长度,然后再遍历求中间结点(但是该方法至少要遍历1.5遍)
方法2:要求只遍历一遍:使用快慢指针–slow所需时间是fast的两倍,则当fast走到末尾时,slow刚好走到中间位置!(但是奇偶数走到最后是有差异的,只要满足其中一个条件就说明fast走到了最后,但是如果两个条件只要其中一个不满足就说明是还没有到达末结点,需要继续走,其实一定要注意这里的逻辑符是
逻辑与,只要一个不满足就false!!即:格外注意while循环的条件!!还有两个条件的顺序:fast在前!!:走了两步很可能先为空。

  1. 代码:
/**
 * 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 middleNode(ListNode head) {
        // 首先判断是否为空
        if(head == null) {
            return null;
        }

        ListNode slow = head;
        // 注意此处错误!!快慢结点都应该从头结点开始!!
        //ListNode fast = head.next;
        ListNode fast = head;
        // 注意此处while语句的循环条件:只要一个不满足就说明已经走到了末指针,即false的短路-逻辑与
        // 注意两个表达式的顺序关系
        while(fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;

        /*// 方法二:求长度+求中间值进行返回结点
        int count = 0;
        ListNode cur = head;
        while((size()/2) != count) { // 这里的话要新写size()方法
            cur = cur.next;
            count++;
        }
        return cur;*/
    }
}

4. 输入一个链表,输出该链表中倒数第k个结点。

链表中倒数第k个结点

  1. 思路:

k进行合法性判断、
方法1:遍历求长度、然后(长度-k)就是所要打印的节点,进行遍历就行
方法2:要求只遍历一遍:使用快慢指针–相差(k-1)步,fast先走(k-1)步,然后slow与fast同时走,直到fast.next
==null才停止走!

  1. 代码:
import java.util.*;
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        // 首先进行合法性判断
        if(k<0 || head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        //fast 先走k-1步 然后再一起走:要求是只遍历一遍
        while(k-1 != 0) { // 如果k超过长度,会空;但是k-1并不会==0而退出循环
            fast = fast.next; // 如果head为空,这里就是异常语句,所以要进行空指针判断!
            if(fast == null) {
                return null; // k太大了就无输出!!
            }
            k--;  // 注意k要进行变换
        }
        // 同时走
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

合并两个有序链表

  1. 思路:

设置一个傀儡结点,然后两个链表的节点依次进行大小比较,串在傀儡结点之后(但是要注意判断两个头结点的情况)

  1. 代码:
/**
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
         // 新建一个虚拟结点
        ListNode newHead = new ListNode(-1); // 要让该结点保持不变,稍后好进行返回
        ListNode tmp = newHead;
        // 开始依次进行大小比较
        while(list1!=null && list2!=null) {
            if(list1.val > list2.val) {
                tmp.next = list2;
                list2 = list2.next;
                tmp = tmp.next;
            } else {
                tmp.next = list1;
                list1 = list1.next;
                tmp = tmp.next;
            }
        }
        // 出来则说明要么head1==null 要么head2==null (短路原则)
        if(list1==null) {
            tmp.next = list2;
        }
        if (list2==null) {
            tmp.next = list1;
        }
        return newHead.next;
    }
}

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。

链表分割

  1. 思路:

进行一次遍历,然后把结点分别放在两个链表上(这两个链表就是以判断标识来进行区分的),然后结束后进行两个分链表的连接就行,然后把最后指向null(链表的话定义头尾结点就行)(插入方式是进行尾插)
还要考虑一个问题:第一个段是否有结点满足! 把最后指向null是在存在后半段的情况下

  1. 代码:
import java.util.*;

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        // write code here
        // 分成两段:before 与 after (start & end)
        ListNode bs = null;
        ListNode be = null;
        ListNode as = null;
        ListNode ae = null;
        // 再创建一个临时结点表示当前结点位置
        ListNode cur = pHead;

        // 进行遍历
        while(cur != null) {
            if(cur.val<x) {
                // 处于前半段
                if(bs==null) {
                    // 说明现在是一个空结点
                    bs = cur;
                    be = cur;
                } else {
                    be.next = cur;
                    be = be.next; //不要忘记!!
                }
            } else {
                // 处于后半段
                if(as==null) {
                    // 说明现在是一个空结点
                    as = cur;
                    ae = cur;
                } else {
                    ae.next = cur;
                    ae =ae.next;
                }
            }
            cur = cur.next;
        }
        //如果前半段直接没有
        if(bs==null) {
            return as;
        }
        // 如果后半段存在
        if(ae!=null) {
            ae.next = null;
        }
        // 进行连接
        be.next = as;
        return bs;
    }
}

7. 链表的回文结构。

链表的回文结构

  1. 思路:

回文结构:前后对称!
要求空间复杂度O(1),但是前后同时遍历的思路是不可行的,因为是单向的
解决:找中间位置、将中间之后的位置进行反转;然后就可以实现head往后走、slow往前走(快慢指针)(需要创建变量)
注意赋值方式!!

  1. 代码:
import java.util.*;

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        // write code here
       // 找中间结点 + 中间之后进行反转 + 前后遍历 (会使用快慢指针)

        // 进行空判断
        if(A == null) {
            return true;
        }

        // 使用快慢指针找中间位置
        ListNode slow = A;
        ListNode fast = A;
        //while(fast != null) { // 注意这里条件!!!
        // 有奇偶之分,则最后判断条件也是不一样的,同时满足才进行,只要不满足一个就停止
        // 注意两个表达式的顺序!!!
        while (fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 此时slow就是中间结点
        // 开始对中间结点之后的节点进行反转 + 建立变量
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = slow;
            //slow = slow.next;// 注意此处是错误的 这样会造成一个循环
            slow = cur; // 直接这样子进行赋值,类似后面的赋值
            cur = curNext;
        }
        // 反转完毕 + 进行回文比较--head与slow分别进行前后遍历
        // 注意循环条件:考虑奇偶:只是简单的head!=slow 只能解决奇数问题,
        // 还有偶数问题就要另外考虑:head
        // 而偶数问题:head.next == slow 就是需要终止的时候了
        while(A!=slow ) {
            if(A.val != slow.val) {
                return false;
            }
            // 在这里进行判断!!! 重要!!!
            if(A.next==slow) {
                return true;
            }
            // 进行结点变换
            A = A.next;
            slow = slow.next;
        }
        // 来到这儿 说明正确
        return true;
    }
}

8. 输入两个链表,找出它们的第一个公共结点。

相交链表

  1. 思路:

注意:两个链表相交是地址一样,而不是值一样!!!相交是一个Y字形
思路:1.先求两个链表的长度
2.计算两个链表的差值(创建两个变量,使得一个永远指向长的,另一个是短的)
3.让最长的链表走差值步
4.两个链表一起走,相遇的点就是相交点或者是没有相交点为null

  1. 代码:
/**
 * 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) {
        // 1. 求两个链表的长度
        int lengthA = 0;
        int lengthB = 0;
        // 创建两个变量,使之一直指向最长和最短链表,然后原来头结点就会保持不变
        ListNode nodeL = headA; // long
        ListNode nodeS = headB; // short
        while(nodeL!=null) {
            lengthA++;
            nodeL=nodeL.next;
        }
        while (nodeS!=null) {
            lengthB++;
            nodeS = nodeS.next;
        }
        // 注意要执行后面的各条语句 一定要先把nodeL/S处于头结点的位置上!!
        //而此时并没有在头结点处 所以执行后面语句!!!
        nodeL = headA;
        nodeS = headB;
        // 2. 进行长度比较 + 最长最短的变换
        int len = lengthA-lengthB;
        if(len<0) {
            nodeL = headB;
            nodeS = headA;
            len = lengthB-lengthA;
        }
        
        // 3. 长的先走差值步
        while(len!=0) {
            nodeL = nodeL.next;
            len--;
        }

        // 4. 两个链表同时走
        // 这里写法有问题,就会缺少返回值
        /*while(nodeL!=null) {
            if(nodeL==nodeS) {
                return nodeL;
            }
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }*/
        // 注意这个还可以再进行修改:还可以更加简便!
        /*while(nodeL!=nodeS && nodeL!=null) { //如果等于就是相交
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }
        if (nodeS==null) {
            return null;
        }*/
        while(nodeL!=nodeS ) { //如果等于时要么是相交 要么是二者都已经走完为空
            nodeL = nodeL.next;
            nodeS = nodeS.next;
        }
        // 此时来到这里,要么找到交点 要么是遍历完为null,
        // 不管哪种情况都是二者相等的时候,所以随便输出那个都行,况且也不用进行判断
        return nodeL;
    }
    
}

9. 给定一个链表,判断链表中是否有环。

环形链表

  1. 思路:

环一定是最后一个又指向之前的某一个,所以有环链表中是不存在null的;不可能是中间的某一个又指向之前的某一个,因为一个结点只能存储一个地址
– 思路:使用快慢指针,快慢指针是有速度差的(那么重点来了:为什么slow走一步 fast走两步,而不是fast走其他步数呢? 这是一个追击问题,最坏的情况是快慢指针相差一圈,而一次两步即使是在最坏情况下也是最多走一圈就可以追上,不会存在套圈问题。(如果环是2个,走3步就会迎来擦肩而过的问题)
– 如果fast或者fast.next==null,就说明不存在环(考虑奇偶问题,所以null有两种情况)
– 每次走完都判断一下是否相等

  1. 代码:
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针
        ListNode fast = head;
        ListNode slow = head;
        // 循环判断是否有环 如果相遇就说明有环 如果其中一个(fast or fast.next)是null就说明不存在环
        // 环是没有null的
        /*while(fast.next!=null && fast!=null) { //注意这里的顺序是错误的!!!!!
            fast = fast.next.next;  //一次走两步
            slow = slow.next;
            // 放在后面而不是前面的原因,环至少都有两个结点!!
            if(fast == slow) {
                return true;
            }
        }
        // 出来就说明遇到了null
        return false;*/

        // 可以修改如下;
        while( fast!=null && fast.next!=null ) { // 注意顺序,只有fast补null才有next
            fast = fast.next.next;  //一次走两步
            slow = slow.next;
            if(fast == slow) {
                break;
            }
        }
        // 出来有两种情况:要么遇到null 要么相等 进行判断
        // 然后一定要注意这里的判断使用声明进行判断,用是否为null是因为要考虑一个结点的情况:
        // 一个结点时并不是环,环至少要两个结点,如果是用地址相等判断就会为真,
        // 但是实际上是假的,因为while循环的条件中满足其中一个为假直接出来应该为假
        /*if(fast == slow) {
            return true;
        }
        return false;*/
        // 所以进行修改如下:
        if(fast==null || fast.next==null) {  // 只要一个为真就是真 所以要注意逻辑
            return false;
        }
        return true;
    }
}

10. 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

环形链表II

  1. 思路:

原理:设起始点到入口点的距离为x,设相遇点到入口点的长度为y,环的长度为C;
在相同时间内,fast速度是slow的2倍,则fast路程也是slow的两倍:x=y时就是时就可以找到入口点
so:当找到相遇点后 将其中一个指针移到头结点位置,两种=者再以相同的速度走,当二者相等时就是入口点位置。
(注:即使是fast转了好多圈也是一样的情况,slow只走一圈;然后转圈越多说明环越小)

【JAVA】链表面试题_第1张图片

  1. 代码:
/**
 * 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) {
        // 先判断有无环:使用快慢指针 +是否相等or遇到null(循环条件)
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
            // 进行判断
            if(slow==fast) {
                break;
            }
        }
        // 出来就说明要么==null 要么相等
        if(fast==null || fast.next==null) {
            return null;
        }

        // 如果有环在进行前后同时走相遇的操作:走到这里就说明有环,且此时相遇
        slow = head;
        while(slow!=fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

11. 其他练习。

力扣链表 + 牛客网


THINK

注意习题的练习!思路以及逻辑!
还有快慢指针的使用!!!

你可能感兴趣的:(Note-数据结构,java,链表,leetcode,单链表,linkedList)