首页 > 其他分享 >手撕代码之链表

手撕代码之链表

时间:2023-08-29 12:36:26浏览次数:30  
标签:结点 ListNode 代码 nullptr head next 链表



文章目录

  • 一、反转链表(leetcode 206)
  • 二、两个链表的交点(leetcode 160)
  • 三、链表的中间结点(leetcode 876)
  • 四、判断链表是否存在环(leetcode 141)
  • 五、输出链表环的入口结点(leetcode 142)
  • 六、删除链表中倒数第k个节点(leetcode 19)
  • 七、合并两个排序链表(leetcode 21)
  • 八、O(1)时间删除链表中的一个结点(leetcode 237)
  • 九、删除重复的结点(leetcode 83)
  • 十、删除链表中所有指定的节点(leetcode 203)
  • 十一、链表重排/对折(leetcode 143)
  • 十二、链表排序(leetcode 148)
  • 十三、调整链表顺序使奇数位置的结点位于偶数位置结点的前面(leetcode 328)
  • 十四、合并k个有序链表(leetcode 23)
  • 十五、部分翻转链表(leetcode 92)
  • 十六、链表相加(leetcode 2)


【注意1】:对于链表的问题,根据以往的经验一般都是要建一个dummy node,连上原链表的头结点,这样的话就算头结点变动了,我们还可以通过dummy->next来获得新链表的头结点。

【注意2】:链表的问题很多都可以使用双指针解决,有时候是快慢指针

// 创建链表和输出链表
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct ListNode
{
	int val;
	ListNode *next;
	ListNode(int value) : val(value), next(nullptr) { }
};

ListNode *test_fun(ListNode *l1) {

}

int main() {
	vector<int> data1 = { 2,4,3 };
	ListNode *head1 = new ListNode(data1[0]), *ptr1 = head1;
	for (int i = 1; i < data1.size(); ++i) {
		ListNode *tmp1 = new ListNode(data1[i]);
		ptr1->next = tmp1;
		ptr1 = tmp1;
	}
	ptr1->next = nullptr;
	
	ListNode *ptr_tmp = test_fun(head1);
	while (ptr_tmp) {
		cout << ptr_tmp->val << endl;
		ptr_tmp = ptr_tmp->next;
	}
	system("pause");
	return 0;
}

一、反转链表(leetcode 206)

手撕代码之链表_链表


  思路:用两个指针记录下当前指针的前驱和后继,然后不断更新指针的指向,保存当前结点的下一个结点,防止链表断裂找不到后继

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 用两个指针记录下当前指针的前驱和后继,然后不断更新指针的指向
        ListNode *pPre = nullptr, *pNext; 
        while (head != nullptr){
            // 保存当前结点的下一个结点,防止链表断裂找不到后继
            pNext = head->next;
            // 修改当前结点的指向
            head->next = pPre;
            // 更新指针
            pPre = head;
            head = pNext;
        }
        return pPre;
    }
};

二、两个链表的交点(leetcode 160)

手撕代码之链表_链表_02


  思路:先计算两个链表的长度,让长度较长的链表先走一段距离,之后两个结点齐头并进,如果相遇则找到相交的结点了

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int len1 = 0, len2 = 0;
        ListNode *tmpA = headA;
        ListNode *tmpB = headB;
        // 先计算两个链表的长度
        while (tmpA != nullptr){
            len1++;
            tmpA = tmpA->next;
        }
        while (tmpB != nullptr){
            len2++;
            tmpB = tmpB->next;
        }
        if (len1 > len2){
            int tmp = len1 - len2;
            // 让长度较长的链表先走一段距离
            for (int i = 0; i < tmp; ++i)
                headA = headA->next;
            // 之后两个结点齐头并进,如果相遇则找到相交的结点了
            while (headA != nullptr && headB != nullptr){
                if (headA == headB)
                    return headA;
                headA = headA->next;
                headB = headB->next;
            }
        }
        else{
            int tmp = len2 - len1;
            for (int i = 0; i < tmp; ++i)
                headB = headB->next;
            while (headB != nullptr && headA != nullptr){
                if (headB == headA)
                    return headB;
                headA = headA->next;
                headB = headB->next;
            }
        }
        return nullptr;
    }
};

三、链表的中间结点(leetcode 876)

手撕代码之链表_链表_03


  思路:快慢指针,快指针每次走两步,慢指针每次走一步,当快指针到头了,那么慢指针就是中间结点

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *pTmp = head;
        while (head != nullptr && head->next != nullptr){
            // 快慢指针,快指针每次走两步,慢指针每次走一步,当快指针到头了,
            // 那么慢指针就是中间结点
            pTmp = pTmp->next;
            head = head->next->next;
        }
        return pTmp;
    }
};

四、判断链表是否存在环(leetcode 141)

手撕代码之链表_快慢指针_04


  思路:快慢指针,快指针每次走两步,慢指针每次走一步,当快慢指针相遇时,即链表存在环

class Solution {
public:
    bool hasCycle(ListNode *head) {
        // 快慢指针
	    ListNode *pSlow = head, *pFast = head;
	    while (pSlow != nullptr && pFast != nullptr && pFast->next != nullptr)
	    {
	    	pSlow = pSlow->next;
	    	pFast = pFast->next->next;
            // 当快慢指针相遇时,即链表存在环
            if (pSlow == pFast)
	    		return true;
		}
		return false;
    }
};

五、输出链表环的入口结点(leetcode 142)

手撕代码之链表_结点_05


  思路:先使用快慢指针找到相遇的结点,将慢指针移动到头结点,然后快慢指针前进速度变为一样,之后再相遇则为入口结点

手撕代码之链表_结点_06


  图中,head到环路起点的距离为K,起点到fast和slow的相遇点的距离为M,环路周长为L。假设,在fast和slow相遇时,fast走过了Lfast,slow走过了Lslow。根据题意:Lslow=K+M;Lfast=K+M+nL(n为正整数);Lfast=2Lslow。可以推出:Lslow=nL;K=nL-M。则当slow重新回到head,而fast还在相遇点,slow和fast都向前走,且每次走一个节点。则slow从head走到起点走了K,而fast从相遇点出发也走了K,而fast向前走了距离K后到了哪里呢?由于K=(n-1)*L+(L-M),所以fast转了n-1圈,再走L-M,也到了起点。这样起点就找到了。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *pSlow = head, *pFast = head;
        // 先使用快慢指针找到相遇的结点
        while (pFast != nullptr && pFast->next != nullptr){
            pSlow = pSlow->next;
            pFast = pFast->next->next;
            if (pSlow == pFast)
                break;
        }
        if (pFast == nullptr || pFast->next == nullptr)
            return nullptr;
        // 将慢指针移动到头结点,然后快慢指针前进速度变为一样,之后再相遇则为入口结点
        pSlow = head;
        while (pSlow != pFast){
            pSlow = pSlow->next;
            pFast = pFast->next;            
        }
        return pSlow;
    }
};

六、删除链表中倒数第k个节点(leetcode 19)

手撕代码之链表_结点_07


  思路:使用双指针,让其中一个指针先走K步,当它到达链表尾部了,则另一个指针为倒数第K个结点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if (head == nullptr) return nullptr;
        ListNode *p1 = head, *p2 = head;
        // 使用双指针找到倒数第K个结点
        for (int i = 0; i < n; ++i){
            p2 = p2->next;
        }
        if (p2 == nullptr)
            return head->next;
        while (p2->next != nullptr){
            p1 = p1->next;
            p2 = p2->next;
        }
        // 删除第K个结点
        p1->next = p1->next->next;
        return head;
    }
};

七、合并两个排序链表(leetcode 21)

手撕代码之链表_结点_08


  思路:新链表的头结点是两个链表中较小的一个,头结点的下一个结点则是由原来较小的链表的下一个结点和原来较长的链表产生,是一个递归过程。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *pHead;
        // 某一个链表为空则返回较长的那个
        if (l1 == nullptr) return l2;
        if (l2 == nullptr) return l1;
        if (l1->val <= l2->val){
            // 新链表的头结点是两个链表中较小的一个
            pHead = l1;
            // 新链表的下一个结点则是由原来较小的链表的下一个结点和原来较长的链表产生
            pHead->next = mergeTwoLists(l1->next, l2);
        }
        else{
            pHead = l2;
            pHead->next = mergeTwoLists(l1, l2->next);               
        }
        return pHead;
    }
};

八、O(1)时间删除链表中的一个结点(leetcode 237)

手撕代码之链表_快慢指针_09


  思路:保存当前结点的下一个结点,把下一个结点复制给当前结点,删除下一个结点

class Solution {
public:
    void deleteNode(ListNode* node) {
        // 保存当前结点的下一个结点
        ListNode *pTemp = node->next;
        // 把下一个结点复制给当前结点
        node->val = pTemp->val;
        node->next = pTemp->next;
        // 删除下一个结点
        delete pTemp;
        pTemp = nullptr;
    }
};

九、删除重复的结点(leetcode 83)

手撕代码之链表_快慢指针_10


  思路:双指针,记录下一个结点是否和当前结点相等,如果两个结点相等,则删除后面一个结点

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        // 双指针,记录下一个结点是否和当前结点相等,如果两个结点相等,则删除后面一个结点
        ListNode *p1 = head, *p2 = head->next;
        while (p2 != nullptr){
            if (p1->val == p2->val){
                p1->next = p2->next;
                p2 = p2->next;
            }else{
                p1 = p1->next;
                p2 = p2->next;
            }
        }
        return head;
    }
};

十、删除链表中所有指定的节点(leetcode 203)

手撕代码之链表_链表_11


  思路:对于要删除节点的题目,可以先创建一个不存在的新节点。至于本题,对于当前的指针,如果下一个节点值和当前想等,则直接移动指针指向

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (head == nullptr) return head;
        // 创建一个不存在的新节点
        ListNode* ptr_newhead = new ListNode(-1);
        ptr_newhead->next = head;
        ListNode *cur = ptr_newhead;
        while (cur != nullptr && cur->next != nullptr) {
            // 遇到相同的元素则移动当前指针的指向
            if (cur->next->val == val){
                cur->next = cur->next->next;
            } else {
                cur = cur->next;
            }
        }
        return ptr_newhead->next;
    }
};

十一、链表重排/对折(leetcode 143)

手撕代码之链表_快慢指针_12


  思路:分三步走:第一步,使用快慢指针找到中间节点;第二步,翻转后半段;第三步,将两段连接起来

手撕代码之链表_结点_13

class Solution {
public:
    void reorderList(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return;
        // 先使用快慢指针找到中间节点
        ListNode *fast = head, *slow = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        // 然后翻转后半部分的链表
        ListNode *cur = slow->next;
        slow->next = nullptr;
        ListNode *cur_pre = nullptr;
        while (cur != nullptr) {
            ListNode *cur_next = cur->next;
            cur->next = cur_pre;
            cur_pre = cur;
            cur = cur_next;
        }
        // 最后将两段链表插空连接起来
        ListNode *first = head, *second = cur_pre;
        while (first != nullptr && second != nullptr) {
            ListNode *first_next = first->next;
            first->next = second;
            ListNode *second_next = second->next;
            second->next = first_next;
            first = first_next;
            second = second_next;
        }
    }
};

十二、链表排序(leetcode 148)

手撕代码之链表_快慢指针_14


  思路:使用快排对链表排序

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        QuickSortList(head, nullptr);
        return head;
    }
    // 分别对前半部分和后半部分排序
    void QuickSortList(ListNode* pBegin, ListNode* pEnd) {
        if (pBegin != pEnd){
            ListNode *pMid = partition(pBegin, pEnd);
            QuickSortList(pBegin, pMid);
            if (pMid->next)
                QuickSortList(pMid->next, pEnd);
        }
    }
    // 一次划分(和数组类似)
    ListNode* partition(ListNode* pBegin, ListNode* pEnd){
        ListNode *pMid = pBegin, *pTmp = pMid->next;
        while (pTmp != pEnd){
            if (pTmp->val < pBegin->val){
                pMid = pMid->next;
                swap(pMid->val, pTmp->val);
            }
            pTmp = pTmp->next;
        }
        swap(pMid->val, pBegin->val);
        return pMid;
    }
};

十三、调整链表顺序使奇数位置的结点位于偶数位置结点的前面(leetcode 328)

手撕代码之链表_链表_15


  思路:用两个奇偶指针分别指向奇偶节点的起始位置,用一个单独的指针even_head来保存偶节点的起点位置,然后把奇节点的指向偶节点的下一个(一定是奇节点),把奇节点后移一步,再把偶节点指向下一个奇节点的下一个(一定是偶节点),把偶节点后移一步,以此类推直至末尾,此时把分开的偶节点的链表连在奇节点的链表后即可

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *odd = head, *even = head->next, *even_head = even;
        while (even != nullptr && even->next != nullptr){
            odd = odd->next = even->next;
            even = even->next = odd->next;
        }
        odd->next = even_head;
        return head;
    }
};

十四、合并k个有序链表(leetcode 23)

手撕代码之链表_结点_16


  思路:使用优先队列存储k个链表的每一个元素,它们会自动排好序,然后重新建立一个新的链表

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 使用优先队列存储k个链表的每一个元素,它们会自动排好序
        priority_queue<int, vector<int>, greater<int>> pq;
        for (auto node : lists) {
            while (node) pq.push(node->val), node = node->next;
        }
        // 先创建一个首节点
        ListNode *ptr_pre = new ListNode(-1), *ptr = ptr_pre;
        while (!pq.empty()) {
            ListNode *node = new ListNode(pq.top());
            ptr->next = node;
            ptr = ptr->next;
            pq.pop();
        }
        return ptr_pre->next;
    }
};

十五、部分翻转链表(leetcode 92)

手撕代码之链表_链表_17


  思路:建一个dummy node,连上原链表的头节点,定义pre、cur节点。后续的操作是先取出当前节点的下一个节点,然后插入到要翻转的位置上,不断把后面的元素插入到前面

class Solution {
public:
    ListNode *reverseBetween(ListNode *head, int m, int n) {
        // 建一个dummy node,连上原链表的头结点
        ListNode *dummy = new ListNode(-1), *pre = dummy;
        dummy->next = head;
        for (int i = 0; i < m-1; ++i)
            pre = pre->next;
        ListNode *cur = pre->next;
        // 后续的操作是先取出当前节点的下一个节点,然后插入到要翻转的位置上,不断把后面的元素插入到前面
        for (int i = m; i < n; ++i) {
            ListNode *tmp = cur->next;
            cur->next = tmp->next;
            tmp->next = pre->next;
            pre->next = tmp;
        }
        return dummy->next;
    }
};

十六、链表相加(leetcode 2)

手撕代码之链表_结点_18


  思路:每次加完之后,创造一个新的结点,让当前结点指向它,并移动当前结点

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry = 0, sum = 0;
        // 先创造一个前驱结点,并赋值给当前结点
        ListNode *ptr_pre = new ListNode(-1);
        ListNode *ptr = ptr_pre;
        while (l1 || l2 || carry){
            if (l1){
                sum += l1->val;
                l1 = l1->next;
            }
            if (l2){
                sum += l2->val;
                l2 = l2->next;
            }
            if (carry){
                sum += carry;
                carry = 0;
            }
            // 计算进位
            carry = sum / 10;
            sum %= 10;
            // 创造一个新的结点,让当前结点指向它,并移动当前结点
            ptr->next = new ListNode(sum);
            ptr = ptr->next;
            sum = 0;
        }
        ptr->next = nullptr;
        // 返回前驱结点的下一个结点
        ptr = ptr_pre->next;
        return ptr;
    }
};


标签:结点,ListNode,代码,nullptr,head,next,链表
From: https://blog.51cto.com/u_6526235/7274773

相关文章

  • 快排及链表排序
    文章目录一、普通的快排二、链表的创建三、链表的冒泡排序四、链表快排五、链表归并排序一、普通的快排voidQuickSort(vector<int>&vec,intlow,inthigh){ intpivot=vec[low];//选择第一个元素作为枢纽元 //mid总是指向最后一个比枢纽元小的位置,当需要交换元素时,先......
  • 撮合前端平台在低代码平台的落地实践 | 京东云技术团队
    在京东技术的发展当下,不同的业务线,不同的区域,甚至于很多触达消费者的端,正在被中台架构能力所支撑。大家都很清楚,中台建设能够带来技术的规模化效应,具有提高业务协同、加速创新和交付速度、提高系统稳定性和可靠性、降低成本和支持业务快速发展等优势。中台架构往往和领域产品有密切......
  • [代码随想录]Day30-贪心算法part04
    题目:860.柠檬水找零思路:收到钱三种情况:5刀:直接收起来就可以了,不需要找钱10刀:收到10刀,需要找5刀,如果没有5刀,就返回false,否则5刀-120刀:收到20刀(但是没用,找钱也不能找20所以不需要记录数量),优先考虑找105,因为10只能在这里用,其次再考虑找555代码:funclemonadeChange(bil......
  • 手撕代码之数组
    文章目录一、二维数组中的查找(leetcode240)二、旋转数组的最小数字(leetcode153)三、旋转数组中的查找(leetcode33)四、数组中出现次数超过一半的数字(leetcode169)五、把数组排成最大的数(leetcode179)六、数组中只出现一次的数字(leetcode136)七、排序数组中查找某一个数第一次和最后......
  • 手撕代码之其他类型
    文章目录一、根据rand7生成rand10(leetcode470)二、快速幂(leetcode50)三、数字二进制表示后1的个数(leetcode191)四、判断点是否在三角形内五、下一个全排列(leetcode31)六、带精度的开根号(leetcode69)七、实现strcpy和memcpy八、路径简化(leetcode71)九、字母异位词分组(leetcode49)十......
  • 手撕代码之字符串
    文章目录一、反转字符串中的每一个单词(leetcode151、557)二、多个字符串的最长公共前缀(leetcode14)三、字符串转整数(leetcode8)四、N位数字串删除K个数字,使剩下的数字串最小(leetcode402)五、回文子串的个数(Leetcode647)六、最长无重复字符的子串(leetcode3)七、最长回文子串(leetcod......
  • 手撕代码之栈和队列
    文章目录一、括号匹配(leetcode20)二、最小栈(leetcode155)三、两个栈实现一个队列(leetcode232)一、括号匹配(leetcode20)classSolution{public:boolisValid(strings){if(s.empty())returntrue;stack<char>stk;stk.push(s[0]......
  • 手撕代码之二叉树
    文章目录一、根据排序数组构造二叉搜索树(leetcode108)二、根据前序遍历和中序遍历构造二叉树(leetcode105)三、二叉树的非递归遍历(leetcode94、144、145)四、二叉树中和为某一值的路径(leetcode112)五、二叉树的最大深度(leetcode104)六、二叉树的层次遍历(leetcode102)七、判断两个二......
  • Java代码审计之目录穿越
    一、目录穿越漏洞1、什么是目录穿越所谓的目录穿越指利用操作系统中的文件系统对目录的表示。在文件系统路径中,".."表示上一级目录,当你使用"../"时,你正在引用当前目录的上一级目录。如果你使用"../../",你实际上在两次".."的基础上,再次引用上一级目录,从而返回到上两级目录。......
  • Vscode如何如何显示vue代码提示
    Vscode使用版本 下载这个插件 ......