糟糕的数组扩容
想象一下,你还是一名幽默风趣帅气多金的程序员,你的头发还是秃了,我们还是管你叫“秃头大帅”(whatever, who cares)。
这一天你戴着你发光的假发来到了一家4S店,你豪掷百金订了365辆不同的劳斯莱斯,你把它们按一定规则排好,希望新的一年每天开一辆天天不重样。
当你把这365辆劳斯莱斯开回家后,你发现你家的停车场太小了☹️。
于是你准备扩建你家的停车场,就像下面这样。扩容
这时你体内的洪荒基因告诉你。
不要这样,不要这样,不要这样!
发明链表
于是你聪明的氢二氧再一次占据第4脑室,你思考片刻后,不费吹灰之力解决了这个问题。
很简单,你只是合理利用了你拥有的128个别墅的停车位,你把你的劳斯莱斯分布在这128个别墅的停车场上,你在每辆劳斯莱斯屁股上贴上接下来一天要开的莱斯莱斯。
这样,你得意的笑了笑,有钱人的生活就是这么朴实无华。
你给你的这个方案,起名为链表,英文名linked list,它的通用结构像下面这样。
你用下面的代码表示它
/* 链表节点结构体 */
type ListNode struct {
Val int // 节点值
Next *ListNode // 指向下一节点的指针
}
链表的优势(相对于数组)
这一天你老婆花枝招展正好来找你,于是你顺便跟你老婆吹嘘了你一下你这个新方面,链表结构几方面的优势:
- ** 灵活性和扩展性:**链表允许你根据当前的需求动态地添加更多的存储空间(即停车场)。这避免了一开始就必须预留大量连续空间的要求。
- **高效的插入和删除:**当你需要在链表中插入或删除车辆(节点)时,你只需修改前后停车场(节点)的指向,而不需要移动大量的车辆(数据)。这在你频繁新购车或者卖车时特别有用。
- **节省空间:**只有在需要时才会增加新的停车场,这比起一开始就建一个大停车场来说,更加经济和高效。这样做不仅节省了资源,还能更灵活地应对不同的停车需求。
到这里,你老婆又对你投去了崇拜的目光。
你并未理会,你打算给你老婆深入讲一堂课《链表(你的新发明)》,包括链表的种类,增删(也即)操作,性能分析等
链表特性
新增节点(车子) :
删除节点(车子)
性能分析
从上面可以看出,链表的操作性能
插入 O(1)
删除 O(1)
随机访问 O(n)
链表种类
- 单向链表,从当前节点只能找到下一节点
- 双向链表,从当前节点即能向后遍历,也可以向前遍历,方便,这下你可以方便的知道昨天开的什么车。
- 环形链表,收尾相连,因果轮回、生生世世,你可以明年继续开。
你又做了一下补充,对于链表的插入和删除,如果
讲到这里,你忻忻得意,你老婆情不自禁。
不过你并不满意,这个Moment,你心中又燃起了一团火,你感觉到,要爆了。
你咬了一口你老婆吃了还剩一半的苹果,掀开尘封多年的笔记本盖,默默的敲下了leetcode.com。
经典题解
2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
考察链表遍历基础。
理解题目的关键点后,有以下思路:
同时遍历两个链表,把他们对应节点的值相加,不断插入新链表就可以了。可见这里其实是链表的插入问题,要处理的无非是进位的问题。
需要注意的是两个链表的长度可能不一致。
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
dummy := &ListNode{}
result := dummy
carry := 0 // 记录进位
for l1 != nil && l2 != nil {
x := l1.Val + l2.Val + carry
carry = x / 10
x = x % 10
dummy.Next = &ListNode{Val: x}
dummy = dummy.Next
l1 = l1.Next
l2 = l2.Next
}
for l1 != nil { // 如果l1长,继续遍历
x := l1.Val + carry
carry = x / 10
x = x % 10
dummy.Next = &ListNode{Val: x}
dummy = dummy.Next
l1 = l1.Next
}
for l2 != nil {
x := l2.Val + carry
carry = x / 10
x = x % 10
dummy.Next = &ListNode{Val: x}
dummy = dummy.Next
l2 = l2.Next
}
if carry >= 1 {
dummy.Next = &ListNode{Val: carry}
dummy = dummy.Next
}
return result.Next
}
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n **个结点,并且返回链表的头结点。
思路:
一般想法,先一趟计算长度length,然后再一遍遍历,找到第 length−n+1个节点,执行删除。
一趟扫描:快慢指针,快指针先走,计数到第n个节点时,慢指针再走,快指针到结尾时,慢指针位置即要删除位置。
func removeNthFromEnd(head \*ListNode, n int) \*ListNode {
dummy := \&ListNode{0, head}
first, second := head, dummy
for i := 0; i < n; i++ {
first = first.Next
}
for ; first != nil; first = first.Next {
second = second.Next
}
second.Next = second.Next.Next
return dummy.Next
}