c++鏈表問題彙總(代碼及解析)

目錄

一、鏈表與指針

1.1 單向雙向表

1.2 數組指針

1.3 鏈表的合併

二、鏈表應用匯總

2.1 鏈表中的值倒序

2.2 鏈表的倒數第k個節點

2.3 反轉鏈表

2.4 鏈表的公共節點

2.5 鏈表環的入口節點

三、複雜鏈表的複製

3.1 題幹

3.2 思路及注意事項

3.3 正確答案

3.4 錯誤代碼找錯

四、二叉樹轉爲雙向鏈表


相應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;
    }
};

方法二、輔助存儲,需要存儲棧

最簡單的方法,相當於將兩個鏈表存入棧中,來實現:

要注意,pop的時候一定要確定 !stack.empty()

class Solution {
public:
	ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
		if (pHead1 == NULL || pHead2 == NULL)return NULL;
		stack<ListNode*> node1;
		stack<ListNode*> node2;
		ListNode* node_ptr = pHead1;
		while (node_ptr != NULL){
			node1.push(node_ptr);
			node_ptr = node_ptr->next;
		}
		node_ptr = pHead2;
		while (node_ptr != NULL){
			node2.push(node_ptr);
			node_ptr = node_ptr->next;
		}
		while (!node1.empty() && !node2.empty()&&node1.top() == node2.top() ){
			node_ptr = node1.top();
			node1.pop();
			node2.pop();
		}
		return node_ptr;
	}
};

方法三,最簡單方法

統計出來鏈表的長度,不需要輔助存儲。結尾的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;
        }
};

 

三、複雜鏈表的複製

OJ:https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

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>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章