面试算法题

Posted 水郁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试算法题相关的知识,希望对你有一定的参考价值。

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\)
手写代码,大概是下面这样。

#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;
        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 的小根堆。先把每个数组第一个放入小根堆。每次把最小的取出来放入答案,并且把它所属数组的下一个放入小根堆。

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个结点为链表

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

struct LinkNode {  int m_nKey;  LinkNode* m_pNext;  };
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. 给定一个二叉树中的两个节点,返回它们的最近公共祖先节点。

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;
    }
    if(pDep <= qDep){
      qDep--;
      nowq = nowq->Fa;
    }
  }
  return nowp;
}

To be continued...

以上是关于面试算法题的主要内容,如果未能解决你的问题,请参考以下文章

算法面试手撕代码高频题汇集

算法刷题范围建议 和 代码规范

算法刷题范围建议 和 代码规范

高频算法面试题_旋转字符串(完整的代码实现)

Java进阶之光!2021必看-Java高级面试题总结

深度学习/机器视觉/数字IC/FPGA/算法手撕代码目录总汇