算法学习打卡day3 |203.移除链表元素、707.设计链表、24. 两两交换链表中的节点、206.反转链表、92. 反转链表 II、25. K 个一组翻转链表

数组和链表的区别

  • 数组在内存空间上是连续的,而链表是通过指针域将各个节点连接,实现逻辑连续,而物理空间不联系
  • 数组在按索引查询时时间复杂度为O(1),而链表为O(n)
  • 数组在插入删除时,由于需要移动其他数组元素,故时间复杂度为O(n),而链表在插入删除的时间复杂度为O(1)。

今日学习题目:

203.移除链表元素

力扣题目链接
题目描述:
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
思路:

  • 遍历数组元素,因为链表删除元素需要找到前一个元素,所以用cur的下一个节点去判断val,找到后,原地删除即可。
  • 在链表中添加或删除节点(一般一点是在需要修改链表之间的指针指向时,要想到用虚拟头节点)时,尽量借助虚拟头节点,这样可以不用单独处理头节点的逻辑

代码实现

  1. 带虚拟头节点代码如下:
ListNode* removeElements(ListNode* head, int val) {
	if (head == nullptr) {
            return nullptr;
        }
        ListNode* virtual_head = new ListNode(0, head);
        ListNode* cur_head = virtual_head;
        while(cur_head->next) {
            if (cur_head->next->val == val) {
                ListNode* tmp_head = cur_head->next;
                cur_head->next = tmp_head->next;
                delete tmp_head;
            } else {
                cur_head = cur_head->next;
            }
            
        }
        head = virtual_head->next;
        delete virtual_head;
        return head;
    }
  1. 不带虚拟头节点代码如下:
	ListNode* removeElements(ListNode* head, int val) {
		if (head == nullptr) {
            return nullptr;
     	}
     	if (head && head->val == val) {
			ListNode* tmp_node = head;
			head = head->next;
			delete tmp_node;
		}
        ListNode* cur_head = head;
        while(cur_head && cur_head->next) {
            if (cur_head->next->val == val) {
                ListNode* tmp_head = cur_head->next;
                cur_head->next = tmp_head->next;
                delete tmp_head;
            } else {
                cur_head = cur_head->next;
            }
            
        }
        head = virtual_head->next;
        delete virtual_head;
        return head;
    }

通过对比发现使用虚拟头节点时不需要单独处理链表开始位置

707.设计链表

力扣题目链接
题目描述:
你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

MyLinkedList() 初始化 MyLinkedList 对象。
int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
[“MyLinkedList”, “addAtHead”, “addAtTail”, “addAtIndex”, “get”, “deleteAtIndex”, “get”]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

思路:

  • 使用虚拟头节点进行插入和删除

代码实现

class MyLinkedList {
public:
    struct LinkedNode{
        int val;
        LinkedNode *next;
        LinkedNode(int val) : val(val), next(nullptr) {}    
    };
    MyLinkedList() {
        _pri_node = new LinkedNode(0);
        _size = 0;
    }
    
    int get(int index) {
        if (index >= _size) {
            return -1;
        }
        LinkedNode* cur_node = _pri_node;
        while(index--) {
            cur_node = cur_node->next;
        }
        return cur_node->next->val;
    }
    
    void addAtHead(int val) {
       LinkedNode* new_node = new LinkedNode(val);
       LinkedNode* tmp_node = _pri_node->next;
       _pri_node->next = new_node;
       new_node->next = tmp_node;
       _size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* new_node = new LinkedNode(val);
        LinkedNode* cur_node = _pri_node;
        while(cur_node->next) {
            cur_node = cur_node->next;
        }
        cur_node->next = new_node;
        
        //cout << cur_node->val << " " << _pri_node->next->val;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if (index > _size) {
            return;
        }
        LinkedNode* new_node = new LinkedNode(val);
        LinkedNode* cur_node = _pri_node;
        while(index--) {
            cur_node = cur_node->next;
        }
        new_node->next = cur_node->next;
        cur_node->next = new_node;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if (index >= _size) {
            return;
        }
        LinkedNode* cur_node = _pri_node;
        while(index--){
            cur_node = cur_node->next;
        }
        LinkedNode* tmp_node = cur_node->next;
        cur_node->next = tmp_node->next;
        delete tmp_node;
        _size--;
    }
private:
    LinkedNode* _pri_node;
    int _size;
};

24. 两两交换链表中的节点

力扣题目链接
题目描述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]
思路:

  • 这道题就是手动模拟的题目,比较容易混,首先,因为需要改变链表之间的指针指向,建立一个虚拟头节点,然后将交换时分四步,
    • 第一步: 因为要交换前两个节点,那么可以用tmp保存第三个节点(防止断链)
    • 第二步: 此时第三个节点已经存下来了,那么我们可以改变第二个节点的指向了吧?那么让第二个节点指向第一个节点。
    • 第三步: 此时第一个节点也跑不了,通过第二步我们可以通过第二个节点访问到第一个节点,现在要做的就是确保能访问到第二个节点,可以将虚拟头节点指向第二个节点。
    • 第四步: 最后把第三个节点接到第一个节点后面就可以了,这样就完成了一组交换,此时cur->2->1->3,然后后面节点依次类推。

代码实现:

  • 模拟法
ListNode* swapPairs(ListNode* head) {
        if (!head || !head->next)   return head;
        ListNode* virtual_node = new ListNode(0, head);
        ListNode* cur_node = virtual_node;
        while (cur_node->next && cur_node->next->next) {
            ListNode* tmp_node = cur_node->next->next->next;//第一步:保存第三个节点
            cur_node->next->next->next = cur_node->next;//第二步:让2指向1
            cur_node->next = cur_node->next->next;//第三步:让cur指向2
            cur_node->next->next->next = tmp_node;//第四步:让1指向3
            cur_node = cur_node->next->next;//最后更新cur到1,进行3和4的交换,依此类推
        }
        head = virtual_node->next;
        delete virtual_node;
        return head;
    }
  • 递归法:比较简单
ListNode* swapPairs(ListNode* head) {
        //递归法
        if(head == nullptr || head->next == nullptr) {
            return head;
        }
        ListNode* new_head = head->next;//保存头节点
        head->next = swapPairs(new_head->next);//更新当前头节点的下一个元素
        new_head->next = head;//更新新的头节点指针域
        return new_head;
    }

206.反转链表

力扣题目链接
题目描述:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

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

思路:

  • 两种方法,迭代法和递归法,递归法
  • 迭代法:定义三个指针,pre存放cur前一个指针,用于交换指针域,cur用于遍历链表,tmp存放cur下一个元素,用于防止断链,然后按图下步骤交换指针域,最后pre为头节点
    在这里插入图片描述

代码实现:

//迭代法
ListNode* reverseList(ListNode* head) {
        if (head == nullptr) {
            return head;
        }
        ListNode* pre_node = nullptr;
        ListNode* cur_node = head;
        ListNode* tmp_node = nullptr;
        while(cur_node) {
            tmp_node = cur_node->next;
            cur_node->next = pre_node;
            pre_node = cur_node;
            cur_node = tmp_node;
        }
        return pre_node;
    }
  • 递归法:对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的reverseList函数定义是这样的:输入一个节点head,将「以head为起点」的链表反转,并返回反转之后的头结点。
    代码如下:
ListNode* reverseList(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        ListNode* new_node = reverseList(head->next);//新的头节点
        head->next->next = head;
        head->next = nullptr;
        return new_node;
    }

ListNode* new_node = reverseList(head->next);
这一行代码得到的结果其实是下图这样的,看完图理解代码其实比较好理解了,new_node为我们反转之后的头节点
在这里插入图片描述

总结:

  • 虽然递归法看起来简洁,但是它的空间复杂度为O(n),比迭代法O(1)更大。

92. 反转链表 II

力扣题目链接
题目描述:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:

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

思路:

  • 这个题是反转链表的升级,首先是实现反转前N个链表的反转,然后将区间反转转化为前N个链表反转即可。
  • 如何实现前N个元素反转?
    • 其实稍微修改下反转链表递归函数即可,我们增加参数n,然后每递归一次就减1,递归函数的退出条件也是 n == 1 的时候,同时要记录successor为head->next,因为刚是反转整个链表所以为nullptr,而这次不一定是最后一个节点,所以我们的head->next 不再是null,而是我们退出时的后驱节点successor.
      代码如下:
ListNode* successor = nullptr;
ListNode* reverseListN(ListNode* head, int n) {
        if (n == 1) {
        	successor = head->next;
            return head;
        }
        ListNode* new_node = reverseList(head->next, n - 1);//新的头节点
        head->next->next = head;
        head->next = successor;
        return new_node;
    }
  • 如何转化为求前N个节点?
    • 思考:如果我们把head的索引视为 1,那么我们是想从第left个元素开始反转是吧?如果把head.next的索引视为 1 呢?那么相对于head.next,反转的区间应该是从第 left - 1个元素开始的;那么对于head.next.next呢……
    • 那么,将 left == 1作为递归退出的判断条件,然后不等于1时,我们就让下一个节点去递归调用函数,此时区间就减少1,直到left减为1就找到了我们要反转的地方,然后就可以调用反转前N个节点执行反转,而我们递归函数只需要把下一个节点反转的结果接到head后面即可。
    • 当然也可以for循环遍历到那个位置,但是相对于递归会麻烦一点。
    • 代码如下:
ListNode* reverseBetween(ListNode* head, int left, int right) {
        if (left == 1) {
            return reverseListN(head, right);
        }
        head->next = reverseBetween(head->next, left - 1, right - 1);
        return head;
    }

整体代码实现如下:

ListNode* successor = nullptr;
    ListNode* reverseListN(ListNode* head, int n) {
        if (n == 1) {
        	successor = head->next;
            return head;
        }
        ListNode* new_node = reverseListN(head->next, n - 1);//新的头节点
        head->next->next = head;
        head->next = successor;
        return new_node;
    }
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        if (left == 1) {
            return reverseListN(head, right);
        }
        head->next = reverseBetween(head->next, left - 1, right - 1);
        return head;
    }

25. K 个一组翻转链表

力扣题目链接
题目描述:
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

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

思路:

  • 本题是 24. 两两交换链表中的节点 升级,而本题是k个一组交换。
  • 还是递归法,分解为如下几步:
    1. 先反转以head开头的k个节点(反转的逻辑就是普通链表反转的迭代法,稍微修改下,现在是判断cur是否等于right为循环退出条件)
    2. 将k + 1个元素开头的节点递归调用reverseKGroud函数
    3. 将第k个节点和k+1后面反转的节点连接即可
    4. 递归退出条件为,剩余节点小于k个

代码实现

ListNode* reverse(ListNode* left, ListNode* right) {
        ListNode* pre = nullptr, *cur = left;
        while(cur != right) {
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* cur = head;
        int i = k;
        while(i--) {
            if (cur == nullptr) {
                return head;
            }
            cur = cur->next;
        }
        ListNode* new_node = reverse(head, cur);
        head->next = reverseKGroup(cur, k);

        return new_node;
    }
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值