练习题(2024/5/13)

1移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

复制代码
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

复制代码
输入:head = [], val = 1
输出:[]

示例 3:

复制代码
输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

思路:

使用虚拟头结点的方法来简化链表的删除操作。首先,创建一个值为0的虚拟头结点,然后将其指向原始链表的头部。接着,使用一个指针 cur 遍历整个链表,当 cur->next 的值等于目标值 val 时,删除当前节点的下一个节点,并将当前节点指向下下个节点;否则,将 cur 指针向后移动一个节点。最后,返回虚拟头结点的下一个节点作为新的链表头部。

代码:

cpp 复制代码
class Solution {
public:
    // 删除链表中值为 val 的所有节点
    ListNode* removeElements(ListNode* head, int val) {
        // 设置一个虚拟头结点
        ListNode* dummyHead = new ListNode(0);
        // 将虚拟头结点指向 head,这样方便后面做删除操作
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        // 遍历链表
        while (cur->next != NULL) {
            // 如果当前节点的下一个节点的值等于 val,则删除下一个节点
            if(cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        // 更新 head 指针
        head = dummyHead->next;
        // 删除虚拟头结点
        delete dummyHead;
        return head;
    }
};

2设计链表

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:valnextval 是当前节点的值,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]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

提示:

  • 0 <= index, val <= 1000
  • 请不要使用内置的 LinkedList 库。
  • 调用 getaddAtHeadaddAtTailaddAtIndexdeleteAtIndex 的次数不超过 2000

思路:

这道题的要求有五个:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点
  1. 定义节点结构体: 开始先定义了一个嵌套的节点结构体LinkedNode,它包含两个成员变量,一个是节点的值val,另一个是指向下一个节点的指针next

  2. 初始化链表:MyLinkedList类的构造函数中,创建了一个虚拟头结点_dummyHead,它的val为0,nextnullptr,用来简化链表的操作。同时初始化了链表的大小_size为0。

  3. 获取节点值: get(int index)函数用于获取链表中第index个节点的值。通过判断index是否合法,然后从虚拟头结点开始遍历找到目标节点,并返回其值。

  4. 在头部插入节点: addAtHead(int val)函数用于在链表的头部插入一个新节点。先创建一个新节点,然后将新节点的next指向原头结点,再将虚拟头结点的next指向新节点,最后更新链表大小。

  5. 在尾部插入节点: addAtTail(int val)函数用于在链表的尾部插入一个新节点。从虚拟头结点开始遍历找到最后一个节点,然后将其next指向新节点,最后更新链表大小。

  6. 在指定位置插入节点: addAtIndex(int index, int val)函数用于在指定位置插入一个新节点。首先判断插入位置的合法性,然后从虚拟头结点开始遍历找到目标位置的前一个节点,插入新节点,并更新链表大小。

  7. 删除指定位置的节点: deleteAtIndex(int index)函数用于删除指定位置的节点。同样需要判断位置的合法性,然后找到目标位置的前一个节点,将其next指针跳过待删除节点,删除节点并释放内存,最后更新链表大小。

代码:

cpp 复制代码
class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val; // 节点的值
        LinkedNode* next; // 指向下一个节点的指针
        LinkedNode(int val):val(val), next(nullptr){}
//LinkedNode(int val) : val(val), next(nullptr) {}这行代码创建了一个LinkedNode结构体的构造函数,它接受一个整数参数val,并将val赋值给节点的val成员,同时将next指针初始化为nullptr。
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }

    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if (index > (_size - 1) || index < 0) { // 判断index是否合法
            return -1;
        }
        LinkedNode* cur = _dummyHead->next; // 从第一个真正的节点开始
        while(index--) { // 循环找到第index个节点
            cur = cur->next;
        }
        return cur->val; // 返回该节点的值
    }

    // 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val); // 创建一个新节点
        newNode->next = _dummyHead->next; // 将新节点的next指针指向原来的第一个节点
        _dummyHead->next = newNode; // 将虚拟头结点指向新节点
        _size++; // 更新链表长度
    }

    // 在链表最后面添加一个节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val); // 创建一个新节点
        LinkedNode* cur = _dummyHead; // 从虚拟头结点开始遍历
        while(cur->next != nullptr){ // 找到最后一个节点
            cur = cur->next;
        }
        cur->next = newNode; // 将新节点加在最后一个节点之后
        _size++; // 更新链表长度
    }

    // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    // 如果index小于0,则在头部插入节点
    void addAtIndex(int index, int val) {

        if(index > _size) return; // 如果index大于链表长度,则直接返回
        if(index < 0) index = 0; // 如果index小于0,则在头部插入节点
        LinkedNode* newNode = new LinkedNode(val); // 创建一个新节点
        LinkedNode* cur = _dummyHead; // 从虚拟头结点开始遍历
        while(index--) { // 找到第index个节点的前一个节点
            cur = cur->next;
        }
        newNode->next = cur->next; // 将新节点的next指针指向当前节点的next指针指向的节点
        cur->next = newNode; // 将当前节点的next指针指向新节点
        _size++; // 更新链表长度
    }

    // 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) { // 如果index大于等于链表长度或小于0,则直接返回
            return;
        }
        LinkedNode* cur = _dummyHead; // 从虚拟头结点开始遍历
        while(index--) { // 找到第index个节点的前一个节点
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next; // 保存待删除节点
        cur->next = cur->next->next; // 将待删除节点的前一个节点指向待删除节点的后一个节点
        delete tmp; // 释放待删除节点的内存
        tmp=nullptr; // 将指向待删除节点的指针置空,避免成为野指针
        _size--; // 更新链表长度
    }
    private:
    int _size; // 链表长度
    LinkedNode* _dummyHead; // 虚拟头结点指针
};

3有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:st中每个字符出现的次数都相同,则称 st互为字母异位词。

示例 1:

复制代码
输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

复制代码
输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

思路:

  1. 记录字母出现次数: 首先,代码使用一个长度为26的整型数组record来记录每个字母出现的次数,数组的索引与字母的ASCII码对应,这样可以将小写字母映射到record数组的相应位置。

  2. 遍历第一个字符串: 然后,代码遍历字符串s,对于每个字符,将其出现的次数记录在record数组中相应的位置。

  3. 遍历第二个字符串: 接着,代码同样遍历字符串t,对于每个字符,将其出现的次数从record数组中相应位置减去。

  4. 检查记录数组: 最后,代码遍历record数组,如果发现有任何一个位置的值不为0,说明两个字符串中有不同数量的某个字母,即它们不是字母异位词,返回false;如果record数组所有位置的值都为0,则说明两个字符串是字母异位词,返回true

代码:

cpp 复制代码
class Solution {
public:
    // 判断两个字符串是否是字母异位词
    bool isAnagram(string s, string t) {
        // 记录每个字母出现的次数的数组,初始值都为0
        int record[26] = {0};
        
        // 遍历字符串s,统计每个字母出现的次数
        for (int i = 0; i < s.size(); i++) {
            // 将字母映射到record数组的索引,统计出现次数
            record[s[i] - 'a']++;
        }
        
        // 遍历字符串t,统计每个字母出现的次数
        for (int i = 0; i < t.size(); i++) {
            // 将字母映射到record数组的索引,减去出现次数
            record[t[i] - 'a']--;
        }
        
        // 遍历record数组,如果有非零值,则说明不是字母异位词
        for (int i = 0; i < 26; i++) {
            if (record[i] != 0)
                return false;
        }
        
        // 如果record数组所有元素都为0,则说明是字母异位词
        return true;
    }
};

4两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的

交集

。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

复制代码
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

复制代码
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路:

代码:

cpp 复制代码
class Solution {
public:
    // 求两个数组的交集
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // 用于存储结果的无序集合
        unordered_set<int> result_set;
        // 将 nums1 转化为无序集合,方便进行查找
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        
        // 遍历 nums2,查找是否存在于 nums1 中,存在则加入结果集合
        for (int num : nums2) {
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        
        // 将结果集合转化为数组并返回
        return vector<int>(result_set.begin(), result_set.end());
    }
};
相关推荐
A懿轩A12 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神13 分钟前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人16 分钟前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香17 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.1 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
️南城丶北离1 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
✿ ༺ ོIT技术༻1 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java