【数据结构】链表的LeetCode题:206.反转链表,24.两两交换相邻节点,21.合并两个有序链表

在讲解链表的LeetCode题之前先说几点注意:

  • head指针与p指针:

    • head 指针指向参数链表的头,用于标识链表
    • p 指针一般是 p = head,用于操作链表
    • 两者都可以变,都可以用来操作当前链表。但若只是内部操作,即操作完后还要返回原链表,比如删除指定节点,然后返回链表,就必须要返回head。
    • 推荐每次链表传来先 p = head ,然后拿p去操作链表,若head无用再考虑将p替换
  • 有头链表与无头链表在遍历时的区别:

    • 有头链表:while (p.next != null)
    • 无头链表:while (p != null)
  • 技巧:先画图找出指针变换关系,然后将关系写下来,再补充

  • 注意:next只是指针而已,并不是Node

206.反转链表¹

反转一个单链表。

示例:

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

解法一:双指针(前后)

  • 思路:p & pre 前后指针四步走
  • 复杂度:
    • Time:O(n),与链表长度n正相关
    • Space:O(1),没有额外内存空间
public ListNode reverseList(ListNode head) {
    
    
        ListNode p = head, pre = null;
        while (p != null) {
    
    
            // 四步走
            ListNode tmp = p.next;
            p.next = pre;
            pre = p;
            p = tmp;
        }
    	// 注:这里是返回pre,因为此时p指向null
        return pre;
}

以上四步可以死记硬背成反转的定式。

解法二:递归*

简单图示:

initial:
1 -> 2 -> 3 -> 4 -> 5

after reverseList(2):
1 -> 2 <- 3 <- 4 <- 5
     |
     v
    null
	
after operate on 1:
null <- 1 <- 2 <- 3 <- 4 <- 5

代码如下:

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

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

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

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

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

解法一:哨兵结点

  • 思路:看见两两交换可以联想到交换两个数a,b:tmp = a;a = b;b = tmp。放到本题中
    • 第3个节点可以看做a,第四个节点可以看做b,第二个节点可以看做tmp。从而实现两两交换
    • 但是有一个问题,第一个节点和第二个几点的tmp还没有着落 —> 引入一个哨兵节点
  • 复杂度
    • Time:O(n)
    • Space:O(1)
public ListNode swapPairs(ListNode head) {
    
    
    	// 空,或者只有一个节点,直接返回
        if (head == null || head.next == null) return head;
        
        ListNode p = head;
    	// 因为第一个节点会和第二个节点交换,所以第二个节点会成为最终的head
        head = head.next;
    	// 创建哨兵结点tmp,作为链表的头结点,帮助前两个节点完成交换
        ListNode tmp = new ListNode(-1);
		
    	// 节点偶数个:p == null结束
    	// 节点奇数个:p.next == null 结束
        while(p != null && p.next != null) {
    
    
            // 开始:tmp 1 -> 2 -> 3 -> 4
            // tmp -> 2
            tmp.next = p.next;
            // 1 -> 3
            // 这一步很关键,一定要先把两边连好再连中间
            p.next = p.next.next;
            // tmp -> 2 -> 1    
            tmp.next.next = p;
			
            // tmp = 1
            tmp = tmp.next.next;
            // p = 3  
            p = p.next; 
            // 结束:2 -> 1(tmp) -> 3(p) -> 4
        }
        return head;
}

解法二:递归*

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

21. 合并两个有序链表¹

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

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

解法一:归并

  • 思路:新建一个头,链表1,链表2比大小插入即可。就像归并排序中合并两个子数组一样。
    注:这里能这么做的前提一定是待合并的两部分是排好序的!!
  • 复杂度
    • Time:O(m + n)
    • Space:O(1)
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    
    
    	// 创建一个虚拟头结点
        ListNode head = new ListNode(0), p = head;
        while (l1 != null && l2 != null) {
    
    
            if (l1.val < l2.val) {
    
    
                p.next = l1;
                l1 = l1.next;
            } else {
    
    
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
    	// 可能还存在一个链表未遍历完的情况
        if (l1 != null) p.next = l1;
        if (l2 != null) p.next = l2;
		// 注:这里要返回head.next,因为head是我们new的虚拟头结点
        return head.next;
    }

猜你喜欢

转载自blog.csdn.net/weixin_43935927/article/details/108735873