常见面试题总结2020秋招

1、LRU缓存机制

哈希表+双向链表,保证可以随时在头结点和尾节点进行数据插删除,同时记得更新dict哈希表中存储的key值

最新使用的程序插在双向链表的头部。

class DLinkedNode:
    """
    双链表节点
    """
    def __init__(self, key: int = 0, value: int = 0):
        self.prev = None
        self.next = None
        self.key = key
        self.value = value

class LRUCache:

    def __init__(self, capacity: int):
        self.size = 0
        self.capacity = capacity
        self.cache = dict()
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        self.head.next = self.tail
        self.tail.prev = self.head

    def get(self, key: int) -> int:
        # 如果key不在cache中, 直接返回-1
        if key not in self.cache:
            return -1
        
        # 从cache中获取key对应的节点
        node = self.cache[key]
        # 移动节点到链表的头部
        self.move_to_head(node)
        # 返回节点的值
        return node.value

    def put(self, key: int, value: int) -> None:
        
        node = DLinkedNode(key, value)

        # 如果key不在cache中
        if key not in self.cache:
            # 且此时缓存容量已满
            if self.capacity == self.size:
                # 移除链表中最后一个节点
                last_node = self.remove_last()
                # 同时从cache中弹出最后一个节点
                self.cache.pop(last_node.key)
                # 把新节点加入到链表的头部
                self.add_to_head(node)
                # 更新cache
                self.cache[key] = node
            else:
                # 容量未满, 直接把新节点加入到链表头部                
                self.add_to_head(node)
                # 同时更新缓存
                self.cache[key] = node
                # 缓存大小加一
                self.size += 1
        else:
            # 如果key在cache中, 那就不用再考虑缓存容量的影响
            # 把老节点cache和链表中移除
            old_node = self.cache.pop(key)
            self.remove_node(old_node)
            # 再把新节点加入到cache和链表中
            self.add_to_head(node)
            self.cache[key] = node

    def add_to_head(self, node: DLinkedNode):
        """
        把节点加入到链表头部
        """
        next_node = self.head.next

        self.head.next = node
        node.prev = self.head

        node.next = next_node
        next_node.prev = node
    
    def remove_node(self, node: DLinkedNode):
        """
        从链表中移除指定节点
        """
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def move_to_head(self, node: DLinkedNode):
        """
        把节点移动到链表头部
        """
        self.remove_node(node)
        self.add_to_head(node)
    
    def remove_last(self) -> DLinkedNode:
        """
        移除链表中最后一个节点并返回
        """
        last_node = self.tail.prev

        self.tail.prev.prev.next = self.tail
        self.tail.prev = self.tail.prev.prev
        return last_node

2、抢红包算法实现

二倍均值法
方法实现的原理是:每次以 [最小值,红包剩余金额 / 人数 * 2] 的区间进行取值。

假设红包金额为 88.88,红包数量为 8 个,理想情况下,方法的实现效果:

第一个人领取红包金额区间为 [0.01, 88.88 / 8 * 2],即是 [0.01, 22.22] 之间随机获取金额数。假设取平均值 11.11,则剩余金额 77.77;

第二个人领取红包金额区间为 [0.01, 77.77 / 7 * 2],即是 [0.01, 22.22] 之间随机获取金额数。假设取平均值 11.11,则剩余金额 66.66;

以此类推…

import random
def get_packet(amount,number):
    res =[]
    person_num = number
    cur_amount = amount*100
    for _ in range(number-1):
        money  = random.randint(1,cur_amount//person_num*2)
        cur_amount -= money
        person_num -= 1
        res.append(money/100)
    res.append(cur_amount/100)
    return res
    
res = get_packet(88.88, 8)
for amount in res:
    print('红包金额:{}'.format(amount))

3、最小生成元

如果x+x的各个数字之和得到y,就是说x是y的生成元。给出n(1<=n<=100000),求最小生成元。无解输出0.例如,n=216,121,2005时的解分别是198,0,1979.

利用打表法,提前把1到100000的最小生元给存下来,你要知道谁的最小生成元,直接输出就行了。

#include<stdio.h>
#define N 10005
int a[N]

int main()
{
    int n;
    scanf("%d",&n);

    for(int i = 1;i<=10000;i++)
    {
        int x = i,y =i;
        while(y>0)
        {
            x += y%10;
            y/=10;

        }
        if (a[x] ==0)
            a[x] = i;      
    }

    printf("%d",a[n]);
    return 0 ;

}

4、python实现一段文本中单词个数的统计

string = "hello world nihao world hey hello java world hi python yeoman word"
list1 = string.split()
set1 = set(list1)
list2 = list(set1)
dir2 = {}
for i in range(len(list2)):
    dir2[list2[i]] = 0
    for j in range(len(list1)):
        if list1[j] == list2[i]:
            dir2[list2[i]]+=1
print(dir2)



========
from collections import Counter
list1 = sentence.split() 
dir1 = Counter(list1)

5、辗转相除法求最大公约数,求分数之和

def sum(a,b,c,d):
    fenzi = b*c +a*d
    fenmu = a*c
    
    a =fenmu
    b =fenzi
    c = a% b
    while(c):
        a = b
        b = c
        c = a % b
    fenzi /= b
    fenmu /= b
    return fenzi,fenmu

print(sum(2,1,3,1))

6、十进制转换

def divbase(num,base):
    stack = []
    digital = "0123456789ABCDEF"
  
    while num >0:
        temp = num %base
        stack.append(temp)
        num = num//base
    #print(stack[::-1])
    
    res=""
    while stack:
        #print(stack[-1])
        if base>10:
            res +=str(digital[stack[-1]])
            stack.pop()
            
        else:
            res +=str(stack[-1]) 
            stack.pop()
    return res

print(divbase(43,16))

7、两根香,一根烧完1小时,如何测量15分钟

---->开始时一根香两头点着,一根香只点一头,两头点着的香烧完说明过去了半小时,这时将只点了一头的香另一头也点着,从这时开始到烧完就是15分钟。

8、写一个函数,求平方根,函数参数为目标数字和精度,测试案例 fn(4.1,0.001) fn(501.1,0.001) fn(0.045,0.001)

def mySqrt(target,g):
    l = float(1)
    r = float(target)
    mid = float()
    while r-l>g:
        mid = (l+r)/2
        if mid * mid > target:
            r = mid
        else:
            l = mid
    return (l+r)/2  
    
print(mySqrt(4.1,0.001))

9、给定一个 0-4随机数生成器 如何生成0-6随机数

rand6() = {rand4() * 5 + rand4() }<=20?x/3:loop

我们可以想,第一个rand6()产生一个0~6范围的随机数a,第二个rand6()产生一个0~6范围的随机数b,如果(a, b)这一对数可以确定一个唯一的数,从而可以产生的49个数(a和b都有7种可能,则(a, b)确定的数就有7*7=49种可能)都是等概率的。这就相当于有一个二维坐标系,X轴和Y轴上都可以取0~6的数,对于取x=a,y=b可以确定一个唯一的点(a, b)。0-6生成0-9随机数

想到这个就是我们的关键,接下来就是想办法构造函数使得产生的每个数都是唯一的。我们不难想到把第一个rand6()产生的随机数a放在个位上,而把第二个rand6()产生的随机数b放在十位上,所有可能为:00~06,10~16,20~26,30~36,40~46,50~56,60~66。很容易看出这些就是7进制数,所以我们用rand6()*7+rand6()就能生成0~49范围的随机数,而且每个数产生的概率相等,都是1/49。产生40~49之间的随机数时不保留而重新产生一个新的随机数,这样产生0~39之间的数也是等概率的,仍然都是1/49,对于产生的数我们记为r(0<= r <=39),则 r/4 即可返回0~9范围的数。而且 r 取0~3这四个数时 r/4 返回0,r 取4~7这四个数时 r/4 返回1,……,r 取36~39这四个数时 r/4 返回9。因此返回0~9范围的每个数字的概率都是4/49,满足等概率。

10、海盗分金币

问题描述:

        五个极其聪明的海盗抢到100颗宝石,每一颗宝石都一样大小和价值连城。他们决定以抽签投票的方式来分配这些宝石:有(1、2、3、4、5)五个号码,每人抽取一个。首先,由1号提出分配方案,然后大家举手表决,当且仅当超过半数的人同意时,按照他的分配方案 进行分配,否则将被扔进海里喂鲨鱼。如果1号死后,再由2号提出分配方案,规则如前所述,以此类推。

问:第一个海盗提出怎样的分配方案才能使自己的收益最大化?为什么?

解答:https://blog.csdn.net/guo_jia_liang/article/details/53957393

答案分析: 1号海盗分给3号1枚金币,4号或5号2枚金币,自己则独得97枚金币,即分配方案为(97,0,1,2,0)或(97,0,1,0,2)。

11、场景题:一个硬币,正面概率0.7,反面概率0.3,怎么掷能让两个人公平的喝到水

---->抛两次,先正后反A喝,先反后正B喝

某一次抛出硬币,正面向上的概率是p,反面向上的概率是1 - p,当p不等于0.5时,这两个事件的概率就不一样了。怎么能凑出等概率呢?还是要利用概率的加法和乘法法则。这里用乘法,也就是连续的独立事件。

连续抛两次硬币,正反面的出现有四种情况,概率依次为:

  1. 两次均为正面:p * p
  2. 第一次正面,第二次反面:p * (1 - p)
  3. 第一次反面,第二次正面:(1 - p) * p
  4. 两次均为反面:(1 - p) * (1 - p)

这不,中间两种情况的概率是完全一样的。于是问题的解法就是连续抛两次硬币,如果两次得到的相同则重新抛两次;否则根据第一次(或第二次)的正面反面情况,就可以得到两个概率相等的事件。

 

12、64匹马,8个跑道,选跑最快的4匹马需要比赛多少次

 

13、概率:两个人轮流抛硬币,先抛到正面的赢,问先抛的人赢的概率

设先抛先吃的概率为p1, 后抛先吃的概率为p2

那么有:

p1 = 1/2 + 1/2 * p2

p1 + p2 = 1

解方程可得,

p1 = 2/3

14、2个鸡蛋扔100层楼,求最坏情况下所需次数

  • 1、只有一个小球怎么办,遍历,O(n)的时间复杂度
  • 2、2个鸡蛋的时候具体来说,假设我们每10层楼扔第一个蛋,当在10x层扔坏,我们从10(x-1) 开始顺序扔到 10x。那么最坏情况下需要 10 + 9 = 19. 10 是第一个蛋的,9是第二个蛋的次数。按照这个方法,当第一个蛋达到最坏情况时,比第二个刚好多一。这样是平衡的。问题在于,如果第一个蛋在10就碎了,那么第二个蛋要扔9次,总共10次。两种最坏情况不平衡。也就是说,我们希望动态地调整扔蛋一的层数,使得(蛋一+蛋二)的总和平均。

那么考虑这个方法:假设蛋一第一次扔在X层,第二次扔在比原来高 X-1层,... 高2, 高1,使得X+(X-1)+...+2+1=100,那么最坏情况总的次数就是:

如果在第X层就碎,那么蛋二最坏情况要扔X-1,所以总数是 1+X-1=X
如果在X+(X-1)碎,那么蛋二最坏情况要扔 X-2,所以2+X-2=X
...1+2+3+4+......+14>100

解得 X = 14.

⼆分查找排除楼层的速度⽆疑是最快的, 那⼲脆先⽤⼆分查找, 等到只剩 1 个鸡蛋的时候再执⾏线性扫描, 这样得到的结
果是不是就是最少的扔鸡蛋次数呢?
很遗憾, 并不是, ⽐如说把楼层变⾼⼀些, 100 层, 给你 2 个鸡蛋, 你在 50层扔⼀下, 碎了, 那就只能线性扫描 1〜49 层了, 最坏情况下要扔 50 次。

如果不要「⼆分」 , 变成「五分」 「⼗分」 都会⼤幅减少最坏情况下的尝试次数。 ⽐⽅说第⼀个鸡蛋每隔⼗层楼扔, 在哪⾥碎了第⼆个鸡蛋⼀个个线性扫描, 总共不会超过 20 次​。
最优解其实是 14 次。

15、两个很大的数据集存着URL,找到两个数据集共有的URL

参考链接:https://www.jianshu.com/p/f9f5b66f8a32

 

1)首先我们最常想到的方法是读取文件a,建立哈希表(为什么要建立hash表?因为方便后面的查找),然后再读取文件b,遍历文件b中每个url,对于每个遍历,我们都执行查找hash表的操作,若hash表中搜索到了,则说明两文件共有,存入一个集合。

(2)但上述方法有一个明显问题,加载一个文件的数据需要50亿*64bytes = 320G远远大于4G内存,何况我们还需要分配哈希表数据结构所使用的空间,所以不可能一次性把文件中所有数据构建一个整体的hash表。所以虽然可行,但是无法满足需求。

(3)针对上述问题,我们分治算法的思想。

step1:遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,...,a999,每个小文件约300M),为什么是1000?主要根据内存大小和要分治的文件大小来计算,我们就大致可以把320G大小分为1000份,每份大约300M(当然,到底能不能分布尽量均匀,得看hash函数的设计)

step2:遍历文件b,采取和a相同的方式将url分别存储到1000个小文件(记为b0,b1,...,b999)(为什么要这样做? 文件a的hash映射和文件b的hash映射函数要保持一致,这样的话相同的url就会保存在对应的小文件中,比如,如果a中有一个url记录data1被hash到了a99文件中,那么如果b中也有相同url,则一定被hash到了b99中)

所以现在问题转换成了:找出1000对小文件中每一对相同的url(不对应的小文件不可能有相同的url)

step3:因为每个hash大约300M,所以我们再可以采用(1)中的想法

16、多叉树的最长路径

二叉树的最长路径的进阶版:https://blog.csdn.net/wangbaochu/article/details/53870330

#include<iostream>
#include<vector>
#include<algorithm>
int maxDistance = 0;
std::vector<std::vector<int> > G(100001);//存储边信息
inline void AddEdge(int v, int s)//把结点v和s关联
{
    G[v].push_back(s);
    G[s].push_back(v);
}
 
int LastOrder(int pre, int cur)
{
    int first = 0, second = 0;//最大值和次大值
    for (size_t i = 0; i < G[cur].size(); ++i)
    {
        if (G[cur][i] == pre)//一直向下,不着重复的边
            continue;
        int temp = LastOrder(cur, G[cur][i]);//向下找
        if (temp>first)
        {
            second = first;
            first = temp;
        }
        else if (temp > second)
        {
            second = temp;
        }
 
    }
    //注意:在以当前结点为根节点的子树中,最远两个结点的距离为first+second。
    maxDistance = std::max(maxDistance, first + second);
    return first + 1; //只返回当前子树的最大深度,父结点只需要知道子树的最大深度。
}
 
int main()
{
    int N;
    std::cin >> N;
    int Ai, Bi;
    for (int i = 1; i < N; ++i)
    {
        std::cin >> Ai >> Bi;
        AddEdge(Ai, Bi);
    }
    LastOrder(0, 1);
    std::cout << maxDistance;
    return 0;
}

17、给定一个有向无环图,按拓扑排序的顺序输出节点

#使用循环进行拓扑排序
def topoSort(G):
    #初始化计算被指向节点的字典
    cnt=dict((u,0) for u in G.keys())
    #若某节点被其他节点指向,该节点计算量+1
    for u in G:
        for v in G[u]:
            cnt[v]+=1
    #收集被指向数为0的节点,此时Q只有一个节点,即起始节点a
    Q=[u for u in cnt.keys() if cnt[u]==0]
    #记录结果
    seq=[]
    while Q:
        s=Q.pop()
        seq.append(s)
        for u in G[s]:
            cnt[u]-=1
            if cnt[u]==0:
                Q.append(u)
    return seq

#有向无环图的邻接字典
G={
    'a':{'b','f'},
    'b':{'c','d','f'},
    'c':{'d'},
    'd':{'e','f'},
    'e':{'f'},
    'f':{}
}

20、抛硬币10000次,7000次朝上,求置信区间(中心极限定理)

这种属于二项分布的检验,会有2个数值方向。当概率p<0.626时,P(X<=70)>=0.9506(此时可以理解为概率过小,100次里不超过70次正面的可能性很大);当概率p>=0.775时,P(X>70)=1-0.0502=0.9498(此时可以理解为概率过大,100次里超过70次正面的可能性很大);所以当概率p在[0.626,0.775)区间内,在95%的置信区间(有95%把握)判断硬币出现正面的概率区间为[0.626,0.775)


可以得到对应的概率值,然后取计算Z值,可以得到95%置信区间对应的两边端点值

https://pic1.zhimg.com/80/23d96ba0bcafd47aa100b7caa4522ec4_720w.jpg?source=1940ef5c


 

其中95置信区间下u一般为1.96

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值