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了吧? 为的就是保存链表的头部, 如果链表头部丢失了,那么是无法遍历整个链表的,这也是链表的缺陷之一
关于这道题目,还有一个递归的解决方法,写法更优雅,下次给大家写个递归版本,大家两个版本都掌握!
加油吧!