1、LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
LRU 我们都熟悉,实在分页式管理中的一个分页算法。对LRU有个大致了解,也不至于会慌,毕竟通过这个也考察了对LRU的理解。
这道题,leetcode上失败了好多次,后来就在网上找了一些答案,看他们怎么做的了。我一开始觉得首先要存数据,用map比较好,但是map又是排序后的一个容器。所以用另外一个版本,unordered_map,会好很多,而这个解题方案中,其实要写的东西挺多,看起来挺多而已。其实原理是,unordered_map存储node指针,而node是一个双向链表,模拟一个队列,利用队列进行操作。其实之前也有考虑用队列,但是队列不好存值啊,特别是key value这种。然后下面就好做多了,插入,如果存在就删除,然后从head处插入,然后删除就删除尾部的,这里为了方便添加了一个head和tail,都是一个指针而已。知道思路就好。
struct Node{ //双向链表存储cache信息
Node* pre;
int key;
int val;
Node* next;
Node(int x,int y):key(x),val(y),pre(NULL),next(NULL){
}
};
class LRUCache{
unordered_map<int,Node*> mp; //设置map是为了方便查找节点,不用遍历链表
Node* head;
Node* tail;
int cap;
int size;
public:
LRUCache(int capacity) { //给cache赋值时的初始化
if(capacity<=0)return;
head=new Node(0,0);
tail=new Node(0,0);
head->next=tail;
tail->pre=head;
mp.clear();
size=0;
cap=capacity;
}
int get(int key) {
if(cap<1)return -1;
unordered_map<int,Node*>::iterator it=mp.find(key);
if(it==mp.end())return -1;//没找到
else{
cutNode(it->second); //将节点抽出
pushToHead(it->second);//讲节点压到链表表头
return it->second->val;
}
}
void set(int key, int value) {
if(cap<1)return;
unordered_map<int,Node*>::iterator it=mp.find(key);
if(it==mp.end()){//没找到
Node* tmp=new Node(key,value);
pushToHead(tmp);//讲新的节点压到首部
size++;
mp[key]=tmp;//在mp里赋值,方便查找节点
if(size>cap){//如果空间不够
popNode();//删除最后的节点
}
}
else{
it->second->val=value; //如果在map里找到节点,赋值后,做抽出节点在压到表头就好
cutNode(it->second);
pushToHead(it->second);
}
}
void cutNode(Node* p){//从链表中抽出节点(不是删除,是为了方便在插入节点)
p->pre->next=p->next;
p->next->pre=p->pre;
}
void pushToHead(Node *p){//压到表头
head->next->pre=p;
p->next=head->next;
head->next=p;
p->pre=head;
}
void popNode(){//删除链表末端节点
Node* p=tail->pre;
p->pre->next=tail;
tail->pre= p->pre;
unordered_map<int,Node*>::iterator it=mp.find(p->key);
mp.erase(it);
delete p;
size--;
}
};
2、Binary Tree Postorder&&Binary Tree Preorder Traversal
后续遍历树,递归好了。。。如果不用递归那就用栈,一个道理,还有一个先序遍历。
class Solution {
private:
vector<int> ret;
public:
vector<int> postorderTraversal(TreeNode *root) {
if(root==NULL){
return ret;
}
postorderTraversal(root->left);
postorderTraversal(root->right);
ret.push_back(root->val);
return ret;
}
};
class Solution {
private:
vector<int> ret;
public:
vector<int> preorderTraversal(TreeNode *root) {
if(root==NULL){
return ret;
}
ret.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return ret;
}
};
class Solution {
private:
vector<int> vec;
public:
vector<int> inorderTraversal(TreeNode *root) {
inorder(root);
return vec;
}
void inorder(TreeNode *root){
if(root==NULL){
return ;
}
inorder(root->left);
vec.push_back(root->val);
inorder(root->right);
}
};
3、Reorder List
Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…
You must do this in-place without altering the nodes' values.
For example,
Given {1,2,3,4}, reorder it to {1,4,2,3}.
题意简单,无非就是前半部分正续,后半部分倒序插入前半部分。
暴力,挨个去找,然后挨个放进去。。。
不过我们可以将两部分链表拆开,这里的意思还是用两个指针,一个快指针,一个慢指针,找到中间结点,然后,剩下的事情,就是两个链表的插入了。插入还是会遇到这种问题。可以反转后面的链表,然后进行插入。这种方式似乎在面试的时候更容易出错。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void reorderList(ListNode *head) {
if(head == NULL||head->next == NULL){
return ;
}
ListNode* fast = head,*low = head;
while(fast->next!=NULL&&fast->next->next!=NULL){//寻找中间结点,一个快指针,一个慢指针
low = low->next;
fast = fast->next->next;
}
//将链表分成两半
ListNode* cur = low;
low=low->next;//头指针
cur->next = NULL;//前一个指针串末尾指向null
fast = low->next;//链表第二个元素
low->next = NULL;//链表第一个元素指向0,反转需要
cur = low;
low = fast;
while(low!=NULL){//反转链表
fast=low;
low = low->next;
fast->next = cur;
cur = fast;
}
//fast是指第二个链表的头结点,两个链表合并
low = head;
ListNode* curfast = cur;
fast = cur;
while(low!=NULL&&fast!=NULL){
cur = low->next;
curfast = fast->next;
low->next = fast;
fast->next=cur;
low = cur;
fast=curfast;
}
}
};
4、Linked List Cycle && Linked List Cycle II
Given a linked list, determine if it has a cycle in it.
Follow up:
Can you solve it without using extra space?
判断是否有环,这个被考烂了。using extra space的方法,我们可以定一个缓冲区,比如hash_map保存遇到的所有的结点,当出现重复的时候就可以判断出有环了。
还有一个是还是两个指针,一个指针跑一步,另外一个跑两步,这个是一定可以遇到的,而且遇到的时间复杂度可以计算得出。无非就是一个方程式。
bool hasCycle(ListNode *head) {
if(head == NULL||head->next == NULL){
return false;
}
ListNode *fast = head,*low=head;
while(fast->next!=NULL&&fast->next->next!=NULL){
fast=fast->next->next;
low = low->next;
if(fast == low){//相遇了,继续跑
return true;
}
}
return false;
}
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Follow up:
Can you solve it without using extra space?
找到环的开始地点,然后进行输出,也是两个方案,
一个放入map中,第一个找到map则是第一个点。156ms
第二个方案,还是上面的思路,两个指针,必定找到第一个交叉点,这个交叉点是他们的第一次交叉点,第一次相遇,慢指针必定还没有遍历完这个圈。这个结论是可以证明的。证明两个指针必定相遇,用到的是mod原理
(转)证明步长法的正确性:<br>
如果链表有环,不妨假设其环长度为M(>=2)。
p指针首次到达交点(N0)时,q指针已经进入环。
设p=0;q=q-p;
再进过i(i>=0)步后,p=(p+i)%m;q=(q+2*i)%m;
则必存在一个i使得(p+i)%m = (q+2*i)%m。(p,q 都为常数)。
而相遇点i必定是>=0 <M的,所以这也给第二个题目一个提醒,这里2b = c+b,也就是快指针和慢指针的2倍关系。还有,仔细发现也可以看出的关系式是,xm + d + c = a,也就是,我们让一个指针从开头开始遍历,让另外一个指针从A点开始,最终他们会在N0这里集合。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == NULL||head->next == NULL){
return NULL;
}
ListNode * p=head;
while(p!=NULL){
map<ListNode *, int>::iterator iter = mymap.find(p);
if(iter!=mymap.end()){
return iter->first;
}
mymap.insert(pair<ListNode*,int>(p,p->val));
p = p->next;
}
return NULL;
}
private:
map<ListNode *, int> mymap;
};
156ms,这个在查map的时候nlogn的复杂度,因为map是红黑树实现的嘛,这个降低了速度。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == NULL||head->next == NULL){
return NULL;
}
ListNode *fast = head,*low=head;
while(fast->next!=NULL&&fast->next->next!=NULL){
fast=fast->next->next;
low = low->next;
if(fast == low){//相遇了,继续跑
fast = head;
while(fast!=NULL&&low!=NULL){
if(fast == low){
return fast;
}
fast = fast->next;
low = low->next;
}
}
}
return NULL;
}
};
5、Word Break
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
难道只能继续暴力?首先一个问题是按顺序将s的单词提出来,然后和dict匹配。一开始我的思路就是暴力的,但是出错,memory limited,还有很多其他错误。还是去找了下其他人的写法,思路一下就打开了,这也是我后面做这类型的题目的感悟。当时我还不是很懂动态规划,后面有去算法导论恶补了一下。原理也挺简单,就是动态规划的想法,申请一个数组,然后标记该位是否可以被单词遍历到这里,被单词遍历到这里(I)的前提是
V[I-word.size] == true && word == (i-word.size....i)
bool wordBreak(string s, unordered_set<string> &dict)
{
int len = s.length();
bool *vec =new bool[len+1];
for(int i = 0;i<=len;i++){
vec[i] = false;
}
vec[0]= true;
for(int i=1;i<=len;i++){
int len1 = i;
for(unordered_set<string>::iterator iter = dict.begin();iter!=dict.end();iter++){
int len2 = iter->size();
if(len1>=len2&&!vec[i]){
if(vec[len1-len2]&&s.substr(len1-len2,len2)==*iter){//关键点。。。。
vec[i] = true;
break;
}
}
}
}
bool ret = vec[len];
delete vec;
return ret;
}
第二个的解法,从尾开始遍历字符串,找到以完整word结尾的所有情况,然后根据这个情况往前追溯。
<pre name="code" class="cpp">vector<string> wordBreak(string s, unordered_set<string> &dict)
{
vector<string> rs;
string tmp;
vector<vector<int> > tbl = genTable(s, dict);
word(rs, tmp, s, tbl, dict);
return rs;
} //word函数用来对产生的table进行输出操作,递归操作
void word(vector<string>&rs,string&str,string s,vector<vector<int>>tab,unordered_set<string>dict,int start = 0){
if(start == s.length()){
rs.push_back(str);//str是构造好的一个单词序列
return;
}
for(int i=0;i<tab[start].size();i++){//从开始的结点开始,保证了前面的所有的word是符合规定的
string t = s.substr(start,tab[start][i]-start+1);
if(!str.empty()){
str.push_back(' ');
}
str.append(t);
word(rs,str,s,tab,dict,tab[start][i]+1);
while(!str.empty()&&str.back()!=' '){//删除元素
str.pop_back();
}
if(!str.empty()){//删除空格
str.pop_back();
}
}
}
vector<vector<int> > genTable(string &s, unordered_set<string> &dict)
{
int n = s.length();
vector<vector<int> > tbl(n);
for (int i = n - 1; i >= 0; i--)
{
if(dict.count(s.substr(i))) tbl[i].push_back(n-1); //word结尾的字符串
}
for (int i = n - 2; i >= 0; i--)
{
if (!tbl[i+1].empty())//if we can break i->n //以word结尾的,才是可以操作的
{
for (int j = i, d = 1; j >= 0 ; j--, d++)
{
if (dict.count(s.substr(j, d))) tbl[j].push_back(i); //添加如vector,这里面vector 0 数组是和结尾的那个一个道理,在word里面有对他的操作
}
}
}
return tbl;
}
6、Copy List with Random Pointer A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
思路是,为每个结点复制一个结点,然后结点之后,然后在操作完毕后再将结点之间的链删除。
类似这样:A----A'------B------B'------C------C'------D-------D'------...
RandomListNode *copyRandomList(RandomListNode *head) {
if(head == NULL){
return NULL;
}
RandomListNode * p = head,*q;
while(p != NULL){
q = p;
p = p->next;
RandomListNode * temp = new RandomListNode(q->label);
temp->next = p;
q->next = temp;//复制一份
}
p = head;
while(p!=NULL){
q=p->next->next;
if(p->random!=NULL){
p->next->random = p->random->next;
}
p=p->next->next;//构造好random指针
}
p = head;
RandomListNode * res = p->next;
while(p != NULL){//删除
q=p->next;
p->next = q->next;
if(q->next != NULL){
p=q->next;
q->next = q->next->next;
}
else{
q->next = NULL;
break;
}
}
return res;
}
7:Single Number&&Single Number II
Given an array of integers, every element appears twice except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
这个就是位操作了,感觉也见过很多次了,出现一次的数通过不断异或,最终剩下的是唯一的数。
int singleNumber(int A[], int n) {
if(n==1){
return A[0];
}
int value = A[0];
for(int i = 1;i<n;i++){
value = value^A[i];
}
return value;
}
Given an array of integers, every element appears three times except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
下面这个算法,是一个很神的写的,只能跪拜,如果用其他方法也是可以,无非是位操作。但是这么简洁的不多见了。
int singleNumber(int A[], int n) {
if(n<=1){
return A[0];
}
int ones = 0, twos = 0, xthrees = 0;
for(int i = 0; i < n; ++i) {
twos |= (ones & A[i]);//1出现2次的情况,one出现一次,one & A【i】
ones ^= A[i];//这里one代表的是出现两次的情况,为1表示出现两次
xthrees = ~(ones & twos);//出现三次的情况
ones &= xthrees;//one是最终的结果
twos &= xthrees;//two出现两次
}
return ones;
}
利用三个变量分别保存各个二进制位上 1 出现一次、两次、三次的分布情况,最后只需返回变量一就行了。
解释:每次循环先计算 twos,即出现两次的 1 的分布,然后计算出现一次的 1 的分布,接着 二者进行与操作得到出现三次的 1 的分布情况,然后对 threes 取反,再与 ones、twos进行与操作,这样的目的是将出现了三次的位置清零。
不过可以利用其他的方法的,int 数据共有32位,可以用32变量存储 这 N 个元素中各个二进制位上 1 出现的次数,最后 在进行 模三 操作,如果为1,那说明这一位是要找元素二进制表示中为 1 的那一位。
这个是从转过来的,没写代码了。
class Solution {
public:
int singleNumber(int A[], int n) {
int bitnum[32]={0};
int res=0;
for(int i=0; i<32; i++){
for(int j=0; j<n; j++){
bitnum[i]+=(A[j]>>i)&1;
}
res|=(bitnum[i]%3)<<i;
}
return res;
}
};
8、Candy
There are N children standing in a line. Each child is assigned a rating value.
You are giving candies to these children subjected to the following requirements:
Each child must have at least one candy.
Children with a higher rating get more candies than their neighbors.
What is the minimum candies you must give?
每个小孩一个分数,每个小孩至少一个candy,分数高的要比他周围的人多
分两遍,第一遍从头到尾,当前的比前一个要多就分给他一个,至少一个,然后从尾到头,当前比前一个高,给他分一个,如果他本来就比前面的高就不用分了。
int candy(vector<int> &ratings) {
if(ratings.size()<=1){
return max(ratings[0],1);
}
int length = ratings.size();
int * candy = new int[length];
int i = 0;
for(i = 0;i<length;i++){
candy[i] = 1;
}
for(int i = 1;i<length;i++){
if(ratings[i]>ratings[i-1]){
candy[i]=candy[i-1]+1;
}
}
for(int i = length-2;i>=0;i--){
if(ratings[i]>ratings[i+1]){
candy[i]=max(candy[i],candy[i+1]+1);
}
}
for(int i = 1;i<length;i++){
candy[i] += candy[i-1];
}
return candy[length-1];
}
9、Gas Station
There are N gas stations along a circular route, where the amount of gas at station i is gas[i].
You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.
Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.
Note:
The solution is guaranteed to be unique.
到现在,我才搞懂,这个模型和连续累加和一个类型,只要保证出发点和到结束点,之间的累加和每个点都是大于0的就可以,也就是保证要有gas
int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
if(gas.size() == 1){
if(gas[0]-cost[0]<0){
return -1;
}
else
return 0;
}
int start = 0,end = 0,max=0;
for(int i = 0;i<2*gas.size()-1;i++){
if(max+(gas[i%gas.size()]-cost[i%gas.size()])>=0){
max = max+(gas[i%gas.size()]-cost[i%gas.size()]);
end = i%gas.size();
}
else{
start = (i+1)%gas.size();
end = start;
}
if(start!=end&&(end+gas.size()-start)%gas.size()==gas.size()-1){
return start;
}
}
return -1;
}
这个模型就是那个连续累加和的那个模型的嘛
10、Clone Graph
Clone an undirected graph. Each node in the graph contains a label
and a list of its neighbors
.
OJ's undirected graph serialization:
Nodes are labeled uniquely.
We use#
as a separator for each node, and
,
as a separator for node label and each neighbor of the node.
As an example, consider the serialized graph {0,1,2#1,2#2,2}
.
The graph has a total of three nodes, and therefore contains three parts as separated by #
.
- First node is labeled as
0
. Connect node0
to both nodes1
and2
. - Second node is labeled as
1
. Connect node1
to node2
. - Third node is labeled as
2
. Connect node2
to node2
(itself), thus forming a self-cycle.
Visually, the graph looks like the following:
1 / \ / \ 0 --- 2 / \ \_/
这道题,一开始是觉得和链表带有random 指针的那个题目相似,但是后来想想是不对的,那个有指针,而这个则是list。
dfs先遍历得到所有结点,创建结点信息,放入map,然后在遍历一次,这次加入queue,然后构造得到。
/**
* Definition for undirected graph.
* struct UndirectedGraphNode {
* int label;
* vector<UndirectedGraphNode *> neighbors;
* UndirectedGraphNode(int x) : label(x) {};
* };
*/
class Solution {
public:
UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
if(node == NULL){
return node;
}
map<int,UndirectedGraphNode*> smap;
dfs(smap,node);
//得到smap,进行复制就可以了
queue<UndirectedGraphNode*> treequeue;
treequeue.push(node);
while(!treequeue.empty()){
UndirectedGraphNode * q = treequeue.front();
treequeue.pop();
UndirectedGraphNode * p = smap[q->label];
if(p->neighbors.empty()&&!q->neighbors.empty()){
for(int i =0;i<q->neighbors.size();i++){
treequeue.push(q->neighbors[i]);
p->neighbors.push_back(smap[q->neighbors[i]->label]);//从map中得到然后加入neighbors
}
}
}
return smap[node->label];
}
void dfs(map<int,UndirectedGraphNode*> &smap,UndirectedGraphNode *node){//加入map
if(node == NULL){
return;
}
if(smap.find(node->label) != smap.end()){
return;
}
UndirectedGraphNode * p = new UndirectedGraphNode(node->label);
smap.insert(pair<int,UndirectedGraphNode*>(p->label,p));
for(int i = 0;i<node->neighbors.size();i++){
dfs(smap,node->neighbors[i]);
}
}
};