数据结构(链表)

题目1:反转链表

输入一个链表的头结点,反转该链表,并返回反转后链表的头结点。链表结点定义如下:

struct ListNode {       int       m_nKey;       ListNode* m_pNext; };

分析:这是一道广为流传的微软面试题。由于这道题能够很好的反应出程序员思维是否严密,在微软之后已经有很多公司在面试时采用了这道题。

为了正确地反转一个链表,需要调整指针的指向。与指针操作相关代码总是容易出错的,因此最好在动手写程序之前作全面的分析。在面试的时候不急于动手而是一开始做仔细的分析和设计,将会给面试官留下很好的印象,因为在实际的软件开发中,设计的时间总是比写代码的时间长。与其很快地写出一段漏洞百出的代码,远不如用较多的时间写出一段健壮的代码。

为了将调整指针这个复杂的过程分析清楚,我们可以借助图形来直观地分析。假设下图中l、m和n是三个相邻的结点:

a?b?…?l  mànà…

假设经过若干操作,我们已经把结点l之前的指针调整完毕,这些结点的m_pNext指针都指向前面一个结点。现在我们遍历到结点m。当然,我们需要把调整结点的m_pNext指针让它指向结点l。但注意一旦调整了指针的指向,链表就断开了,如下图所示:

a?b?…l?m  nà…

因为已经没有指针指向结点n,我们没有办法再遍历到结点n了。因此为了避免链表断开,我们需要在调整m的m_pNext之前要把n保存下来。

接下来我们试着找到反转后链表的头结点。不难分析出反转后链表的头结点是原始链表的尾位结点。什么结点是尾结点?就是m_pNext为空指针的结点。

 static Node<T> Reverse<T>(Node<T> node)
        {
            Node<T> current = null;
            Node<T> previous = null;
            Node<T> next = null;
            Node<T> reversedHeader = null;

            current = node;
            while (current != null)
            {
                next = current.Next;
                if (next == null) //如果next是null,则表示已经遍历到最后一个Node,
                {
                    reversedHeader = current;//此时将需要返回的node指向current,不能等到遍历完了返回current,因为那时current必然为null
                }
                current.Next = previous;
                previous = current;
                current = next;
            }

            return reversedHeader;
        }

 题目2:求链表的中间结点 如果链表中间结点总数为奇数,返回中间节点,如果结点总数为偶数,则返回中间两个结点的任意一个。

思路:定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走的快的指针走到链表末尾时,走得慢的指针正好在链表中间。

   static ChainNode<T> GetMiddleNode<T>(ChainNode<T> node)
        {
            if (node == null)
            {
                return null;
            }
            var currentNode = node;
            var skipNode = node;

            while (skipNode != null)
            {
                if (skipNode.Next == null || skipNode.Next.Next == null)
                {
                    skipNode = null;
                }
                else
                {
                    skipNode = skipNode.Next.Next;
                    currentNode = currentNode.Next;
                }
            }
            return currentNode;
        }

题目3:判断一个单向链表是否形成了环形结构

思路:和前面的问题一样,定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上走的慢的指针,那么链表就是环形链表,如果走得快的指针走到了链表的末尾(Next指向null)都没有追上第一个指针,那么链表就不是环形链表。

 

   static bool IsCircle<T>(ChainNode<T> node)
        {
            if (node == null)
            {
                return false;
            }
            bool isCircle = false;
            var currentNode = node;
            var skipNode = node;
            while (skipNode != null && !isCircle)
            {
                if (skipNode.Next == null || skipNode.Next.Next == null)
                {
                    skipNode = null;
                }
                else
                {
                    skipNode = skipNode.Next.Next;
                    currentNode = currentNode.Next;
                    isCircle = Object.ReferenceEquals(skipNode, currentNode);
                }
            }
            return isCircle;
        }

题目4:合并两个排序的链表,输入两个递增排序的链表,合并这两个链表并使新链表中的结点任然是按照递增排序的。

思路:采用递归,考虑鲁棒性

 

static ChainNode<int> MergeNode(ChainNode<int> node1, ChainNode<int> node2)
        {
            if (node1 == null && node2 != null)
            {
                return node2;//鲁棒性考虑
            }
            if (node2 == null && node1 != null)
            {
                return node1;//鲁棒性考虑
            }
            if (node2 == null && node1 == null)
            {
                return null;//鲁棒性考虑
            }

            ChainNode<int> mergedHead = null;

            if (node1.Value < node2.Value)
            {
                mergedHead = node1;
                mergedHead.Next = MergeNode(node1.Next, node2);
            }
            else
            {
                mergedHead = node2;
                mergedHead.Next = MergeNode(node1, node2.Next);
            }

            return mergedHead;
        }

 

题目5:从后往前打印链表

思路:采用递归

  static void PrintReverseNode(ChainNode<int> node)
        {
            if (node != null)
            {
                PrintReverseNode(node.Next);
                Console.WriteLine(node.Value);
            }
        }

 题目6:链表中环的入口结点

思路:

  • 首先利用一快一慢两个指针,判断这个链表是否有环,并且当快指针套圈慢指针时,相交的结点必定在环内。(快的指针始终都沿着环打转)
  • 从相交的结点开始向前Move,当再次回到该结点的时候,表示绕了一圈,即知道了该环有几个结点。
  • 两个指针,一个指针从头开始移动,另一个从结点数开始移动,当两个指针相遇时即环的入口

 

  static ChainNode<T> GetEnterNode<T>(ChainNode<T> node)
        {

            var crossNode = GetCrossNode(node);//得到相交的Node
            if (crossNode == null)
            {
                return null;
            }

            int circleCount = 0;
            var circleNode = crossNode;
            circleCount++;
            while (!object.ReferenceEquals(circleNode.Next, crossNode))//从相交的结点开始绕环一圈
            {
                circleCount++;//求出环的大小(结点数)
                circleNode = circleNode.Next;
            }
            var aheadNode = node;//定义一前一后两个结点
            var behindNode = node;
            for (int i = 0; i < circleCount; i++)
            {
                aheadNode = aheadNode.Next;//根据环的大小,先移动前一个结点。
            }

            while (!object.ReferenceEquals(aheadNode, behindNode)) //同时向前移动指针,直到相交。
            {
                aheadNode = aheadNode.Next;
                behindNode = behindNode.Next;
            }

            return aheadNode;
        }

        static ChainNode<T> GetCrossNode<T>(ChainNode<T> node)
        {
            if (node == null)
            {
                return null;
            }
            ChainNode<T> crossNode = null;

            var currentNode = node;
            var skipNode = node;
            while (skipNode != null && crossNode == null)
            {
                if (skipNode.Next == null || skipNode.Next.Next == null)
                {
                    skipNode = null;
                }
                else
                {
                    skipNode = skipNode.Next.Next;
                    currentNode = currentNode.Next;
                    if (Object.ReferenceEquals(skipNode, currentNode))
                    {
                        crossNode = currentNode;
                    }
                }
            }
            return crossNode;
        }

  题目7:删除链表中重复的结点。

  题目:在一个排序的链表中,如何删除重复的结点?例如:1,2,3,3,4,4,5,删除重复结点后变成1,2,5

static ChainNode<int> DeleteDuplicateNodes(ChainNode<int> node)
        {
            if (node == null)
            {
                return null; //鲁棒性考虑
            }

            ChainNode<int> current = node;//当前结点
            ChainNode<int> previous = null;//前一个结点的指针
            ChainNode<int> next = null;//下一个结点的指针
            ChainNode<int> head = node;//最终要返回的头结点
            while (current != null)
            {
                next = current.Next;
                if (next == null)
                {
                    break; //鲁棒性考虑 如果已经到最后一个结点了,就打断循环。
                }
                if (next.Value > current.Value)//如果后一个值比当前的值大
                {
                    previous = current;//记住当前的值,把它设置为之前的值。
                    current = current.Next;//移动当前指针。
                   
                }
                else//如果下个值和当前的值相等。(重复了)
                {
                    while (next != null && next.Value == current.Value)
                    {
                        next = next.Next;//不停地向后移动指针,找出往后第一个不重复的值。
                    }
                    if (previous == null)//如果之前的指针是空(头n个值就重复)
                    {
                        head = next;//将头结点指向第一个不重复的值
                    }
                    else
                    {
                        previous.Next = next;//如果之前的指针不是空,将之前的指针指向next,即跳过一个重复的数字
                    }
                    current = next;//将current的指针向后指
                }
            }

            return head;

        }

 题目8:求链表的倒数第k个结点

 思路:定义两个指针,让前一个指针先走k-1步,然后两个指针一起走,当前一个指针走到最后一个结点的时候,第一个指针指向的就是倒数第k个结点

 

    static Node<T> GetLastKNode<T>(Node<T> node, int k)
        {
            if (node == null || k <= 0)
            {
                return null;//鲁棒性考虑
            }
            //定义一前一后两个指针
            Node<T> fastNode = node;
            Node<T> behindNode = node;
            int n = 0;
            while (fastNode != null)
            {
                if (n == k - 1)
                {
                    break;
                }
                fastNode = fastNode.Next; //让前一个指针先走k-1步
                n = n + 1;
            }


            if (fastNode == null)
            {
                return null; //鲁棒性考虑,如果k比n大就返回null
            }
            //当前一个指针指向最后的时候,前一个指针就是倒数第k个结点。
            while (fastNode.Next != null)
            {
                behindNode = behindNode.Next;
                fastNode = fastNode.Next;
            }
            return behindNode;
        }

 

posted @ 2015-09-10 15:52  莱茵哈特  阅读(188)  评论(0编辑  收藏  举报