C#数据结构-单链表实现及常见面试题

 

1. 单链表的插入,删除,搜索

2. 反转链表

实现思路:

  • 表头指向原链表的尾结点
  • 创建一个空的prev指针,指向当前结点,也就是记录下一个结点的上一个结点
  • 修改结点的next指针,指向prev指向的结点,注:头结点的next指向空结点
  • 依次从头结点进行遍历,即可

3. 合并两个递增排序的链表

实现思路:

假设上面虚线内的两个链表,上面的链表为链表1,下面的链表为链表2。

  • 如果链表1的头结点小于链表2的头结点的值,把链表1的头结点作为合并后链表的头结点;
  • 在剩余结点中,链表2的头结点小于链表1的头结点的值,把链表2的头结点链接在之前已经合并好的链表的头结点之后。
  • 依次递归完成合并链表。

4. 遍历一次,找到倒数第k个结点

实现思路:

正常找到倒数第K个结点,可以先反转链表,然后遍历链表到第K个结点即可。不过需要两次遍历。

  • 创建两个指针,第一个指针先从0遍历到第k - 1个结点,然后第二个指针从头结点开始与第一个指针(当前位置)一起遍历。
  • 当第一个指针遍历到尾结点时,第二个指针指向的结点就是第K个结点。

5. 给定头指针和一个结点指针,在O(1)时间内删除该结点

实现思路:

一般删除结点,都要遍历找到要删除的结点,时间复杂度为O(n)

  • 把删除结点的下一个结点内容覆盖当前删除结点的内容
  • 删除结点指向删除结点的下下个结点,也就是跳过了删除结点的下一个结点
  • 这里需要注意的是:头结点和尾结点的处理。
  • 如果头结点是唯一的结点,删除头结点后,需要把头结点设置为null。
  • 如果删除结点是尾结点,尾结点没有下一个结点,这时就需要从头开始遍历,删除尾结点。

6. 输入两个链表,找到它们的第一个公共结点。

实现思路:

首先,要明确一点,从第一个公共结点之后,所有的结点都是重合的,不可能再分叉。也就是向左旋转的Y的形状。所以,如果有公共结点,一定不会在长链表比短链表多出的前半部分。

  • 首先,遍历得到两个链表的长度,并得到他们长度的差值k。
  • 长的链表先从0开始遍历到第k-1个结点,然后,短链表从头开始和长链表(当前位置)一起遍历。

7. 判断链表是否有环

实现思路:快慢指针

首先创建两个指针1和2(在C#里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。

此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

假设链表的环长是S。那么循环会进行S次,可以简单理解为O(N)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。

8. 如何找出有环链表的入环点?

实现思路:首先判断是否有环,然后找到入环点。如何找到入环点,这里需要一点推导,

  • 假设链表环的长度为r,慢指针走了s步,快指针就走了2s步。当快慢指针相遇时,2s = s + r,得到s = r。
  • 假设链表的总长度为L,环入口与相遇点距离为x,表头到环入口点的距离为a,得出a + x = s,根据上面的结论,得到a + x = r,交换一下得到a = r - x。
  • 同时,L - a = r,也就是链表总长度减去表头到环入口点的距离等于环的长度,这个直接可以得到的结论。
  • 因此,a = L - a - x。(L - a - x)就是相遇点到环入口的距离。

最终得到结论:表头到环入口点的距离等于相遇点到环入口的距离。

根据上面的结论,我们可以从链表头结点、相遇点分别设置一个指针,每次走一步,两个指针必然相遇,而且相遇第一点就是环入口点。

using System;
using System.Collections;
using System.Collections.Generic;

namespace StructScript
{
    public class SequentialLinkedList<T>
    {
        // 表头结点
        private Node mHead;
        private int mCount;
        // 比较器,用于合并两个链表时,比较
        private Comparer<T> mComparer;

        public SequentialLinkedList()
        {
            mHead = null;
            mCount = 0;
            mComparer = Comparer<T>.Default;
        }

        // 添加了一个circleNode参数,可以人为设置有环链表,一般不需要设置
        // 为了设置有环链表,同时还增加了返回值,返回当前插入的结点
        public Node Add(T value, Node circleNode = null)
        {
            if (value == null)
            {
                throw new ArgumentNullException();
            }
            if (mHead == null)
            {
                mHead = new Node(value, null);
                mCount++;
                return mHead;
            }
            else
            {
                if (!Contains(value))
                {
                    //这里每添加一个新的项,可以往前依次添加,新增项作为新的表头
                    //mFirst = new Node(value, mFirst);

                    //往后添加的,需要遍历所有节点,在最后的位置添加新的项
                    Node node = mHead;
                    while (node.next != null)
                    {
                        node = node.next;
                    }
                    node.next = new Node(value, circleNode);
                    mCount++;
                    return node.next;
                }
            }
            return null;
        }

        public bool Remove(T value)
        {
            if (value == null)
            {
                throw new ArgumentNullException();
            }
            if (mHead == null)
            {
                return false;
            }
            // 如果是表头
            if (value.Equals(mHead.value))
            {
                mHead = mHead.next;
                mCount--;
                return true;
            }
            // 不是表头
            Node node = mHead;
            while (node.next != null)
            {
                if (value.Equals(node.next.value))
                {
                    node.next = node.next.next;
                    mCount--;
                    return true;
                }
                node = node.next;
            }
            return false;
        }

        // 判断链表是否有环
        public bool HasCircle()
        {
            Node slowNode = mHead;
            Node fastNode = mHead;
            // 使用快慢指针,慢指针每走一步,快指针走两步
            while(fastNode != null && fastNode.next != null)
            {
                slowNode = slowNode.next;
                fastNode = fastNode.next.next;
                // 如果快慢指针相遇了则有环
                if(slowNode.value.Equals(fastNode.value))
                {
                    return true;
                }
            }
            return false;
        }

        // 找到环链表的环入口点
        public Node FindEnterCircleNode()
        {
            bool hasCircle = false;
            Node slowNode = mHead;
            Node fastNode = mHead;
            // 使用快慢指针,慢指针每走一步,快指针走两步
            while (fastNode != null && fastNode.next != null)
            {
                slowNode = slowNode.next;
                fastNode = fastNode.next.next;
                // 如果快慢指针相遇了则有环
                if (slowNode.value.Equals(fastNode.value))
                {
                    hasCircle = true;
                    break;
                }
            }
            // 如果是环链表
            if (hasCircle)
            {
                // 一个指针从相遇点开始、一个从表头结点开始遍历
                // 每次走一步,相遇的第一个结点就是环入口点
                fastNode = mHead;
                while(slowNode != null && slowNode.next != null)
                {
                    // 这里一定要先进行比较,再往下走
                    // 如果链表整个都是环,当环的长度为奇数时,相遇点就是头结点
                    if (slowNode.value.Equals(fastNode.value))
                    {
                        return fastNode;
                    }
                    slowNode = slowNode.next;
                    fastNode = fastNode.next;
                }
            }
            return null;
        }

        // 给定两个链表,返回第一个公共结点
        public Node GetPublicNode(SequentialLinkedList<T> st1, SequentialLinkedList<T> st2)
        {
            int st1Length = st1.Count;
            int st2Length = st2.Count;
            if(st1Length == 0 || st2Length == 0)
            {
                return null;
            }
            int diff = 0;
            Node node1;
            Node node2;
            if(st1Length > st2Length)
            {
                diff = st1Length - st2Length;
                node1 = st1.mHead;
                node2 = st2.mHead;
            }
            else
            {
                diff = st2Length - st1Length;
                node1 = st2.mHead;
                node2 = st1.mHead;
            }
            // 先在长链表上执行diff步,然后同时在两链表上遍历
            for (int i = 0; i < diff; i++)
            {
                node1 = node1.next;
            }
            // 因为在第一个公共结点之后,两链表的所有结点都是重合的,相同的
            // 所以,公共结点不可能在长链表比短链表多出来的前半部分,直接比较剩下的部分即可
            while(node1 != null && node2 != null)
            {
                if(node2.value.Equals(node1.value))
                {
                    return node1;
                }
                node1 = node1.next;
                node2 = node2.next;
            }
            return null;
        }

        // 给定头指针和一个结点指针,在O(1)时间内删除该结点
        public void RemoveNode(Node node)
        {
            if (node != null)
            {
                // 删除结点是头结点
                if (node.value.Equals(mHead.value))
                {
                    mHead = mHead.next;
                    mCount--;
                    return;
                }
                // 删除结点是尾结点
                else if (node.next == null)
                {
                    Node node1 = mHead;
                    while (node1.next != null)
                    {
                        if (node.value.Equals(node1.next.value))
                        {
                            node1.next = node1.next.next;
                            mCount--;
                            return;
                        }
                        node1 = node1.next;
                    }
                }
                else
                {
                    // 用下一个指针的内容覆盖当前结点的内容
                    node.value = node.next.value;
                    // 当前结点指向下下个结点
                    node.next = node.next.next;
                    mCount--;
                }
            }
        }

        // 反转链表
        public void Reverse()
        {
            Node node = mHead;
            Node prev = null;
            while (node != null)
            {
                // 保存原结点的next指向的结点,因为下面会修改结点next指向的结点
                Node next = node.next;
                // 表头指向原链表的尾结点
                if (node.next == null)
                {
                    mHead = node;
                }
                node.next = prev;
                // 保存当前节点作为下一个节点的next指向的节点
                prev = node;
                // node指向下一个节点,继续遍历
                node = next;
            }
        }

        // 合并两个增量排序的链表
        public SequentialLinkedList<T> Merge(SequentialLinkedList<T> st1, SequentialLinkedList<T> st2)
        {
            st1.mHead = Merge(st1.mHead, st2.mHead);
            return st1;
        }

        public Node Merge(Node head1, Node head2)
        {
            if (head1 == null)
            {
                return head2;
            }
            else if (head2 == null)
            {
                return head1;
            }
            Node mergeHead = new Node(default(T), null);
            // 如果链表1的头结点小于链表2的头结点,把链表1的头结点作为合并链表的头结点
            // 剩余结点,继续比较头结点大小,并把较小的头结点放在合并链表的头结点之后。
            if (mComparer.Compare(head1.value, head2.value) < 0)
            {
                mergeHead = head1;
                mergeHead.next = Merge(head1.next, head2);
            }
            else
            {
                mergeHead = head2;
                mergeHead.next = Merge(head1, head2.next);
            }
            return mergeHead;
        }

        public void Print()
        {
            for (Node current = mHead; current != null; current = current.next)
            {
                Console.Write(current.value + " ");
            }
        }

        // 找到链表中倒数第k个结点,在一次遍历中
        public Node FindKFromTail(SequentialLinkedList<T> st1, int k)
        {
            if (st1.mHead == null)
            {
                return null;
            }
            // 创建两个指针p1,p2,
            // p1先遍历到k-1的位置,然后p2开始遍历
            // 当p1遍历到链表尾部时,p2的位置就是倒数第k个结点
            Node p1 = st1.mHead;
            for (int i = 0; i < k - 1; i++)
            {
                // 防止k小于链表长度
                if (p1.next != null)
                {
                    p1 = p1.next;
                }
                else
                {
                    return null;
                }
            }
            Node p2 = st1.mHead;
            while (p1.next != null)
            {
                p1 = p1.next;
                p2 = p2.next;
            }
            return p2;
        }

        public void Clear()
        {
            for (Node current = mHead; current != null; current = current.next)
            {
                Node tempNode = current;
                tempNode.next = null;
                tempNode.value = default(T);
            }
            mHead = null;
            mCount = 0;
        }

        public bool Contains(T value)
        {
            if (value == null)
            {
                throw new ArgumentNullException();
            }
            for (Node current = mHead; current != null; current = current.next)
            {
                if (value.Equals(current.value))
                {
                    return true;
                }
            }
            return false;
        }

        public int Count
        {
            get
            {
                return mCount;
            }
        }

        public class Node
        {
            public T value;
            public Node next;

            public Node(T value, Node next)
            {
                this.value = value;
                this.next = next;
            }
        }
    }
}

附上,测试用例:

using System;

namespace StructScript
{
    public class TestSequentialLinkedList
    {
        static void Main(string[] args)
        {
            // 创建一个有环链表
            SequentialLinkedList<int> st11 = new SequentialLinkedList<int>();
            st11.Add(1);
            st11.Add(2);
            SequentialLinkedList<int>.Node newNode = st11.Add(3);
            st11.Add(4);
            st11.Add(5);
            st11.Add(6);
            st11.Add(7, newNode);

            Console.WriteLine("当前链表是否有环:" + st11.HasCircle());
            Console.WriteLine("当前链表的环入口点是:" + st11.FindEnterCircleNode().value);

            SequentialLinkedList<int> st = new SequentialLinkedList<int>();
            st.Add(1);
            st.Add(3);
            st.Add(5);
            st.Add(7);

            SequentialLinkedList<int> st1 = new SequentialLinkedList<int>();
            st1.Add(2);
            st1.Add(4);
            st1.Add(6);
            st1.Add(8);


            //if (st.Contains(1))
            //{
            //    Console.WriteLine("包含1");
            //}
            //st.Remove(1);

            //得到第一个公共结点
            SequentialLinkedList<int>.Node node = st.GetPublicNode(st, st1);
            if (node != null)
            {
                Console.WriteLine("第一个公共结点是:" + node.value);
            }
            else
            {
                Console.WriteLine("没有公共结点");
            }

            // 合并两个链表
            st = st.Merge(st, st1);
            st.Print();

            Console.WriteLine();
            int value = st.FindKFromTail(st, 4).value;
            Console.WriteLine("倒数第4个的值:" + value);

            Console.WriteLine("反转链表:");
            st.Reverse();
            st.Print();

            Console.ReadLine();
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值