LeetCode 21 - 合并有序链表 - 解题思路记录 - GoLang

1,098 阅读4分钟

Hello, 今天我们来看一道简单的涉及到一点基础数据结构的算法题
LeetCode的第21题, 合并有序链表
还是老样子,先看题目描述,然后再娓娓道来

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

描述很简单,给定两个已经排好序的链表,将它们合并成为一个新的链表后返回

可能有些小伙伴,不知道链表是什么样的数据结构,来快速复习一下链表
现实生活中,一条链子长什么样,在这边就是长什么样

一条链子环环相扣,每个圈都套着下一个圈子,在数据结构中,相当于每个指针指向下一个结构体

跟手牵手一样,A->B->C->D 每个小朋友拉着下一个小朋友的手。
那么,跟数组的区别是什么呢?,数组看起来似乎也是这样子的吖

首先数组,在内存空间中需要开辟一段连续的内存空间,而且一旦开辟了,那段空间不管用不用,他都是存在了的,而且如果说突然不够用的情况下,你无法扩展,只能创建一个更大的,把小的放进去

对于数组的添加/删除时间复杂度都是O(n),而查询的时间复杂度则是O(1)
那链表的好处是什么呢? 动态添加、添加删除只需要修改元素的邻接元素 相当于时间复杂度只有O(1)

讲了这么多,都还没开始说链表到底长什么样,我们来看一张图,以官方例子作为数据

看到这个图 是不是很熟悉呢? 那我们再来看看这个代码是什么

type ListNode struct {
 Val  int //图片中数字的地方
 Next *ListNode //图片中指针的地方
}

这样是不是就一目了然了。看完简单的数据结构,我们再来看主代码

func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
 var head *ListNode
 var current *ListNode = new(ListNode)
 head = current
 for l1 != nil && l2 != nil {
  if l1.Val < l2.Val {
   current.Next = l1
   l1 = l1.Next
  } else {
   current.Next = l2
   l2 = l2.Next
  }
  current = current.Next
 }
 if l1 != nil {
  current.Next = l1
 }
 if l2 != nil {
  current.Next = l2
 }
 return head.Next
}

这一次的代码一样,没什么特别复杂的逻辑需要注意, 但是我们也照样拆分一下他

for l1 != nil && l2 != nil {}

第一个循环for很清晰, L1 和 L2不能是空
然后我们来看第一个if

if l1.Val < l2.Val {
 current.Next = l1
 l1 = l1.Next
} else {
 current.Next = l2
 l2 = l2.Next
}

这一个Val 就是我们数据结构中的int部分,存储的是一个数字,我们来图解一下这个部分的逻辑
给定2个链表 官方例子 我们先创建一个空链表,并把指针指向这个空的链表的地址,也就是代码中的head 和 current

我们先进入第一个if L1<L2的情况

看图我们很清楚, l1_0 < l2_0 是不成立的 所以会进入else分支 我们看一下进入else分支以后,发生什么事情
首先我们看一下代码

current.Next = l2

把current.Next的指针指向当前这个l2,画个图

然后 第二句

l2 = l2.Next

我们把l2_0的节点摘掉 从原本的这样子 变成了这样子

注意,摘掉的节点需要释放内存哈Go 有内部的Gabage Collection 最后,我们把current 移到 current.NEXT的节点上

为的就是我们下一次的赋值,可以直接使用current.Next = l1

看完了这里,相信大家都很明白链表的基础知识了,那么接下来看下面2个If的判断

if l1 != nil {
 current.Next = l1
}
if l2 != nil {
 current.Next = l2
}

这里这个if是什么意思?,如果说,我们都比较结束以后,l1 或者 l2 还有剩余的元素怎么办?,那就是直接把链表最后一个指向身下的元素则可以了,因为 剩下的元素肯定比之前比较完的元素要大。

结束以后,我们返回head.Next,这里大家知道为什么当初要先创建一个head了吧? 为的就是保存链表的头部, 如果链表头部丢失了,那么是无法遍历整个链表的,这也是链表的缺陷之一

关于这道题目,还有一个递归的解决方法,写法更优雅,下次给大家写个递归版本,大家两个版本都掌握!

加油吧!