面试题22:链表的倒数第K个节点
题目:输入一个链表,输出该链表的倒数第K个节点(计数从1开始)。
思路:由于是找出倒数第K个节点,且计数从1开始,所以设置两个快慢指针,快指针先走K-1步,再让两个指针一起走。当快指针指向最后一个节点时,慢指针指向倒数第K个节点。
边界考虑:1、输入的链表头指针为空;
2、输入的k值为0;
3、输入的以pHead为头节点的链表的节点总数少于K;
链表节点定义:
struct ListNode
{
int value;
ListNode* next;
};
解题代码:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
{
if(pListHead == NULL || k == 0) //考虑头节点为空或k值为0的情况
return NULL;
ListNode *p = pListHead;
ListNode *q = pListHead;
for(int i = 1;i < k;i++)
{
if(p->next != NULL)
{
p = p->next;
}
else //若在循环中p->next为空,说明链表节点数<k,该链表没有倒数第k个节点
return NULL;
}
while(p->next != NULL)
{
p = p->next;
q = q->next;
}
return q;
}
面试题23:链表中环的入口
题目:如果一个链表中包含环,如何找出环的入口节点
思路:首先是判断链表是否有环:设置两个快慢指针,快指针一次走两步,慢指针一次走一步,若快指针和慢指针指向了同一块区域,那么链表中包含了环;若快指针走到了链表的末尾(next域指向空),慢指针还没有追上快指针,那么链表中没有环。
其次是如何找到环的入口。有两个方法:方法一:假设链表环中有n个节点,设置快慢指针,快指针先走n步,然后两个指针以相同的速度向前移动,当慢指针走到入口节点处,快指针已经绕着环走了一圈,回到入口节点处(快指针==慢指针)。方法二:利用数学计算,假设链表到环入口的距离为x,环入口到相遇点的距离为a(已经走过的距离),环的长度为c,当快慢指针相遇时,快指针路程:fast = x + a + c * m(假设快指针已经在圈里转了m圈),慢指针路程:slow = x + a + c * n(假设慢指针已经在圈里转了n圈),fast = 2 * slow,可以推导出x = (n - 2 * m - 1) * c + c - a,即x = c - a,所以设置两个指针,一个从链表头节点开始走,一个从相遇点向后走,两指针速度相同,当两指针相遇时,相遇的节点就是入口节点。
链表节点定义:
struct ListNode
{
int value;
ListNode* next;
};
判断链表中是否有环:
ListNode* HasCycle(ListNode* pHead) //若有环,返回相遇点的节点指针;若没有,返回空
{
if(pHead == NULL || pHead->next == NULL)
return NULL;
ListNode *fast = pHead;
ListNode *slow = pHead;
while(fast->next != NULL && fast != NULL)
{
if(fast == slow)
return fast;
fast = fast->next->next;
slow = slow->next;
}
return NULL;
}
寻找环入口:
方法一:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* meetingNode = HasCycle(pHead);
if(meetingNode == null)
return null;
int nodesInLoop = 1; //得到环中的节点个数
ListNode* p1 = meetingNode;
while(p1->next != meetingNode)
{
p1 = p1->next;
++nodesInLoop;
}
p1 = pHead; //移动p1
for(int i = 0;i < nodesInLoop;i++)
{
p1=p1->next;
}
ListNode p2 = pHead; //移动p1,p2
while(p1!=p2)
{
p1=p1.next;
p2=p2.next;
}
return p1;
}
方法二://将判断是否有环和寻找入口节点结合在一起
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead == NULL || pHead->next == NULL)
return NULL;
ListNode *fast = pHead;
ListNode *slow = pHead;
while(fast->next != NULL && fast != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
slow = pHead;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return fast;
}
}
return NULL;
}
面试题24:反转链表
题目:定义·一个函数,输入一个链表的的头节点,反转该链表并输出反转后链表的头节点
思路:创建一个新的头节点,顺序遍历链表,再将链表的每一节点以头插的方式插入新的头节点上。这样,新的头节点后面的链表是原来链表的逆序,返回新的头节点的下一个节点(newHead->next)。
边界考虑:1、传入的头节点为空或这个链表只有一个节点
2、返回的节点不是原链表的尾结点
3、注意反转后的链表会不会出现断裂
链表节点定义
struct ListNode
{
int value;
ListNode* next;
};
反转链表:
ListNode* ReverseList(ListNode* pHead)
{
if(pHead == NULL) //考虑输入的链表为空情况
return NULL;
ListNode *newhead = new ListNode(-1);//创建一个新的头节点
newhead->next = NULL;
while(pHead)//将原有链表的节点以头插的方式插入新的头节点后
{
ListNode *tmp = pHead;
pHead = pHead->next;
tmp->next = newhead->next;
newhead->next = tmp;
}
return newhead->next;
面试题25:合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的节点也是递增排序的。
思路:先比较两个链表的头节点值,假设链表1头节点值 < 链表2头节点值,链表1的头节点将是合并后的链表头节点。再来比较两个链表剩余的节点,步骤如上,将两链表的节点一一比较,值较小的节点插入到合并后的链表中,并且指针往后走,当一个链表全部插入后,将另一个链表的剩余节点接到合并后链表的尾端。
边界考虑:考虑两个链表是否为空
节点定义:
struct ListNode
{
int value;
ListNode* next;
};
合并链表:
非递归
//创建一个新的头节点,然后将两个链表进行比较,小的连接到新的头结点上
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == NULL && pHead2 == NULL)
return NULL;
ListNode *head = new ListNode(-1) ;
head->next = NULL;
ListNode *phead = head;
ListNode *p1 = pHead1;
ListNode *p2 = pHead2;
while(p1 && p2)
{
if(p1->val > p2->val)
{
phead->next = p2;
phead = p2;
p2 = p2->next;
}
else
{
phead->next = p1;
phead = p1;
p1 = p1->next;
}
}
if(p1 != NULL)
{
phead->next = p1;
}
if(p2 != NULL)
{
phead->next = p2;
}
return head->next;
}
//比较两个链表的头节点,将较小的节点当做合并后链表的头节点
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(NULL == pHead1 && NULL == pHead2)
return NULL;
if(NULL == pHead1)
return pHead2;
if(NULL == pHead2)
return pHead1;
ListNode *pNode1 = pHead1;
ListNode *pNode2 = pHead2;
ListNode *newListHead = (pNode1->Value < pNode2->Value) ? pNode1 : pNode2; ListNode *newListNode = newListHead;
if(newListHead == pNode1)
pNode1 = pNode1->Next;
else
pNode2 = pNode2->Next;
while(pNode1 && pNode2)
{
if(pNode1->Value < pNode2->Value)
{
newListNode->Next = pNode1;
pNode1 = pNode1->Next;
}
else
{
newListNode->Next = pNode2;
pNode2 = pNode2->Next;
}
newListNode = newListNode->Next;
}
if(pNode1)
{
newListNode->Next = pNode1;
}
if(pNode2)
{
newListNode->Next = pNode2;
}
return newListHead;
}
递归:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(NULL == pHead1)
return pHead2;
if(NULL == pHead2)
return pHead1;
ListNode *newListHead = NULL;
if(pHead1->Value < pHead2->Value)
{
newListHead = pHead1;
newListHead->Next = Merge(pHead1->Next,pHead2);
}
else
{
newListHead = pHead2;
newListHead->Next = Merge(pHead1,pHead2->Next);
}
return newListHead;
}
面试题26:树的子结构
题目:输入两颗二叉树A和B,判断B是不是A的子结构。
思路:第一步,在树A中找到和树B的根节点的值一样的节点R;第二步,判断树A中以R为根节点的子树是否包含和树B一样的结构。
边界考虑:1、树A和树B的头节点有一个或两个都是空指针;
2、在树A和树B中所有节点都只有左子节点或右子节点;
3、树A和树B的节点中含有分叉;
二叉树节点定义:
struct TreeNode
{
int val;
struct TreeNode *left;
struct TreeNode *right;
}
解题过程:首先注意pRoot1和pRoot2是否为空的判断顺序,先判断pRoot2的,再来判断pRoot1的,判断顺序不能改变。
bool DoesT1hasT2(TreeNode*pRoot1,TreeNode* pRoot2)
{
//如果Tree2已经遍历完了都能对应的上,返回true
if(pRoot2 == NULL)
return true;
//如果Tree2还没有遍历完,Tree1却遍历完了。返回false
if(pRoot1 == NULL && pRoot2 != NULL)
return false;
//如果其中有一个点没有对应上,返回false
if(pRoot1->val != pRoot2->val)
return false;
//如果根节点对应的上,那么就分别去子节点里面匹配
return DoesT1hasT2(pRoot1->left,pRoot2->left) && DoesT1hasT2(pRoot1->right,pRoot2->right);
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result = false;
//当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
if(pRoot1 != NULL && pRoot2 != NULL)
{
//如果找到了对应Tree2的根节点的点
if(pRoot1->val == pRoot2->val)
{
//以这个根节点为为起点判断是否包含Tree2
result = DoesT1hasT2(pRoot1,pRoot2);
}
//如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
if(!result)
result = HasSubtree(pRoot1->left,pRoot2);
//如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
if(!result)
result = HasSubtree(pRoot1->right,pRoot2);
}
return result;
}
二叉树节点中保存的是int整型,可以直接比较两个节点的值是否相等。若保存的值类型为double或float,就不能直接比较,因为计算机内表示小数时有误差,若判断两个数是否相等,只能判断他们之间的差值的绝对值是否在一个很小的范围内。
bool Equal(double num1,double num2)
{
if((num1 - num2 > -0.0000001) && (num1 - num2) < 0.0000001)
return true;
else
return false;
}
本文深入解析链表的倒数第K个节点、环的入口、反转链表、合并排序链表及二叉树子结构判断等经典算法问题,提供详细思路与实现代码。
6450

被折叠的 条评论
为什么被折叠?



