LeetCode--链表篇

本文介绍了LeetCode中关于链表的一系列问题,包括从尾到头打印链表、相交链表的查找、反转链表、合并两个有序链表、删除倒数第N个节点、两两交换链表节点、两数相加、判断回文链表、分隔链表以及奇偶链表的重构。文中详细解析了各种方法,如使用栈、递归、双指针等技巧。
摘要由CSDN通过智能技术生成

请添加图片描述请添加图片描述


一、剑指 Offer 06. 从尾到头打印链表

剑指 Offer 06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000

思路:
1.方法一:使用栈,将节点依次存入栈,再依次取出,存入数组

class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        while(head != null) {
            stack.push(head);
            head = head.next;
        }
        int size = stack.size();
        int[] num = new int[size];
        size = 0;
        while (!stack.isEmpty()) {
            num[size++] = stack.pop().val;
        }
        return num;
    }
}

2.方法二:递归,递归将元素加入ArrayList中,再反过来遍历一遍加入数组

class Solution {
    ArrayList<Integer> al = new ArrayList<>();
    public int[] reversePrint(ListNode head) {
        if (head == null) return new int[0];
        cursion(head);
        int[] num = new int[al.size()];
        int size = al.size();
        for(int i = 0 ;i < size; i++) {
            num[i] = al.get(size-i-1);
        }
        return num;
    }
    public void cursion(ListNode ln) {
        if (ln == null) return;
        al.add(ln.val);
        cursion(ln.next);
    }
}

二、160. 相交链表

160. 相交链表

编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表
在这里插入图片描述
在节点 c1 开始相交。返回交点

注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

思路:
1.方法一:哈希表存入节点,遍历两条链表,如果有重复节点则相交,时间复杂度(Om+n),空间复杂度(Om+n)

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> hashSet = new HashSet<>();
        while (headA != null) {
            hashSet.add(headA);
            headA = headA.next;
        }
        while (headB != null) {
            if (hashSet.contains(headB)) {
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }

方法二:两个指针A,B,分别顺着链表headA和链表headB往下走,当A走到链表末尾null时,将A指向headB,反之B到末尾也指向headA,如果两条链表有交集则必定A,B指针会重合,如果没有则A,B会同时为Null;
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode pA = headA, pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}

三、反转链表

反转链表

方法一:两个指针pre和cur,cur指向当前节点,pre指向cur的前一个节点

public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

方法二:递归,走到倒数第二个节点开始反转,cur是最后一个节点,反转后变成头结点

 public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head; //
        ListNode cur = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return cur;
    }

四、合并两个有序链表

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
示例 1:
在这里插入图片描述
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

思路:
方法一:递归,递归调用,判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
       if (l1 == null) return l2;
       else if (l2 == null) return l1;
       else if (l1.val <= l2.val) {
           l1.next = mergeTwoLists(l1.next,l2);
           return l1;
       }
       else {
           l2.next = mergeTwoLists(l1,l2.next);
           return l2;
       }      
    }

方法二:迭代,两个指针prehead.next指向头节点,cur 指向当前节点前一个节点

 public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
       ListNode prehead = new ListNode(-1);
       ListNode cur = prehead;
       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.next = l1 == null ? l2 : l1;
        return prehead.next; 
    }

五.19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?
在这里插入图片描述

示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

思路:
原始代码:两遍遍历链表

     public ListNode removeNthFromEnd(ListNode head, int n) {
        int size = 0;
        ListNode temp = head;
        ListNode pre = head;
        while (head != null) {
            head= head.next;
            size++;
        }
        head = temp;
        int index = 1;
        while (head != null) {
            if (n == size) {
                temp = head.next;
                break;
            }
            else if (size - index + 1 == n){
                pre.next = head.next;
                break;
            }
            pre = head;
            head = head.next;
            index++;
        }
        return temp;
    }

方法一:新增一个dummy节点,指向头结点,遍历链表统计长度,移动指针到要删除的节点的前一个节点,return dummy.next;

 public ListNode removeNthFromEnd(ListNode head, int n) {
        int size = 0;
        ListNode dummy = new ListNode(-1,head);
        ListNode temp = head;
        ListNode cur = dummy;
        while (temp != null) {
            temp = temp.next;
            size++;
        }
        for (int i = 1;i < size - n + 1 ;i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return dummy.next;
    }

方法二:栈,将节点依次加入栈中,然后出n个节点,将栈顶元素node.next = node.next.next;

 public ListNode removeNthFromEnd(ListNode head, int n) {
        Stack<ListNode> stack = new Stack<>();
        ListNode dummy = new ListNode(-1,head);
        ListNode temp = dummy;
        while (temp != null) {
            stack.push(temp);
            temp = temp.next;
        }
        for (int i = 0; i < n; i++) {
            stack.pop();
        }
        ListNode cur =  stack.peek();
        cur.next = cur.next.next;
        return dummy.next;
    }

方法三:增加一个dummy节点,dummy.next指向head,双指针yyds,一个快指针先走n步,然后两个指针一起走,直到快指针走到头

public ListNode removeNthFromEnd(ListNode head, int n) {
        //Stack<ListNode> stack = new Stack<>();
        ListNode dummy = new ListNode(-1,head);
        ListNode fast = head;
        ListNode slow = dummy;
        for (int i = 0 ;i < n ;i++) {
            fast = fast.next;
        }
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }

六.24. 两两交换链表中的节点

24. 两两交换链表中的节点

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

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

示例 1:
在这里插入图片描述
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

思路:
方法一:迭代,dummy指针next指向head,left指向dummy.next, right指向dummy.next.next ; 指针交换,然后dummy往后走

 public ListNode swapPairs(ListNode head) {
        if (head == null) return null;
        ListNode temp = new ListNode(-1,head);
        ListNode dummy = temp;
        while (dummy.next != null && dummy.next.next != null) {
            ListNode left = dummy.next;
            ListNode right = dummy.next.next;
            dummy.next = right;
            left.next = right.next;
            right.next = left;
            dummy = left;
        }
        return temp.next;

方法二:递归,分解出子过程,然后交给下一级去执行剩下重复的过程

public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode temp = head.next;
        head.next = swapPairs(temp.next);
        temp.next = head;
        return temp;
    }

七.445. 两数相加 II

445. 两数相加 II
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7

思路:用栈存入链表的节点,然后取出构造新的链表,

class Solution {        
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<ListNode> stackOne = new Stack<>();
        Stack<ListNode> stackTwo = new Stack<>();
        while (l1 != null) {
            stackOne.push(l1);
            l1 = l1.next;
        }
        while (l2 != null) {
            stackTwo.push(l2);
            l2 = l2.next;
        }   
        int preNum = 0;
        ListNode tail = null;
        while (!stackOne.empty() || !stackTwo.empty() || preNum != 0) {
            int a = stackOne.isEmpty() ? 0 : stackOne.pop().val;
            int b = stackTwo.isEmpty() ? 0 : stackTwo.pop().val;
            int cur = (a + b + preNum);
            preNum = cur / 10;
            cur = cur % 10;
            ListNode curNode = new ListNode(cur);
            curNode.next = tail;
            tail = curNode;
        }
        return tail;
    }
}

八.234. 回文链表

234. 回文链表

请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

方法一:使用双端对列,将元素都加入队列中,然后从两头出对,当只剩一个元素或者全部都出队为止,时间O(n),空间O(n)

class Solution {
    public boolean isPalindrome(ListNode head) {
        LinkedList<ListNode> ll = new LinkedList<>();
        while(head != null) {
            ll.addLast(head);
            head = head.next;
        }
        while(ll.size() != 1 && !ll.isEmpty()) {
            int num1 = ll.removeFirst().val;
            int num2 = ll.removeLast().val;
            if (num1 != num2) {
                return false;
            }
        }
        return true;
    }
}

方法二:快慢指针,将前半段反转,比较两段

class Solution {
 public static boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) {
            return true;
        }
        ListNode slow = head, fast = head;
        ListNode pre = null;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            ListNode temp = slow.next;
            slow.next = pre;
            pre = slow;
            slow = temp;
        }
        if(fast != null) {
            slow = slow.next;
        }
        while(pre != null && slow != null) {
            if(pre.val != slow.val) {
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }
        return true;
    }
}

方法三:找到前半段尾部,然后将后半段翻转,比较,最后将后半段重新翻转回去

class Solution {
    public static boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) {
            return true;
        }
        ListNode firLinkedList = getFirstLinkedList(head);
        ListNode secLinkedList = reverseLinkedList(firLinkedList.next);
        boolean flag = true;
        while(flag && secLinkedList != null  ) {
            if(head.val != secLinkedList.val) {
                flag = false;
            }
            head = head.next;
            secLinkedList = secLinkedList.next;
        }
        firLinkedList.next = reverseLinkedList(secLinkedList);
        return flag;
    }
    public static ListNode getFirstLinkedList(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    public static ListNode reverseLinkedList(ListNode head) {
        ListNode temp = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode node = cur.next;
            cur.next = temp;
            temp = cur;
            cur = node;
        }
        return temp;
    }
}

九.725. 分隔链表

725. 分隔链表
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
root 的长度范围: [0, 1000].
输入的每个节点的大小范围:[0, 999].
k 的取值范围: [1, 50].

思路:遍历一遍链表统计长度,然后计算 int count = len / k ;int res = len % k ;ListNode数组要存入每节链表开头的节点

class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        ListNode[] list = new ListNode[k];
        ListNode cur = root;
        int len = 0;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        int count = len / k;
        int res = len % k ;
        for (int i = 0;root != null && i < k;i++) {
            list[i] = root;
            int size = count + (res-- > 0 ? 1 : 0);
            for (int j =0;j < size - 1;j++) {
                root = root.next;
            }
            ListNode temp = root.next;
            root.next = null;
            root = temp;
        }
        return list;
    }
}

十.328. 奇偶链表

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

思路:两条链表,一条是奇链,一条是偶链,然后将偶链接在奇链的后面

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode oddhead = head;//奇链头结点
        ListNode evenhead = head.next;//偶链头结点
        
        ListNode odd = head,even = evenhead;
        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenhead;//将偶链表接在奇链表后面
        return oddhead;
    }
}

总结

1.指针对于链表的操作来说太重要了,用好指针,链表就是起飞
2.对于倒序,栈是个很应该考虑的方式
3.链表与数组很大的差别在于,链表插入,删除实在太方便了
4.适当构造新节点,可以使链表操作更方便

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Free的午后

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

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

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

打赏作者

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

抵扣说明:

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

余额充值