链表算法
目录
链表算法
初始化节点
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)
}
选择了IT,必定终身学习