数据结构与算法期末复习第四天

反转链表

原题

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

示例 1:


输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:


输入:head = [1,2]
输出:[2,1]
示例 3:

输入:head = [ ]
输出:[ ]
 

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

思路

方法一:迭代

在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

 方法二:递归

递归其关键在于反向工作,假设链表的其余部分已经被反转,现在应该如何反转它前面的部分,

那么可以让nk+1下一个节点指向nk,即  

代码实例

//方法一:迭代
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL; //反转链表,第一个应该指向的尾结点为空
    struct ListNode* curr = head; //当前节点为头结点
    while (curr) {
        struct ListNode* next = curr->next; //移动指针到下一个
        curr->next = prev; //将curr指向curr前面的那个prev
        prev = curr; //往下走,prev变为curr
        curr = next; //往下走,curr变成后面的next
    }
    return prev; //最后返回prev
}

反转链表 II

原题

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例 1:


输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

提示:

链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n

进阶: 你可以使用一趟扫描完成反转吗?

思路

需要设置一个哨兵节点,用于left=1时

参照该视频 :反转链表【基础算法精讲 06】_哔哩哔哩_bilibili

class Solution {
public:
    ListNode *reverseBetween(ListNode *head, int left, int right) {
        ListNode *dummy = new ListNode(0, head), *p0 = dummy;
        for (int i = 0; i < left - 1; ++i)
            p0 = p0->next;

        ListNode *pre = nullptr, *cur = p0->next;
        for (int i = 0; i < right - left + 1; ++i) {
            ListNode *nxt = cur->next;
            cur->next = pre; // 每次循环只修改一个 next,方便大家理解
            pre = cur;
            cur = nxt;
        }

        // 见视频
        p0->next->next = cur;
        p0->next = pre;
        return dummy->next;
    }
};

 力扣官方穿针引线

使用「206. 反转链表」的解法,反转 left 到 right 部分以后,再拼接起来。我们还需要记录 left 的前一个节点,和 right 的后一个节点。如图所示:

算法步骤:

第 1 步:先将待反转的区域反转;
第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。

代码实例

void reverseLinkedList(struct ListNode *head) {
    // 也可以使用递归反转一个链表
    struct ListNode *pre = NULL;
    struct ListNode *cur = head;

    while (cur != NULL) {
        struct ListNode *next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
}

struct ListNode *reverseBetween(struct ListNode *head, int left, int right) {
    // 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
    struct ListNode *dummyNode = malloc(sizeof(struct ListNode));
    dummyNode->val = -1;
    dummyNode->next = head;

    struct ListNode *pre = dummyNode;
    // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
    // 建议写在 for 循环里,语义清晰
    for (int i = 0; i < left - 1; i++) {
        pre = pre->next;
    }

    // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
    struct ListNode *rightNode = pre;
    for (int i = 0; i < right - left + 1; i++) {
        rightNode = rightNode->next;
    }

    // 第 3 步:切断出一个子链表(截取链表)
    struct ListNode *leftNode = pre->next;
    struct ListNode *curr = rightNode->next;

    // 注意:切断链接
    pre->next = NULL;
    rightNode->next = NULL;

    // 第 4 步:同第 206 题,反转链表的子区间
    reverseLinkedList(leftNode);

    // 第 5 步:接回到原来的链表中
    pre->next = rightNode;
    leftNode->next = curr;
    return dummyNode->next;
}

奇偶链表

原题

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。

示例 1:

输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
示例 2:

输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]
 

提示:

n ==  链表中的节点数
0 <= n <= 104
-106 <= Node.val <= 106

思路

如果链表为空,则直接返回链表。

对于原始链表,每个节点都是奇数节点或偶数节点。头节点是奇数节点,头节点的后一个节点是偶数节点,相邻节点的奇偶性不同。因此可以将奇数节点和偶数节点分离成奇数链表和偶数链表,然后将偶数链表连接在奇数链表之后,合并后的链表即为结果链表。

代码实例

struct ListNode* oddEvenList(struct ListNode* head) {
    if (head == NULL) {
        return head;
    }
//分离
    struct ListNode* evenHead = head->next;
    struct ListNode* odd = head; //奇节点
    struct ListNode* even = evenHead; //偶节点
    while (even != NULL && even->next != NULL) {
        odd->next = even->next;
        odd = odd->next;
        even->next = odd->next;
        even = even->next;
    }
//结合
    odd->next = evenHead; //奇节点的尾结点是偶节点
    return head;
}

作

分隔链表

原题

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:


输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:

输入:head = [2,1], x = 2
输出:[1,2]
 

提示:

链表中节点的数目在范围 [0, 200] 内
-100 <= Node.val <= 100
-200 <= x <= 200

思路

直观来说我们只需维护两个链表 small 和 large即可,small 链表按顺序存储所有小于 xxx 的节点,large 链表按顺序存储所有大于等于 xxx 的节点。遍历完原链表后,我们只要将 small链表尾节点指向 large链表的头节点即能完成对链表的分隔。

代码实例

struct ListNode* partition(struct ListNode* head, int x) {
    struct ListNode* small = malloc(sizeof(struct ListNode));
    struct ListNode* smallHead = small;
    struct ListNode* large = malloc(sizeof(struct ListNode));
    struct ListNode* largeHead = large;
    while (head != NULL) {
        if (head->val < x) {
            small->next = head;
            small = small->next;
        } else {
            large->next = head;
            large = large->next;
        }
        head = head->next;
    }
    large->next = NULL;
    small->next = largeHead->next;
    return smallHead->next;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值