找工作--笔试面试--准备2

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 #.

  1. First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
  2. Second node is labeled as 1. Connect node 1 to node 2.
  3. Third node is labeled as 2. Connect node 2 to node 2 (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]);
		}
	}
};
































  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值