题目:给出一个链表和一个整数k,比如链表1→2→3→4→5→6,k=2,则翻转后2→1→4→3→6→5,若k=3,翻转后3→2→1→6→5→4,若k=4,翻转后4→3→2→1→5→6,用程序实现。
思路:先遍历链表,将其按每组k个元素划分。然后在每组中实现翻转后进行重新连接。代码如下:
// reverelistbyk.cpp
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
typedef struct Node{
int value;
Node *next;
}LinkList;
void PrintList(Node* head)
{
Node *cur = head;
while(NULL != cur)
{
cout << cur->value << " ";
cur = cur->next;
}
cout << endl;
return;
}
void Reverse(Node *begin, Node *end)
{
if(begin == NULL || end == NULL)
return;
Node* ReverseEndNext = end->next;
Node* pNode = begin;
Node* pNext = NULL;
Node* pPrev = NULL;
while(pNode != ReverseEndNext)
{
pNext = pNode->next;
pNode->next = pPrev;
pPrev = pNode;
pNode = pNext;
}
}
Node* ReverseListByK(Node* Head, int k)
{
if(NULL == Head || k <= 0)
return NULL;
else if(k == 1)
return Head;
Node* begin = Head;
Node* end = begin;
Node* ReverseEnd = NULL;
Node* ReverseBegin = NULL;
while(begin != NULL)
{
for(int i = 1; i < k; i++)
{
end = end->next;
if(NULL == end)
break;
}
if(NULL == end)
return Head;
ReverseEnd = end->next;
Reverse(begin, end);
begin->next = ReverseEnd;
if(ReverseBegin == NULL)
{
ReverseBegin = begin;
Head = end;
}
else
{
ReverseBegin->next = end;
ReverseBegin = begin;
}
begin->next = ReverseEnd;
begin = ReverseEnd;
end = begin;
}
return Head;
}
Node* CreateList(int *a, int len)
{
Node* head = (Node*)malloc(sizeof(Node));
Node* tail = (Node*)malloc(sizeof(Node));
head->value = a[0];
head->next = NULL;
tail = head;
for(int i = 1; i < len; i++)
{
Node* node = (Node*)malloc(sizeof(Node));
node->value = a[i];
tail->next = node;
tail = node;
}
tail->next = NULL;
return head;
}
int main()
{
int input[] = {1,2,3,4,5,6,7,8,9};
int len = sizeof(input) / sizeof(input[0]);
Node* MyList = CreateList(input, len);
PrintList(MyList);
PrintList(ReverseListByK(MyList, 2));
return 0;
}
其中,翻转链表部分思路来自于网上的自由天空的博客,如下:
题目:输入一个链表的头结点,反转该链表,并返回反转后链表的头结点。
分析:这是一道广为流传的微软面试题。由于这道题能够很好的反应出程序员思维是否严密,在微软之后已经有很多公司在面试时采用了这道题。
为了正确地反转一个链表,需要调整指针的指向。与指针操作相关代码总是容易出错的,因此最好在动手写程序之前作全面的分析。在面试的时候不急于动手而是一开始做仔细的分析和设计,将会给面试官留下很好的印象,因为在实际的软件开发中,设计的时间总是比写代码的时间长。与其很快地写出一段漏洞百出的代码,远不如用较多的时间写出一段健壮的代码。
为了将调整指针这个复杂的过程分析清楚,我们可以借助图形来直观地分析。假设下图中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为空指针的结点。
基于上述分析,我们不难写出如下代码:
///////////////////////////////////////////////////////////////////////
// Reverse a list iteratively
// Input: pHead - the head of the original list
// Output: the head of the reversed head
///////////////////////////////////////////////////////////////////////
ListNode* ReverseIteratively(ListNode* pHead)
{
ListNode* pReversedHead = NULL;
ListNode* pNode = pHead;
ListNode* pPrev = NULL;
while(pNode != NULL)
{
// get the next node, and save it at pNext
ListNode* pNext = pNode->m_pNext;
// if the next node is null, the currect is the end of original
// list, and it's the head of the reversed list
if(pNext == NULL)
pReversedHead = pNode;
// reverse the linkage between nodes
pNode->m_pNext = pPrev;
// move forward on the the list
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
但是在具体实施上还是有很大的问题:
因为传入的是begin和end,所以while结束条件会不一样。因为每次循环pnode将于后面的结点断开而连接前面的结点,并且若以end->next为终止条件的话,因为最后一次end将指向前一个结点,所以end->next将不知道指向何处,出现内存错误,所以要先用ReverseEnd保存end->next;这和ReverseListByK中要保存每组的前一个指针和后一个指针一样重要;还有要注意的就是ReverseListByK中在翻转每组后的处理要分第一次和之后两种情况,因为他们的连接方式是不一样的,具体的举例体会。