2023-04-15 算法面试中常见的链表问题

news2025/4/7 5:59:03

2023-04-15 算法面试中常见的链表问题

本章的两个基础类如下

链表的节点类。toString()在debug时实时查看链表很有用

/***********************************************************
 * @Description : 链表的节点
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/17 22:13
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter05LinkedList;

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

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

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        ListNode cur = this;
        while (cur != null) {
            sb.append(cur.val).append(" -> ");
            cur = cur.next;
        }
        sb.append("NULL");
        return sb.toString();
    }
}

链表的显示工具类。create用于从数据创建链表,show用于把head作为头结点的链表表示成一个字符串的形式

/***********************************************************
 * @Description : 链表的显示工具类
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/17 22:14
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter05LinkedList;

class LinkedListTool {
    /**
     * 根据数组创建链表
     *
     * @param nums 数组
     * @return 创建的链表的头节点
     */
    public static ListNode create(int[] nums) {
        if (nums.length == 0) {
            return null;
        }
        ListNode head = new ListNode(nums[0]);
        ListNode curr = head;
        for (int i = 1; i < nums.length; i++) {
            curr.next = new ListNode(nums[i]);
            curr = curr.next;
        }
        return head;
    }

    public static void show(ListNode head) {
        ListNode curr = head;
        while (curr != null) {
            System.out.print(curr.val + " -> ");
            curr = curr.next;
        }
        System.out.println("NULL");
    }
}

1~2 题目206 反转列表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

代码实现分两步:

  • 1.初始化

    反转链表之初始化

  • 2.指针移动

    反转链表之移动

public class Solution {
    public ListNode reverseList(ListNode head) {
        // 当前节点的上一个节点,初始化为null,是因为所有所有的链表最后一个元素都可以看为null
        ListNode prev = null;
        // 当前节点
        ListNode curr = head;
        // 当前节点的下一个节点
        while (curr != null) {
            // 当当前节点的下一个节点不为空时
            ListNode next = curr.next;
            // 翻转当前节点,指向上一个节点
            curr.next = prev;
            // 上一个节点后移动一位
            prev = curr;
            // 当前节点后移动一位
            curr = next;
        }
        // 遍历到最后,pre指向了最后一个节点
        return prev;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5};
        ListNode head = LinkedListTool.create(nums);
        LinkedListTool.show(head);
        ListNode headReverse = new Solution().reverseList(head);
        LinkedListTool.show(headReverse);
    }
}

/**
 * 1 -> 2 -> 3 -> 4 -> 5 -> NULL
 * 5 -> 4 -> 3 -> 2 -> 1 -> NULL
 */

类似的题目还有92. 反转部分链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
  • 第一步:找到待反转节点的前一个节点。
  • 第二步:反转m到n这部分。
  • 第三步:将反转的起点的next指向反转的后面一部分。
  • 第四步:将第一步找到的节点指向反转以后的头节点。
    部分反转链表
/***********************************************************
 * @Description : 反转链表的m~n的链表
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/17 23:20
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter05LinkedList.LeetCode92ReverseLinkedListPart;

import Chapter05LinkedList.LinkedListTool;
import Chapter05LinkedList.ListNode;

class Solution {

    public ListNode reverseBetween(ListNode head, int m, int n) {
        // 创建虚拟头结点,防止一些null导致的问题
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        for (int i = 1; i < m; i++) {
            // 找到了m的上一个节点
            cur = cur.next;
        }
        // node.next就是要翻转的起点,mHead表示开始翻转的起点
        ListNode mHead = cur.next;
        ListNode next = null;
        ListNode pre = null;
        // 翻转m到n这一段,起点是mHead,参考206题
        for (int i = m; i <= n; i++) {
            next = mHead.next;
            mHead.next = pre;
            pre = mHead;
            mHead = next;
        }
        // m位置的节点指向n位置的下一个节点
        cur.next.next = next;
        // m前一个节点指向n位置处的节点
        cur.next = pre;
        // 返回虚拟头结点的下一个节点即新的头结点
        return dummyHead.next;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5};
        ListNode head = LinkedListTool.create(nums);
        LinkedListTool.show(head);
        // 返回新链表的dead
        ListNode headNew = new Solution().reverseBetween(head, 2, 4);
        LinkedListTool.show(headNew);
    }
}
/**
 * 1 -> 2 -> 3 -> 4 -> 5 -> NULL
 * 1 -> 4 -> 3 -> 2 -> 5 -> NULL
 */

还有83、86、328、2、445号问题也是链表相关的问题

83.删除排序链表中的重复元素

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.next != null && cur.val == cur.next.val) {
                cur.next = cur.next.next;
            }else {
                cur = cur.next;
            }
        }
        return head;
    }

    /**
     * 1->1->2  ==> 1->2
     * [] -> []
     * [1,1,1,1] ==> [1]
     * {1,1,2,3,3}  ==> [1, 2, 3]
     */
    public static void main(String[] args) {
        int[] nums = {1,1,2,3,3};
        ListNode head = LinkedListTool.create(nums);
        LinkedListTool.show(head);
        ListNode newHead = new Solution().deleteDuplicates(head);
        LinkedListTool.show(head);
    }
}

86.分隔链表

核心思想是把小于x的节点和大于等于x的节点拆成两个链表,最后把后者连接到前者形成一条新的链表就是满足题目的链表

 // 核心思想是把小于x的节点和大于等于x的节点拆成两个链表,最后把后者连接到前者形成一条新的链表就是满足题目的链表
class Solution {
    public ListNode partition(ListNode head, int x) {
        // 存储所有大于等于x的节点的链表的虚拟头结点
        ListNode bigHead = new ListNode(0);
        // 存储所有小于x的节点的链表的虚拟头结点
        ListNode smallHead = new ListNode(0);
        // 上面两个链表的指针,用于插入元素
        ListNode big = bigHead;
        ListNode small = smallHead;
        while(head != null){
            if(head.val < x){
                // 插入节点到小元素链表
                small.next = head;
                // 指针往后移动
                small = small.next;
            }else {
                // 插入节点到大元素链表
                big.next = head;
                big = big.next;
            }
            head = head.next;
        }
        // 把大元素连接到小元素链表后面
        small.next = bigHead.next;
        // 大元素链表的尾部要清空下,防止还连着些乱七八糟的节点
        big.next = null;
        // 返回小元素链表的头结点,即虚拟头结点的下一个节点
        return smallHead.next;
    }
}

328.奇偶链表

和上面的第86号问题非常类似,区别在于这里是按照奇数编号和偶数编号分成了两个链表,除了if逻辑不通,其他地方完全相同

/***********************************************************
 * @Description : 新建两个不同的链表,遍历一遍原链表后,分别存储奇数编号节点和偶数编号节点,最后把奇数链表连接到偶数链表即可
 * 和LeetCode第86号问题按照边界分成两个链表的思路基本一致
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/18 18:24
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter05LinkedList.LeetCode382OddEvenLinkedList;

import Chapter05LinkedList.ListNode;

class Solution {
    public ListNode oddEvenList(ListNode head) {
        // 奇数链表的虚拟头结点
        ListNode oddHead = new ListNode(0);
        // 偶数链表的虚拟头结点
        ListNode evenHead = new ListNode(0);
        // 上面两个链表的移动指针
        ListNode odd = oddHead;
        ListNode even = evenHead;
        int index = 0;
        while(head != null){
            if(index % 2 == 1){ // 奇数编号节点
                // 插入节点到奇数链表
                odd.next = head;
                // 移动指针往后一位
                odd = odd.next;
            }else { // 偶数编号节点
                even.next = head;
                even = even.next;
            }
            head = head.next;
            index++;
        }
        // 奇数链表连接到偶数链表后面
        even.next = oddHead.next;
        // 奇数链表的最后清空下,防止连接些乱七八糟的节点
        odd.next = null;
        return evenHead.next;
    }
}

2.两数相加

涉及到大数相加~~需要用到BigDecimal

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
import java.math.BigDecimal;
class Solution {
    private String add2Sum(String s1, String s2) {
        BigDecimal b1 = new BigDecimal(s1);
        BigDecimal b2 = new BigDecimal(s2);
        return b1.add(b2).toString();
    }

    /**
     * 两个数相加,要考虑大数相加的情况,所以数字都以字符串的形式来存储
     */
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 用字符串来存储两个数
        StringBuilder num1 = new StringBuilder();
        StringBuilder num2 = new StringBuilder();
        while (l1 != null) {
            num1.append(l1.val);
            l1 = l1.next;
        }
        while (l2 != null) {
            num2.append(l2.val);
            l2 = l2.next;
        }
        String sum = add2Sum(num1.reverse().toString(), num2.reverse().toString());
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        for (int i = sum.length() - 1; i >= 0; i--) {
            cur.next = new ListNode(Integer.parseInt(sum.charAt(i) + ""));
            cur = cur.next;
        }
        return dummyHead.next;
    }
}

445.两数相加 II

和上面的第2题类似,甚至更简单点,因为不用再逆序了

import java.math.BigDecimal;
class Solution {
    private String add2Sum(String s1, String s2) {
        BigDecimal b1 = new BigDecimal(s1);
        BigDecimal b2 = new BigDecimal(s2);
        return b1.add(b2).toString();
    }

    /**
     * 两个数相加,要考虑大数相加的情况,所以数字都以字符串的形式来存储
     */
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 用字符串来存储两个数
        StringBuilder num1 = new StringBuilder();
        StringBuilder num2 = new StringBuilder();
        while (l1 != null) {
            num1.append(l1.val);
            l1 = l1.next;
        }
        while (l2 != null) {
            num2.append(l2.val);
            l2 = l2.next;
        }
        String sum = add2Sum(num1.toString(), num2.toString());
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        for (int i = 0; i < sum.length(); i++) {
            cur.next = new ListNode(Integer.parseInt(sum.charAt(i) + ""));
            cur = cur.next;
        }
        return dummyHead.next;
    }
}

3 设立链表的虚拟头节点DummyHead可以大大简化代码逻辑

LeetCode203号问题 Remove Linked List Elements

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

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

不设置虚拟头结点时,需要对头结点作各种异常判断

package Chapter05LinkedList.RemoveElements;

/***********************************************************
 * 反转一个单链表。
 *
 * 示例:
 *
 * 输入: 1->2->3->4->5->NULL
 * 输出: 5->4->3->2->1->NULL
 *
 * @note      : 创建pre、cur和next三个节点指针,不断移动节点来达到目的
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/21 15:23
 ***********************************************************/
public class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 每次都要更新head
        while (head != null && head.val == val) {
            // 头结点的val就是想找的val,那么就要删除头节点
            head = head.next;
        }

        // 最后的head仍然可能为空
        if (head == null) {
            // 需要对头结点判空
            return null;
        }

        ListNode curr = head;
        while (curr.next != null) {
            if (curr.next.val == val) {
                // 把current.next这个节点释放掉
                curr.next = curr.next.next;
            } else {
                // 没有找到符合的节点就继续向下找
                curr = curr.next;
            }
        }
        return head;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 6, 4, 5, 6};
        int val = 6;
        ListNode head = LinkedListTool.create(nums);
        LinkedListTool.show(head);
        ListNode headRemove = new Solution().removeElements(head, val);
        LinkedListTool.show(headRemove);
    }
}

/**
 * 1 -> 2 -> 3 -> 4 -> 5 -> NULL
 * 5 -> 4 -> 3 -> 2 -> 1 -> NULL
 */

引入虚拟头结点后,算法就简化多了

虚拟头节点,可以避免head为空的各种情况,代码逻辑可以大大简化

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 虚拟头节点,可以避免head为空的各种情况,代码逻辑可以大大简化     
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;

        // 所有节点的操作都统一了
        ListNode curr = dummyHead;
        while (curr.next != null) {
            if (curr.next.val == val) {
                // 把current.next这个节点释放掉
                curr.next = curr.next.next;
            } else {
                // 没有找到符合的节点就继续向下找
                curr = curr.next;
            }
        }
        // 虚拟头结点后面的点才是真正的头结点
        return dummyHead.next;
    }
}

更简化的实现:递归

class Solution {
   public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        head.next = removeElements(head.next, val);
        // head节点要删除就直接跳过head节点,否则就返回原来的
        return head.val == val ? head.next : head;
    }
}

本节的练习题:82、21

82.删除排序链表中的重复元素 II

注意这里是要删除所有相等的元素~~

/***********************************************************
 * @Description : 删除所有重复的节点
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/18 20:21
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter05LinkedList.LeetCode82RemoveDuplicatesII;

import Chapter05LinkedList.LinkedListTool;
import Chapter05LinkedList.ListNode;

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                // 重复元素的起点
                ListNode del = cur.next;
                while (del.next != null && del.val == del.next.val) {
                    del = del.next;
                }
                // while退出时,del走到了最后一个重复元素的位置,在这里设置下
                cur.next = del.next;
            } else {
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }

    /**
     * 特殊用例:
     * []  ==> []
     * [1] ==> [1]
     * [1, 1] ==> []  这个是 while (cur.next!=null)的来源
     * 
     * [1,2,3,3,4,4,5] ==> [1, 2, 5]
     */
    public static void main(String[] args) {
        int[] nums = {1, 1};
        ListNode head = LinkedListTool.create(nums);
        ListNode newHead = new Solution().deleteDuplicates(head);
        LinkedListTool.show(newHead);
    }
}

21.合并两个有序链表

创建个新链表,一次比较存储两个链表的元素即可

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 合并后的链表
        ListNode dummyHead = new ListNode(0);
        // 链表的移动指针
        ListNode cur = dummyHead;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        // 一个链表遍历完了另一个可能还有元素没遍历,把还没遍历完的链表直接挂到cur后面即可
        if(l1 == null){
            cur.next = l2;
        }else{
            cur.next = l1;
        }
        return dummyHead.next;
    }
}

4 第24号问题:Swap Nodes in Pairs

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

LeetCode第24号问题之翻转链表节点1

LeetCode第24号问题之翻转链表节点2

LeetCode第24号问题之翻转链表节点3

LeetCode第24号问题之翻转链表节点4

class Solution {
    public ListNode swapPairs(ListNode head) {
         // 至少要有两个点,否则直接返回
        if(head == null || head.next == null){
            return head;
        }
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        // 正在挪动的点对的前面那个元素
        ListNode pre = dummyHead;
        while(pre.next!= null && pre.next.next != null){
            // 更新node1和node2
            ListNode node1 = pre.next;
            ListNode node2 = pre.next.next;

            // 旋转两个点
            pre.next = node2;
            node1.next = node2.next;
            node2.next = node1;

            // p变成交换后靠后的那个元素
            pre = node1;
        }
        return dummyHead.next;
    }
}

LeetCode上面类似的题目:25、147、148

25.K个一组翻转链表

困难级别的题目,实际很简单,利用下92题_翻转部分列表的函数即可

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     /**
     * 参考 92. 反转部分链表 II,把第m个元素到第n个元素及之间的元素逆序
     */
    public ListNode reverseBetween(ListNode head, int m, int n) {
        // 创建虚拟头结点,防止一些null导致的问题
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        for (int i = 1; i < m; i++) {
            // 找到了m的上一个节点
            cur = cur.next;
        }
        // node.next就是要翻转的起点,mHead表示开始翻转的起点
        ListNode mHead = cur.next;
        ListNode next = null;
        ListNode pre = null;
        // 翻转m到n这一段,起点是mHead,参考206题
        for (int i = m; i <= n; i++) {
            next = mHead.next;
            mHead.next = pre;
            pre = mHead;
            mHead = next;
        }
        // m位置的节点指向n位置的下一个节点
        cur.next.next = next;
        // m前一个节点指向n位置处的节点
        cur.next = pre;
        // 返回虚拟头结点的下一个节点即新的头结点
        return dummyHead.next;
    }

    public ListNode reverseKGroup(ListNode head, int k) {
        if(head == null){
            return null;
        }
        // 链表节点的个数
        int len = 1;
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
            len++;
        }
        // 累计要进行多少轮翻转
        int cnt = len / k;
        for (int i = 0; i < cnt; i++) {
            int m = 1 + k * i;
            int n = 1 + k * (i + 1);
            head = reverseBetween(head, m, n - 1);
        }
        return head;
    }
}

147.对链表进行插入排序

class Solution {
    public ListNode insertionSortList(ListNode head) {
        // 初始化虚拟头结点为最小节点
        ListNode dummyHead = new ListNode(Integer.MIN_VALUE);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        while (cur.next != null) {
            // pre记录需要插入位置的前面的节点
            ListNode pre = dummyHead;
            boolean swap = false;
            // 遍历cur之前的节点,找到cur所指的节点应该插入的位置
            while (pre != cur) {
                if (pre.next.val > cur.next.val) {
                    // 存储要进行插入的节点
                    ListNode tmp = cur.next;
                    // 移除cur.next节点
                    cur.next = cur.next.next;
                    // 下面3行是把cur.next移动到合适位置
                    ListNode tmp2 = pre.next;
                    pre.next = tmp;
                    tmp.next = tmp2;
                    // 是否经过了元素插入
                    swap = true;
                    // 插入完毕,提前退出
                    break;
                }
                pre = pre.next;
            }
            if (!swap) {
                // 如果没有经过元素置换,cur是不需要移动
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }
}

148.排序链表

直接用上一题的代码即可

class Solution {
    // 直接用147的插入排序
    public ListNode sortList(ListNode head) {
        // 初始化虚拟头结点为最小节点
        ListNode dummyHead = new ListNode(Integer.MIN_VALUE);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        while (cur.next != null) {
            // pre记录需要插入位置的前面的节点
            ListNode pre = dummyHead;
            boolean swap = false;
            // 遍历cur之前的节点,找到cur所指的节点应该插入的位置
            while (pre != cur) {
                if (pre.next.val > cur.next.val) {
                    // 存储要进行插入的节点
                    ListNode tmp = cur.next;
                    // 移除cur.next节点
                    cur.next = cur.next.next;
                    // 下面3行是把cur.next移动到合适位置
                    ListNode tmp2 = pre.next;
                    pre.next = tmp;
                    tmp.next = tmp2;
                    // 是否经过了元素插入
                    swap = true;
                    // 插入完毕,提前退出
                    break;
                }
                pre = pre.next;
            }
            if (!swap) {
                // 如果没有经过元素置换,cur是不需要移动
                cur = cur.next;
            }
        }
        return dummyHead.next;
    }
}

5 LeetCode237:删除链表中的节点

只给出了当前节点,可以通过把当前节点的值改成当前节点的下一个节点的值,然后删除下一个节点即可

删除链表中指定节点步骤:

  • 1.将该节点下一个节点的值复制给当前节点。
  • 2.删除该节点的下一个节点
class Solution {
    public void deleteNode(ListNode node) {
        // 相当于删除当前节点,因为不知道当前节点的上一个节点,所以把当前节点的下一个节点的val复制到当前节点并释放下一个节点即可
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

6 双指针法:LeetCode第19号问题:删除链表的倒数第N个节点

一般的解法:遍历两遍

删除倒数第n个节点

更优的算法:遍历一遍

使用间距为n的两个点p和q从起始点开始往后移动,当q到达最后一个节点时,p所在的节点就是导数第n个节点

双指针法删除倒数第n个元素1

双指针法删除倒数第n个元素2

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        // p和q初始化为dummyHead可以防止很多null的异常
        ListNode p = dummyHead;
        ListNode q = dummyHead;
        for(int i = 0; i < n + 1; i++){
            // q移动到和p间距为n的地方
            q = q.next;
        }
        // p和q一直往后移动直到q移动到链表结尾
        while(q != null){
            p = p.next;
            q = q.next;
        }
        // q到达null了,此时p就是要删除的节点的上一个节点
        p.next = p.next.next;
        return dummyHead.next;
    }
}

双指针法的其他题目:61、143、234

61.旋转链表

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || k == 0) {
            return head;
        }
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        // 链表的元素个数
        int cnt = 0;
        ListNode cur = dummyHead;
        // 找到链表的最后节点
        while (cur.next != null) {
            cur = cur.next;
            cnt++;
        }
        // 只要把链表最后k个元素移动到最前面就达到目标啦
        int n = k % cnt;
        // p和q初始化为dummyHead可以防止很多null的异常
        ListNode p = dummyHead;
        ListNode q = dummyHead;
        for (int i = 0; i < n + 1; i++) {
            // q移动到和p间距为n的地方
            q = q.next;
        }
        // p和q一直往后移动直到q移动到链表结尾
        while (q != null) {
            p = p.next;
            q = q.next;
        }
        dummyHead.next = p.next;
        p.next = null;
        cur = dummyHead;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = head;
        return dummyHead.next;
    }
}

143.重排链表

在中间把链表后一半逆序,然后隔一个把后一半对应的元素插入到前一半

class Solution {
    /**
     * 参考 92. 反转部分链表 II
     */
    public ListNode reverseBetween(ListNode head, int m, int n) {
        // 创建虚拟头结点,防止一些null导致的问题
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        for (int i = 1; i < m; i++) {
            // 找到了m的上一个节点
            cur = cur.next;
        }
        // node.next就是要翻转的起点,mHead表示开始翻转的起点
        ListNode mHead = cur.next;
        ListNode next = null;
        ListNode pre = null;
        // 翻转m到n这一段,起点是mHead,参考206题
        for (int i = m; i <= n; i++) {
            next = mHead.next;
            mHead.next = pre;
            pre = mHead;
            mHead = next;
        }
        // m位置的节点指向n位置的下一个节点
        cur.next.next = next;
        // m前一个节点指向n位置处的节点
        cur.next = pre;
        // 返回虚拟头结点的下一个节点即新的头结点
        return dummyHead.next;
    }

    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }
        ListNode cur = head;
        // n是链表的节点总数
        int m, n = 1;
        while (cur.next != null) {
            cur = cur.next;
            n++;
        }
        if (n % 2 == 0) { // 偶数个节点
            m = n / 2 + 1;
        } else { // 奇数个节点
            m = n / 2 + 2;
        }
        // 反转第m个节点到第n个节点之间的链表,包含第m个和第n个,反转后的链表刷新下head
        head = reverseBetween(head, m, n);
        // 下面利用双指针法进行二次连接
        ListNode preM = head;
        for (int i = 2; i < m; i++) {
            // for循环结束,mNodePre就是第m个节点前一个节点的位置
            preM = preM.next;
        }
        // 要和m节点进行位置交换的节点
        ListNode pre = head;
        // pre != preM是为了防止偶数的最后一对,这一对不用交换
        while (preM.next != null && pre != preM) {
            // 下面对调链表中的两个节点,参考 第24号问题:Swap Nodes in Pairs
            // 1.更新两个要进行交换的节点,各自的前面那个节点分别是pre和preM
            ListNode node1 = pre.next;
            ListNode node2 = preM.next;

            // 把node2插入到node1前面
            preM.next = node2.next;
            pre.next = node2;
            node2.next = node1;
            // preM不用换,还可以用之前的?
            // pre变成交换后的node2的下一个节点,直接后面就是要进行交换的另一个节点
            pre = node2.next;
        }
    }
}

234.回文链表

基本上和上面的143号问题相同,先逆序后一半,然后用双指针判断前后两半部分对应的节点值是否相等即可

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
 // 和143号问题类似,先逆转后一半的链表,然后用双指针法逐个判断是否相等即可
class Solution {
    /**
     * 参考 92. 反转部分链表 II
     */
    public ListNode reverseBetween(ListNode head, int m, int n) {
        // 创建虚拟头结点,防止一些null导致的问题
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        for (int i = 1; i < m; i++) {
            // 找到了m的上一个节点
            cur = cur.next;
        }
        // node.next就是要翻转的起点,mHead表示开始翻转的起点
        ListNode mHead = cur.next;
        ListNode next = null;
        ListNode pre = null;
        // 翻转m到n这一段,起点是mHead,参考206题
        for (int i = m; i <= n; i++) {
            next = mHead.next;
            mHead.next = pre;
            pre = mHead;
            mHead = next;
        }
        // m位置的节点指向n位置的下一个节点
        cur.next.next = next;
        // m前一个节点指向n位置处的节点
        cur.next = pre;
        // 返回虚拟头结点的下一个节点即新的头结点
        return dummyHead.next;
    }

    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode cur = head;
        // n是链表的节点总数
        int m, n = 1;
        while (cur.next != null) {
            cur = cur.next;
            n++;
        }
        if (n % 2 == 0) { // 偶数个节点
            m = n / 2 + 1;
        } else { // 奇数个节点
            m = n / 2 + 2;
        }
        // 反转第m个节点到第n个节点之间的链表,包含第m个和第n个,反转后的链表刷新下head
        head = reverseBetween(head, m, n);
        // 下面利用双指针法进行二次连接
        ListNode node2 = head;
        for (int i = 1; i < m; i++) {
            // for循环结束,node2就是第m个节点前一个节点的位置
            node2 = node2.next;
        }
        // 比较前后两半部分对应的节点值是否相等
        ListNode node1 = head;
        while (node2 != null) {
            if(node1.val != node2.val){
                return false;
            }
            node1 = node1.next;
            node2 = node2.next;
        }
        return true;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/434465.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Oracle数据库的java程序员注意:不要再使用generated always as identity了!

Identity Columns是在Oracle版本≥12c中的新特性&#xff1a;自增字段 在自增字段后使用以下2种语句的1种即可完成自增&#xff1a; generated by default as identitygenerated always as identity 在userinfo表的基础上&#xff0c;我们来看下区别&#xff1a; 1、使用ge…

VMware vSphere 8.0 Update 1 正式版发布 - 企业级工作负载平台

ESXi 8.0 U1 & vCenter Server 8.0 U1 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vsphere-8-u1/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 2023-04-18&#xff0c;VMware vSphere 8.0 Update 1 正式…

V2G模式下含分布式能源网优化运行研究(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

LAMP架构的配置

一.LAMP概述 1、LAMP的概念 LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态web站点服务及其应用开发环境 LAMP是一个缩写词&#xff0c;具体包括Linux操作系统、Apache网站服务器、MySQL数据库服务器、…

加速文件传输协议如何工作?

流行的文件传输协议&#xff08;例如FTP / S&#xff0c;SFTP和HTTP / S&#xff09;取决于名为TCP的基础协议。TCP的问题在于&#xff0c;随着网络条件&#xff08;例如延迟和数据包丢失&#xff09;的增加&#xff0c;网络吞吐量会大大降低。这在很大程度上归因于用于确保TCP…

当对象的引用计数为零时

上一篇文章&#xff0c;我提到要避免对象的析构函数被调用两次&#xff0c;有一位读者声称&#xff1a;当对象第一次被构建的时候&#xff0c;它的引用计数应该为 0&#xff0c;在某些时候&#xff0c;例如调用 QueryInterface 的时候&#xff0c;它的 AddRef 方法应该被调用以…

【算法题解】24. 模拟机器人行走

这是一道 中等难度 的题 https://leetcode.cn/problems/walking-robot-simulation/description/ 题目 机器人在一个无限大小的 XY 网格平面上行走&#xff0c;从点 (0, 0) 处开始出发&#xff0c;面向北方。该机器人可以接收以下三种类型的命令 commands &#xff1a; -2 &am…

C++ 引用

什么是引用 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。&#xff08;语法层面来讲&#xff09; 但在底层实际上引用是开辟空间的&#xff0c;类似于指针 …

大数据能力提升项目|学生成果展系列之八

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

操作系统前置知识

进程 程序存储在磁盘之中&#xff0c;需要加载内存才能执行&#xff0c;包含堆空间、栈空间、全局和静态变量数据、代码&#xff0c;具体执行效果如下所示&#xff1a; 所谓的进程概念就是操作系统为了执行某个程序为其分配的内存资源&#xff0c;该内存资源并不是连续的&…

【数据结构】二叉树的链式结构(笔记总结)内附递归展开图(炒鸡详细)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&…

Web前端基础——盒子模型

&#xff08;1&#xff09;盒子模型的作用&#xff1a; 布局网页&#xff0c;摆放盒子和内容 &#xff08;2&#xff09;盒子模型重要组成部分&#xff1a; 内容区域 - width & height内边框 - padding&#xff08;出现在内容与盒子边缘之间&#xff09;边框线 - border外…

毕业2年,跳槽到下一个公司就25K了,厉害了···

本人本科就读于某普通院校&#xff0c;毕业后通过同学的原因加入软件测试这个行业&#xff0c;角色也从测试小白到了目前的资深工程师&#xff0c;从功能测试转变为测试开发&#xff0c;并顺利拿下了某二线城市互联网企业的Offer&#xff0c;年薪 30W 。 选择和努力哪个重要&a…

建模技能C位秘诀 | 装配式建筑操作技能

剪力墙结构PC构件-预制剪力墙 YUGOU SCHOOL 1、承载力计算&#xff1a;对一、二、三级抗震等级的装配式剪力墙结构&#xff0c;应进行剪力墙水平接缝的抗震受剪承载力验算。 由公式可以看出预制剪力墙水平抗剪主要是靠垂直穿过结合面的竖向抗剪钢筋以及结合面上的轴向压力&a…

RSA-2048-Encoded-Modulus

裸公钥和x509格式公钥的区别 (公钥&#xff0c;非证书) x509 30820122300D06092A864886F70D01010105000382010F003082010A02820101||00 || 256字节的modulus||0203010001 解析: 0203010001 tag length value 结构 &#xff0c;pubExponent 010001 大于7F补 00 &#xff1f;…

C++11多线程:原子操作std::automic-用于多个线程之间共享的变量。

系列文章目录 文章目录 系列文章目录前言一、std::automic二、使用步骤1.代码案例 总结 前言 原子操作std::automic的基本概念和用法。 一、std::automic std::atomic来代表原子操作&#xff0c;std::automic是个类模板。其实std::atomic这个东西是用来封装某个类型的值的。 …

常用 Composition API--ref函数

ref函数--处理基本类型 以前我们的ref属性用处主要用于打标识&#xff0c;像原生js中的id标签一样。我们可以通过这个ref函数可以实现获取input元素&#xff0c;并让他获取焦点触发事件 而在v3中的是ref函数 先提出一个例子&#xff0c;我点击一个按钮&#xff0c;但是页面并…

WordCount 在 MapReduce上运行详细步骤

注意&#xff1a;前提条件hadoop已经安装成功&#xff0c;并且正常启动。 1.准备好eclipse安装包&#xff0c;eclipse-jee-juno-linux-gtk-x86_64.tar.gz&#xff0c;使用SSH Secure File Transfer Client工具把安装包上传于Hadoop集群的名称节点。 2.上传Hadoop在eclipse上运…

C++缺省参数的具体使用

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 本文来详细介绍C中的缺省参数。正文开始&#xff1a; 目录 一、缺省参数概念二、缺省参数分类2.1全缺省2.2半缺省 三、缺省参数…

Linux上搭建Discuz论坛

一.准备工作 1.下载php*&#xff0c;mariadb-server 2.上传Discuz3.5压缩包并解压 二.搭建过程 基于redhat 9 版本和Discuz3.5&#xff0c;php8.0&#xff0c;mariadb10.5演示 一.准备工作 1.下载php*&#xff0c;mariadb-server [rootredhat9 aaa]# yum install -y php*…