链表题目之模拟类

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

分隔链表(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
}
相关推荐
冉佳驹9 分钟前
数据结构 ——— 快速排序的时间复杂度以及规避最坏情况的方法
c语言·数据结构·算法·排序算法·快速排序算法·三数取中
李小白661 小时前
各种排序算法
数据结构·算法·排序算法
浪前1 小时前
排序算法之冒泡排序篇
数据结构·算法·排序算法
格雷亚赛克斯7 小时前
黑马——c语言零基础p139-p145
c语言·数据结构·算法
HABuo9 小时前
【数据结构与算法】合并链表、链表分割、链表回文结构
c语言·开发语言·数据结构·c++·学习·算法·链表
晚睡的鸟儿有夜宵吃9 小时前
Day2 洛谷1035+1047+1085+1089+1150+1151
数据结构·算法
冉佳驹11 小时前
数据结构 ——— 快速排序算法的实现(前后指针法版本)
c语言·数据结构·算法·排序算法·快速排序算法
疯狂吧小飞牛12 小时前
无锁编程–C语言
c语言·数据结构·c++·算法
梦深时有鹿12 小时前
C#基础上机练习题
数据结构·算法·c#
cccccc语言我来了12 小时前
详解 【AVL树】
数据结构·c++