【算法刷题之链表篇(2)】

目录

  • 1.leetcode-23. 合并 K 个升序链表(较难)
    • (1)题目描述
    • (2)方法一:顺序合并
    • (3)方法二:分治合并
    • (4)方法三:使用优先队列合并
  • 2.leetcode-92. 反转链表 II
    • (1)题目描述
    • (2)方法及思路(穿针引线)
    • (3)代码实现
  • 3.leetcode-2. 两数相加
    • (1)题目描述
    • (2)方法及思路(模拟)
    • (3)代码实现
  • 4.leetcode-21. 合并两个有序链表
    • (1)题目描述
    • (2)方法一:迭代
    • (3)方法二:递归
  • 5.leetcode-24. 两两交换链表中的节点
    • (1)题目描述
    • (2)方法一:迭代
    • (3)方法二:递归

1.leetcode-23. 合并 K 个升序链表(较难)

(1)题目描述

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
【算法刷题之链表篇(2)】_第1张图片

(2)方法一:顺序合并

思路
首先写出合并两个链表的代码
1.定义两个指针分别位于两个链表的头处,再定义一个哨兵位用于接受元素;
2.两个链表头处的指针开始遍历,并且相互比较,此时哨兵位newnode的节点head指向比较中更小的那个元素。
3.将此链表往前移一位,并且哨兵位的head也往前移动以为。
重复上述步骤即可合并两个链表;
对于k组链表的合并
对于所给k组链表,从头开始遍历,逐步将两个链表先合并为一个,然后再与后面合并,这样就可以调用上述的两两合并的函数
代码实现

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

(3)方法二:分治合并

考虑优化方法一,用分治的方法进行合并。
将 k 个链表配对并将同一对中的链表合并;
第一轮合并以后, k 个链表被合并成了k/2个链表,然后是k/4
重复这一过程,直到我们得到了最终的有序链表。
【算法刷题之链表篇(2)】_第2张图片

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* merge(vector <ListNode*> &lists, int l, int r) {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size() - 1);
    }
};

(4)方法三:使用优先队列合并

思路
这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,k 个链表就最多有 k 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。
代码实现

class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }
    };
    priority_queue <Status> q;
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for (auto node: lists) {
            if (node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while (!q.empty()) {
            auto f = q.top(); q.pop();
            tail->next = f.ptr; 
            tail = tail->next;
            if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};

代码解释:
这段代码是一个用于合并K个有序链表的解决方案。它使用了优先队列(priority_queue)来选择当前K个链表中最小的节点,并将其连接到结果链表中。
在mergeKLists函数中,定义了一个结构体Status,用于存储节点的值和指针。结构体中还重载了小于运算符,以便在优先队列中按照节点值的大小进行排序。
首先,将K个链表中的第一个节点加入到优先队列中。然后,创建一个虚拟头节点head和一个指针tail指向它。接下来,进入循环,直到优先队列为空。在每次循环中,取出优先队列中的最小节点f,将其连接到结果链表的末尾,即tail->next = f.ptr,然后更新tail指针为新的末尾节点。如果最小节点f的指针f.ptr还有下一个节点,将下一个节点的值和指针加入到优先队列中。重复这个过程,直到所有链表中的节点都被处理完。
最后,返回结果链表的头节点,即head.next。

2.leetcode-92. 反转链表 II

(1)题目描述

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
(注意left和right是位置)
【算法刷题之链表篇(2)】_第3张图片

(2)方法及思路(穿针引线)

思路示意图如下
【算法刷题之链表篇(2)】_第4张图片
如图所示,我们需要找到left前一个结点,也需要找到right后一个节点,
并在反转链表前将其与前后断开。
具体思路如下
.第 1 步:先将待反转的区域反转;
第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。
【算法刷题之链表篇(2)】_第5张图片

(3)代码实现

class Solution {
    private:
        void reverseLinkedList(ListNode* head)
        {
            ListNode* pre=NULL;
            ListNode* cur=head;
            while(cur!=NULL)
            {
                ListNode* next=cur->next;
                cur->next=pre;
                pre=cur;
                cur=next;
            }
        }
   public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* newnode=new ListNode(0);
        ListNode* prev=newnode;
        prev->next=head;
       for (int i = 0; i < left - 1; i++) {
            prev = prev->next;
        }
        ListNode* leftnode=prev->next;
        ListNode* rightnode=prev;
        while(right-left+1>0)
        {
            rightnode=rightnode->next;
            right--;
        }
        ListNode* curr=rightnode->next;
        prev->next=NULL;
        rightnode->next=NULL;
        reverseLinkedList(leftnode);
        prev->next=rightnode;
        leftnode->next=curr;
        return newnode->next;
    }
};

3.leetcode-2. 两数相加

(1)题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
【算法刷题之链表篇(2)】_第6张图片

(2)方法及思路(模拟)

思路:由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry,其中,答案链表处相应位置的数字为(n1+n2+carry)%10,而新的进位值为(n1+n2+carry)/10。

(3)代码实现

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr, *tail = nullptr;
        int carry = 0;
        while (l1 || l2) {
            int n1 = l1 ? l1->val: 0;
            int n2 = l2 ? l2->val: 0;
            int sum = n1 + n2 + carry;
            if (!head) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail->next = new ListNode(sum % 10);
                tail = tail->next;
            }
            carry = sum / 10;
            if (l1) {
                l1 = l1->next;
            }
            if (l2) {
                l2 = l2->next;
            }
        }
        if (carry > 0) {
            tail->next = new ListNode(carry);
        }
        return head;
    }
};

4.leetcode-21. 合并两个有序链表

(1)题目描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
【算法刷题之链表篇(2)】_第7张图片

(2)方法一:迭代

思路
我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
算法
首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
代码实现

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);

        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;
        return prehead.next;
    }
}

(3)方法二:递归

思路
我们可以如下递归地定义两个链表里的 merge 操作(忽略边界情况,比如空链表等):
在这里插入图片描述
1.如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。
2.否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。
3.如果两个链表有一个为空,递归结束。
代码如下:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1==NULL)
        {
            return list2;
        }
        else if(list2==NULL)
        {
            return list1;
        }
        else if(list1->val<list2->val)
        {
            list1->next=mergeTwoLists(list1->next,list2);
            return list1;
        }
        else
        {
            list2->next=mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

5.leetcode-24. 两两交换链表中的节点

(1)题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
【算法刷题之链表篇(2)】_第8张图片

(2)方法一:迭代

思路:
1.首先创建一个哨兵位newnode,并定义一个指针prev指向它。
2.在prev后定义一个当前节点n1,后一个为n2,并先将prev指向n2,n2,指向n1,再把n1指向原来n2的下一位
3.最后更新prev节点到n1的新位置。

代码实现

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* newnode=new ListNode(0);
        ListNode* prev=newnode;
        prev->next=head;
        while(prev->next&&prev->next->next)
        {
            ListNode* n1=prev->next;
            ListNode* n2=prev->next->next;
            prev->next=n2;
            n1->next=n2->next;
            n2->next=n1;
            prev=n1;
        }
        return newnode->next;
    }
};

(3)方法二:递归

思路与算法
可以通过递归的方式实现两两交换链表中的节点。
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
1.如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。
2.链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

代码实现:

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

你可能感兴趣的:(算法,链表,数据结构)