二十一、调整数组顺序使奇数位于偶数前面
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
测试用例:
- 功能测试:输入数组中的奇数、偶数交替出现;输入的数组中所有偶数都出现在奇数的前面;输入的数组中所有奇数都出现在偶数的前面。
- 特殊输入测试:输入nullptr指针;输入的数组只包含一个数字。
只完成功能的解法:
void record_odd_before_even(int *pData, int length) { if(pData == nullptr || length == 0) return; int *pBegin = pData; int *pEnd = pData + length - 1; while(pBegin < pEnd) { if(pBegin < pEnd && !is_even(*pBegin)) pBegin++; if(pBegin < pEnd && is_even(*pEnd)) pEnd--; if(pBegin < pEnd) { int temp = *pBegin; *pBegin = *pEnd; *pEnd = temp; } } } /* 判断n是否为偶数 */ bool is_even(int n) { bool isEven = ((n & 0x1) == 0); return isEven; }
分析:上段代码在扫描传入的数组时,如果发现有偶数出现在奇数的前面,则交换它们的顺序。所以,它维护两个指针,pBegin初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化时指向数组的最后一个数字,它只向前移动。
二十二、链表中倒数第K个节点
题目:输入一个链表,输出该链表中倒数第K个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
分析:为了得到倒数第K个节点,很自然地想到先遍历链表,从而得到链表的节点数n,于是获悉倒数第K个节点就是从头节点开始的第n-k+1个节点。
遍历链表两次的解法:
ListNode* get_kth_from_tail(ListNode *pHead, unsigned int k) { // 鲁棒性测试点1 if(pHead == nullptr || k == 0) return nullptr; int cnt = 0; ListNode *pNode = pHead; while(pNode != nullptr) { // 第一次遍历链表 pNode = pNode->next; ++cnt; } // 鲁棒性测试点2 if(k > cnt) return nullptr; pNode = pHead; while(cnt != k) { // 第二次遍历链表 pNode = pNode->next; --cnt; } return pNode; }
分析:如果面试官告诉我们只需遍历链表一次,我们应该想点办法了。可以定义两个指针,第一个指针pAhead从链表的头指针开始遍历向前走K-1步,第二个指针pBehind保持不动;从第K步开始,第二个指针pBehind也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当pAhead指针到达链表的尾节点时,pBehind指针正好指向倒数第K个节点。
遍历链表一次的解法:
ListNode* get_kth_from_tail(ListNode *pHead, unsigned int k) { if(pHead == nullptr || k == 0) return nullptr; ListNode *pAhead = pHead; ListNode *pAhead = pHead; for(int i = 0; i < k - 1; ++i) { // 向前走k-1步 if(pAhead->next != nullptr) pAhead = pAhead->next; else // 链表节点数小于k return nullptr; } while(pAhead->next != nullptr) { pAhead = pAhead->next; pBehind = pBehind->next; } return pBehind; }
二十三、链表中环的入口节点
题目:如果一个链表中包含环,如何找出环的入口节点?
分析:①判断一个链表中是否包含环;②如果有环,就要找到环的入口。
解法:
/* 返回环的入口节点 */ ListNode* entry_node_of_loop(ListNode *pHead) { if(pHead == nullptr) return nullptr; // 判断是否有环,如果有,则返回一个环中的节点 ListNode *meetNode = meet_node(pHead); if(meetNode == nullptr) return nullptr; // 计算环中的节点数 int nodeNumOfLoop = 1; ListNode *pCurrent = meetNode; while(pCurrent->next != meetNode){ pCurrent = pCurrent->next; nodeNumOfLoop++; } // 找出环的入口 ListNode *pAhead = pHead; ListNode *pBehind = pHead; for(int i = 0; i < nodeNumOfCode; ++i) { pAhead = pAhead->next; } while(pAhead != pBehind) { pAhead = pAhead->next; pBehind = pBehind->next; } return pAhead; } /* 判断链表中是否包含环 */ /* 如果有环,则返回两个指针相遇的节点 */ ListNode meet_node(ListNode *pHead) { ListNode *pSlow = pHead->next; // 一次走一步 if (pSlow == nullptr) return nullptr; ListNode *pFast = pSlow->next; // 一次走两步 while(pFast != nullptr) { if(pSlow == pFast) // 如果相遇即有环 return pFast; // 相遇的节点一定在环中 pSlow = pSlow->next; pFast = pFast->next; if(pFast != nullptr) pFast = pFast->next; } return nullptr; }
小结:利用两个指针pFast和pSlow来判断链表中是否包含环,如果有,就返回它们相遇的节点,显然这个节点是环中的一个节点。根据环中的一个节点,我们就可以遍历该环以得到环中的节点数。根据节点数,结合面试题22,可以利用两个指针来得到环的入口节点。
二十四、反转链表
题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
二十五、合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。