C#力扣链表题目逐行讲解 初学者必看(题目会更新)

在大部分程序员眼里是不起眼的小题目,我自己是做了挺久的,希望能帮到你
203. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

分析:不知道初学者有没有next左右两边分不清什么意思的情况,我是这样理解的,比如这道题中的temp它本质上就是一个节点,所以temp = xxxx,这里就代表的是temp节点的值等于xxxx;那temp.next = xxxx,这里temp.next可以理解为temp指向下一个节点的指针,xxxx此时代表的是目标节点。再简单点来说,左边.next都是代表指针,右边.next都是代表节点。
回到题目,本题利用虚拟头节点的思想,将头节点的前一个节点设置为虚拟头,同时对temp节点赋值虚拟头节点,为什么要有temp这个临时节点呢?因为题目最终要返回的就是头节点,所以用temp代表头节点,参与运算,最后返回头节点的值,值也不会被改变。
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     public int val;
 *     public ListNode next;
 *     public ListNode(int val=0, ListNode next=null) {
 *         this.val = val;
 *         this.next = next;
 *     }
 * }
 */
public class Solution {
    public ListNode RemoveElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(0,head);
        ListNode temp = dummyHead;
        while(temp.next != null)
        {
            if(temp.next.val == val)
            {
                temp.next = temp.next.next;
            }
            else
            {
                temp = temp.next;
            }
        }
        return dummyHead.next;
    }
}
    }
}

206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

分析:pre(preview)用来代表前一个节点(null),curr(current)代表当前节点,比如从第一个节点 也就是1开始反转,需要用temp先记录一下2的值(因为1反转的时候,连接2的这个指针会断开)将curr.next指针指向prev,将pre = curr,也就是对pre节点赋值curr节点,将pre节点移动到curr节点,同时curr也移动到temp节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     public int val;
 *     public ListNode next;
 *     public ListNode(int val=0, ListNode next=null) {
 *         this.val = val;
 *         this.next = next;
 *     }
 * }
 */
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while(curr != null)
        {
            ListNode temp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = temp;
        }
        return prev;
    }
}

707. 设计链表

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

MyLinkedList() 初始化 MyLinkedList 对象。
int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
[“MyLinkedList”, “addAtHead”, “addAtTail”, “addAtIndex”, “get”, “deleteAtIndex”, “get”]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3

分析:题目很长 但是别害怕,本质其实就是实现几个方法
1.获取链表第index个节点的数值
2.在链表的最前面插入一个节点
3.在链表的最后面插入一个节点
4.在链表第index个节点前面插入一个节点
5.删除链表的第index个节点
本题我使用的是单链表,具体都在代码中逐行分析了

public class ListNode
{
	//首先,我们需要创建一个“概念”,也就是类,就是节点
	//节点中需要包含两个元素,一个是值val,一个是next代表下一个节点
    public int val;
    public ListNode next;
    //构造函数中初始化两个变量,next指向null就行,节点我们自己赋值
    public ListNode(int val) 
    {
        this.val = val;
        this.next = null;
    }
}
public class MyLinkedList
{
	//链表需要头节点和元素数量
    ListNode head;
    int count;

	//构造函数初始化
    public MyLinkedList()
    {
        head = null;
        count = 0;
    }
	
	//首先是根据索引得到元素
    public int Get(int index)
    {
    	//index在本题中是从0开始的,index = count = 1 是错的
    	//而不是其他题目中index从1开始,所以此处是>=,都要返回-1
        if(index < 0 || index >= count) return - 1;
        ListNode curr = head;
        //for循环就是用来移动curr节点的,如果上面两道题也会做的话,这里可以看懂
        for(int i = 0; i < index; i++)
        {
            curr = curr.next;
        }
        //头节点不会进入for循环,所以直接会返回值
        return curr.val;
    }

    public void AddAtHead(int val)
    {
        AddAtIndex(0, val);
    }

    public void AddAtTail(int val)
    {
        AddAtIndex(count, val);
    }
	
	//上面两个方法都可以由下面这个方法来实现
	
	//在链表第index个节点前面插入一个节点
    public void AddAtIndex(int index, int val)
    {
    	//注意这里是不能=的,因为AddAtHead是添加头节点的方法,会传入index = 0
        if(index > count) return;
		//以下三行是固定的,就是声明一个虚拟头节点,让它的指针指向头节点
		//同时curr拿到虚拟头节点,开始进行运算
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode curr = dummyHead;
		//创建要插入的节点
        ListNode newNode = new ListNode(val);
        for(int i = 0; i < index; i++)
        {
            curr = curr.next;
        }
        //这里举个例子,比如链表是虚拟头,4,5,6,要在4前插入一个节点
        //那就需要让新节点指向4,虚拟头指向新节点即可(这个顺序不能变)
        //如果让虚拟头直接指向新节点的话,那4就会丢失
        //****************************************
        //将新节点的下一个指针 指向 头节点
        newNode.next = curr.next;
        //头节点的下一个指针 指向 新节点
        curr.next = newNode;
        //链表元素+1
        count++;
        //将头节点更新一下
        head = dummyHead.next;
    }

	//删除链表的第index个节点
    public void DeleteAtIndex(int index)
    {
    	//自己尝试理解一下
        if(index >= count) return;
        //上述一样的几行代码
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode curr = dummyHead;
        for(int i = 0; i < index; i++)
        {
            curr = curr.next;
        }
        //移除头节点其实就是将虚拟头节点指向 头节点的下一个节点即可
        //头节点就会丢失
        //然后赋值新的头节点
        curr.next = curr.next.next;
        count--;
        head = dummyHead.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.Get(index);
 * obj.AddAtHead(val);
 * obj.AddAtTail(val);
 * obj.AddAtIndex(index,val);
 * obj.DeleteAtIndex(index);
 */

练习以下三题 可以更加熟悉ACM模式
题目一
在这里插入图片描述
分析:需要自己定义节点、链表和反转,接下来我会逐行写出思想,方便记忆

public class 图书整理 : MonoBehaviour
{
    //题目一 图书整理
    //要求反转一个链表形式的书单
    //例:
    //输入head:[1,2,3,4]
    //输出:[4,3,2,1]
    void Start()
    {
    	//演示使用的是Unity 使用控制台或其他亦可 
        LinkedList list = new LinkedList();
        list.Append(1);
        list.Append(2);
        list.Append(3);
        list.Append(4);
        list.Reverse();
        list.Print();
    }
    //节点类中 就是值和next 构造函数即可
    //next就代表下一个节点
    class Node
    {
        public int val;
        public Node next;
        public Node(int val)
        {
            this.val = val;
            this.next = null;
        }
    }
    //链表类
    class LinkedList
    {
    	//首先链表中都有头节点 所以定义出来
        public Node head;
        //最基本的构造函数要写 就是初始化变量嘛
        public LinkedList()
        {
            head = null;
        }
        //添加节点
        //添加节点就得传进来添加值
        public void Append(int val)
        {
        	//添加值不能直接加入链表对吧 所以需要一个容器也就是节点来存放
            Node newNode = new Node(val);
            //如果链表第一次添加 那头节点就是空的 所以新节点就是头节点
            if(head == null)
            {
                head = newNode;
            }
            //如果不是第一次添加 
            else
            {
            	//因为头节点是不能动的 需要想到有一个临时变量去代表头节点进行移动
                Node temp = head;
                //因为要找的是最后一个节点 所以不能写temp != null
                //如果temp = null 那就拿不到最后一个节点了
                //while循环 加入头节点的下一个节点不为空
                while(temp.next != null)
                {
                	//那就移动temp去等于下一个节点
                    temp = temp.next;
                }
                //直到temp.next == null 说明temp就是最后一个节点对吧
                //所以直接让最后一个节点 指向新的节点 即可
                temp.next = newNode;
            }
        }
        //反转
        public void Reverse()
        {	
        	//定义三个变量 prev代表前一个 curr代表当前节点 next代表下一个节点
        	//做了前面的题目 你应该知道 下一个节点必须被记录 这样反转的时候才不会丢失
            Node prev = null;
            Node curr = head;
            Node next = null;
            //所以当前节点不为空
            while(curr != null)
            {
            	//先记录下一个节点的值
                next = curr.next;
                //再让当前节点的指针指向前一个节点 也就是null节点
                curr.next = prev;
                //指向完成了 该移动了 那就先移动prev节点到curr节点
                prev = curr;
                //再让curr节点指向刚才记录的下一个节点
                curr = next;
                //注意顺序不能调换
            }
            //curr == null
            //说明是最后一个节点的下一个节点 也就是null节点
            //那head头节点等于前一个节点 才是最后一个节点
            head = prev;
        }
        //打印
        public void Print()
        {
            Node temp = head;
            while(temp != null)
            {
                Debug.Log(temp.val);
                temp = temp.next;
            }
        }
    }
}

在这里插入图片描述
题目二
在这里插入图片描述
分析:在上一题的基础上添加一个删除方法即可(重复代码未截取)
(1)删除头节点,那直接让head = head.next就是删除了,头节点丢失就是删除头节点
(2)删除其他节点,记录删除节点的下一个节点,然后让前一个节点指向该节点,这样删除节点就会丢失

	void Start()
    {
        LinkedList list = new LinkedList();
        list.Append(4);
        list.Append(5);
        list.Append(1);
        list.Append(9);
        //list.Reverse();
        list.Delete(5);
        list.Print();
    }
	public void Delete(int val)
        {
        	//头节点为空 return
            if(head == null)
            {
                return;
            }
            //删除头节点
            if (val == head.val)
            {
            	//直接移动头节点到下一个节点即可
                head = head.next;
                return;
            }
            //使用两个变量 没有使用prev为什么呢?
            //prev代表前一个节点 是为了删除头节点的时候 头节点可以指向前一个节点
            //这里头节点已经处理过了 就不需要prev节点了
            Node curr = head;
            Node next = head.next;
            //假设next一直移动到最后 就代表最后一个节点 所以当next节点不为空进入循环
            while(next != null)
            {
            	//先处理next节点 当next节点等于目标值时
                if (next.val == val)
                {
                	//(看不懂这里的请看本篇第一题讲解)
                	//就需要用next记录删除节点的下一个节点
                    next = next.next;
                    //curr当前节点直接指向该节点 这样删除节点就会丢失 就是删除了
                    curr.next = next;
                    return;
                }
                else
                {
                	//如果next不等于目标值
                	//那就移动curr和next节点即可
                    curr = next;
                    next = curr.next;
                }
            }
        }

在这里插入图片描述
题目三
在这里插入图片描述
分析:题意就是在一个链表中,一个节点可以有两个指针分别是next和random(随机指针),它可以指向任意的节点或者null,最后要求返回返回出该链表,也就是要得到各个节点的两个指针的指向。
(1)下图就是题目要求(画的不好 见谅)
在这里插入图片描述
(2)那根据题意,我们可以通过复制一个一模一样的节点,分别插入到原来的节点之后,将这个复制节点去实现random的指向,最后再将这个以复制节点为首的链表脱离出来,如下图
在这里插入图片描述

public class 复制链表 : MonoBehaviour
{
    //题目三
    //请实现copyRandomList函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next指针指向下一个节点
    //还有一个random指针指向链表中的任意节点或者nu11
    void Start()
    {
        Node n0 = new Node(7);
        Node n1 = new Node(3);
        Node n2 = new Node(1);
        Node n3 = new Node(0);
        Node n4 = new Node(2);

        n0.next = n1;n0.random = null;
        n1.next = n2;n1.random = n0;
        n2.next = n3;n2.random = n4;
        n3.next = n4;n3.random = n2;
        n4.random = n0;

        Node newList = CopyRandomList(n0);
        Print(newList);
    }
    //首先就是节点的定义 多了一个random
    public class Node
    {
        public int val;
        public Node next;
        public Node random;
        public Node(int val)
        {
            this.val = val;
            this.next = null;
            this.random = null;
        }
    }
    //复制链表函数
    public Node CopyRandomList(Node head)
    {
    	//传入头节点
        if(head == null)
        {
            return null;
        }
        //我们要将一个初始链表构造出来
        Node curr = head;
        while(curr != null)
        {
        	//定义复制节点
            Node copy = new Node(curr.val);
            //以下两个顺序不能变(不懂就再看一下本篇第一题)
            //复制节点指向原节点的下一个节点
            copy.next = curr.next;
            //当前节点指向复制节点
            curr.next = copy;
            //移动curr
            curr = copy.next;
        }
        //结束后记得将curr回到头节点
        curr = head;
        //接下来就是处理random指针
        while(curr != null)
        {
        	//随机指针不为空才可以去赋值
            if(curr.random != null)
            {
            	//有点绕 举个例子 比如前两个节点 7-复制7-3-复制3 其中复制3要指向复制7
            	//那原节点(3)的下一节点(复制3)的随机指针(random)指向
            	//原节点(3)的随机指针(7)的下一节点(复制7)
                curr.next.random = curr.random.next;
            }
            //移动curr
            curr = curr.next.next;
        }
        //curr归位
        curr = head;
        //构建复制链表的头节点和curr节点
        Node copyHead = head.next;
        Node copyCurr = copyHead;
        //curr不为空即可 因为curr后面还有复制节点 最后节点访问curr.next.next是null
        while(curr != null)
        {
        	//将原链表拼接回去
            curr.next = curr.next.next;
            //移动
            curr = curr.next;
            //复制节点.next不为空 因为复制节点后面没有节点 最后节点访问copyCurr.next.next会报错
            if(copyCurr.next != null)
            {
            	//将复制链表拼接出来
                copyCurr.next = copyCurr.next.next;
                //移动
                copyCurr = copyCurr.next;
            }
        }
        return copyHead;
    }
    void Print(Node head)
    {
        Node temp = head;
        while(temp != null)
        {
            Debug.Log(temp.val + " random:" + (temp.random == null ? -1 : temp.random.val));
            temp = temp.next;
        }
    }
}

在这里插入图片描述

  • 36
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值