• 【数据结构】——链表面试题详解


    目录

    一、  203. 移除链表元素 - 力扣(LeetCode)

    二、206. 反转链表 - 力扣(LeetCode)

     三、876. 链表的中间结点 - 力扣(LeetCode)

    ​​​​​四、链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

    五、21. 合并两个有序链表 - 力扣(LeetCode)

    六、链表分割_牛客题霸_牛客网 (nowcoder.com) 

    七、链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

    八、160. 相交链表 - 力扣(LeetCode) 

    九、141. 环形链表 - 力扣(LeetCode) 

    十、 142. 环形链表 II - 力扣(LeetCode)


    一、  203. 移除链表元素 - 力扣(LeetCode)

     我们遍历一次链表来解决这个问题。首先我们定义两个节点,ListNode cur = head.next;

    ListNode prve = head;在while(cur != null){ } 循环里判断是否等于要删除的元素,当等于要删除的元素时:

      

    当不等于要删除的元素时:

       

    最后在单独判断一下头结点:

    1. if(head.val == key){
    2. head = head.next;
    3. }

    完整代码展示:

    1. public ListNode removeElements(ListNode head, int key) {
    2. if (head == null){
    3. return null;
    4. }
    5. ListNode cur = head.next;
    6. ListNode prve = head;
    7. while (cur != null){
    8. if(cur.val == key){
    9. prve.next = cur.next;
    10. cur = cur.next;
    11. }else{
    12. prve = cur;
    13. cur = cur.next;
    14. }
    15. }
    16. if(head.val == key){
    17. head = head.next;
    18. }
    19. return head;
    20. }

    二、206. 反转链表 - 力扣(LeetCode)

      

    我们可以先定义一个head.next节点,然后再将头结点置为null:

    1. ListNode cur = head.next;
    2. head.next = null;

    然后我们在循环里面需要记住cur.next,所以定义一个curNext = cur.next: 

      

    然后我们运用头插法,将cur插入到head前面,再移动head和cur和curNext:
       

    当然在一开始需要判断head和head.next是否为空,完整代码如下:

    1. public ListNode reverseList(ListNode head) {
    2. if(head == null){
    3. return null;
    4. }
    5. if(head.next == null){
    6. return head;
    7. }
    8. ListNode cur = head.next;
    9. head.next = null;
    10. while (cur != null){
    11. ListNode curNext = cur.next;
    12. cur.next = head;
    13. head = cur;
    14. cur = curNext;
    15. }
    16. return head;
    17. }

     三、876. 链表的中间结点 - 力扣(LeetCode)

     

     这个题在遍历一次链表的情况下,我们可以使用快慢指针:

    即定义一个fast指针一次走两步,slow指针一次走一步,再遍历整个链表,当fast指针指向空时,slow指针便指向的是链表的中间位置。也会有两种情况,链表的长度为奇数和偶数时。

    奇数时:

      

    偶数时:

      

     完整代码:

    1. public ListNode middleNode(ListNode head) {
    2. if (head == null){
    3. return null;
    4. }
    5. ListNode slow = head;
    6. ListNode fast = head;
    7. while (fast != null && fast.next != null){
    8. fast = fast.next.next;
    9. slow = slow.next;
    10. }
    11. return slow;
    12. }

    注:fast != null  && fast.next  != null 这两个的顺序不能调换,否则会空指针异常(先判断fast.next 是否为空时,fast已经是空了).

    ​​​​​四、链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

      

    这道题我们还是运用了快慢指针的思想:

    定义fast和slow指针,先让fast指针走k-1步,再两个指针一起走,直到fast.next为空。当然首先要判断k值是否合理,可以在fast指针走k-1步时实现;

    代码如下:

    1. public ListNode FindKthToTail(ListNode head, int k){
    2. if (head == null || k <= 0){
    3. return null;
    4. }
    5. ListNode slow = head;
    6. ListNode fast = head;
    7. while (k-1 != 0){
    8. fast = fast.next;
    9. if (fast == null){ //判断k是否合理
    10. return null;
    11. }
    12. k--;
    13. }
    14. while (fast.next != null){
    15. fast = fast.next;
    16. slow = slow.next;
    17. }
    18. return slow;
    19. }

    五、21. 合并两个有序链表 - 力扣(LeetCode)

       

     我们可以先实例化一个虚拟节点,然后在while(head1 != null && head2 != null){} 的时候,比较一下head1.val和head2.val的大小,将小的节点放到虚拟节点后面,,最后遍历完一个链表时,将剩下的链表整合到一起.  

    完整代码:

    1. public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
    2. ListNode newHead = new ListNode(-1);//虚拟节点
    3. ListNode tmp = newHead;
    4. while (head1 != null && head2 != null) {
    5. if (head1.val < head2.val) {
    6. tmp.next = head1;
    7. head1 = head1.next;
    8. tmp = tmp.next;
    9. } else {
    10. tmp.next = head2;
    11. head2 = head2.next;
    12. tmp = tmp.next;
    13. }
    14. }
    15. if (head1 != null) {
    16. tmp.next = head1;
    17. }
    18. if (head2 != null) {
    19. tmp.next = head2;
    20. }
    21. return newHead.next;
    22. }

    六、链表分割_牛客题霸_牛客网 (nowcoder.com) 

    现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针:

    1.  这道题我们可以先分割再将它们串起来,先实例化两个结点 bs 和 as(before start,after start)
    2. 遍历链表,将小于 x 的结点用尾插法插入到 bs 后,将大于 x 的结点用尾插法插入到 as后
    3. 再实例化两个结点 be 和 ae(before end ,after end)作为两个结点的终点,再把 be 和 ae 串起来,返回 bs ,就得到了分割后的链表了

     

     下面我们来具体的实现代码:

    1. public ListNode partition(ListNode head, int x) {
    2. ListNode bs = null;
    3. ListNode be = null;
    4. ListNode as = null;
    5. ListNode ae = null;
    6. ListNode cur = head;
    7. while (cur != null){
    8. if(cur.val < x){
    9. //插入到第一个段【尾插法】
    10. if(bs == null){
    11. //这是第一次插入元素
    12. bs = cur;
    13. be = cur;
    14. }else{
    15. be.next = cur;
    16. be = be.next;
    17. }
    18. }else {
    19. //插入到第二个段【尾插法】
    20. if (as == null) {
    21. //这是第一次插入元素
    22. as = cur;
    23. ae = cur;
    24. } else {
    25. ae.next = cur;
    26. ae = ae.next;
    27. }
    28. }
    29. cur = cur.next;
    30. }
    31. //当没有小于x的结点时,bs就是null
    32. if(bs == null){
    33. return as;
    34. }
    35. //将be和as串起来
    36. be.next = as;
    37. //ae.next不一定null,需要手动将其置空
    38. if(as != null){
    39. ae.next = null;
    40. }
    41. return bs;
    42. }

    七、链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

     

    这道题的思路如下: 

    1. 首先通过快慢指针找到链表的中间位置
    2. 然后从中间位置到末尾进行翻转链表(翻转单链表)
    3. 最后一边从头一边从末尾开始遍历链表并且比较val值是否相等,知道相遇结束

     

    1. public boolean chkPalindrome(ListNode head) {
    2. if(head == null){
    3. return true;
    4. }
    5. ListNode fast = head;
    6. ListNode slow = head;
    7. //通过快慢指针找到中间位置,
    8. while (fast != null && fast.next != null){
    9. fast = fast.next.next;
    10. slow = slow.next;
    11. }
    12. //逆置后半段的链表
    13. ListNode cur = slow.next;
    14. while (cur != null){
    15. ListNode curNext = cur.next;
    16. cur.next = slow;
    17. slow = cur;
    18. cur = curNext;
    19. }
    20. //两头开始遍历比较val值是否相同
    21. while (head != slow){
    22. if (head.val != slow.val){
    23. return false;
    24. }
    25. if (head.next == slow){//两边相遇了
    26. return true;
    27. }
    28. head = head.next;
    29. slow = slow.next;
    30. }
    31. return true;
    32. }

    八、160. 相交链表 - 力扣(LeetCode) 

     思路:

    1. 本题中两个链表相交是 Y 字形的,且相交的点的地址是相同。
    2. 需要先求出两个链表的差值,因为两个链表的长度不一定相同,其相交之前的部分不一定相同,但是其相交的话相交之后的部分长度一定相同:
    3. 让最长的链表走差值步
    4. 然后再让两个链表一起走,相遇点就是相交的节点
    1. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    2. //分别求出两个链表的长度
    3. int lenA = 0;
    4. int lenB = 0;
    5. ListNode p1 = headA;//代表永远指向长的链表
    6. ListNode p2 = headB;//代表永远指向短的链表
    7. while(p1 != null){
    8. lenA++;
    9. p1 = p1.next;
    10. }
    11. while (p2 != null){
    12. lenB++;
    13. p2 = p2.next;
    14. }
    15. p1 = headA;
    16. p2 = headB;
    17. //求出差值
    18. int len = lenA - lenB;
    19. if(len < 0){
    20. p1 = headB;//代表永远指向长的链表
    21. p2 = headA;//代表永远指向短的链表
    22. len = lenB - lenA;//len一定是一个正数
    23. }
    24. //让长的链表走了差值步
    25. while (len != 0){
    26. p1 = p1.next;
    27. len--;
    28. }
    29. //找到相同的点,直到相遇
    30. while(p1 != p2){
    31. p1 = p1.next;
    32. p2 = p2.next;
    33. }
    34. return p1;
    35. }

    九、141. 环形链表 - 力扣(LeetCode) 

    思路:

    1.  还是运用了快慢指针的思想,定义一个fast结点和一个slow结点。
    2. slow走一步,fast走两步:因为如果链表带环,按照这样走的话,fast和slow最差的情况就是相差一圈,然后就会慢慢逼近。
    3. 如果是slow走一步,fast走三步四步的情况下,slow和fast就会相遇的很慢,或者就会永远的错过。
    1. public boolean hasCycle(ListNode head) {
    2. ListNode slow = head;
    3. ListNode fast = head;
    4. while (fast != null && fast.next != null){
    5. fast = fast.next.next;
    6. slow = slow.next;
    7. if(fast == slow){
    8. return true;
    9. }
    10. }
    11. return false;
    12. }

    十、 142. 环形链表 II - 力扣(LeetCode)

     

    思路:

    1.  这道题运用到了上一题的的找环的方法,然后还要找到环的入口。
    2. 我们可以设起始点到入口点的距离为X,入口点到相遇点的距离为Y,环的长度为C,因为fast的速度是slow的两倍,所以距离也是两倍,fast的路程为:X + C + (C - Y),slow的路程为:X + C - Y,便可以联立等式:2(X + C - Y)= X + C + (C - Y),化简可以得到 X = Y,所以当slow和fast相遇之后,我们可以让slow = headslow走X这段距离,fast刚好是走Y这段距离,因为距离相同,所以他们相遇的地方就是入口点:
    1. public ListNode detectCycle(ListNode head) {
    2. ListNode slow = head;
    3. ListNode fast = head;
    4. while (fast != null && fast.next != null){
    5. fast = fast.next.next;
    6. slow = slow.next;
    7. if(fast == slow){
    8. break;
    9. }
    10. }
    11. if (fast == null || fast.next == null){
    12. return null;
    13. }
    14. slow = head;
    15. while (slow != fast){
    16. slow = slow.next;
    17. fast = fast.next;
    18. }
    19. return fast;
    20. }
  • 相关阅读:
    node将对象按照ASCII码进行升序排列生成签名字符串,然后加载 pfx证书进行SHA256withRSA加密
    华硕ROG吹雪和微星刀锋钛两者如何选择
    java毕业设计体育场馆预定网站演示录像源码+lw文档+mybatis+系统+mysql数据库+调试
    Java8 Optional使用
    Toaster - Android 吐司框架,专治 Toast 各种疑难杂症
    正则表达式
    Leetcode(665)——非递减数列
    python实现串口通信
    如何选择适合自己练手的 Java 源码项目?
    QT之QChart的简介
  • 原文地址:https://blog.csdn.net/weixin_62678196/article/details/126436926