代码随想录算法训练营day04| 24.两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

24.两两交换链表中的节点

虚拟头节点

使用虚拟头节点来同一处理逻辑,使用一个中间量记录上一节点的值。根据一个区间有两个元素要处理分为两个状态,一是第一个节点,二是第二个节点

func swapPairs(head *ListNode) *ListNode {
	var dummyHead = new(ListNode)
	dummyHead.Next = head // 虚拟头节点
	preVal := 0           // 上一节点的值
	curNode := dummyHead  // 当前节点
	count := -1           // 用来记录单双数
	for curNode.Next != nil {
		switch count % 2 {
		case 0: // 当前为交换的两个节点的第一个
			preVal = curNode.Val
			curNode.Val = curNode.Next.Val
		case 1: // 当前为交换的两个节点的第二个
			curNode.Val = preVal
		}
		curNode = curNode.Next
		count++
	}
	if count % 2 == 1 {
		curNode.Val = preVal
	}
	return dummyHead.Next
}

19.删除链表的倒数第N个节点

两次遍历

先遍历一次获取长度,再根据长度来确定要删除的目标值位置,注意使用虚拟头指针处理头部问题

func removeNthFromEnd(head *ListNode, n int) *ListNode {
	// 暴力
	length := 0 // 链表长度
	dummyHead := new(ListNode)
	dummyHead.Next = head
	current := dummyHead
	for current.Next != nil {
		current = current.Next
		length++
	}
	current = dummyHead
	for length-n > 0 {
		// 当length == n 时当前节点的下一节点即要删除的节点
		current = current.Next
		length--
	}
	current.Next = current.Next.Next // 删除节点
	return dummyHead.Next
}

一次遍历

空间换时间,用数组储存对应的节点,类似栈的入栈和出栈。注意使用虚拟头节点处理头节点情况

func removeNthFromEnd(head *ListNode, n int) *ListNode {
	// 栈做法
	nodeStack := make([]*ListNode, 0) // 存放链表节点的数组
	dummyNode := new(ListNode)
	dummyNode.Next = head    // 虚拟头节点
	currentNode := dummyNode // 当前的节点
	for currentNode != nil {
		nodeStack = append(nodeStack, currentNode)
		currentNode = currentNode.Next
	}
	preNode := nodeStack[len(nodeStack)-n - 1]
	preNode.Next = preNode.Next.Next
	return dummyNode.Next
}

面试题 02.07. 链表相交

暴力

直接将一个链表存起来然后暴力遍历,时间复杂度为O(n2),空间复杂度为O(n)

func getIntersectionNode(headA, headB *ListNode) *ListNode {
	// 存一个链表到数组中,再遍历
	nodeList := make([]*ListNode, 0)  // 存放链表的数组
	for headA != nil{
		nodeList = append(nodeList, headA)
		headA = headA.Next
	}
	for headB != nil{
		for _, node := range nodeList{
			if node == headB{
				return node
			}	
		} 
		headB = headB.Next
	}
	return nil
}

 双指针

先遍历获取两个链表的长度,计算长度差值再通过双指针使两个链表尾部对齐,移动双指针进行比较

func getIntersectionNode(headA, headB *ListNode) *ListNode {
	// 先全遍历到底部获取长度,并判断是否相交
	lenA, lenB := 0, 0  // 两个链表的长度
	dummyA := &ListNode{0, headA}
	dummyB := &ListNode{0, headB}	
	currentA := dummyA
	for currentA.Next != nil{
		lenA++
		currentA = currentA.Next
	}
	currentB := dummyB	
	for currentB.Next != nil{
		lenB++
		currentB = currentB.Next
	}
	if currentA != currentB{  // 不相交
		return nil
	}
	currentA, currentB = dummyA, dummyB
	if lenA > lenB {
		for count:= lenA - lenB; count > 0; count--{
			currentA = currentA.Next	
		}
	}else{
		for count:= lenB - lenA; count > 0; count--{
			currentB = currentB.Next
		}
	}
	for currentA != currentB{
		currentA = currentA.Next
		currentB = currentB.Next
	}
	return currentA
}

142.环形链表II

暴力做法

直接使用一个数组来存放已经遍历过的元素,每次到达一个节点时都判断是否在数组中,若是,则返回该节点;若不是,则下一节点,直到遍历完或找到节点为止

时间复杂度O(n2)

空间复杂度O(n)

func detectCycle(head *ListNode) *ListNode {
	// 暴力做法 使用一个数组存放已经遍历过的元素
	nodeList := make([]*ListNode, 0)  // 存放节点的列表 
	dummyHead := &ListNode{Val: 0, Next: head}
	current := dummyHead
	// 遍历链表并将当前节点放入列表中
	for current.Next != nil {
		for _, node := range nodeList {
			if node == current.Next{
				return node
			}
		}
		nodeList = append(nodeList, current.Next)	
		current = current.Next
	}
	return nil
}

哈希 

在上一种解法的基础上将遍历列表换成查询哈希表,时间复杂度变为O(n),空间复杂度仍为O(n)

func detectCycle(head *ListNode) *ListNode {
	hashTable := make(map[*ListNode]bool) // 哈希表,存放已访问过的节点
	current := head
	for current != nil {
		if _, ok := hashTable[current]; ok{
			return current
		}
		hashTable[current] = true // 标记已访问
		current = current.Next
	}
	return nil
}

双指针                                                                                      

使用快慢两个指针,通过数学推理得到解决方案(详见代码随想录讲解),有一说一,算法题还得看数学啊


func detectCycle(head *ListNode) *ListNode {
	// 使用双指针来解决
	dummyHead := &ListNode{Val: 0, Next: head}
	slow, fast := dummyHead, dummyHead // 一快一慢两个指针
	isMeet := false
	for fast.Next != nil && fast.Next.Next != nil {
		fast = fast.Next.Next // 走两步
		slow = slow.Next      // 走一步
		if fast == slow {
			// 相遇,但此时不是环的起始节点
			isMeet = true
			break
		}
	}
	if !isMeet {  // 没相遇
		return nil
	}
	slow = dummyHead
	for slow != fast {
		slow = slow.Next
		fast = fast.Next
	}
	return slow
}

总结

链表方面主要掌握三个思想:

1、虚拟头节点:将头部节点的边界问题统一化,方便编写代码

2、栈:单向链表无法逆向访问,那就开个数组存起来

3、快慢指针:方便访问上一节点

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值