您的位置:首页 > 理论基础 > 数据结构算法

数据结构(链表)

2015-09-10 15:52 411 查看
题目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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: