連結リストのアルゴリズム問題

目次

質問の種類 1. rand ポインターを含むリンク リストの複製

筆記試験:ハッシュテーブル

インタビュー: コンテナなしでハッシュ テーブルの機能をシミュレートする

質問タイプ 2. 単一リンク リストのヘッド ノード Head が与えられた場合、それが回文を構成するかどうかを判断します

設問の種類 3. 単連リストをある値に応じて左を小、中を等分、右を大に分ける

6変数、コンテナなし、安定性も確保

質問タイプ 4. 2 つのリンクされたリストの最初の交差ノードを見つける

1. まず、2 つのリンクされたリストのエントリ ポイントを見つけます。

2. 次の状況について話し合う

質問タイプ 5. ヘッド ノードを指定せずに、特定のノードの削除を依頼しますか?

 1.賢い方法:死体を借りて魂を復活させる

トピック 6. リンク リスト内のノードは、k グループごとに反転されます


筆記試験の場合:時間の複雑さのために、空間の複雑さはあまり気にしないでください。

インタビューの場合:時間の複雑さが依然として最優先事項ですが、最もスペースを節約する方法を見つける必要があります。

質問の種類 1. rand ポインターを含むリンク リストの複製

筆記試験:ハッシュテーブル

 コード:

	Node* Hashfunc(Node* head)
	{
		unordered_map<Node*,Node*> Node_map;
		Node* cur = head;
		while (cur!= nullptr)
		{
			Node* new_cur = new Node(cur->value);
			
			Node_map.insert(make_pair(cur, new_cur));
			cur = cur->next;
		}
		cur = head;
		while (cur != nullptr)
		{
			Node_map.find(cur)->second->next = cur->next;
			Node_map.find(cur)->second->rand = cur->rand;
			cur = cur->next;
		}
		return Node_map.find(head)->second;
	}

インタビュー: コンテナなしでハッシュ テーブルの機能をシミュレートする

	Node* copyRandomList2(Node* head) {
		if (head == nullptr) {
			return nullptr;
		}
		Node* cur = head;
		Node* next = nullptr;
		// 1 -> 2 -> 3 -> nullptr
		// 1 -> 1' -> 2 -> 2' -> 3 -> 3'
		while (cur != nullptr) {
			next = cur->next;
			cur->next = new Node(cur->value);
			cur->next->next = next;
			cur = next;
		}
		cur = head;
		Node* copy = nullptr;
		// 1 1' 2 2' 3 3'
		// 依次设置 1' 2' 3' random指针
		while (cur != nullptr) {
			next = cur->next->next;
			copy = cur->next;
			copy->rand = cur->rand != nullptr ? cur->rand->next : nullptr;
			cur = next;
		}
		Node* res = head->next;
		cur = head;
		// 老 新 混在一起,next方向上,random正确
		// next方向上,把新老链表分离
		while (cur != nullptr) {
			next = cur->next->next;
			copy = cur->next;
			cur->next = next;
			copy->next = next != nullptr ? next->next : nullptr;
			cur = next;
		}
		return res;
	}

質問タイプ 2. 単一リンク リストのヘッド ノード Head が与えられた場合、それが回文を構成するかどうかを判断します

筆記試験:

順番にトラバースし、スタックに置き、比較のためにポップアウトします

インタビュー:

1. 最初に高速ポインタと低速ポインタを介して中間ノードを見つけ、次の中間ノードが null を指すようにします

2. 次に、後半を逆にして、最後のノードを保存します。

3.逆順の後、L、Rから順にたどって比較し、違いがあれば戻る

4. 回文が成り立つかどうかに関係なく、最後に連結リストの構造を元の状態に戻すことを忘れないでください。

 コード:

class FindLink_Mid;
class Node
{
	friend class FindLink_Mid;
public:
	Node(int v)
	{
		value = v;
	}
private:
	int value;	
	Node* next;
};

class FindLink_Mid
{

public:
	//奇数返回中点偶数返回上中点
	Node* midOrUpMidNode(Node* head) 
	{
		if (head == nullptr || head->next == nullptr || head->next->next == nullptr) {
			return head;
		}
		// 链表有3个点或以上
		Node* slow = head->next;
		Node* fast = head->next->next;
		while (fast->next != nullptr && fast->next->next != nullptr) 
		{
			slow = slow->next;
			fast = fast->next->next;
		}
		return slow;
	}
	//奇数返回中点,偶数返回下中点
	Node* midOrDownMidNode(Node* head) 
	{
		if (head == nullptr || head->next == nullptr) 
		{
			return head;
		}
		Node* slow = head->next;
		Node* fast = head->next;
		while (fast->next != nullptr && fast->next->next != nullptr)
		{
			slow = slow->next;
			fast = fast->next->next;
		}
		return slow;
	}
	//奇数返回中点前一个,偶数返回上中点前一个
	Node* midOrUpMidPreNode(Node* head) {
		if (head == nullptr || head->next == nullptr || head->next->next == nullptr)
		{
			return nullptr;
		}
		Node* slow = head;
		Node* fast = head->next->next;
		while (fast->next != nullptr && fast->next->next != nullptr)
		{
			slow = slow->next;
			fast = fast->next->next;
		}
	}

	//奇数返回中点前一个,偶数返回下中点前一个
	Node* midOrDownMidPreNode(Node* head) 
	{
		if (head == nullptr || head->next == nullptr)
		{
			return head;
		}
		if (head->next->next == nullptr)
		{
			return head;
		}
		Node* slow = head;
		Node* fast = head->next;
		while (fast->next != nullptr && fast->next->next != nullptr) {
			slow = slow->next;
			fast = fast->next->next;
		}
		return slow;
	}

};

設問の種類 3. 単連リストをある値に応じて左を小、中を等分、右を大に分ける

筆記試験:

リンクされたリストを配列に入れてから、パーティションに移動します

インタビュー:

6変数、コンテナなし、安定性も確保

プロセスの概略図:

最初に 3 つの領域を構築し、次に 3 つの領域をつなぎ合わせる必要があります

コード:

class SmallerEqualBigger
{
public:
	Node* listPartition2(Node* head, int pivot) 
	{
		Node* sH = nullptr; // small head
		Node* sT = nullptr; // small tail
		Node* eH = nullptr; // equal head
		Node* eT = nullptr; // equal tail
		Node* mH = nullptr; // big head
		Node* mT = nullptr; // big tail
		Node* next = nullptr; // save next node
		// every node distributed to three lists
		while (head != nullptr)
		{
			next = head->next;
			head->next = nullptr;
			if (head->value < pivot) 
			{
				if (sH == nullptr) 
				{
					sH = head;
					sT = head;
				}
				else 
				{
					sT->next = head;
					sT = head;
				}
			}
			else if (head->value == pivot) 
			{
				if (eH == nullptr) 
				{
					eH = head;
					eT = head;
				}
				else {
					eT->next = head;
					eT = head;
				}
			}
			else {
				if (mH == nullptr) 
				{
					mH = head;
					mT = head;
				}
				else {
					mT->next = head;
					mT = head;
				}
			}
			head = next;
		}
		// 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头
		if (sT != nullptr) 
		{ // 如果有小于区域
			sT->next = eH;
			eT = eT == nullptr ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT
		}
		// 下一步,一定是需要用eT 去接 大于区域的头
		// 有等于区域,eT -> 等于区域的尾结点
		// 无等于区域,eT -> 小于区域的尾结点
		// eT 尽量不为空的尾巴节点
		if (eT != nullptr) 
		{ // 如果小于区域和等于区域,不是都没有
			eT->next = mH;
		}
		//return sH != nullptr ? sH : (eH != nullptr ? eH : mH);
		return sH;
	}
public:
	void printList(Node* node)
	{
		cout<<"Linked List: ";
		while (node != nullptr) 
		{
			cout<<node->value <<" ";
			node = node->next;
		}
		cout << endl;
	}

質問タイプ 4. 2 つのリンクされたリストの最初の交差ノードを見つける

この問題とジョセフ・リング問題は、連結リストの2 つの悪夢と呼ばれています。

1. まず、2 つのリンクされたリストのエントリ ポイントを見つけます。

1. set を使用してエントリ ポイントを直接検索する

2.高速ポインターと低速ポインターを使用する

この関数を実現する関数を書く

// 找到链表第一个入环节点,如果无环,返回nullptr
Node* GetLoopNode(Node* head)
{
	if (head == nullptr || head->next == nullptr || head->next->next == nullptr)
	{
		return nullptr;
	}
	// 快慢指针
	Node* slow = head->next; 
	Node* fast = head->next->next; 
	while (slow != fast)
	{
		if (fast->next == nullptr || fast->next->next == nullptr)
			return nullptr;//说明这个链表就没有环

		slow = slow->next;
		fast = fast->next->next;
	}
	//这时候是找到了第一个相遇的位置
	fast = head;
	while (slow != fast)//第二次是两个指针都一次走一步
	{
		slow = slow->next;
		fast = fast->next;
	}
	return fast;
}

2. 次の状況について話し合う

最初のエントリ ポイントを見つける関数が既にあります。head1 は loop1 に対応し、head は loop2 です。

2.1、loop1==nullptr && loop2==nullptr

 1.ハッシュテーブルを使用して、最初の交差部分を確認できます

連結リストをセットに入れ、2 番目の連結リストをトラバースして、それがセット内にあるかどうかを確認します

2.容器なしも可能

 コード:

// 如果两个链表都无环,返回第一个相交节点,如果不相交,返回nullptr
Node* NoLoop(Node* head1, Node* head2) {
	if (head1 == nullptr || head2 == nullptr) {
		return nullptr;
	}
	Node* cur1 = head1;
	Node* cur2 = head2;
	int n = 0;
	while (cur1->next != nullptr)
	{
		n++;
		cur1 = cur1->next;
	}
	while (cur2->next != nullptr)
	{
		n--;
		cur2 = cur2->next;
	}
	if (cur1 != cur2) {//说明没有相交的部分
		return nullptr;
	}
	if (n < 0)
	{
		cur1 = head2;
		cur2 = head1;
	}
	// n  :  链表1长度减去链表2长度的值
	cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1
	cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
	n = abs(n);
	while (n)
	{
		cur1 = cur1->next;
		n--;
	}
	while (cur1 != cur2)
	{
		cur1 = cur1->next;
		cur2 = cur2->next;
	}
	return cur1;
}

2.2. リンクされたリストは両方ともリングを持っています (一方がリングを持ち、もう一方がリングを持たないということはあり得ないため)

2 つのリンクされたリストが交差する場合、リングを共有する必要があります

 

 状況 (2) は非常に簡単に実行できます。上記の非巡回連結リストの方法とほぼ同じです。

このリングは直接無視できます。2 つのリンクされたリストの最後を loop1 と見なして、上記と同じことを行います。

(1)と(2)も区別できる

ポインターは loop1->next を指し、円をたどって loop2 があるかどうかを確認し、ある場合は (3) loop1 と loop2 の両方を返し、

そうでない場合は、(2) nullptr を直接返す

// 两个有环链表,返回第一个相交节点,如果不想交返回nullptr
Node* BothLoop(Node* head1, Node* loop1, Node* head2, Node* loop2) {
	Node* cur1 = nullptr;
	Node* cur2 = nullptr;
	if (loop1 == loop2)
	{
		cur1 = head1;
		cur2 = head2;
		int n = 0;
		while (cur1->next != loop1)
		{
			n++;
			cur1 = cur1->next;
		}
		while (cur2->next != loop2)
		{
			n--;
			cur2 = cur2->next;
		}
		cur1 = n > 0 ? head1 : head2;//让cur1表示长的,cur2表示短的
		cur2 = cur1 == head1 ? head2 : head1;
		n = abs(n);
		while (n) {
			cur1 = cur1->next;
			n--;
		}
		while (cur1 != cur2) {
			cur1 = cur1->next;
			cur2 = cur2->next;
		}
		return cur1;
	}
	else
	{
		cur1 = cur1->next;
		while (cur1 != loop1)
		{
			if (cur1 == loop2)
				return loop1;
		}
		return nullptr;
	}
}

質問タイプ 5. ヘッド ノードを指定せずに、特定のノードの削除を依頼しますか?

 1.賢い方法:死体を借りて魂を復活させる

 しかし、この方法には多くの問題があります

1. コピー機能が呼びにくいと電話が切れる

2. 最後のノードは削除できません

ノードを削除するには、ヘッド ノードを指定する必要があります。

トピック 6. リンク リスト内のノードは、k グループごとに反転されます

 

ListNode* reverseKGroup(ListNode* head, int k) {
        int n = 0;
        for (ListNode *cur = head; cur; cur = cur->next)
            ++n; // 统计节点个数

        ListNode *dummy = new ListNode(0);
        dummy->next=head;
        ListNode *p0=dummy;
        ListNode *pre=nullptr,*cur=p0->next;
        while(n>=k){
            n-=k;
            for(int i=0;i<k;++i){
            ListNode* next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        ListNode* next=p0->next;
        p0->next->next=cur;
        p0->next=pre;
        p0=next;
        }
       
        return dummy->next;
        }

おすすめ

転載: blog.csdn.net/flyingcloud6/article/details/128690978