前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试算法题

面试算法题

作者头像
饶文津
发布2020-06-02 15:22:40
3530
发布2020-06-02 15:22:40
举报
文章被收录于专栏:饶文津的专栏饶文津的专栏

1. 给一颗多叉树,求 从一个节点出发到其它所有节点的距离之和 的最小值。

树形 dp。一般两遍 dfs 就能解决。 第一遍 dfs 用 son[i] 记录每个节点多少个子孙,用 dis[i] 记录 i 点到其所有子孙的距离之和。 son[i]和 dis[i]都在回溯的过程进行维护。假设 v 是 u 的孩子节点,\(son[u]+=son[v]+1\), \(dis[u] += dis[v]+son[v]+1\),也就是说 v 的每个子孙到 u 的距离是他们到 v 的距离+1,然后再加上 v 到 u 的距离1。 第二遍 dfs,维护 dis[i] 为到所有点的距离之和。节点 v 到其它所有节点的距离之和可以用 u 到其它所有节点的距离之和计算出来。因为v和v 的子孙到 v 的距离要比到 u 的距离少1,就减去了son[v]+1,然后剩下 n-son[v]-1个点到 v 的距离要比到 u 的距离多1,就加上了 n-son[v]-1,所以就是 \(dis[u]+n-2\times son[v]-2\)。 手写代码,大概是下面这样。

代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;
const int N=201000;
struct edge{
	int to,next;	
}e[N];
int head[N],cnt;
void add_edge(int u,int v){
	e[++cnt]=(edge){v,head[u]};
	head[u]=cnt;
}
int son[N],dis[N];
void dfs1(int u,int fa){
	for(int i=head[u];i;i=e[i].next){
		int v = e[i].to;
		if(v == fa)continue;
		dfs1(v, u);
		son[u] += son[v]+1;
		dis[u] += dis[v]+son[v]+1;
	}
}
int ans=1000000009;
int n,m;
void dfs2(int u,int fa){
	ans=min(ans,dis[u]);
	for(int i=head[u];i;i=e[i].next){
		int v = e[i].to;
		if(v == fa)continue;
		dis[v]=dis[u]+n-2-2*son[v];
		dfs2(v, u);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u, v);
		add_edge(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 0);
	printf("%d",ans);
	return 0;
}

2. 现有 n 个木条,第 i 个长度为 a[i],切割这 n 个木条得到 k 个长度为 L 的木条,L最长是多少?

二分 L,每个木条就能得到 n/L 个,计算总共能否得到至少 k 个木条。复杂度是O(n)。

3. k个有序数组怎么归并

维护一个大小为 k 的小根堆。先把每个数组第一个放入小根堆。每次把最小的取出来放入答案,并且把它所属数组的下一个放入小根堆。

代码语言:javascript
复制
const int N=205;

vector <int> mergeKArray(int k, vector<int> arr[N]){
	vector<int> ans;
	int now[N];
	priority_queue<pair<int, int>, vector<pair<int,int> >, greater<pair<int,int> > > heap;
	for(int i=0;i<k;i++){
		heap.push(make_pair(arr[i][0], i));
	}
	while(heap.size()){
		pair<int,int> u=heap.top(); heap.pop();
		ans.push_back(u.first);
		int i=u.second;
		now[i]++;
		if(now[i]<arr[i].size()){
			heap.push(make_pair(arr[i][now[i]],i));
		}
	}
	return ans;
}

4. 求长度为 2n 的字典序第 k 大的合法括号序列,合法是符合下面两个要求:

1)空串 2)若 s 合法,()s、(s)也合法 思路是长度为2i的合法序列有\(2^{i-1}\)个,所以可以递归构造。如果\(2^{n-2} > k\),说明一定是()开头,再去构造长度为2(n-1)的第 k 大序列。否则,一定是(s)构成,再去构造长度为2(n-1)的第 \(k-2^{n-2}\)大的序列。

5. 平面有 n 个 x 坐标不同的点。求斜率最大的两个点。

按 x 坐标排序,然后斜率最大的两个点一定是相邻的,所以再两个两个判断一遍即可。

6. 有序数组找出出现超过一半的数。如果不知道有没有超过一半的,怎么判断。如果是判断超过1/3的呢?

中位数。用中位数的 lowerbound 和 upperbound 判断。用1/3和2/3位置的数的上下界判断。

7. 给定某单link链表,输出这个链表中倒数第k个结点。链表的倒数第0个结点为链表

的尾指针。链表结点定义如下:

代码语言:javascript
复制
struct LinkNode {  int m_nKey;  LinkNode* m_pNext;  };
代码语言:javascript
复制
LinkNode* kthNode(LinkNode* head, int k){
  LinkNode* p = head;
  for(int i = 0; i < k; i++){
    if (p->m_pNext == nullptr) {
      return nullptr; // 不存在倒数第 k 个
    }
    p = p->m_pNext;
  }

  LinkNode* ans = head;

  for(; p->m_pNext != nullptr; p = p->m_pNext) {
    ans = ans->m_pNext;
  }
  return ans;
}

8. 给定一个二叉树中的两个节点,返回它们的最近公共祖先节点。

代码语言:javascript
复制
struct Node {
  int key;
  Node* Fa;
  Node* Lson, *Rson;
};

Node* LeastCommonAncestors(Node* p, Node* q){

  int pDep = 0, qDep = 0;

  for(Node* now = p; now->Fa != nullptr; now = now->Fa) {
    pDep++;
  }
  for(Node* now = q; now->Fa != nullptr; now = now->Fa) {
    qDep++;
  }
  Node* nowp = p, *nowq = q;
  for(; nowp != nowq; ) {
    if(pDep > qDep) {
      pDep--;
      nowp = nowp->Fa;
    }
    else if(pDep < qDep){
      qDep--;
      nowq = nowq->Fa;
    }
    else(pDep == qDep){
        qDep--;
        pDep--;
        nowp = nowp->Fa;
        nowq = nowq->Fa;
    }
  }
  return nowp;
}

9. 有 n 个节点,m 条边,求最小生成树最长的边,n,m 都是100000以内。

用小根堆优化 prim 算法。

代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201000;
int n,m;

struct edge{
	int to,next,w;
}e[N];
int head[N],cnt;
void add_edge(int u,int v,int w){
	e[++cnt]=(edge){v,head[u],w};
	head[u]=cnt;
}
int dis[N];
bool b[N],vis[N];
struct node{
	int id;
	node(int v){id=v;}
	bool operator < (const node&b) const{
		return dis[id]>dis[b.id];
	}
};
priority_queue<node>q;
int prim(){
	int ans=0;
	memset(dis, INF, sizeof(dis));
	dis[1]=0;b[1]=true;
	q.push(node(1));
	while(!q.empty()){
		int u=q.top().id;q.pop();
		if(vis[u])continue;
		vis[u]=true;
		ans=max(dis[u],ans);
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			if(dis[v]>e[i].w){
				dis[v]=e[i].w;
				q.push(v);
			}
		}
	}
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	printf("%d",prim());
	return 0;
}

10. n 个武器,告诉你每个武器的五个属性值,现在要选择 k 个武器,最大化这五个属性的最大值之和,n,k 都是10000以内。

k>=5时,可以直接取每个属性的最大值。 k<5时,我们用mask[i]表示 i 代表的二进制位上为1的所有位置对应的属性之和最大的一个武器(用 pair 记录<最大和,编号>)。接下来用 dp[i][j]表示前 i 个已经选了 j 二进制为1的位置对应的属性时,最大是多少。那么就有 dp[i][j|m]=dp[i-1][j]+mask[i].second,其中 m和 j 在二进制相同位置不能同时为1。

代码语言:javascript
复制
#include <bits/stdc++.h>
using namespace std;

const int N=201000;
int n,k,w[N][5],big[5];
vector<pair<int,int> >mask(1<<5);
int dp[5][1<<5];
int main(){
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++){
		for(int j=0;j<5;j++){
			scanf("%d",&w[i][j]);
			big[j]=max(big[j],w[i][j]);
		}
	}
	int ans=0;
	if(k>=5){
		for(int i=0;i<5;i++){
			ans+=big[i];
		}
	}else{
		for(int i=0;i<n;i++){
			for(int j=0;j<(1<<5);j++){
				int tot=0;
				for(int t=0;t<5;t++)
					if(j&(1<<t))
						tot+=w[i][t];
				mask[j]=max(mask[j], make_pair(tot,i));
			}
		}
		for(int i=1;i<=k;i++){
			for(int j=0;j<(1<<5);j++){
				for(int m=0;m<(1<<5);m++)if((m&j)==0){
					dp[i][j|m]=max(dp[i][j|m],dp[i-1][j]+mask[m].first);
				}
			}
		}
		ans=dp[k][(1<<5)-1];
	}
	printf("%d",ans);
	return 0;
}

11. 有20亿个无序存储的int32整数,找出一个不在里面的数。

我的方法是将int32按区间划分为10万个组,每组4万多个数。第一遍扫描,维护每组数量:对每个数计算出它所在的组,然后该组数量+1。找出数量小于区间长度的一组,在第二次扫描时,只考虑在该区间内的数,存下来。接下来将存下的数排个序,再扫一遍这个排序的数组,如果相邻的总是连续的,那么若最小的大于左边界就返回左边界否则返回右边界。如果相邻的不连续就返回中间没出现的值。 这样的时间复杂度是O(2*n+m+mlogm),n=2,000,000,000, m=int32范围/K≈40000,K= 空间复杂度是O(K+m)

在编程珠玑上看到了解法(题目类似): 二分,位图的思想。 第一次扫描,记录2进制第一位为0的个数,第一位为1的个数, 如果0开头的少,第二次扫描,就只看00,01开头的。 以此类推。要32次扫描就能确定这个数。 时间复杂度是O(32*n),空间复杂度是O(1)。实现起来也更简洁。

12. LRUCache(LeetCode146)

面试完才知道这是道经常考的面试题,有原题,只不过把键值都改为字符串。 当时写的 bug 太多了,对链表的操作也没有抽出函数来。回来又改了好久才 AC。

需要实现一个 LRU Cache 的类, 总共最多维护 n 个键值对,并具有以下两个方法: put(k, v),不存在则插入,存在则更新键为 k 的值为v。 get(k),读取键为 k 的值,不存在输出 -1。 LRU 就是说,如果已经有 n 个了,那么再次插入时,要删去最久没有访问的。插入和读取都算是访问。 两个操作复杂度都要求是 O(1)。 插入是 O(1)的,就要求 O(1) 时间得到最久没有访问的。 我们就想维护一个队头最新,队尾最旧的队列。 如果拿双端队列,每次读取时,不能 O(1)地维护队头最新,队尾最旧的性质。 需要一个可以 O(1)地将中间的元素拿到队头,又能 O(1)地访问任何一个值的数据结构。 考虑用双向链表来存键值对,为了能 O(1)访问,再拿 hash map 来存每个键对应的链表节点的指针。

代码语言:javascript
复制
class LRUCache {
	struct Node {
		Node(int _k, int _v){
			k = _k;
			v = _v;
			pre = next = NULL;
		}
		int k, v;
		Node* pre;
		Node* next;
	};
	int n;
	unordered_map<int, Node*> m;
	Node* head, *tail;
	int len;
        // 将 p 移到队头。
	void move_to_head(Node*p){
		if(p==head)return;//已经是队头。
		if(head==NULL){//之前没有元素。
			head=tail=p;
		}else{
                        //如果 p 后面有元素,那么它前面的节点变成了 p 前面的节点了(也可能是 NULL)。
			if(p->next) {
				p->next->pre = p->pre;
			}
                        //如果 p 前面有元素,那么它后面的节点变成了 p 后面的节点了。
			if(p->pre) {
				p->pre->next = p->next;
			}
                        // 如果 p 本身就是队尾,那么新队尾是 p 前面的。这里链长一定大于1,否则 p 也会是队头,之前就返回了。
			if(p==tail){
				tail=p->pre;
			}
                        // 这里就是 p 成为了新的队头。
			head->pre=p;
			p->next=head;
			head=p;
		}
	}
public:
	LRUCache(int _n):n(_n), len(0){
		head = tail = NULL;
		m.clear();
	};
	void reset(int _n){
		len=0;
		n=_n;
		head = tail = NULL;
		m.clear();
	}
	void put(int k, int v) {
		if(m[k]){
			move_to_head(m[k]);
			m[k]->v = v;
		}else {
			m[k] = new Node(k, v);
			if(len<n){
				move_to_head(m[k]);
				len++;
			}else {// len == n
                                // 删掉最旧的。
				m[tail->k]=NULL;
				if(head==tail){//只有1个节点,直接删掉(n==1)
					delete tail;
					head=tail=NULL;
				}else{//多个节点,要记得更新队尾指针。
					Node*tmp=tail->pre;
					delete tail;
					tmp->next=NULL;
					tail=tmp;
				}
				move_to_head(m[k]);
			}
		}
	}
	
	int get(int k) {
		if(m[k]==NULL) return -1;
		move_to_head(m[k]);
		return m[k]->v;
	}
};

13. 给定一个数组和滑动窗口的大小,请找出滑动窗口滑动过程的所有最大值。

双端队列

14. 单链表上的快排

To be continued...

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-03-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 给一颗多叉树,求 从一个节点出发到其它所有节点的距离之和 的最小值。
  • 2. 现有 n 个木条,第 i 个长度为 a[i],切割这 n 个木条得到 k 个长度为 L 的木条,L最长是多少?
  • 3. k个有序数组怎么归并
  • 4. 求长度为 2n 的字典序第 k 大的合法括号序列,合法是符合下面两个要求:
  • 5. 平面有 n 个 x 坐标不同的点。求斜率最大的两个点。
  • 6. 有序数组找出出现超过一半的数。如果不知道有没有超过一半的,怎么判断。如果是判断超过1/3的呢?
  • 7. 给定某单link链表,输出这个链表中倒数第k个结点。链表的倒数第0个结点为链表
  • 8. 给定一个二叉树中的两个节点,返回它们的最近公共祖先节点。
  • 9. 有 n 个节点,m 条边,求最小生成树最长的边,n,m 都是100000以内。
  • 10. n 个武器,告诉你每个武器的五个属性值,现在要选择 k 个武器,最大化这五个属性的最大值之和,n,k 都是10000以内。
  • 11. 有20亿个无序存储的int32整数,找出一个不在里面的数。
  • 12. LRUCache(LeetCode146)
  • 13. 给定一个数组和滑动窗口的大小,请找出滑动窗口滑动过程的所有最大值。
  • 14. 单链表上的快排
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档