目录
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. 反转链表
学习:本题是一道经典的使用双指针方法来解决的题型。
我们可以定义一个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 缓存
学习:本题很难,我们需要从两个函数出发。
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; //当前所用容量
};