目錄
相應oj鏈接:
https://www.nowcoder.com/ta/coding-interviews
一、鏈表與指針
1.1 單向雙向表
設某鏈表中最常用的操作是在鏈表的尾部插入或刪除元素,則選用下列( )存儲方式最節省運算時間
正確答案: D
- 單向鏈表
- 單向循環鏈表
- 雙向鏈表
- 雙向循環鏈表
迅速從表頭到表尾,單向鏈表於單向循環鏈表顯然不行,雙向不循環也不行,最快還是雙向循環。
1.2 數組指針
下面代碼會輸出 ?。
int a[4] = { 1, 2, 3, 4 };
cout << sizeof(a) << endl;
int *ptr = (int*)(&a + 1);
printf("%d", *(ptr - 1));
解析:
- &a+1是整個數組的指向棏下一個地址,
- ptr-1 就是從下一個地址(隨機地址)回到數組的最後一個數存儲的地方
- &a + 1: 取數組a 的首地址,該地址的值加上sizeof(a) 的值,即&a + 4*sizeof(int),也就是下一個數組的首地址,顯然當前指針已經越過了數組的界限。
- (int *)(&a+1): 則是把上一步計算出來的地址,強制轉換爲int * 類型,賦值給ptr。
- *(a+1): a,&a 的值是一樣的,但意思不一樣,a 是數組首元素的首地址,也就是a[0]的首地址,&a 是數組的首地址,a+1 是數組下一元素的首地址,即a[1]的首地址,&a+1 是下一個數組的首地址。所以輸出2
- *(ptr-1): 因爲ptr 是指向a[3],並且ptr 是int * 類型,所以*(ptr-1) 是指向a[3] ,
經過在編譯器編譯之後,輸出爲
16
4
1.3 鏈表的合併
已知兩個鏈表list1 和list2 內的數據都是有序的,請把它們合併成一個鏈表,保持裏面的數據依然有序,要求用遞歸的方法實現()。下面是定義的鏈表的節點:
struct Node {
int data;
Node *next;
};
typedef struct Node Node;
請寫出函數Node * MergeRecursive(Node *head1, Node *head2)的實現。
解析:運用遞歸實現了鏈表的合併,迴歸表頭,其他部分對更小的值進行遞歸。
程序作用就是,
- 如果兩個head指向的都有值,則返回指向值小的head,這個head指向的值往下遞歸
- 如果一個head爲NULL,則返回另一個head。這部分適用於程序結束時候的情況。即出現這種情況遞歸就結束了。
Node * MergeRecursive(Node *head1, Node *head2)
{
if (head1 == NULL)
return head2;
if (head2 == NULL)
return head1;
Node *head = NULL;
if (head1->data < head2->data){
head = head1;
head->next = MergeRecursive(head1->next, head2);
}
else {
head = head2;
head->next = MergeRecursive(head1, head2->next);
}
return head;
}
二、鏈表應用匯總
2.1 鏈表中的值倒序
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
ListNode* node_ptr=head;
vector<int> val_vector;
for( ; node_ptr!=NULL; node_ptr=node_ptr->next){
val_vector.insert(val_vector.begin(),node_ptr->val);
}
// reverse vector
return val_vector;
}
};
難度不難,但是需要對vector這個工具進行熟練運用。
比如insert命令是從前面插入。
然後就是鏈表的遍歷。
2.2 鏈表的倒數第k個節點
輸入一個鏈表,輸出該鏈表中倒數第k個結點。
倒數第k個節點c++
思路清晰即可
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(pListHead==NULL)return NULL;
int list_size=0;
ListNode* node_ptr=pListHead;
while(node_ptr!=NULL){
node_ptr=node_ptr->next;
list_size++;
}
int idx_back_k=list_size-k;
if(idx_back_k<0)return NULL;
node_ptr=pListHead;
for(int idx=0;idx<idx_back_k;idx++){
node_ptr=node_ptr->next;
}
return node_ptr;
}
};
可以採用劍指offer中的快慢指針來解,即快指針指向慢指針之後的k位,一起向後遍歷,快指針指向NULL的時候,慢指針剛好在倒數第k個節點。
另外,需要注意程序魯棒性,即鏈表不夠k位的情況。這種方法應該是最簡單的方法了。
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* fast_ptr=pListHead;
ListNode* slow_ptr=pListHead;
for(int idx=0;idx<k;idx++){
if(fast_ptr==NULL)return NULL;
fast_ptr=fast_ptr->next;
}
while(fast_ptr!=NULL){
fast_ptr=fast_ptr->next;
slow_ptr=slow_ptr->next;
}
return slow_ptr;
}
};
2.3 反轉鏈表
反轉鏈表需要將之前的鏈表進行反轉。因爲內存限制,c++代碼最好不要引入新的變量。數據結構的題,有必要畫出相應的圖方便不出錯與理解。
一定要考慮程序魯棒性,即如果空鏈表的話,返回NULL,不要直接return。
並且需要少量的內存佔用,儘量運用已有的節點。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead==NULL)return NULL;
ListNode* pre_node=new ListNode(pHead->val);
ListNode* cur_node=pHead->next;
ListNode* next_node=cur_node;
while(next_node!=NULL){
next_node=cur_node->next;
cur_node->next=pre_node;
pre_node=cur_node;
cur_node=next_node;
}
return pre_node;
}
};
2.4 鏈表的公共節點
找到兩個鏈表公共節點,思路清晰即可,注意非void函數一定要返回值。且第一個鏈表固定一個節點時候,第二個鏈表需要從頭開始。兩次遍歷,找到公共節點。不知是否有更簡便的方法。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL||pHead2==NULL)return NULL;
ListNode* node_1=pHead1;
ListNode* node_2;
while(node_1!=NULL){
node_2=pHead2;
while(node_2!=NULL){
if(node_1==node_2)return node_1;
node_2=node_2->next;
}
node_1=node_1->next;
}
return NULL;
}
};
2.5 鏈表環的入口節點
鏈表其中包含環,如果有環則輸出入口節點,沒有則輸出NULL
需要一快一慢兩個指針,相遇則表明有環。可以通過環中的節點判斷環的大小。
class Solution {
public:
// fast ptr and slow ptr find meeting node
ListNode* meeting_node(ListNode* pHead){
if (pHead == NULL)return NULL;
ListNode* fast_ptr = pHead->next;
ListNode* slow_ptr = pHead;
while (fast_ptr != slow_ptr){
if (fast_ptr == NULL)return NULL;
fast_ptr = fast_ptr->next;
if (fast_ptr == NULL)return NULL;
fast_ptr = fast_ptr->next;
slow_ptr = slow_ptr->next;
}
return fast_ptr;
}
// entry node
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* meet_node_ptr = meeting_node(pHead);
if (meet_node_ptr == NULL)return NULL;
ListNode* fast_ptr = meet_node_ptr->next;
ListNode* entry_node = pHead->next;
while (fast_ptr != meet_node_ptr){
fast_ptr = fast_ptr->next;
entry_node = entry_node->next;
}
fast_ptr = pHead;
while (fast_ptr != entry_node){
fast_ptr = fast_ptr->next;
entry_node = entry_node->next;
}
return entry_node;
}
};
三、複雜鏈表的複製
3.1 題幹
輸入一個複雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)
3.2 思路及注意事項
首先,把新鏈表複製到舊鏈表的結尾
然後,複製randm指針(注意,random指針可能爲NULL的情況出現)
最後,把鏈表拆開。
3.3 正確答案
分開用子函數實現:
class Solution {
public:
//void CloneNodes(RandomListNode *);
//void ConnectRandomNodes(RandomListNode* );
//RandomListNode* ReconnectNodes(RandomListNode* );
RandomListNode* Clone(RandomListNode* pHead)
{
if (pHead == nullptr)
return NULL;
CloneNodes(pHead);
ConnectRandomNodes(pHead);
return ReconnectNodes(pHead);
}
void CloneNodes(RandomListNode *Head)
{
RandomListNode* pNode = Head;
while (pNode != nullptr)
{
RandomListNode* pCloned = new RandomListNode(pNode->label);
//pCloned->label=pNode->label;
pCloned->next = pNode->next;
pCloned->random = nullptr;
pNode->next = pCloned;
pNode = pCloned->next;
}
}
void ConnectRandomNodes(RandomListNode* Head)
{
RandomListNode* pNode = Head;
while (pNode != nullptr)
{
RandomListNode* pCloned = pNode->next;
if (pNode->random != nullptr)
{
pCloned->random = pNode->random->next;
}
pNode = pCloned->next;
}
}
RandomListNode* ReconnectNodes(RandomListNode* Head)
{
RandomListNode *pNode = Head;
RandomListNode *result = Head->next;
while (pNode != NULL)
{
RandomListNode *pClone = pNode->next;
pNode->next = pClone->next;
pNode = pNode->next;
if (pNode != NULL)
pClone->next = pNode->next;
}
return result;
}
};
合併實現:
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if (!pHead) return NULL;
RandomListNode *cur = pHead;
while (cur){
RandomListNode *node = new RandomListNode(cur->label);
node->next = cur->next;
cur->next = node;
cur = node->next;
}//直到cur指向了原先鏈表的結尾null處
cur = pHead;
RandomListNode *p;
while (cur){
p = cur->next;
if (cur->random){
p->random = cur->random->next;
}
cur = p->next;
}
RandomListNode *pCloneHead = pHead->next;
RandomListNode *tmp;
cur = pHead;
while (cur->next){
tmp = cur->next;
cur->next = tmp->next;
cur = tmp;
}
return pCloneHead;
}
};
個人編寫正確的答案
class Solution {
public:
void simple_clone(RandomListNode* pHead){
RandomListNode* old_ptr = pHead;
while (old_ptr != NULL){
RandomListNode* new_ptr = new RandomListNode(old_ptr->label);
new_ptr->next = old_ptr->next;
old_ptr->next = new_ptr;
old_ptr = new_ptr->next;
}
}
void clone_random(RandomListNode* pHead){
RandomListNode* old_ptr = pHead;
while (old_ptr != NULL){
RandomListNode* new_ptr = old_ptr->next;
if (old_ptr->random != NULL){
new_ptr->random = old_ptr->random->next;
}
old_ptr = new_ptr->next;
}
}
RandomListNode* split(RandomListNode* pHead){
RandomListNode* old_ptr = pHead;
RandomListNode* new_head = pHead->next;
RandomListNode* new_ptr = new_head;
while (new_ptr->next != NULL){
old_ptr->next = new_ptr->next;
old_ptr = old_ptr->next;
new_ptr->next = old_ptr->next;
new_ptr = new_ptr->next;
}
new_ptr->next = NULL;
old_ptr->next = NULL;
return new_head;
}
RandomListNode* Clone(RandomListNode* pHead)
{
if (pHead == NULL)return NULL;
//simple clone
simple_clone(pHead);
clone_random(pHead);
return split(pHead);
}
};
3.4 錯誤代碼找錯
下面代碼找錯誤:提醒,用oj會出現堆棧溢出
#include<vector>
#include<iostream>
using namespace std;
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
class Solution {
public:
void simple_append_clone(RandomListNode* pHead){
if (pHead == NULL)return;
RandomListNode* old_node = pHead;
while (old_node != NULL){
RandomListNode* new_node = new RandomListNode(old_node->label);
new_node->next = old_node->next;
old_node->next = new_node;
old_node = new_node->next;
}
}
void clone_random(RandomListNode* pHead){
if (pHead == NULL)return;
RandomListNode* old_node = pHead;
while (old_node != NULL){
if (old_node->random != NULL)old_node->next->random = old_node->random->next;
old_node = old_node->next->next;
}
}
RandomListNode* divide(RandomListNode* pHead){
if (pHead == NULL)return NULL;
RandomListNode* new_head = pHead->next;
RandomListNode* new_node = new_head;
RandomListNode* old_node = pHead;
while (new_node->next != NULL){
new_node = old_node->next;
old_node->next = old_node->next->next;
old_node = old_node->next;
if (new_node->next != NULL){
new_node->next = new_node->next->next;
new_node = new_node->next;
}
}
return new_head;
}
RandomListNode* Clone(RandomListNode* pHead)
{
if (pHead == NULL)return NULL;
simple_append_clone(pHead);
clone_random(pHead);
return divide(pHead);
}
};
int main(){
Solution Solution;
vector<int> rating;
int num; cin >> num;
while (num--){
int rate; cin >> rate;
rating.push_back(rate);
}
int end; cin >> end;
return 0;
}
改正方法:把第三個函數最終的判斷刪掉,加上兩個指針都指向NULL,表示到了結尾
四、二叉樹轉爲雙向鏈表
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。
中序遍歷即可,多一個記錄pre的指針
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if (pRootOfTree == nullptr) return nullptr;
TreeNode* pre = nullptr;
convertHelper(pRootOfTree, pre);
TreeNode* res = pRootOfTree;
while (res->left)
res = res->left;
return res;
}
void convertHelper(TreeNode* cur, TreeNode*& pre)
{
if (cur == nullptr) return;
convertHelper(cur->left, pre);
cur->left = pre;
if (pre) pre->right = cur;
pre = cur;
convertHelper(cur->right, pre);
}
};
來自 <https://www.nowcoder.com/profile/3900945/codeBookDetail?submissionId=16370979>
或者:
class Solution {
public:
void ConvertNode(TreeNode* pNode, TreeNode** pLastNodeInList){
if (pNode == nullptr)
return;
//if(pNode->left!=nullptr)
ConvertNode(pNode->left, pLastNodeInList);
pNode->left = *pLastNodeInList;
if (*pLastNodeInList != nullptr)
(*pLastNodeInList)->right = pNode;
*pLastNodeInList = pNode;
//if(pNode->right!=nullptr)
ConvertNode(pNode->right, pLastNodeInList);
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode* pLastNodeInList = nullptr;
ConvertNode(pRootOfTree, &pLastNodeInList);
TreeNode* pHeadOfList = pLastNodeInList;
while (pHeadOfList != nullptr&&pHeadOfList->left != nullptr){
pHeadOfList = pHeadOfList->left;
}
return pHeadOfList;
}
};
來自 <https://www.nowcoder.com/profile/349656/codeBookDetail?submissionId=17180119>