Studying-CodeTop | 3. 无重复字符的最长子串、206. 反转链表、146. LRU 缓存

目录

[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)

[206. 反转链表](#206. 反转链表)

[146. LRU 缓存](#146. LRU 缓存)

解题过程:


3. 无重复字符的最长子串

题目:3. 无重复字符的最长子串 - 力扣(LeetCode)

学习:本题题意很好理解,我们需要从所有不含有重复字符的子串中,找到长度最长的那个。首先明确子串的概念:子串不是子序列,子串中的元素是连续的,因此对于示例3来说,wke连续的三个字符是子串,而pwke中间跳掉了一个字符w,因此不是子串,是子序列。

本题我们可以使用回溯算法,遍历所有字符串的所有子串,并判断是否还有重复字符,如果没有则记录一次答案,最后找到最长的长度。这是一种暴力解法,显然时间复杂度极高。

我们其实可以从实际模拟出发,采用滑动窗口的方式进行本题的求解。

以示例1为例,我们可以从头开始遍历数组,先把'a'加入窗口,然后b与a不同,再把b加入窗口,接着再把c加入窗口。再接着就到a了,此时滑动窗口里面已经有a了,出现了重复的字符。此时我们需要将滑动窗口缩窄,也就是将窗口左边界向右移动,这里要注意,这个过程我们需要使用while进行,虽然本题第四个字符是a,我们只需要左边界移动一次,但如果第四个字符是c,则我们需要把左边界一直移动到第四个字符下标,其实也就是我们需要一直缩减滑动窗口,直到其中不包含重复字符。

统计滑动窗口内字符和找到重复字符的方法,我们可以使用哈希表进行。滑动窗口的移动则可以通过for循环来进行。

代码:

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() <= 1) return s.size();
        //滑动窗口,使用哈希表来求解
        unordered_set<char> ans;
        int result = 1;
        ans.insert(s[0]); //初始化哈希表
        for(int i = 0, j = 1; j < s.size(); j++) {
            while(i < j && ans.find(s[j]) != ans.end()) {
                ans.erase(s[i]);
                i++;
            }
            ans.insert(s[j]);
            result = max(result, j - i + 1);
        }
        return result;
    }
};

206. 反转链表

题目:206. 反转链表 - 力扣(LeetCode)

学习:本题是一道经典的使用双指针方法来解决的题型。

我们可以定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。然后开始进行反转。

首先我们要把cur->next节点用tmp指针保存一下,也就是保存一下这个节点。这是因为接下来要改变cur->next的指向,后续我们还需要回到原先的cur->next的节点进行下一步操作。

接着就是将cur->next = pre;然后pre = cur; cur = next 进行下一轮反转。

代码:

cpp 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur != nullptr) {
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
        
    }
};

146. LRU 缓存

题目:146. LRU 缓存 - 力扣(LeetCode)

学习:本题很难,我们需要从两个函数出发。

  1. get() 函数

该函数,要求我们判断关键字key是否在缓存中,如果在返回关键字的值,如果不在返回-1。显然这是一个map的结构,并且要求我们时间复杂度为O(1),因此,我们应该采取哈希表来进行存储。同时这里还包含了,如果存在,则代表该节点被访问了的信息。

2.put() 函数

该函数除了要求我们判断key是否存在以外。还要求我们在不存在的时候,将一个新的结点插入到该组中,如果导致节点数量超过了capacity,则需要删除最久未使用的关键字。这里就两个任务需要我们去实现:①找到最久为使用的节点,并将其删除;②将新节点插入到组中,并且是最新的。

上述两个函数的操作都要求O(1)的时间复杂度进行,而对于删除节点和插入节点,我们能想到的O(1)时间复杂度的结构就是链表(数组的插入删除时间复杂度是O(n)) 。

因此本题需要使用 哈希表 和 链表来进行解决。

解题过程:

链表我们需要使用双向链表结构,这样更方便我们进行节点的删除了插入。

cpp 复制代码
struct Linknode {
    Linknode* prev;
    Linknode* next;
    int key;
    int val;
    Linknode(): key(0), val(0), prev(nullptr), next(nullptr) {}
    Linknode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr) {}
};

接着我们需要一个哈希表,来进行节点的映射。同时可以设置两个虚拟头结点和尾节点,方便我们插入最新的节点,和找到最久的节点。当然我们也需要保存该组的容量和当前大小。

cpp 复制代码
private:
    unordered_map<int, Linknode*> map;
    Linknode* dummyhead; //虚拟头尾节点
    Linknode* dummytail;
    int _capacity; //容量
    int _size; //当前所用容量

接着在实现get() 和 put() 函数之前,我们需要实现两个额外的函数,插入头节点的函数和删除节点的函数。

cpp 复制代码
void addtohead(Linknode* node) { //将节点加入到头结点
    node->prev = dummyhead;
    node->next = dummyhead->next;
    dummyhead->next->prev = node;
    dummyhead->next = node;
}
void remove(Linknode* node) { //删除一个节点
    node->prev->next = node->next; //将该节点的前一个节点的next指向该节点的next
    node->next->prev = node->prev; //将该节点的下一个节点的prev指向该节点的prev
}

我们还可以对其进行封装,以实现,将访问过的节点,重新插入到头结点(最新的节点)的方法:

cpp 复制代码
void movetohead(Linknode* node) {
    remove(node);
    addtohead(node);
}

下面我们就可以对get()函数进行实现了,判断两种情况,存在key或者不存在key

cpp 复制代码
int get(int key) {
    if(!map.count(key)) {
        return -1;
    }
    Linknode* node = map[key];
    movetohead(node);
    return node->val;
}

put()函数则还需要插入新的节点,并判断容量:

cpp 复制代码
void put(int key, int value) {
    if(!map.count(key)) {
        Linknode* node = new Linknode(key, value);
        map[key] = node;
        addtohead(node);
        ++_size;
        if(_size > _capacity) {
            Linknode* removenode = dummytail->prev;
            remove(removenode);
            map.erase(removenode->key);
            delete removenode;
            removenode = nullptr;
            --_size;
        }
    }
    else {
        Linknode* node = map[key];
        node->val = value;
        movetohead(node);
    }
}

最后得到代码:

cpp 复制代码
class LRUCache {
public:
    //定义双向链表结构体
    struct Linknode {
        Linknode* prev;
        Linknode* next;
        int key;
        int val;
        Linknode(): key(0), val(0), prev(nullptr), next(nullptr) {}
        Linknode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr) {}
    };

    LRUCache(int capacity) : _capacity(capacity), _size(0) {
        dummyhead = new Linknode();
        dummytail = new Linknode();
        dummyhead->next = dummytail;
        dummytail->prev = dummyhead;
    }
    
    int get(int key) {
        if(!map.count(key)) {
            return -1;
        }
        Linknode* node = map[key];
        movetohead(node);
        return node->val;
    }
    
    void put(int key, int value) {
        if(!map.count(key)) {
            Linknode* node = new Linknode(key, value);
            map[key] = node;
            addtohead(node);
            ++_size;
            if(_size > _capacity) {
                Linknode* removenode = dummytail->prev;
                remove(removenode);
                map.erase(removenode->key);
                delete removenode;
                removenode = nullptr;
                --_size;
            }
        }
        else {
            Linknode* node = map[key];
            node->val = value;
            movetohead(node);
        }
    }

    void addtohead(Linknode* node) { //将节点加入到头结点
        node->prev = dummyhead;
        node->next = dummyhead->next;
        dummyhead->next->prev = node;
        dummyhead->next = node;
    }
    void remove(Linknode* node) { //删除一个节点
        node->prev->next = node->next; //将该节点的前一个节点的next指向该节点的next
        node->next->prev = node->prev; //将该节点的下一个节点的prev指向该节点的prev
    }
    void movetohead(Linknode* node) {
        remove(node);
        addtohead(node);
    }
private:
    unordered_map<int, Linknode*> map;
    Linknode* dummyhead; //虚拟头尾节点
    Linknode* dummytail;
    int _capacity; //容量
    int _size; //当前所用容量
};
相关推荐
蜀黍@猿17 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40918 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
Yan.love27 分钟前
开发场景中Java 集合的最佳选择
java·数据结构·链表
zh路西法28 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
冠位观测者37 分钟前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
轩辰~42 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿1 小时前
C/C++基础错题归纳
c++
雨中rain2 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程2 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法