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

目录

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

206. 反转链表

146. LRU 缓存 

解题过程: 


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

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

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

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

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

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

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

代码:

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 进行下一轮反转。

代码:

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)) 。

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

解题过程:

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

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

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

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

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

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

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

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

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

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

最后得到代码:

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; //当前所用容量
};
  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值