链表算法

链表算法

初始化节点

type ListNode struct {
	Val  int
	Next *ListNode
}

// 初始化5个节点
func InitNode() *ListNode {
	one := &ListNode{Val: 1}
	two := &ListNode{Val: 2}
	three := &ListNode{Val: 3}
	four := &ListNode{Val: 4}
	five := &ListNode{Val: 5}
	one.Next = two
	two.Next = three
	three.Next = four
	four.Next = five
	return one
}

/*
题目: 读取链表中全部节点
*/
func readNodeAll(node *ListNode) {
	for node != nil {
		fmt.Println(node.Val)
		node = node.Next
	}
}

func main(){
  node:=InitNode()
  readNodeAll(node)
}

1.删除链表的第N个节点

/*
题目: 删除单链表中第N个节点
*/
func DeleteNNode(node *ListNode, n int) *ListNode {
	pre := &ListNode{Next: node} //虚拟头节点
	if n == 1 {
		pre.Next = node.Next
		return pre.Next
	}

	i := 1
	for node != nil {
		i++
		if i == n {
			//删除下一个节点
			node.Next = node.Next.Next
			break
		}
		if node.Next != nil {
			node = node.Next
		} else {
			fmt.Println("超出链表长度")
			break
		}
	}
	return pre.Next
}
func main() {
	node := InitNode()
	n := DeleteNNode(node, 1)
	readNodeAll(n)
}

2.删除链表中倒数第N个节点

/*
题目: 删除链表中倒数第n个节点
leetcode: 19
思路:双指针方法
1.定义两个指针p和q,初始时均指向链表的头节点;
2.把p向后移动n-1个节点,如果p已经到达链表末尾,说明链表长度小于n,无法进行倒数第n个节点的删除操作;
3.此时再同时将p,q向后同步移动,直到p到未节点
4.此时,q指向的节点就是要删除的节点的前一个节点,将q的next指针指向要删除节点的下一个节点;
5.删除,q.next = q.next.next
*/
func removeNthFromEnd(node *ListNode, n int) *ListNode {
	q, p := node, node //q前面节点,p后面节点
	for i := 0; i < n; i++ {
		//p向后移动n个节点
		p = p.Next
	}
	if p == nil {
		//如果是删除的第一个节点
		return node.Next
	}

	for p.Next != nil {
		//p,q同时向后移动,当p到达尾节点,q指向的就是倒数第n个节点
		p = p.Next
		q = q.Next
	}
	//删除节点
	q.Next = q.Next.Next
	return node
}

func main() {
	node := InitNode()
	a := removeNthFromEnd(node, 5)

	readNodeAll(a)
}

3.分块反转链表(K 个一组翻转链表)

/*
题目:K 个一组翻转链表
leetcode:25
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
思路:
把k个为一组,前后断开连接,然后反转这一组,再前后接上连接。分组反转之后组的头节点就变成尾节点,尾节点就变成头节点
*/
func reverseKGroup(head *ListNode, k int) *ListNode {
	dump := &ListNode{Next: head} //虚拟头结点

	pre := dump //上一组反转的尾节点
	end := dump //这一组需要反转的尾节点
	for end != nil {
		for i := 0; i < k; i++ {
			end = end.Next
			if end == nil {
				return dump.Next
			}
		}
		next := end.Next  //下一组开始的节点
		end.Next = nil    //这一组与下一组断开连接
		start := pre.Next //这一组的第一个节点
		pre.Next = nil    //这一组与上一组断开连接

		pre.Next = myReverse(start) //pre.Next=反转之后的头节点
		start.Next = next           //现在已经反转了,start变成尾节点了,跟下一组连接

		pre = start
		end = start
	}
	return dump.Next
}
func myReverse(node *ListNode) *ListNode {
	pre := &ListNode{} //上一个节点
	cur := node        //当前节点
	for cur != nil {
		next := cur.Next //下一个节点
		cur.Next = pre
		pre = cur
		cur = next
	}
	return pre
}

func ReverseKGroupRun() {
	node := InitNode()
	n := reverseKGroup(node, 2)
	readNodeAll(n)
}

4.反转链表 I

/*
题目:反转链表 I
leetcode:206
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
思路:
1.pre存储当前节点的上一个节点
2.curr存储当前节点
3.next存储当前节点的下一个节点,因为当前节点的下一个节点重新指向了上一个节点,原来的下一个节点就找不到了,所以保存下来
4.当前节点的下一个节点指向上一个节点
*/
func reverseList(head *ListNode) *ListNode {
	pre := &ListNode{} //上一个节点
	curr := head       //当前节点
	for curr != nil {
		next := curr.Next //下一个节点
		curr.Next = pre   //当前节点的下一个节点指向上一个节点
		pre = curr        // 向后移动
		curr = next       //向后移动
	}
	return pre
}

func main() {
	node := InitNode()
	n := reverseList(node, 3)
	readNodeAll(n)
}

5.反转链表 II(给定区间反转)

/*
题目:反转链表 II
leetcode:92
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
思路:
1.中间需要反转链表做个截断
2.把截断的做反转
3.把截断的再首尾相连
*/
func reverseBetween(head *ListNode, left, right int) *ListNode {
	// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
	dummyNode := &ListNode{Val: -1}
	dummyNode.Next = head

	pre := dummyNode //字链表的前一个节点
	// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
	// 建议写在 for 循环里,语义清晰
	for i := 0; i < left-1; i++ {
		pre = pre.Next
	}
	// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
	rightNode := pre //字链表的最后一个节点
	for i := 0; i < right-left+1; i++ {
		rightNode = rightNode.Next
	}
	// 第 3 步:切断出一个子链表(截取链表)
	leftNode := pre.Next
	curr := rightNode.Next
	//截断链接
	pre.Next = nil
	rightNode.Next = nil
	// 第 4 步:同第 206 题,反转链表的子区间
	reverseList(leftNode)
	// 第 5 步:接回到原来的链表中
	pre.Next = rightNode //rightNode之前为字链表的最后一个节点,反转后为字链表的第一个节点,需要与前面的重新连接
	leftNode.Next = curr //leftNode之前为字链表的第一个节点,反转后为字链表的最后一个节点,需要与后面的重新连接
	return dummyNode.Next
}

func main() {
	node := InitNode()
	head := reverseBetween(node, 3, 5)
	readNodeAll(head)
}

6.判断是否为环形链表

/*
题目:环形链表
leetcode:141
给你一个链表的头节点 head ,判断链表中是否有环。
输入:1,2,3,4,2
输出:true
解释:因为4节点的next为2,2的next为3,3的next又为4。所以为环形链表

思路:遍历所有节点,用map记录访问过的节点,如果map已访问返回true
*/
func hasCycle(node *ListNode) bool {
	nodeMap := map[*ListNode]struct{}{}
	for node != nil {
		if _, ok := nodeMap[node]; ok {
			return true
		}
		nodeMap[node] = struct{}{}
		node = node.Next
	}
	return false
}
func main() {
	//1,2,3,4,2
	one := &ListNode{Val: 1}
	two := &ListNode{Val: 2}
	three := &ListNode{Val: 3}
	four := &ListNode{Val: 4}
	one.Next = two
	two.Next = three
	three.Next = four
	four.Next = two
	t := hasCycle(one)
	fmt.Println(t)
}

7.两数相加(两个链表相同位置相加)

/*
题目:两数相加
leetcode:2
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
每个链表中的节点数在范围 [1, 100] 内
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

思路:
遍历链表,相同位置的相加,进位向后推,如9+9=18,倒过来81,当前节点为8,向下推1。当到达尾节点时检查进位是否大于0,大于0则还有一个额外节点
*/
func addTwoNumbers(l1, l2 *ListNode) *ListNode {
	head := &ListNode{} //记录新的节点的头节点
	pre := head //新的节点,每次操作都是next,所以需要记录下头节点返回
	temp := 0 //向后进位的值
	for l1 != nil || l2 != nil {
		target1, target2 := 0, 0
		if l1 != nil {
			target1 = l1.Val
			l1 = l1.Next
		}
		if l2 != nil {
			target2 = l2.Val
			l2 = l2.Next
		}
		num := target1 + target2 + temp //相同位置节点数相加
		num, temp = num%10, num/10      //当前节点值,向后进位值
		if pre == nil {
			pre = &ListNode{Val: num}
			head = pre
		} else {
			pre.Next = &ListNode{Val: num}
			pre = pre.Next
		}
	}
	//遍历结束之后,检查还有没有需要进位
	if temp > 0 {
		head.Next = &ListNode{Val: temp}
	}
	return head.Next
}

func main() {
	//l1 = [2,4,3], l2 = [5,6,4]
	two := &ListNode{Val: 2}
	three := &ListNode{Val: 3}
	four := &ListNode{Val: 4}
	four2 := &ListNode{Val: 4}
	five := &ListNode{Val: 5}
	six := &ListNode{Val: 6}

	two.Next = four
	four.Next = three
	l1 := two
	five.Next = six
	six.Next = four2
	l2 := five
	node := addTwoNumbers(l1, l2)
	readNodeAll(node)
}

8.合并两个有序链表

/*
题目:合并两个有序链表
leetcode:21
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
思路:一个虚拟节点pre,始终指向最小的节点
*/
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
	head := &ListNode{} //虚拟头节点
	pre := head         //合并过程中的最后一个节点

	for l1 != nil && l2 != nil {
		if l1.Val <= l2.Val {
			//pre指向更小的节点
			pre.Next = l1
			l1 = l1.Next
		} else {
			//pre指向更小的节点
			pre.Next = l2
			l2 = l2.Next
		}

		//pre向后移动
		pre = pre.Next
	}
	//如果l1中还有节点,剩下的串联
	if l1 != nil {
		pre.Next = l1
	}
	//如果l2中还有节点,剩下的串联
	if l2 != nil {
		pre.Next = l2
	}
	return head.Next
}

func main() {
	one := &ListNode{Val: 1}
	two := &ListNode{Val: 2}
	four := &ListNode{Val: 4}
	one.Next = two
	two.Next = four
	l1 := one

	one1 := &ListNode{Val: 1}
	three1 := &ListNode{Val: 3}
	four1 := &ListNode{Val: 4}
	one1.Next = three1
	three1.Next = four1

	l2 := one1

	node := mergeTwoLists(l1, l2)
	readNodeAll(node)
}

9.删除排序链表中的重复元素 II

/*
题目:删除排序链表中的重复元素 II
leetcode:82
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
思路:由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的
1.if node.next.val==node.next.next.val 第二个重复需要删除 再循环判断后面节点是否重复
*/

func deleteDuplicates(head *ListNode) *ListNode {
	dump := &ListNode{Val: 0, Next: head} //防止第一个就是要删除的节点
	pre := dump
	for pre.Next != nil && pre.Next.Next != nil {
		if pre.Next.Val == pre.Next.Next.Val {
			//第二个节点元素重复
			x := pre.Next.Val
			for pre.Next != nil && pre.Next.Val == x {
				//第N个节点元素重复
				pre.Next = pre.Next.Next
			}
		} else {
			pre = pre.Next
		}
	}
	return dump.Next
}

func main() {
	one := &ListNode{Val: 1}
	two := &ListNode{Val: 2}
	three := &ListNode{Val: 2}
	four := &ListNode{Val: 2}
	five := &ListNode{Val: 5}
	one.Next = two
	two.Next = three
	three.Next = four
	four.Next = five

	node := one
	n := deleteDuplicates(node)
	readNodeAll(n)
}

10.旋转链表(将链表每个节点向右移动 k 个位置)

/*题目:旋转链表
leetcode:61
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
思路:
1.尾节点相连头节点,变成环形链表
2.在环形链表中,找到向后移动length-k个节点,断开。断开节点的next就是新的头节点
*/
func rotateRight(head *ListNode, k int) *ListNode {
	if k == 0 || head == nil || head.Next == nil {
		return head
	}
	length := 1 //链表的长度
	pre := head
	for pre.Next != nil {
		pre = pre.Next //循环完了就是尾节点
		length++
	}
	mod := k % length //向后移动的位数
	if mod == 0 {
		//相当于没有移动
		return head
	}
	pre.Next = head //首尾相连,变环形链表

	mod2 := length - mod //找到尾节点向后移动的位数
	for mod2 > 0 {
		pre = pre.Next //循环完,pre就是尾节点
		mod2--
	}
	ret := pre.Next //尾节点的next就是新链表的头节点
	pre.Next = nil  //断开环形
	return ret
}
func RotateRightRun() {
	node := InitNode()
	n := rotateRight(node, 1)
	readNodeAll(n)
}
posted @ 2024-01-27 11:35  Jeff的技术栈  阅读(18)  评论(0编辑  收藏  举报
回顶部