双指针的基本应用

一、环形链表 I

在这里插入图片描述
在这里插入图片描述

方法1:哈希表

在这里插入图片描述

struct hashTable {
    struct ListNode* key;
    UT_hash_handle hh;
};

struct hashTable* hashtable;

struct hashTable* find(struct ListNode* ikey) {
    struct hashTable* tmp;
    HASH_FIND_PTR(hashtable, &ikey, tmp);
    return tmp;
}

void insert(struct ListNode* ikey) {
    struct hashTable* tmp = malloc(sizeof(struct hashTable));
    tmp->key = ikey;
    HASH_ADD_PTR(hashtable, key, tmp);
}

bool hasCycle(struct ListNode* head) {
    hashtable = NULL;
    while (head != NULL) {
        if (find(head) != NULL) {
            return true;
        }
        insert(head);
        head = head->next;
    }
    return false;
}

代码解释
(1) HASH_FIND_PTR(hashtable, &ikey, tmp);
  在这段代码中,hashtable 是哈希表的头指针,&ikey 是要查找的键,它是一个指向链表节点指针的指针的地址,tmp 是查找结果的输出参数,它是一个指向哈希表项的指针。因此,HASH_FIND_PTR(hashtable, &ikey, tmp) 的作用是在哈希表中查找指定键的哈希表项,并将结果存储在 tmp 中。如果找到了,则返回该哈希表项;否则返回 NULL。

(2)HASH_ADD_PTR(hashtable, key, tmp);
  在这段代码中,hashtable 是哈希表的头指针,key 是哈希表项中表示键的字段,它是一个指向链表节点指针的指针,tmp 是要添加的键值对,它是一个指向哈希表项的指针。因此,HASH_ADD_PTR(hashtable, key, tmp) 的作用是将一个键值对添加到哈希表中,其中键是 tmp->key,即链表节点的指针,值是 tmp,即哈希表项的指针,同时更新哈希表的头指针 hashtable。

C++

class Solution {
public:
    bool hasCycle(ListNode *head) {
        //定义了一个 unordered_set<ListNode*> seen,
        //表示存储已经遍历过的链表节点的集合。
        unordered_set<ListNode*> seen;
        while (head != nullptr) {
            if (seen.count(head)) {
                return true;
            }
            seen.insert(head);
            head = head->next;
        }
        return false;
    }
};

方法2:快慢指针

在这里插入图片描述
在这里插入图片描述
C

bool hasCycle(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return false;
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head->next;
    while (slow != fast) {
        //检测快指针 fast 是否到达了链表的末尾。如果 fast 指针到达了末尾,
        //即 fast 指向空节点或者 fast 的下一个节点为空节点,则说明链表没有环,
        //返回 false。
        if (fast == NULL || fast->next == NULL) {
            return false;
        }
        slow = slow->next;
        fast = fast->next->next;
    }
    return true;
}

C++

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

二、环形链表 II

在这里插入图片描述
在这里插入图片描述

方法1:哈希表

在这里插入图片描述

C

struct hashTable {
    struct ListNode* key;
    UT_hash_handle hh;
};

struct hashTable* hashtable;

struct hashTable* find(struct ListNode* ikey) {
    struct hashTable* tmp;
    HASH_FIND_PTR(hashtable, &ikey, tmp);
    return tmp;
}

void insert(struct ListNode* ikey) {
    struct hashTable* tmp = malloc(sizeof(struct hashTable));
    tmp->key = ikey;
    HASH_ADD_PTR(hashtable, key, tmp);
}

struct ListNode* detectCycle(struct ListNode* head) {
    hashtable = NULL;
    while (head != NULL) {
        if (find(head) != NULL) {
            return head;
        }
        insert(head);
        head = head->next;
    }
    return false;
}

C++

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

方法2:快慢指针

在这里插入图片描述
C

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode *slow = head, *fast = head;
    while (fast != NULL) {
        slow = slow->next;
        if (fast->next == NULL) {
            return NULL;
        }
        fast = fast->next->next;
        if (fast == slow) {
            struct ListNode* ptr = head;
            while (ptr != slow) {
                ptr = ptr->next;
                slow = slow->next;
            }
            return ptr;
        }
    }
    return NULL;
}

C++

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast != nullptr) {
            slow = slow->next;
            if (fast->next == nullptr) {
                return nullptr;
            }
            fast = fast->next->next;
            if (fast == slow) {
                ListNode *ptr = head;
                while (ptr != slow) {
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
        }
        return nullptr;
    }
};

三、相交链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法1:哈希集合

在这里插入图片描述
C

struct HashTable {
    struct ListNode *key;
    UT_hash_handle hh;
};

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct HashTable *hashTable = NULL;
    struct ListNode *temp = headA;
    while (temp != NULL) {
        struct HashTable *tmp;
        HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
        if (tmp == NULL) {
            tmp = malloc(sizeof(struct HashTable));
            tmp->key = temp;
            HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp);
        }
        temp = temp->next;
    }
    temp = headB;
    while (temp != NULL) {
        struct HashTable *tmp;
        HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
        if (tmp != NULL) {
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}

代码解释:
(1)HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp); 的作用是在哈希表 hashTable 中查找是否存在指针 temp 所指向的 ListNode 节点,如果找到了就将指向 HashTable 结构体的指针赋值给 tmp,否则将 tmp 赋值为 NULL。

(2)HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp);
HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp); 的作用是将指针 tmp 所指向的 HashTable 结构体添加到哈希表 hashTable 中。具体来说,它将 tmp 关联到 key 成员(即指向 ListNode 的指针),并将 tmp 添加到哈希表中。

C++

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode *> visited;
        ListNode *temp = headA;
        while (temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {
            if (visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
    }
};

方法2:双指针

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

C

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA == NULL || headB == NULL) {
        return NULL;
    }
    struct ListNode *pA = headA, *pB = headB;
    while (pA != pB) {
        pA = pA == NULL ? headB : pA->next;
        pB = pB == NULL ? headA : pB->next;
    }
    return pA;
}

C++

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) {
            return nullptr;
        }
        ListNode *pA = headA, *pB = headB;
        while (pA != pB) {
            pA = pA == nullptr ? headB : pA->next;
            pB = pB == nullptr ? headA : pB->next;
        }
        return pA;
    }
};

四、删除链表的倒数第N个节点

在这里插入图片描述

方法1:计算链表长度

在这里插入图片描述
C

int getLength(struct ListNode* head) {
    int length = 0;
    while (head) {
        ++length;
        head = head->next;
    }
    return length;
}

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* dummy = malloc(sizeof(struct ListNode));
    dummy->val = 0, dummy->next = head;
    int length = getLength(head);
    struct ListNode* cur = dummy;
    for (int i = 1; i < length - n + 1; ++i) {
        cur = cur->next;
    }
    cur->next = cur->next->next;
    struct ListNode* ans = dummy->next;
    free(dummy);
    return ans;
}

C++

class Solution {
public:
    int getLength(ListNode* head) {
        int length = 0;
        while (head) {
            ++length;
            head = head->next;
        }
        return length;
    }

    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        int length = getLength(head);
        ListNode* cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur->next;
        }
        cur->next = cur->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

代码解释:
(1)ListNode* dummy = new ListNode(0, head);
  在C++中,使用new关键字可以动态地在堆上分配内存,返回指向该内存的指针。ListNode(0, head)是调用ListNode类的构造函数,创建一个新的ListNode对象,并将0和head作为参数传递给构造函数。因为该构造函数接收两个参数,所以使用了括号括起来的两个参数。

  这段代码的作用是创建一个虚拟头节点,将其值初始化为0,next指针指向head,这样可以方便处理删除头节点的情况。因为该虚拟头节点是动态分配的,所以在函数结束时需要调用delete关键字释放动态分配的内存。

方法2:栈

在这里插入图片描述
C

struct Stack {
    struct ListNode* val;
    struct Stack* next;
};

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* dummy = malloc(sizeof(struct ListNode));
    dummy->val = 0, dummy->next = head;
    struct Stack* stk = NULL;
    struct ListNode* cur = dummy;
    while (cur) {
        struct Stack* tmp = malloc(sizeof(struct Stack));
        tmp->val = cur, tmp->next = stk;
        stk = tmp;
        cur = cur->next;
    }
    for (int i = 0; i < n; ++i) {
        struct Stack* tmp = stk->next;
        free(stk);
        stk = tmp;
    }
    struct ListNode* prev = stk->val;
    prev->next = prev->next->next;
    struct ListNode* ans = dummy->next;
    free(dummy);
    return ans;
}

代码解释:
(1)tmp->val = cur, tmp->next = stk;stk = tmp;
  第一行代码将 tmp 的 val 字段设置为 cur 的当前值,即将当前节点压入栈中。第二行代码将 tmp 的 next 字段设置为栈的当前顶部节点 stk 的地址,表示将新节点的下一个节点设置为当前栈顶节点。最后,将 stk 的值更新为 tmp 的地址,即将新节点 tmp 设置为栈的新顶部节点。这样,新节点就被成功压入了栈中。

C++

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        stack<ListNode*> stk;
        ListNode* cur = dummy;
        while (cur) {
            stk.push(cur);
            cur = cur->next;
        }
        for (int i = 0; i < n; ++i) {
            stk.pop();
        }
        ListNode* prev = stk.top();
        prev->next = prev->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

代码解释
(1)stk.push(cur);
  这行代码的作用是将当前节点 cur 压入栈中。具体来说,代码使用一个 stack 容器来存储链表中的每个节点,每当遍历到一个节点时,就将其压入栈中,以便后续在栈中访问该节点。由于栈是一种后进先出(LIFO)的数据结构,因此在遍历完整个链表后,栈中存储的节点顺序就是链表中节点的逆序。

(2)stk.pop();
  这行代码的作用是从栈中弹出一个节点,即将栈顶的节点删除。具体来说,代码使用一个 for 循环从栈中弹出前 n 个节点,即跳过倒数第 k 个节点。由于栈是一种后进先出(LIFO)的数据结构,因此每次 stk.pop() 操作都会弹出栈顶的节点,直到循环结束时,栈中存储的节点就是链表中从头节点开始到倒数第 k+1 个节点的所有节点。

(3)stk.top();
  这行代码的作用是获取栈顶节点的指针,即获取栈中最近压入的节点。具体来说,代码使用一个 stack 容器来存储链表中的每个节点,每当遍历到一个节点时,就将其压入栈中,以便后续在栈中访问该节点。

  由于栈是一种后进先出(LIFO)的数据结构,因此栈顶节点就是最近压入栈中的节点,可以使用 stk.top() 来获取栈顶节点的指针。在本实现中,栈中存储的是指向链表节点的指针,因此 stk.top() 返回的是一个指向栈顶节点的指针。

方法3:双指针

在这里插入图片描述
在这里插入图片描述
C

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* dummy = malloc(sizeof(struct ListNode));
    dummy->val = 0, dummy->next = head;
    struct ListNode* first = head;
    struct ListNode* second = dummy;
    for (int i = 0; i < n; ++i) {
        first = first->next;
    }
    while (first) {
        first = first->next;
        second = second->next;
    }
    second->next = second->next->next;
    struct ListNode* ans = dummy->next;
    free(dummy);
    return ans;
}

C++

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* first = head;
        ListNode* second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first->next;
        }
        while (first) {
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值