数据结构(Java):力扣单链表面试OJ题

目录

1、题一:获取链表倒数第k个节点

1.1 思路解析

1.2 代码

2、题二:逆置单链表

2.1 思路解析

2.2 代码

3、题三:移除链表元素(删除所有某一数值的节点,且一次循环)

3.1 思路解析

3.2 代码

4、题四:获取链表的中间节点

 4.1 思路解析

4.2 代码

5、题五:分割链表

5.1 思路解析

5.2 代码

 6、题六:判断链表是否回文

6.1 思路解析

6.2 代码

7、题七:相交链表(找出相交节点)

7.1 思路解析 

7.2 代码

8、题八:判断链表是否带环

8.1 思路解析 

8.2 代码

9、题九:求环的入口点

9.1 思路解析

9.2 代码

10、题十:合并两个有序链表

10.1 思路解析

10.2 代码


1、题一:获取链表倒数第k个节点

. - 力扣(LeetCode)

1.1 思路解析

此题我们使用双指针法求解。

首先,我们要知道,倒数的第k个节点,距离倒数第一个节点还需要移动k-1次

1.那么我们可以定义出两个指针,分别为fast和slow,他们初始均值均为头结点head

2.先让fast指针向后移动k-1次,slow指针保持不动。

3.接着,fast指针和slow指针同步后移直至fast指针指向最后一个节点。

4.此时,slow指针所指向的位置就是倒数第k个节点。

原理:这个解题思路的原理就是,fast指针和slow指针始终保持着k-1个移动次数,而当fast指针指向最后一个节点(即倒数第一个节点)时,那么slow指针指向的就是倒数第k个节点。

注意事项:

1.k的值是否合法 2.头指针head是否为null

1.2 代码

public int kthToLast(ListNode head, int k) {
        //当头指针为空时
        if (head == null) {
            return Integer.MAX_VALUE;
        }

        //当k的值<=0时
        ListNode cur = head;
        int size = 0;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        if (k <= 0) {
            return Integer.MAX_VALUE;
        }

        //双指针法求解
        ListNode fast = head;
        ListNode slow = head;

        //先让fast指针移动k-1次
        int count = 0;
        while (count != k - 1) {
            fast = fast.next;
            //当k值>size时(不合法),fast指针移动的k-1次中,必然会指向null
            if (fast == null) {
                return Integer.MAX_VALUE;
            }
            count++;
        }

        //slow和fast同步后移,直至fast指向倒数第一个元素(slow和fast始终保持k-1个移动次数)
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //返回val
        return slow.val;
    }

2、题二:逆置单链表

. - 力扣(LeetCode)

2.1 思路解析

此题我们使用头插法求解。

这道题的思路很简单:从第二个节点开始,每得到一个节点,将此节点进行头插操作。

注意:要将最开始的头结点的next置为null(因为链表的头节点在逆置后就变成了尾结点)

2.2 代码

public ListNode reverseList(ListNode head) {
        //当头结点head为空时
        if (head == null) {
            return head;
        }
        //获取第二个节点,将第一个节点的next置为null
        ListNode cur = head.next;
        head.next = null;
        
        //头插
        while (cur != null) {
            //先获取当前节点的下一个节点
            ListNode curN = cur.next;
            //将当前节点的next指向头结点(头插)
            cur.next = head;
            //将head更新为新头插的节点
            head = cur;
            //更新cur,继续头插
            cur = curN;
        }

        //返回逆置后的头结点
        return head;
    }

3、题三:移除链表元素(删除所有某一数值的节点,且一次循环)

. - 力扣(LeetCode)

3.1 思路解析

此题我们使用双指针法求解。

1.我们定义两个指针分别为prev(初始指向head头结点 即第一个节点)和cur(初始指向head的next节点 即第二个节点)。

2.遍历链表。

3.当cur所指节点的值为所要删除的val值时,cur向后移动,prev不动,且将prev的next指针指向移动后的cur,完成节点的删除。

4.若cur所指节点值不是所要删除的val值时,cur和prev同步后移一位。

5.当cur指向null时,遍历完成。

6.经过上述操作,除第一个节点外,其余的节点均已删除完成,我们只需额外判断第一个节点的值是否为要删除的val值即可。

3.2 代码

public ListNode removeElements(ListNode head, int val) {
        //当head为空时
        if(head == null) {
            return head;
        }

        //双指针法求解
        ListNode prev = head;
        ListNode cur = head.next;


        while (cur != null) {
            //判断当前节点的值是否为要删除的val值
            if (cur.val == val) {//若是,删除节点
                cur = cur.next;
                prev.next = cur;
            }else {//若不是,均向后移动
                cur = cur.next;
                prev = prev.next;
            }
        }
        //判断第一个节点的值是否我要删除的val值。
        if (head.val == val) {
            head = head.next;
        }
        //返回删除后的头结点
        return head;
    }

4、题四:获取链表的中间节点

. - 力扣(LeetCode)

 4.1 思路解析

此题我们使用快慢指针法求解。

1.首先,定义两个指针分别为fast和slow。

2.fast指针,每次向后移两位;slow指针,每次向后移一位。

3.链表元素个数为奇数时,当fast.next == null时,slow指向的节点就是中间节点;链表元素个数为偶数时,当fast == null时,slow指向的节点就是中间节点。

注意:

循环结束条件一定要写为:

while(fast != null && fast.next != null)

原因:

1.两者只要有一个不满足就要结束循环,说明已经找到了中间节点

2.一定要fast != null在前,避免fast.next时出现空指针异常

4.2 代码

public ListNode middleNode(ListNode head) {
        //快慢指针法求解
        ListNode slow = head;
        ListNode fast = head;
        
        //找中间节点
        //一定要fast != null在前,避免出现空指针异常
        //两个条件只要有一个不满足就结束循环,说明到了中间节点的位置
        while (fast != null && fast.next != null) {
            ListNode fastN = fast.next;
            fast = fastN.next;
            slow = slow.next;
        }

        //返回中间节点
        return slow;
    }

5、题五:分割链表

. - 力扣(LeetCode)

5.1 思路解析

1.将链表进行遍历

2.将val < x的节点添加到新链表head1中

3.将val >= x的节点添加到新链表head2中

4.将head1和head2进行拼接

5.返回拼接后新表的头结点head1

注意:

1.无论如何,一定要将head2的最后一个节点next域置为null

2.当表中所以元素的val值都大于等于x时,那么head1将为空表,直接返回head2即可

5.2 代码

public ListNode partition(ListNode pHead, int x) {
        //当pHead为空时
        if (pHead == null) {
            return null;
        }

        ListNode cur = pHead;
        ListNode prev = null;
        ListNode head1 = null;
        ListNode head2 = null;
        ListNode cur1 = null;
        ListNode cur2 = null;

        //遍历链表 将val < x的节点添加到新表head1中
        //将val >= x的节点添加到新表head2中
        while (cur != null) {
            if (cur.val < x) {
                if (head1 == null) {
                    head1 = cur1 = cur;
                }else {
                    cur1.next = cur;
                    cur1 = cur1.next;
                }
            }else {
                if (head2 == null) {
                    head2 = cur2 = cur;
                }else {
                    cur2.next = cur;
                    cur2 = cur2.next;
                }
            }
            cur = cur.next;
        }

        //注意:需要将head2的最后一个节点的next置为null
        if(head2 != null) {
            cur2.next = null;
        }
        //如果旧表中所有元素的val值都大于x,那么head1将会是空表,直接返回head2即可
        if (head1 == null) {
            return head2;
        }
        //将head1和head2进行拼接
        cur1.next = head2;
        return head1;
    }

 6、题六:判断链表是否回文

链表的回文结构_牛客题霸_牛客网

6.1 思路解析

回文结构,其实就是对称结构:

 

当元素个数为奇数时:

1.找到中间节点(使用上面的例题已经讲到)

2.以中间节点为头,逆置其后链表

3.逆置后,链表存在下图结构,指针从两端开始比较val值是否相等,直到相遇时结束,若均相等则为回文结构。

当元素个数为偶数时:

1.偶数时,newH和slow无法相遇

2.我们只需额外判断 当 newH.next == slow 时,说明为回文结构。

6.2 代码

public boolean chkPalindrome(ListNode A) {
        // write code here
        if (A == null) {
            return true;
        }
        ListNode fast = A;
        ListNode slow = A;

        //找中间节点
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        //逆置中间节点后的链表
        ListNode cur = slow.next;
        ListNode curN = null;
        while (cur != null) {
            curN = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curN;
        }

        //从两端开始遍历,比较值是否相等
        ListNode newCur = A;
        while (slow != newCur) {
            if (slow.val != newCur.val) {
                return false;
            }
            if (newCur.next == slow) {
                return true;
            }
            slow = slow.next;
            newCur = newCur.next;
        }
        return true;
    }

7、题七:相交链表(找出相交节点)

. - 力扣(LeetCode)

7.1 思路解析 

首先,我们要知道,相交链表的形状为Y形,而非X形:

1.遍历求出各链表的长度,并求得长度的差值len。

2.将较长链表的头指针向后移动len个位置。

3.两个链表的头指针同时后移,会在交点处相遇。

7.2 代码

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0;
        int lenB = 0;
        //求链表1的长度
        while (curA != null) {
            lenA++;
            curA = curA.next;//curA发生改变
        }
        //求链表2的长度
        while (curB != null) {
            lenB++;
            curB = curB.next;//curB发生改变
        }
        
        //将curA和curB重新指向链表的起始位置
        curA = headA;
        curB = headB;
        
        //差值
        int len = lenA - lenB;
        
        //将较长的链表头指针后移差值个长度
        if (len < 0) {
            len = -len;
            while (len != 0) {
                curB = curB.next;
                len--;
            }
        }else {
            while (len != 0) {
                curA = curA.next;
                len--;
            }
        }
        
        //相遇处即为交点
        while (curA != curB) {
            curA = curA.next;
            curB = curB.next;
        }
        return curA;
    }

8、题八:判断链表是否带环

. - 力扣(LeetCode)

8.1 思路解析 

此题我们使用快慢指针法求解。

1.定义fast为快指针,一次移动两个位置;slow为慢指针,一次移动一个位置。

2.若链表带环,那么这两个指针必定相遇(追击问题)。

注意:

因为fast一次走两步,slow一次走一步,那进入环中后,每次移动,fast和slow之间的距离必定缩短一个节点,那么必定相遇。

(若每次移动两者间缩短的距离不为1时,那么即使有环,也可能不会相遇)

8.2 代码

public boolean hasCycle(ListNode head) {
        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;
    }

9、题九:求环的入口点

. - 力扣(LeetCode)

9.1 思路解析

设:
起点到入口点的长度为X
相遇节点到入口点长度为Y
环的长度为C

因为fast所走路程为slow所走路程的两倍
故:
X + n*C + (C - Y) = 2*(X + C - Y)
则:
X + (2-n)*C = Y
说明:相遇节点到入口点的距离和起点到入口点的距离相等
则,从相遇节点和起点开始相向而行,相遇时的节点就是入口点

注意:我们还需要处理不是环的情况!!!

9.2 代码

public ListNode detectCycle(ListNode head) {
         //head为空时
         if (head == null) {
             return head;
         }

         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;
         }
         //从起点和相遇节点开始相向而行,相遇的节点就是入口点
         ListNode curH = head;
         while (curH != slow) {
             curH = curH.next;
             slow = slow.next;
         }
         return slow;
     }

10、题十:合并两个有序链表

. - 力扣(LeetCode)

10.1 思路解析

思路很简单,就是从各链表起点开始遍历,比较val值,谁的值小,就插入到新链表当中。

若某一链表的元素全部插入新链表当中后,将另一链表剩余元素直接拼接即可

10.2 代码

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //链表为空时
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }

        ListNode cur1 = list1;
        ListNode cur2 = list2;
        ListNode newH = null;
        ListNode newCur = null;

        //从头遍历各链表 谁值小 谁就尾插到新链表中
        while (cur1 != null && cur2 != null) {
            if (cur1.val < cur2.val) {
                if (newH == null) {
                    newH = newCur = cur1;
                }else {
                    newCur.next = cur1;
                    newCur = newCur.next;
                }
                cur1 = cur1.next;
            }else {
                if (newH == null) {
                    newH = newCur = cur2;
                }else {
                    newCur.next = cur2;
                    newCur = newCur.next;
                }
                cur2 = cur2.next;
            }
        }

        //某一链表的元素全部插入后,将另一链表剩余元素直接拼接即可
        if (cur1 == null) {
            newCur.next = cur2;
        }
        if (cur2 == null) {
            newCur.next = cur1;
        }
        return newH;
    }

  • 33
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目描述: 给你两个版本号 version1 和 version2 ,请你比较它们。 版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由多位数字组成,可能包含前导零。每个版本号至少包含一个字符。修订号从左到右编号,下标从0开始,最左边的修订号下标为0 ,下一个修订号下标为1,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。也就是说,修订号1和修订号001相等。如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,版本1.0 小于版本1.1,因为它们下标为0的修订号相同,而下标为1的修订号分别为0和1,0 < 1。 返回规则如下: 如果 version1 > version2 返回 1, 如果 version1 < version2 返回 -1, 否则返回 0。 示例 1: 输入:version1 = "1.01", version2 = "1.001" 输出:0 解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1" 示例 2: 输入:version1 = "1.0", version2 = "1.0.0" 输出:0 解释:version1 没有指定下标为 2 的修订号,即视为 "0" 示例 3: 输入:version1 = "0.1", version2 = "1.1" 输出:-1 解释:version1 中下标为 0 的修订号是 0,version2 中下标为 0 的修订号是 1 。0 < 1,所以 version1 < version2 示例 4: 输入:version1 = "1.0.1", version2 = "1" 输出:1 示例 5: 输入:version1 = "7.5.2.4", version2 = "7.5.3" 输出:-1 提示: 1 <= version1.length, version2.length <= 500 version1 和 version2 仅包含数字和 '.' version1 和 version2 都是 有效版本号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值