9.代码随想录-链表(上)

文章介绍了如何在LeetCode中处理链表操作,包括移除具有特定值的节点、设计链表实现基本操作、反转链表以及两两交换相邻节点。使用了虚拟头结点的方法来简化问题解决过程。
摘要由CSDN通过智能技术生成

一、移除链表元素 

        题干:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

        测试地址:. - 力扣(LeetCode)

        操作原链表

        首先让头结点进行遍历,找到第一个节点的Val不为val的节点作为头结点。然后设置变量cur指向head,随后进行遍历。同时这里for cur != nil && cur.Next != nil 必须加上cur!=nil的原因是head可能指向nil,然后让cur指向head并操作cur.Next是会报错的。或者在cur赋值语句前加上对head == nil的判断,如果是则return,也可以通过测试。

        核心:时刻注意不要操作空指针

func removeElements(head *ListNode, val int) *ListNode {
	for head != nil && head.Val == val {
		head = head.Next
	}
	cur := head
	for cur != nil && cur.Next != nil {
		if cur.Next.Val == val {
			cur.Next = cur.Next.Next
		} else {
			cur = cur.Next
		}
	}
	return head
}

        虚拟头结点

        在原链表上操作首先是要找到第一个值不为val的节点,然后以此为头再进行遍历操作。而如果使用虚拟头结点,换言之直接找到了第一个不为val的节点,直接对后续进行遍历即可。最后返回的之后返回虚拟头结点的next即可。

func removeElements(head *ListNode, val int) *ListNode {
	dummy := &ListNode{}
	dummy.Next = head
	cur := dummy
	for cur.Next != nil {
		if cur.Next.Val == val {
			cur.Next = cur.Next.Next
		}else {
			cur = cur.Next
		}
	}
	return dummy.Next
}

 二、设计链表

        题干:

        在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

        测试地址:. - 力扣(LeetCode)

        这里好像必须要用虚拟头结点法,不然因为go语言中的引用传递也不过是拷贝一份指针指向堆中变量的位置,所以没有办法对原始指针进行更改,所以这里的addAtHead没有办法更新,当然在自己的代码里面可以通过

a = a.AddAtHead(7)

        进行更改,但是在lc里面不会这样进行操作,所以过不了检测。但是也有一种方案是在头结点之后插入节点,然后把它的值和头结点的值进行互换,这里是采用了这种方式

非虚拟头结点:

func Constructor() MyLinkedList {
	return MyLinkedList{}
}

func (this *MyLinkedList) Get(index int) int {
	node := this
	for i := 0; i <= index; i++ {
		if i == index {
			fmt.Println(node.Value)
			return node.Value
		}
		if node.Next == nil {
			return -1
		}
		node = node.Next
	}
	return -1
}

func (this *MyLinkedList) AddAtHead(val int) *MyLinkedList {
	node := MyLinkedList{
		Value: val,
		Next:  nil,
	}
	node.Next = this
	this = &node
	return this
}

func (this *MyLinkedList) AddAtTail(val int) {
	tail := this
	for tail.Next != nil {
		tail = tail.Next
	}
	node := MyLinkedList{
		Value: val,
		Next:  nil,
	}
	tail.Next = &node
}

func (this *MyLinkedList) AddAtIndex(index int, val int) {
	if index < 0 {
		this.AddAtHead(val)
		return
	}
	var size int
	cur := this
	for cur != nil {
		size++
		cur = cur.Next
	}
	if size == index {
		this.AddAtTail(val)
	}
	if size > index {
		last := this
		cur = this
		for i := 0; i < index; i++ {
			last = cur
			cur = cur.Next
		}
		node := MyLinkedList{
			Value: val,
			Next:  nil,
		}
		node.Next = cur
		last.Next = &node
	}
	if size < index {
		return
	}
}

func (this *MyLinkedList) DeleteAtIndex(index int) {
	if index < 0 {
		return
	}
	var size int
	cur := this
	for cur != nil {
		size++
		cur = cur.Next
	}
	size = size - 1
	if index <= size {
		last := this
		cur = this
		for i := 0; i < index; i++ {
			last = cur
			cur = cur.Next
		}
		last.Next = cur.Next
	}
}

虚拟头结点

        使用虚拟头结点来作为头结点,从而避免了用addthead函数时引用传递导致的头结点无法更新的情况。同时也记录了链表的大小,方便确定index和链表大小的关系。

//单链表实现
package main

import (
	"fmt"
)

type SingleNode struct {
	Val  int         // 节点的值
	Next *SingleNode // 下一个节点的指针
}

type MyLinkedList struct {
	dummyHead *SingleNode // 虚拟头节点
	Size      int         // 链表大小
}


/** Initialize your data structure here. */
func Constructor() MyLinkedList {
	newNode := &SingleNode{ // 创建新节点
		-999,
		nil,
	}
	return MyLinkedList{ // 返回链表
		dummyHead: newNode,
		Size:      0,
	}

}

func (this *MyLinkedList) Get(index int) int {
	if this == nil || index < 0 || index >= this.Size { // 如果索引无效则返回-1
		return -1
	}
	cur := this.dummyHead.Next   
	for i := 0; i < index; i++ { 
		cur = cur.Next
	}
	return cur.Val // 返回节点值
}

func (this *MyLinkedList) AddAtHead(val int) {
	newNode := &SingleNode{Val: val}   // 创建新节点
	newNode.Next = this.dummyHead.Next // 新节点指向当前头节点
	this.dummyHead.Next = newNode      // 新节点变为头节点
	this.Size++                        // 链表大小增加1
}


func (this *MyLinkedList) AddAtTail(val int) {
	newNode := &SingleNode{Val: val} // 创建新节点
	cur := this.dummyHead            // 设置当前节点为虚拟头节点
	for cur.Next != nil {            // 遍历到最后一个节点
		cur = cur.Next
	}
	cur.Next = newNode // 在尾部添加新节点
	this.Size++        // 链表大小增加1
}


func (this *MyLinkedList) AddAtIndex(index int, val int) {
	if index < 0 { // 如果索引小于0,设置为0
		index = 0
	} else if index > this.Size { // 如果索引大于链表长度,直接返回
		return
	}

	newNode := &SingleNode{Val: val} // 创建新节点
	cur := this.dummyHead            // 设置当前节点为虚拟头节点
	for i := 0; i < index; i++ {     // 遍历到指定索引的前一个节点
		cur = cur.Next
	}
	newNode.Next = cur.Next // 新节点指向原索引节点
	cur.Next = newNode      // 原索引的前一个节点指向新节点
	this.Size++             // 链表大小增加1
}

func (this *MyLinkedList) DeleteAtIndex(index int) {
	if index < 0 || index >= this.Size { // 如果索引无效则直接返回
		return
	}
	cur := this.dummyHead        // 设置当前节点为虚拟头节点
	for i := 0; i < index; i++ { // 遍历到要删除节点的前一个节点
		cur = cur.Next
	}
	if cur.Next != nil {
		cur.Next = cur.Next.Next // 当前节点直接指向下下个节点,即删除了下一个节点
	}
	this.Size-- // 注意删除节点后应将链表大小减一
}



三、反转链表

        题干:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

        测试地址:. - 力扣(LeetCode)

        思路很简单,就是本来节点1连向节点2,现在让节点1指向空,节点2指向节点1即可,但是这样的问题会是如果后面有节点3,那不就找不到节点3了吗,所以需要多用一个变量即可。

        循环版本

 func reverseList(head *ListNode) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}
	var last, cur  *ListNode
	last, cur = nil, head
	for cur != nil {
		next := cur.Next
		cur.Next = last
		last = cur
		cur = next
	}
	return last
}

        递归版本

func reverseList(head *ListNode) *ListNode {
	return reverse(nil, head)
}

func reverse(last, cur *ListNode) *ListNode {
	if cur == nil {
		return last
	}
	next := cur.Next
	cur.Next = last
	return reverse(cur, next)
}

四、两两交换链表中的节点

        题干:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

        测试地址:. - 力扣(LeetCode)

        建议画图,还是比较好搞懂的。虚拟头结点法确实是好用的。

func swapPairs(head *ListNode) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}
	dummy := &ListNode{Next: head}
	cur := dummy
	for cur.Next != nil && cur.Next.Next != nil {
		temp := cur.Next
		cur.Next = temp.Next
		cur = temp.Next
		temp.Next = cur.Next
		cur.Next = temp
        cur = temp
	}
	return dummy.Next
}

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这道题目需要我们实现一个循环链表,并在其中删除指定位置的节点。具体实现可以分为以下几个步骤: 1. 定义循环链表的节点结构体,包括数据域和指向下一个节点的指针域。 2. 定义循环链表的头节点,并初始化为空。 3. 读入输入的数据,将其插入到循环链表的尾部。 4. 遍历循环链表,找到需要删除的节点的前一个节点。 5. 将需要删除的节点从链表中摘除,并释放其内存空间。 6. 遍历循环链表,输出剩余节点的数据。 下面是具体的代码实现: ``` #include <iostream> using namespace std; // 定义循环链表的节点结构体 struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {} }; int main() { // 定义循环链表的头节点 ListNode* head = NULL; // 读入输入的数据,将其插入到循环链表的尾部 int num; while (cin >> num && num != 0) { ListNode* node = new ListNode(num); if (head == NULL) { head = node; head->next = head; } else { ListNode* cur = head; while (cur->next != head) { cur = cur->next; } cur->next = node; node->next = head; } } // 遍历循环链表,找到需要删除的节点的前一个节点 ListNode* pre = head; while (pre->next != head) { pre = pre->next; } // 将需要删除的节点从链表中摘除,并释放其内存空间 ListNode* cur = head; while (cur->next != head) { if (cur->val % 2 == 0) { pre->next = cur->next; ListNode* temp = cur; cur = cur->next; delete temp; } else { pre = cur; cur = cur->next; } } if (cur->val % 2 == 0) { pre->next = cur->next; delete cur; } // 遍历循环链表,输出剩余节点的数据 cur = head; while (cur->next != head) { cout << cur->val << "\t"; cur = cur->next; } cout << cur->val << endl; return 0; } --相关问题--:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值