deadline是第一生产力,刚刚收到笔试通知的我到现在居然一道题还没来得及刷,赶紧开始LeetCode刷题记
从回忆版的题入手,据说是今年amazon电面的题,have a try
206. Reverse Linked List
难度:
Easy
思路:
就是个简单链表反转的水题,注意输入可能是空链表这种特殊情况
代码:
/*
Author Owen_Q
*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL)
return NULL;
int n=1;
int l[1000010];
l[0] = head->val;
while(head->next!=NULL)
{
head = head->next;
l[n++] = head->val;
}
ListNode* newHead = new ListNode(l[n-1]);
ListNode* lastNode = newHead;
while(n>1)
{
n--;
ListNode* newNode = new ListNode(l[n-1]);
lastNode->next = newNode;
lastNode = newNode;
}
return newHead;
}
};
146. LRU Cache
难度:
Medium
关键点:
按键值访问链表
思路:
题目描述很简单,以O(1)的时间复杂度实现一个LRU(Least Recent Used)的cache。既然题意理解很容易,最关键的难点就在如何将复杂度降到常数级别。
常规思路就是用一个数据结构存储逻辑空间的cache来通过键值查询元素,另一个数据结构来存储地址空间的cache来模拟cache内更新和删除的过程。逻辑空间那部分毫无问题,O(1)查询更新元素全都ok。然而地址空间这部分,如何可以快速按键值定位元素和更新元素位置顺序,就是一个比较困难的问题了。
于是,为了维护位置信息,想到将key插入到queue中,由于queue中元素无法快速按键值查找,于是想到可以每次查询时均无脑插入queue中,然后对该元素打上lazy标志。待真正新元素入queue导致cache满时,再根据这些lazy标志依次删除。用这种办法,可以实现O(1)的查询和O(1)对旧元素的更新,然而当新元素插入时,不仅需要删除LRU的元素,还需要之前所有被打上lazy标志的元素,这样时间复杂度即被升级为O(已有操作数),于是便可以构造出前面一堆乱七八糟无用查询,最后突然插入一个新元素看似毫无意义但却完全可以把我这种方法卡掉的案例,大量的lazy标志使得这种方法因TLE被迫破产。
正解:
重新分析问题,回归这题的两个关键困难点:
1.快速按键值定位元素
2.快速更新元素位置
为了解决1,常用方法有map映射,然而基于红黑树实现的C++的map只能达到O(logn)的查询。而另一种类似的数据结构unordered_map基于hash表实现,完全可以达到O(1)的查询,于是第一个问题解决
为了解决2,可以用链表实现,C++的list完全可以实现O(1)的移动元素和对链两端元素的访问。
而要是想同时解决这两个问题,就需要用到十分玄学的方法了,将list与unordered_map嵌套
试想,如果list中每一个节点都可以通过哈希表快速访问,那不就可以完美解决这两个问题了!
听起来特别疯狂,但其实也不是不可能。由于对于list中的元素可以根据迭代器访问,于是我们完全可以将这些迭代器储存于unordered_map哈希表中。这样,当需要用按键值查询时,利用键值访问哈希表找到list迭代器从而快速访问list。而list的快速更新元素的功能使得所有操作的复杂度全都被降到了O(1)。完美实现了对list无法按键值访问这一缺陷的优化!
想明白之后,赶紧编码实现走起
STL知识回顾:
pair:
pair<key,value>
为了将cache中键值和实际值包装成对,pair是个很好用的数据结构
可以利用make_pair(key, value)构造函数直接构造一个新pair
对于pair的访问,利用first访问对应key,second访问对应value
list
list<element>
基于链表实现,利用iterator迭代器完成对链表元素的访问,并可以利用这个iterator对元素进行删除。利用erase()可以达到O(1)复杂度的删除,返回删除后链表对应位置下一个元素的iterator
push_back(),pop_back(),push_up(),pop_up()分别可以实现链表两端的插入和删除,无返回值
insert()可以实现特定位置(特定iterator前)的元素插入,返回新插入元素的iterator
front(),back()返回链表两端元素(非iterator)
clear()清空链表
size()返回链表大小
unordered_map
unordered_map<key,value>
基于哈希实现,按键值O(1)查找,元素无序排列
find()函数实现按键值查找,返回对应元素的iterator。若未找到则返回end()(指向unordered_map尾元素后的一个iterator)
利用[]可以直接实现按键值查找,插入,更新,返回对应元素的value值或修改对应元素的value值
erase()函数既可以利用iterator来实现删除元素,也可以直接利用键值来删除元素,返回该元素后一个元素的iterator
size()返回表大小
clear()清空表
代码:
/*
Author Owen_Q
*/
class LRUCache {
public:
LRUCache(int capacity) {
n = capacity;
lru.clear();
cache.clear();
}
int get(int key) {
if(cache.find(key)!=cache.end())
{
pair<int,int> temp = *(cache[key]);
lru.erase(cache[key]);
cache[key] = lru.insert(lru.begin(),temp);
return temp.second;
}
else
return -1;
}
void put(int key, int value) {
if(cache.find(key)!=cache.end())
{
//pair<int,int> temp = *(cache[key]);
lru.erase(cache[key]);
//temp.second = value;
cache[key] = lru.insert(lru.begin(),make_pair(key,value));
return ;
}
else
{
cache[key] = lru.insert(lru.begin(),make_pair(key,value));
if(lru.size()>n)
{
cache.erase(lru.back().first);
lru.pop_back();
}
return ;
}
}
private:
list<pair<int,int>> lru;
unordered_map<int, list<pair<int,int>>::iterator> cache;
int n;
int len;
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/