还有一些题目是没有特别复杂的技巧,但是考察仔细程度以及基本的链表处理能力,从而考察在基本的逻辑处理上考虑的全面和细致层度。这类题目对于业务代码编码能力的鉴定的确是有一些帮助。
分隔链表(86)
题目:86. 分隔链表
把大于等于的放到一个新的链表上,当然第一个可能也被操作,直接用虚拟头节点,最后链表拼接,记得确保新链表的尾部是nil
新链表采用尾插法实现保障顺序
旧链表记得手摸模拟去掉一个节点的逻辑
go
func partition(head *ListNode, x int) *ListNode {
dummyHead := &ListNode{Next:head}
p := dummyHead
newDummyHead := &ListNode{}
pNew := newDummyHead
for p.Next != nil {
if p.Next.Val >= x {
cur := p.Next
p.Next = p.Next.Next
// 接到新的后面
pNew.Next = cur
pNew = pNew.Next
} else {
p = p.Next
}
}
pNew.Next = nil
p.Next = newDummyHead.Next
return dummyHead.Next
}
奇偶链表(328)
题目:328. 奇偶链表
这个题目也是模拟其实和分割链表一样
使用尾插法,使用两个虚拟头,一个用来删除,一个用来尾插,最后记得封尾,然后再接上即可
本题和分割链表,都需要考虑一下移除一个节点之后,当前指针的位置
go
func oddEvenList(head *ListNode) *ListNode {
dummyHead := &ListNode{Next:head}
p1 := dummyHead
dummyHeadNew := &ListNode{}
p2 := dummyHeadNew
flag := false
// 奇在前,第一个是奇
for p1.Next != nil {
if flag {
// 从添加到p2 从p1删除
cur := p1.Next
p1.Next = p1.Next.Next
p2.Next = cur
p2 = p2.Next
} else {
p1 = p1.Next
}
flag = !flag
}
p2.Next = nil
p1.Next = dummyHeadNew.Next
return dummyHead.Next
}
复制带随机指针的链表(138)
题目:138. 随机链表的复制
这个也属于技巧,用map 新节点的Random存放复制时旧的Random,map 存放旧节点和新节点的映射关系
得用尾插法,所以要用虚拟头
go
func copyRandomList(head *Node) *Node {
dummyHead := &Node{}
p := dummyHead
m := make(map[*Node]*Node)
for head != nil {
cur := &Node{
Val:head.Val,
Random:head.Random,
}
m[head] = cur
head = head.Next
p.Next = cur
p = p.Next
}
p = dummyHead.Next
for p != nil {
p.Random = m[p.Random]
p = p.Next
}
return dummyHead.Next
}
两数相加(445)
题目:445. 两数相加 II
因为相加需要考虑进位问题,链表倒着走有很费劲,所以先将链表翻转,求和之后再翻转回来
求和就有很多细节要处理,进位问题、两个链表剩余的数量
最后的进位是1的情况
go
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
// 将两个加到l1上
l1 = reverse(l1)
l2 = reverse(l2)
// 1 2 3
// 4 9
// 5 1 4
l1 = &ListNode{Next:l1}
p1 := l1
p2 := l2
rest := 0
for p1.Next != nil && p2 != nil {
sum := p1.Next.Val + p2.Val + rest
if sum >= 10 {
rest = 1
sum -= 10
} else {
rest = 0
}
p1.Next.Val = sum
p1 = p1.Next
p2 = p2.Next
}
if p2 != nil {
fmt.Println(p2.Val)
p1.Next = p2 // 写到这里发现不好接了,所以需要保留p1 不为nil,所以上面处理要用p1.Next,所以l1要用虚拟头
}
// 剩余的继续
for p1.Next != nil {
sum := p1.Next.Val + rest
if sum >= 10 {
rest = 1
sum -= 10
} else {
rest = 0
}
p1.Next.Val = sum
p1 = p1.Next
}
if rest != 0 {
p1.Next = &ListNode{Val:rest}
}
return reverse(l1.Next)
}
func reverse(head *ListNode) *ListNode {
var newNode *ListNode
for head != nil {
cur := head
head = head.Next
cur.Next = newNode
newNode = cur
}
return newNode
}
LRU
题目:146. LRU 缓存
LRU:最近最久未使用的会被淘汰-->最近使用的优先级要高-->使用链表记录优先级,前面的优先级高
-->使用双向链表,便于处理-->使用虚拟头、尾部,处理没有节点是的特殊情况,这样就没有特殊情况了。
使用Map实现O1查找
Get时如果存在,那就需要判断如果不在最前面,就需要移动到最前面
Put如果存在,则更新值,同时移动到最前面,如果不存在,则构建,之后放到最前面,同时需要先判断如果已经满了则需要链表中删除、清理map
移动到最前面:这里采用先从原位置删除,再放到最前面的方式
go
type Node struct {
Key int
Val int
Pre *Node
Next *Node
}
type LRUCache struct {
Cap int
m map[int]*Node
dummyHead *Node
dummyTail *Node
}
func Constructor(capacity int) LRUCache {
lru := LRUCache{
Cap:capacity,
m:make(map[int]*Node, capacity),
dummyHead:&Node{},
dummyTail:&Node{},
}
lru.dummyHead.Next = lru.dummyTail
lru.dummyTail.Pre = lru.dummyHead
return lru
}
func (this *LRUCache) Get(key int) int {
node, ok := this.m[key]
if !ok {
return -1
}
this.MoveToHead(node)
return node.Val
}
func (this *LRUCache) Put(key int, value int) {
node, ok := this.m[key]
if ok {
node.Val = value
this.MoveToHead(node)
return
}
node = &Node{
Val:value,
Key:key,
}
if len(this.m) >= this.Cap {
lastNode := this.dummyTail.Pre
delete(this.m, lastNode.Key)
this.removeNodeFromList(lastNode)
}
this.m[node.Key] = node
this.addNodeToHead(node)
}
func (this *LRUCache) MoveToHead(node *Node) {
if node.Pre == this.dummyHead {
return
}
this.removeNodeFromList(node)
this.addNodeToHead(node)
}
func (this *LRUCache) addNodeToHead(node *Node) {
// h,2 -- > h,1,2
node.Next = this.dummyHead.Next
node.Pre = this.dummyHead
this.dummyHead.Next = node
node.Next.Pre = node
}
func (this *LRUCache) removeNodeFromList(node *Node) {
// 1 2 3 --> 1 3
node.Pre.Next = node.Next
node.Next.Pre = node.Pre
}