数据结构-链表的简单操作代码实现2【Java版】

目录

写在前:

此篇讲解关于单链表的一些面试题目,续上节。

11.反转一个单链表

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

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

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

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

16.链表的回文结构

17.输入两个链表,找出他们的第一个公共结点

18.链表中是否有环

19.给定一个链表,返回链表开始入环的第一个结点,如果链表没有环,则返回null


11.反转一个单链表

给定一个链表:

进行反转后的链表:

这里我们采用的是“头插法”来解决问题。

首先需要进行判断

(1)当头结点不存在的时候返回null;【说明链表中没有元素】

(2)当头结点的next域为空时,返回头结点;【说明链表中只有一个结点】

当上面的两种情况都不是的时候

(1)定义一个cur结点指向head.next;

(2)将head.next的值置为null;

定义结点curNext指向cur.next;

进行“头插操作”:

第一步:

第二步:

第三步:

最后返回head。

    public ListNode reverseList(){
        if(head == null){
            return null;
        }
        //走到这里就说明链表里只有一个节点
        if(head.next == null){
            return head;
        }
        ListNode cur = head.next;
        head.next = null;
        while (cur != null){
            ListNode curNext = cur.next;
            //头插法插入cur(任何的插入都是先绑后面)
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
    }

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

将问题具象化:

如图,当我们有奇数个节点的时候,返回中间结点。

当我们有偶数个结点的时候返回第二个中间结点:

本题求解采用“快慢指针”

具体思路:

定义两个指针fast和slow,同时指向头结点的位置;每次让fast指针向前走两步,slow指针向前走一步。

当fast指针指向最后的位置的时候,此时fast.next域为空。这个时候slow指针指向的位置就是中间位置。

当链表中是偶数个结点的情况下同理:

以四个结点为例,同样的fast走两步,slow走一步;

当fast走到最后的时候此时fast为空。此时,slow所指向的位置就是中间结点中的第二个位置。

结合以上的分析,可以进行代码的编写:

定义两个结点指向头结点的位置。进入循环的条件为当fast指针域和fast.next域均不为空的时候,执行fast向后走两步,slow向后走一步的。

最后返回slow即为所求。

    public ListNode middleNode(){
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

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

本题仍然使用“快慢指针”的方式来进行编写代码。

这里需要明确的是:在一个链表中倒数第k个结点需要走k-1步到链表的最后一个结点。

定义两个指针fast和slow;先让fast走k-1步,slow和fast再同时向前走;当fast.next为空的时候,此时slow所指的位置就是要求的倒数第k个结点。

编写代码:

首先进行合法性的判断当求得位置小于0或者当头结点为null的时候返回null;

定义两个指针fast和slow;

先让快指针向前走k-1步:这里需要判断的是当fast为null的时候就证明k的值大了,找不到对应的结点,此时也是返回null;

fast指针和slow指针同时向前走,当fast.next为空的时候,证明此时fast指向的链表的末尾,此时slow指向的位置就是需要找到的倒数第k个结点。

    public ListNode findKthToTail(int k){
        //当k不合法的时候
        if(k <= 0 || head == null){
            return null;
        }

        //当k合法时
        ListNode fast = head;
        ListNode slow = head;
        //1.fast走k-1步
        while (k - 1 != 0){
            fast = fast.next;
            if(fast == null){
                return null;
            }
            k--;
        }
        while (fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

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

    public static MySingleList.ListNode mergeTwoLists
            (MySingleList.ListNode head1,MySingleList.ListNode head2){
        MySingleList.ListNode newHead = new MySingleList.ListNode(0);
       MySingleList.ListNode tmp = newHead;
        while (head1 != null && head2 != null){
            if(head1.val < head2.val){
                tmp.next = head1;
                head1 = head1.next;
                tmp = tmp.next;
            }else {
                tmp.next = head2;
                head2 = head2.next;
                tmp = tmp.next;
            }

        }
        if(head1 != null){
            tmp.next = head1;
        }
        if(head2 != null){
            tmp.next = head2;
        }
        return newHead.next;
    }

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

现在我们有一个下图所示的链表:

根据所给题目的要求,我们最终实现的链表应该是如下的样子:

以上是一种情况:最后的末尾本身就是比28大的数据,所以这里不需要进行置空操作。

当末尾的元素需要变换的时候如图:(这里需要手动将最后一个元素的next域设置为null)

需要注意的点:

(1)是否需要进行对最后一个点进行处理

(2)怎么保证顺序不改变

定义一个cur,遍历链表。分成两段--如果小于28,就将数据放在第一段;如果大于28,就将数据放到第二段。

定义4个指针bs、be、as、ae.

定义cur遍历,当cur为空的时候表示链表遍历完成。

编写代码时需要注意:

当遍历完成后,需要考虑到:

有可能所给出的数据都大于给定的x值,或者都小于给定的x值。

如果第一段为空(bs == null)返回第二段as【这样同时解决了两段均为空的情况】

第一段不为空:be.next指向as;

最后将末尾置空【这里需要判空】

    public ListNode partition(int x){
        ListNode as = null;
        ListNode ae = null;
        ListNode bs = null;
        ListNode be = null;

        ListNode cur = head;
        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;
        }
        //有可能不会同时存在小于x 和大于等于x的数据
        if(bs == null){
            return as;
        }
        //第一段不为空
        be.next = as;
        //第2段不为空的问题
        if(as != null){
            ae.next = null;
        }
        return bs;
    }

16.链表的回文结构

链表的回文结构:

此题的解题思路是:首先找到链表的中间结点。

将后半部的链表进行反转。

定义两个指针,一个从头开走,一个从尾走。

走完后head在头部,slow走到了链表末尾。一个向前,一个向后

最后当两个指针如果相遇,则链表是回文结构的。

需要注意的是以上情况是链表中有奇数个结点的时候,

当存在偶数个结点的时候,需要在判断两个指针是否相等的时候进行进一步的进行判断。

    public boolean chkPalindrome(){
        if(head == null){
            return  false;
        }
        if(head.next == null){
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        //1.找中间节点
        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //2.翻转
        ListNode cur = slow.next;//cur代表当前需要翻转的节点
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //3.一个从头往后,一个从后往前
        while (slow != head){
            if(head.val != slow.val){
                return false;
            }
            //偶数情况的判断
            if(head.next == slow){
                return true;
            }
            slow = slow.next;
            head = head.next;
        }
        return true;
    }

17.输入两个链表,找出他们的第一个公共结点

求两个链表的公共结点。

首先分别求出两个链表的长度。定义两个指针pl、ps分别指向两个链表的头结点headA和headB;

遍历完后将pl和ps指回两个链表的头部。

根据求得的len值,进行修改指针的指向:pl指向长的链表头结点;ps指向短的链表头结点;

让pl先走len步,然后二者同时向后走。

当相等的时候进行判空,为空返回null,不为空返回对应结点pl;

    public static MySingleList.ListNode getIntersectionNode
            (MySingleList.ListNode headA,MySingleList.ListNode headB){
        //1.分别求两个链表的长度
        int lenA = 0;
        int lenB = 0;
        MySingleList.ListNode pl = headA;
        MySingleList.ListNode ps = headB;

        while (pl != null){
            lenA++;
            pl = pl.next;
        }
        while(ps != null){
            lenB++;
            ps = ps.next;
        }
        //求完长度之后将pl和ps指回来
        pl = headA;
        ps = headB;
        int len = lenA - lenB;
        //根据len的值,修改指向
        if(len < 0){
            pl = headB;
            ps = headA;
            len = lenB - lenA;
        }
        //1.可以保证len一定是一个正数
        //2.pl指向的链表一定是最长的,ps指向的链表一定是最短的
        while (len != 0){
            pl = pl.next;
            len -- ;
        }
        while (pl != ps){
            pl = pl.next;
            ps = ps.next;
        }
        //pl == ps
        if(pl == ps && pl == null){
            return null;
        }
        return pl;
    }

18.链表中是否有环

判断环的问题其实本质上就是数学上的追击问题。

思路:快慢指针,慢指针一次走一步,快指针一次走两步。两个指针从链表起始位置开始走。如果链表有环则一定会在环中相遇,否则快指针率先到达链表的末尾。

拓展1:

为什么快指针每次走两步,慢指针每次走一步是可行的?

假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了。最差情况下两个指针之间的距离刚好是环的长度,此时两个指针每移动一次,之间的距离就缩小了一步,不会出现每次刚好套圈的情况。因此,在慢指针走到一圈之前,快指针一定是可以追的上慢指针的。

拓展2:

快指针一次走3步,走4步,...n步行么?

假设:快指针每次走三步,慢指针每次走一步,此时快指针一定是先进环的,慢指针后来才进环,假设慢指针进环的时候,快指针的位置:

此时按照上述方法来环绕移动,每次快指针走3步,慢指针走1步,是永远都不会相遇的,快指针刚好将慢指针套圈了,因此是不行的。

只有快指针走2步,慢指针走一步才可以,因为换的最小长度是1,即使套圈了两个也在相同的位置。

编写代码:

定义两个结点:fast和slow,均指向头结点。

当fast不等于null并且fast.next不等于null时进入循环,fast指针向后走两步,slow指针向后走一步,当fast和slow的指针指向同一位置的时候就说明链表存在环,返回true;否则返回11false。

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

另外一种写法是在循环中判断fast和slow的指向的时候,break跳出循环。

这时在循环外面判断:如果是因为while循环跳出来的,就返回false;

如果是因为if条件跳出来的,返回true。

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

19.给定一个链表,返回链表开始入环的第一个结点,如果链表没有环,则返回null

本题的思路:

涉及到一些数学的内容,下面进行分析

   public ListNode detectCycle(){
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }
        if(fast == null || fast.next == null){
            return null;
        }
        //有环,此时fast和slow就相遇了
        slow = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西西¥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值