アルゴリズムの質問 - C++ (4) 悪のリンク リスト

リンクリストは良いのですが、ロープのように切れたり散らかったりしやすいです...
配列も良いですが、移動効率が低いです...

141. 循環リンクリスト

リンクされたリストに循環があるかどうかを判断する

高速ポインタと低速ポインタ: 高速ポインタのステップ サイズは低速ポインタより 1 大きくなります (Vs-Vf=1)。

S+Vs-(F+Vf) = N-1

Vs-Vf = 1

S を低速ポインタのステップ サイズとし、F を高速ポインタのステップ サイズとします。

差: SF=N

S+1-(F+2)=N-1

N回後:SF=0

class Solution {
    
    
public:
    bool hasCycle(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) {
    
    
            return false;
        }
        ListNode* slow = head;
        ListNode* fast = head->next;
        while (slow != fast) {
    
    
            if (fast == nullptr || fast->next == nullptr) {
    
    
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};

876. リンクリストの中間ノード

1. 暴力的な横断 O(N) O(N)

配列を使用したリンク リストの走査

終了長さ len を取得

次に、リンクされたリストの中点を直接取得します l [len//2]

// vector::back() 返回vector容器的最后一个元素...
// vector::push_back(Q) 向vector容器里加入一个元素Q
// vector::pop_back() 删除vector最后一个元素
class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
        vector<ListNode*> A = {
    
    head};
        while(A.back()->next!=NULL)
        {
    
    
            A.push_back(A.back()->next);
        }
        return A[A.size()/2];
    }
};

2. シングルポインタ O(N) O(1)

最初の走査では、リンク リストの長さ N を取得します。

N//2 の位置への 2 番目のトラバースは、リンク リストの中点です。

class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
        int len = 0;
        ListNode* t = head,*tt = head;
        while(t!=NULL)
        {
    
    
            t = t->next;
            len+=1;
        }
        for(int i=0;i<len/2;i++)
            tt = tt->next;
        return tt;
    }
};

3. 速いポインタと遅いポインタ (またあなたです...) O(N) O(1)

ヘッドノードの位置に高速ポインタと低速ポインタを定義します。

高速ポインタは低速ポインタより 1 ステップ速く、高速ポインタは一度に 2 ステップの長さ、低速ポインタは一度に 1 ステップの長さです。

終了条件は、高速ポインタが空であるか、高速ポインタの次のノードが空であることです(偶数の場合は中間の 2 番目のノードを返すため)。

ここで練習してください: 実験後、これは非常に賢いものであり、他のステップ サイズでは満足できないことがわかりました...

A. リンク リストの長さが偶数の場合、4 であると見なされます。高速ポインタは NULL を指す必要があり、低速ポインタはちょうど中央の 2 番目の位置にあります。

slow------slow-----------slow

fast---------------------fast----------------------fast

1 ->       2    ->        3    ->        4       ->NULL

B. リンクされたリストの長さが奇数の場合、それは 5 であると想定されます。高速ポインタは終了ノードを指す必要があり、低速ポインタはちょうど中間ノードを指す必要があります。

slow----------slow-----------slow

fast-------------------------fast--------------------------fast

1 ->          2     ->        3      ->      4       ->      5     ->    NULL
class Solution {
    
    
public:
    ListNode* middleNode(ListNode* head) {
    
    
        ListNode *fast=head,*slow=head;
        while(fast != NULL && fast->next != NULL)
        {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

ソードはオファー 22 を指します。リンクされたリストの k 番目の最後のノードです。

ヘッド ノードを指すように高速ポインターと低速ポインターを設定します。

最初に高速ポインタを k ステップ移動させ、次に高速ポインタと低速ポインタを 1 ステップ移動するように設定します。高速ポインタが末尾ノードに移動すると、低速ポインタは下から k 番目のノードを指します。

どうやって?

1->2->3->4->5->NULL、k=2 ====> [4->5]

より速く: 3 を指して、最初に k 歩進みます。

遅い: 頭を指す 1

次に、高速ポインタが最後に到達するまで、次の 2 つのポインタが同時に 1 ステップ移動します -> NULL

faster: 指向NULL

slow: 指向4

class Solution {
    
    
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
    
    
        ListNode *faster = head, *slow = head;
        int cnt = 0;
        while(1){
    
    
                faster = faster->next;
                cnt += 1;
                if(cnt == k) break;
            }
        while(faster!=NULL)
        {
    
    
            slow = slow->next;
            faster = faster->next;
        }
        return slow;
    }
};

206. 逆リンクリスト

チキンになるのは難しい、0-0はどんなモンスターですか!!!

head 挿入メソッドは、最後のノードを先頭に挿入します。head と head->next の意味は同じではないことに注意してください。

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        ListNode *newHead = nullptr;
        while (head != nullptr) {
    
    
            //使用了一个 newHead 指针来记录新链表的头部,以及一个 head 指针来遍历原始链表
            ListNode *next = head->next;
            head->next = newHead;
            newHead = head;
            head = next;
        }
        return newHead;
    }
};
//newHead -> nullptr
//head -> A -> B -> C -> nullptr

//newHead -> A -> nullptr
//head -> B -> C -> nullptr

//newHead -> B -> A -> nullptr
//head -> C -> nullptr

//newHead -> C -> B -> A -> nullptr
//head -> nullptr

末尾の挿入はコピーを作成するだけなので、逆リストでは機能しません。

ダブルポインタ

// 注意啊 这里的什么*都是指针,并不是节点啊!没有节点需要被操作,我们操作的是他们之间的指针指向
class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        // 双指针辅助移动 pre->指向头节点 cur指向空
        ListNode* pre = head, *cur = NULL;
        while(pre!=NULL)
        {
    
    
           //需要存储一下pre指向下一节点的指针 不然没法移动
           ListNode* temp = pre->next;
           pre->next = cur; //让pre指向cur
           cur = pre; //让cur变成pre实现向右/左移动
           pre = temp;//这里不能写pre->next 因为这时候pre->next=cur...这里就要用temp
        }
        return cur;
    }
};

邪悪な再帰

最後までトラバースしてから戻り、毎回現在のノードの次のノードのポインティングを現在のノードに変更し、次のポインティング ノードのポインティングを空にリセットします。

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        //  使用递归 出口是到尽头NULL
        // 遍历到最后一个节点 然后它将会变成头节点(因为是反转)
        // 让当前节点的下一个节点指向当前节点???4->5->NULL 5->4->NULL
        // 让当前节点next指向NULL
        if(head == NULL || head->next==NULL)
        {
    
    
            return head;
        }
        ListNode* ret = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return ret;
    }
};

悪魔化したダブルポインタ

///
class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        if(head == NULL) return NULL;
        ListNode* cur = head;
        while(head->next!=NULL)
        {
    
    
            // head->next->next => head 先把head->next->next存起来 是新节点t
            // cur = head->next 移动下一个位置
            // 此时head->next要指向下一个新节点 t
            ListNode* t = head->next->next;//存好之后不需要担心之后找不到新节点的问题
            head->next->next = cur;
            cur = head->next;
            head->next = t;
        }
        return cur;
    }
};

92. 逆リンクリストⅡ

逆リンクリストの高度なバージョンとして、固定 lr... のノード順序を逆にします。

主な理由は、リンク リストのセグメントを反転した後に元のリンク リストを埋め込む必要がある場合、先頭と末尾のポインタの順序を調整する必要があるためです。

ヘッド挿入方法:

class Solution {
    
    
    public ListNode reverseBetween(ListNode head, int left, int right) {
    
    
        // 定义一个dummyHead, 方便处理
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;    
	// 初始化指针
        ListNode g = dummyHead;
        ListNode p = dummyHead.next;

    // 将指针移到相应的位置
    for(int step = 0; step < left - 1; step++) {
    
    
        g = g.next; p = p.next;
    }

    // 头插法插入节点
    for (int i = 0; i < right - left; i++) {
    
    
        ListNode removed = p.next;
        p.next = p.next.next;
        removed.next = g.next;
        g.next = removed;
    }
    return dummyHead.next;
}
    

真っ向から逆転…

ダブルポインタ:

left==1 の場合、直接反転が失敗するため、センチネル ノードを挿入する必要もあります。

pre はトラバーサル ノードを指します。 cur は初期化ノードを指します。 NULL

p0 は、ダミーからの反転ノードの前のノード位置です。

リンクリスト: d->1->2->3->4->5->NULL

逆方向 [3,4] p0 = d; 走査回数は (3-1) 回で、p0 ->1->2 は前のノードに正確に到達します

3と書くとちょうど反転したノードになるので、反転前のノードの位置が保存できず、前のノードのリンクリストを接続できなくなります…。

逆にする必要のない前のリンク リストは d->1->2 です。 p0 はこのパスの終了位置の添字を保存します。

ここに画像の説明を挿入

//自己实现的
class Solution {
    
    
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
    
    
     	ListNode *dummy = new ListNode(0);
        dummy->next = head;
    	ListNode *p0 = dummy;
        for(int i=0;i<left-1;i++)
        //遍历到left-1的位置 因为我需要把p0控制在反转节点的前一个节点的位置
            p0 = p0->next;
        //反转次数 right - left + 1 
        // cur 指向当前的被反转的元素 
        // pre 指向上一个元素
        ListNode *cur = p0->next, *pre = NULL;
        for(int i=0;i<right-left+1;i++)
        {
    
    
            ListNode *nxt = cur->next;//指向cur的下一个节点
            cur->next = pre;//修改cur的指向
            pre = cur; // 移动pre到已经反转的节点
            cur = nxt;// 移动cur到下一个节点
        }
        // 还没完 我们只是单纯反转了其中的[l,r]的方向
        // 拼接链表 
        // 此时pre = 已经反转的最后一个节点 是头节点
        // 此时cur = 已经反转节点的下一个节点 未反转节点的第一个
        // 以下的顺序不能改 不然无法修改p0->next->next的指向
        p0->next->next = cur;
        // p0->next 反转节点的第一个节点 再指向未反转链表的头节点cur
        p0->next = pre;
		// p0指向下一个节点是已经反转的最后一个节点
        return dummy->next;//head
    }
};

143. リンクされたリストを並べ替える

3 つの質問を調査してください: (毒女! まったくの恥ずかしさ)

  • 1. 中間ノードを見つけるための高速ポインターと低速ポインター
  • 2. リンクされたリストの後半を反転します
  • 3. 前後のリンクリストを結合します。

1->2->3->4->5->6

-> 1->2->3 を切り捨てる || 4->5->6

->後半を逆転 1->2->3 || 6->5->4

->インターリーブマージ6->1->5->2->4->3

疑似コード:

// 快慢指针找到中间节点...但是这里求的是中间节点的第一个
// 所以终止条件要模拟改变一下...
// 我们希望在链表长度是偶数的时候,slow落在中间部分的左侧
假设len = 4 以下类型是我们想要的 可以发现此时fast没有到末尾并且下一个节点也不是NULL,但是它下一个节点的下一个指向是NULL;
slow----slow
fast-----------fast
  1       2      3      4      NULL
假设len = 5 以下类型是我们想要的:此时fast只到末尾节点
slow----slow---slow
fast-----------fast----------fast
  1       2      3      4      5    NULL 
总结得到只要满足: fast->next!=NULL and fast->next->next!=NULL 就继续走
fast,slow = head,head
while fast->next!=NULL and faste->next->next!=NULL:
	fast = fast->next->next
    slow = slow->next
return slow
temp = slow

B

反转链表的操作...在A之后已经获得切割出来的头节点temp
由于是单链表后半段,故末尾一定指向NULL
head = temp
pre = NULL
while(head!=NULL)
{
    
    
    t = head->next;
    head->next = pre;
    pre = head;
    head = t;
}
return pre //头节点

C

ListNode *i = head1 , *j = head2

while i!=NULL and j!=NULL:
	t1 = i->next
	t2 = j->next
    
	i->next = j
	j->next = t
	
    i = t1
	j = t2

return head1

私のコード:

まずはタスクの目的に応じた3つの機能を実装する

1. リンクされたリストの中点を見つける

2. 逆リンクリスト

3. リンクされたリストを結合する

class Solution {
    
    
public:
    void reorderList(ListNode* head) {
    
    
        // 特判
        if(head == NULL) return;
        // 找中间节点
        ListNode *mid = FindMidNode(head);
        // 这里注意是中间节点的第一个 所以反转的后半段是第二个中间节点开头的
        ListNode *reverse_right = reverseList(mid->next);
        // 断链之后要把中点指向的改写NULL
        mid->next = NULL;
        merge_List(head,reverse_right);
    }
    //找截断中点
    ListNode* FindMidNode(ListNode* head)
    {
    
    
        ListNode *fast = head, *slow = head;
        //这里注意和找中点的那个题不一样,偶数的长度要截断的是中点左侧的那个点
        while(fast->next != NULL && fast->next->next != NULL)
        {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    // 反转链表 这里因为是直接给出头节点所以直接遍历就可以
    // 如果没有找中点的话,就要根据left,right写一个反转链表2的函数...
    ListNode* reverseList(ListNode* head)
    {
    
    
        ListNode* pre = NULL;
        while(head!=NULL)
        {
    
    
            ListNode *t = head->next;
            head->next = pre;
            pre = head;
            head = t;
        }
        return pre;
    }
    void merge_List(ListNode *i, ListNode *j)
    {
    
    
        // 依次接起来 i  ->  j   ->  i->next  ->  j->next  -> ...
        while(i!=NULL && j!= NULL)
        {
    
    
            ListNode *ti = i->next;
            ListNode *tj = j->next;
            
            i->next = j;
            j->next = ti;
            
            i = ti;
            j = tj;
		}
    }
};

148. リンクされたリストの
ソート マージソート C++ マージソート ボードに関する質問を覚えていますか? ? ?

//先写一遍归并排序的板子
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int tmp[N];
int a[N];
void merge_sort(int q[],int l,int r)
{
    
    
    //特判
    if(l>=r) return;
    //先划分中点
    int mid = l+r>>1;
    int i = l, j =mid+1, k = 0;;
    //快乐递归...
    merge_sort(q,i,mid);
    merge_sort(q,j,r);
    while(i<=mid && j<=r)
    {
    
    
        if(q[i]<=q[j]) tmp[k++] = q[i++];//i++ 先返回i 再++
        else tmp[k++] = q[j++];
    }
    // 长的一方并到tmp里
    while(i<=mid) tmp[k++] = q[i++];
   	while(j<=r) tmp[k++] = q[j++];
    for(int i=l,j=0;i<=r;i++,j++)
        q[i] = tmp[j];
}
int main()
{
    
    
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    merge_sort(a,0,n-1);
    for(int i=0;i<n;i++) cout<<a[i]<<" ";
    cout<<endl;
    return 0;
}

Leetcode コード:

再帰

1. セクションの中点を見つける

2. 2 つのサブリストを別々に並べ替えます。

3. サブリストを結合する

//新建一个头节点用于存储合并结果
newnode = new ListNode(0)
*p = newnode
//传入 head1 head2 链表
//同时遍历 head1 head2 的节点 并比较当前节点值大小
while head1!=NULL and head2!=NULL:
	if head1->val < head2->val:
		p->next = head1
        head1 = head1->next
    else:
		p->next = head2
        head2 = head2->next
//合并长的链表
if(head1!=NULL) p->next = head1
if(head2!=NULL) p->next = head2
return newnode

公式コード:

class Solution {
    
    
public:
    ListNode* sortList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr) return head;

        ListNode* head1 = head;
        ListNode* head2 = split_(head);

        head1 = sortList(head1);        //一条链表分成两段分别递归排序
        head2 = sortList(head2);

        return merge(head1, head2);     //返回合并后结果
    }

    //双指针找单链表中点模板 中点左侧
    ListNode* split(ListNode* head)     
    {
    
    
        ListNode *slow = head, *fast = head;

        while (fast->next->next != nullptr && fast->next != nullptr)
        {
    
    
            slow = slow->next;
            fast = fast->next->next;
        }
			// 这里必须要处理把链表断开,不然指针乱指
        ListNode* mid = slow->next;
        slow->next = nullptr;           //断尾
        return mid;
    }
    
    //双指针找单链表中点模板
    ListNode* split_(ListNode* head)     
    {
    
    
        ListNode *slow = head, *fast = head;
        //中点右侧
        ListNode *p = NULL;
        while(fast!= nullptr && fast->next != nullptr)
        {
    
    
            p = slow;
            slow = slow->next;
            fast= fast->next->next;
        }
        p->next = NULL;
        return slow;
    }

    //合并两个排序链表模板
    ListNode* merge(ListNode* head1, ListNode* head2)
    {
    
    
        ListNode *dummy = new ListNode(0), *p = dummy;

        while (head1 != nullptr && head2 != nullptr)
        {
    
    
            if (head1->val < head2->val)
            {
    
    
                p = p->next = head1;
                head1 = head1->next;
            }
            else
            {
    
    
                p = p->next = head2;
                head2 = head2->next;
            }
        }
        if (head1 != nullptr) p->next = head1;
        if (head2 != nullptr) p->next = head2;

        return dummy->next;
    }
};

おすすめ

転載: blog.csdn.net/daxuanzi515/article/details/130140260
おすすめ