链表-01

链表-01

  • 1. 合并两个有序链表
  • 2. 反转链表
  • 3. 两数相加

1. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

真题点击此处:21. 合并两个有序链表

解题思路:
假设有两个有序链表 list1 和 list2,我们需要将它们合并为一个新的有序链表。

  1. 首先,我们比较 list1 和 list2 的头节点,取值较小的节点作为新链表的头节点,并将较小节点所在链表的指针指向下一个节点。
  2. 然后,我们递归地对剩余的部分进行合并操作,继续比较两个链表的头节点,选择较小值的节点连接到已合并的链表上。
  3. 重复以上步骤,直到其中一个链表为空,此时将另一个链表直接连接到已合并的链表的末尾。

具体解题思路可以总结为以下几点:

  • 如果其中一个链表为空,直接返回另一个链表,因为已经是有序的了。
  • 否则,比较两个链表头节点的值,将值较小的节点作为合并后链表的头节点。
  • 对剩余部分进行递归合并,将较小节点的下一个节点与另一个链表递归合并的结果连接起来。

这种方法利用了递归的特性,每次都选择较小节点进行合并,直到一个链表为空,然后将另一个非空链表直接连接到已合并的链表的末尾。

以下为代码实现:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr){
            return list2;
        }else if(list2 == nullptr){
            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;
        }
    }
};

时间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次调用递归都会去掉 list1 或者 list2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O(n+m)。

空间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。递归调用mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次,因此空间复杂度为O(n+m)。

2. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

真题点击此处:206. 反转链表

解题思路:
假设有一个单向链表,我们需要将其翻转。例如,原始链表为 1->2->3->4->5,翻转后的链表为 5->4->3->2->1。

  1. 我们定义两个指针 prev 和 curr,分别指向链表头节点之前的位置和链表头节点。
  2. 遍历链表,每次将当前节点的下一个节点保存在临时变量 next 中。
  3. 然后将当前节点的 next 指针指向 prev,完成节点的翻转。
  4. 接着,将 prev 指针移动到当前节点所在位置,将 curr 指针移动到 next 所指向的位置。
  5. 重复以上步骤,直到遍历完整个链表,此时 prev 所指向的位置即为新链表的头节点。

具体解题思路可以总结为以下几点:

  • 定义两个指针 prev 和 curr,分别指向链表头节点之前的位置和链表头节点。
  • 遍历链表,每次将当前节点的下一个节点保存在临时变量 next 中。
  • 将当前节点的 next 指针指向 prev,完成节点的翻转。
  • 将 prev 指针移动到当前节点所在位置,将 curr 指针移动到 next 所指向的位置。
  • 重复以上步骤,直到遍历完整个链表,此时 prev 所指向的位置即为新链表的头节点。

这种方法利用了迭代的特性,每次将当前节点的下一个节点保存起来,然后将当前节点的 next 指针指向前面已经翻转好的链表,最后将 prev 和 curr 指针向后移动,直到遍历完整个链表。

以下为具体的代码实现:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;

        while(curr != nullptr){
            ListNode* next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
};

时间复杂度:O(n),其中 nnn 是链表的长度。需要遍历链表一次。
空间复杂度:O(1)。

3. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

真题点击此处:2. 两数相加

解题思路:
假设有两个非空的链表 l1 和 l2,它们分别表示两个非负整数的每一位数字,数字低位在链表头部。我们需要将这两个链表表示的数字相加,并以相同形式返回一个新的链表。

  1. 我们首先定义一个头指针 head 和一个尾指针 tail 并初始化为 nullptr。
  2. 然后我们使用一个变量 carry 来表示进位,初始值为 0。
  3. 我们遍历两个链表 l1 和 l2,同时计算当前对应位置的数字和进位的和 sum。
  4. 如果 head 为空,说明是第一个节点,我们创建新节点作为头节点,并将头指针和尾指针都指向这个节点。
  5. 如果 head 不为空,我们创建新节点作为尾节点的下一个节点,并将尾指针指向该节点。
  6. 将进位值加到下一位的计算中。
  7. 遍历完两个链表后,如果最高位有进位,还需在新链表的末尾添加一个节点。

具体解题思路可以总结为以下几点:

  • 定义头指针 head 和尾指针 tail,并初始化为 nullptr。
  • 遍历两个链表 l1 和 l2,同时计算当前对应位置的数字和进位的和 sum。
  • 根据 sum 的个位数创建新的节点,并更新头指针和尾指针。
  • 将进位值加到下一位的计算中。
  • 遍历完两个链表后,如果最高位有进位,还需在新链表的末尾添加一个节点。

这种方法利用了链表的特性,顺序处理两个链表对应位置的数字,并考虑进位的情况,最终得到表示两数相加结果的新链表。

以下为代码实现:

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

时间复杂度:O(max(m,n)),其中 m 和 n 分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的时间。

空间复杂度:O(1)。注意返回值不计入空间复杂度。

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