链表题目之模拟类

还有一些题目是没有特别复杂的技巧,但是考察仔细程度以及基本的链表处理能力,从而考察在基本的逻辑处理上考虑的全面和细致层度。这类题目对于业务代码编码能力的鉴定的确是有一些帮助。

分隔链表(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
}
相关推荐
嵌入式@秋刀鱼3 小时前
《第四章-筋骨淬炼》 C++修炼生涯笔记(基础篇)数组与函数
开发语言·数据结构·c++·笔记·算法·链表·visual studio code
嵌入式@秋刀鱼3 小时前
《第五章-心法进阶》 C++修炼生涯笔记(基础篇)指针与结构体⭐⭐⭐⭐⭐
c语言·开发语言·数据结构·c++·笔记·算法·visual studio code
泽02024 小时前
C++之list的自我实现
开发语言·数据结构·c++·算法·list
物联网嵌入式小冉学长6 小时前
2.线性表的链式存储-链表
数据结构·链表
顾小玙7 小时前
前缀和:leetcode974--和可被K整除的子数组
数据结构·算法
W说编程7 小时前
算法导论第三章:数据结构艺术与高效实现
c语言·数据结构·算法
hn小菜鸡8 小时前
LeetCode 2917.找出数组中的K-or值
数据结构·算法·leetcode
yield-bytes9 小时前
Java并发进阶系列:深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(上)
数据结构
好易学·数据结构9 小时前
可视化图解算法51:寻找第K大(数组中的第K个最大的元素)
数据结构·python·算法·leetcode·力扣·牛客网·堆栈
NULL指向我11 小时前
C语言数据结构笔记5:Keil 编译器优化行为_malloc指针内存分配问题
c语言·数据结构·笔记