21.合并两个有序链表


目录
  • 21.合并两个有序链表
    • 题目
    • 题解-递归
    • 题解-迭代
    • 链表
      • 链表的基本操作

21.合并两个有序链表

题目

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

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

题解-递归

链表问题最需要考虑的是不要断链

递归的三部曲

递归返回值和参数
我们需要把两个链表合并成一个链表,那么参数应该是两个链表的头节点。递归返回排好序的头节点。

public ListNode mergeTwoLists(ListNode list1, ListNode list2)

递归什么时候结束

当其中一个链表为空的时候就可以结束了,直接返回另外一个链表的头节点就行了

if(list1==null)return list2;
if(list2==null)return list1;

本层递归逻辑

本层的两个链表要排好序,本层比较头节点的大小,小的节点做开头。这样处理之后,肯定可以后续的递归中,这个节点就是最小的,就是头节点。
大的和小的后面的链表继续进行排序。


if(list1.val<=list2.val) {
	//list1值更小,list1做头节点。可以确定下来的。剩下的和list2继续排序。
	list1.next = 	mergeTwoLists(list1.next,list2);  //mergeTwoLists表示已经排好序了
	return list1; //把本层处理好的头节点返回
}
else{
	list2.next = mergeTwoLists(list1,list2.next);
	return list2;
}

代码

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1==null)return list2;
        else if(list2==null)return list1;
        else {
           if(list1.val<=list2.val) {
	            list1.next = 	mergeTwoLists(list1.next,list2);
            	return list1; 
            }
        else{
	            list2.next = mergeTwoLists(list1,list2.next);
	            return list2;
        }
        }
}
}

题解-迭代

合并的问题可以新建一个收集结果的容器

合并的问题都可以尝试新建一个链表res

比较list1与list2的值,选出小的值进新链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
      
    
        ListNode result = new ListNode(0); //新建一个节点,0可以是任何值,返回的是result.next,这里新建一个节点是因为下面要使用result.next
        ListNode tmp = result;
        while(list1!=null && list2!=null){
            //小的先出来
            if(list1.val<=list2.val){
                tmp.next = list1;
                list1 = list1.next;
            }else{
                tmp.next = list2;
                list2 = list2.next;
            }
            tmp = tmp.next;
        }
        //把剩下的链接起来
        if(list1==null) tmp.next=list2;
        if(list2==null) tmp.next=list1;
        return result.next;
        
}
}

链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

链表适合在数据需要有一定顺序,但是又需要进行频繁增删除的场景。

头结点:在单链表的第一个结点(有效元素)之前附设的一个结点,称之为头结点。
头指针:指向头结点的指针称为头指针。
首节点(首元结点):首节点就是第一个元素(头元素)的结点

虚拟节点
定义:数据结构中,在链表的第一个结点之前附设一个结点,它没有直接前驱,称之为虚拟结点。虚拟结点的数据域可以不存储任何信息,虚拟结点的指针域存储指向第一个结点的指针。

作用:对链表进行增删时统一算法逻辑,减少边界处理(避免了判断是否是空表或者是增删的节点是否为第一个节点)

尾节点
定义:数据结构中,尾结点是指链表中最后一个节点,即存储最后一个元素的节点。

作用:由于移动到链表末尾需要线性的时间,因此在链表末尾插入元素会很耗时, 增加尾节点便于在链表末尾以 O(1) 的时间插入元素。

链表的基本操作

插入
插入只需要考虑要插入位置前驱节点和后继节点(双向链表的情况下需要更新后继节点)即可,其他节点不受影响,因此在给定指针的情况下插入的操作时间复杂度为O(1)。这里给定指针中的指针指的是插入位置的前驱节点。

伪代码:

temp = 待插入位置的前驱节点.next
待插入位置的前驱节点.next = 待插入指针
待插入指针.next = temp

如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 O(N)。

删除
只需要将需要删除的节点的前驱指针的 next 指针修正为其下下个节点即可,注意考虑边界条件。

伪代码:

待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next

遍历
遍历比较简单,直接上伪代码。

伪代码:

当前指针 =  头指针
while 当前指针不为空 {
   print(当前节点)
   当前指针 = 当前指针.next
}