上来就三道算法、23年腾娱互动、Golang后端面试复盘(已挂)、经验可借鉴

1,492 阅读5分钟

上来就三道算法、23年腾娱互动、Golang后端面试复盘(已挂)、经验可借鉴

一、面试流程(2小时)

1、45分钟笔试(三道算法)

大数相乘: 没有思路
有序链表合并: 有思路,没有写出来
LRU之前看过: 有思路,没有写出来

2、负责项目介绍(难点/挑战点/技术选型/为什么这么选择)

重点: 介绍是一门艺术活,说的好非常不一样,今天感觉就吃在表达上面了,大家记得提前准备好这些

我讲述的是一个后台SQL慢查询的列子,慢的主要原因是关联表太多,临时解决方案是分段查询,终极解决方案是宽表

中间还插入了技术选型:
1、宽表为什么用DB不用ES?(个人复盘观点哈。 主要是在数据量方面差异,ES能支持更多数据搜索)
2、索引优化(覆盖索引/联合索引/最左原则这些什么也可讲讲)
3、如何分段查询(分多步查询,单表查询效率快)
4、自己设计的有用过其他数据库么?(想不到了 真的只有Mysql/Redis/Es)

3、Mysql、网络深挖

1、事物特性?(A 原子性、C 一致性、I 隔离型、D 持久型)
2、隔离型实现原理?(MVCC)
3、MVCC实现原理?(版本链路 + 事物ID + 活跃事物ID)
   事物开启、申请 trac_id
   记录当时活跃事物ID、低的叫低水位、高的 + 1 叫做高水位
   trac_id < 低水位 过去事物创建,可以读
   trac_id > 低水位 过去事物创建,不可以读
   中间的分两种情况、在活跃事物id中,不可以读,其余可读
4、隔离级别、解决问题?
  读已提交(解决脏读)
  读未提交
  可重复读(解决脏读、不可重复读)
  序列化(解决脏读、幻读、不可重复读)
5、事物如何保证一致性、持久性?(未答上)
   一致性:原子性-undolog + 一致性redolog + 隔离级别MVCC 共同保障
   持久性:redolog,innodb引擎特有、WAL循环写入,先写redolog buffer,在写入磁盘,具体策略不同 
6、两阶段提交流程?(未答上)
   说了mysql 的两阶段提交, 保证 redolog 和 binlog 统一
7、 平时如何解决问题,有什么收获?
   遇到问题,自己先想解决方案、在查询业界的整体处理方案、整合好自己的最佳方案,和部门高职级商谈,定下最终方案,告知产品相关影响点,及时通知业务方整顿,避免异常情况产品
   收获:收获了一份解决类型问题通用答案、为后续加入团队人员留下参考文档,增加团队影响力


8、TCP 为什么沾包?
减少多个包来换传输的网络开销

9、UDP 有没有沾包?
TCP是字节流传输
UDP面向报文传输,UDP没有沾包

10、一般如何解决沾包?HTTP是如何解决?
解决沾包:
    包固定长度
    添加特殊字符串隔离
    包头 + 包体格式
HTTP 是通过 (协议头包头 + 包体格式解决)   

4、反问环节

1、介绍一下业务?
小世界运营业务、用户相关

2、目前框架
语言:Go
框架:微服务
中间件:Kafaka、Redis、MongDB
搜索:ES

3、麻烦让面试总结一下?如何学习补充?
  加强算法训练、落实代码落地
  项目没有亮点?描述不好?项目简单?从业务思考?自己设计模块简单?
  基础知识还需要加强

二、再来一遍

三道算法(真题) 腾讯会议图片_20230209193508.png

# 大数相乘
func multiply(num1 string, num2 string) string {
    if num1 == "0" || num2 == "0" {
		return "0"
	}

	len1, len2 := len(num1), len(num2)
	strArr := make([]int, len1+len2)
	for i := len1 - 1; i >= 0; i-- {
		n1 := int(num1[i]) - '0'
		for j := len2 - 1; j >= 0; j-- {
			n2 := int(num2[j]) - '0'
			// 乘基和位置有关系
            tmpNum := n1 * n2
			strArr[i+j+1] += n1 * n2
		}
	}
	
	// 处理乘基数据
	for ii := len1 + len2 - 1; ii > 0; ii-- {
		strArr[ii-1] += strArr[ii] / 10
		strArr[ii] = strArr[ii] % 10
	}

	// m1Str * m2Str 最大位数: m + n, 最小位数: m + n -1
	idx := 0
	if strArr[0] == 0 {
		idx = 1
	}

	// 拼接字符串
	str := ""
	for i := idx; i < len1+len2; i++ {
		str += strconv.Itoa(strArr[i])
	}

	return str
}



# 合并有序链表
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    if list1 == nil {
        return list2
    }

    if list2 == nil {
        return list1
    }

    head := &ListNode{Val:-1}
    cur := head
    for list1 != nil && list2 != nil {
        if list1.Val < list2.Val {
            cur.Next, list1 = list1, list1.Next
        } else {
            cur.Next, list2 = list2, list2.Next
        }
        cur = cur.Next
    }

    if list1 != nil {
        cur.Next, list1 = list1,list1.Next
    } else {
        cur.Next, list2 = list2, list2.Next
    }

    return head.Next
}


# LRU [双向链表]
type DLinkList struct {
	Key, Val   int
	Prev, Next *DLinkList
}

type LRUCache struct {
	Cap, Len   int
	CacheMap   map[int]*DLinkList
	Head, Tail *DLinkList
}

func Constructor(capacity int) LRUCache {
	// 初始化
	head, tail := InitDLinkList(0, 0), InitDLinkList(0, 0)
	head.Next, tail.Prev = tail, head

	lru := LRUCache{
		Cap:      capacity,
		Len:      0,
		CacheMap: map[int]*DLinkList{},
		Head:     head,
		Tail:     tail,
	}

	return lru
}

func (this *LRUCache) Get(key int) int {
	if _, ok := this.CacheMap[key]; !ok {
		return -1
	}

	// 返回值,并移动到最前面
	node := this.CacheMap[key]
	this.MoveToHead(node)
	return node.Val
}

func (this *LRUCache) Put(key int, value int) {
	if _, ok := this.CacheMap[key]; !ok {
		newNode := InitDLinkList(key, value)
        this.CacheMap[key] = newNode
		this.AddToHead(newNode)
		this.Len++

		// 移除最后一个元素
		if this.Cap < this.Len {
			tailKey := this.RemoveTail()
			this.Len--
			delete(this.CacheMap, tailKey)
		}
	} else {
		node := this.CacheMap[key]
		node.Val = value
		this.MoveToHead(node)
	}
}

func InitDLinkList(key, val int) *DLinkList {
	dl := &DLinkList{
		Key: key,
		Val: val,
	}
	return dl
}

func (this *LRUCache) MoveToHead(dl *DLinkList) {
	this.removeNode(dl)
	this.AddToHead(dl)
}

func (this *LRUCache) removeNode(dl *DLinkList) {
	// 移除 node 节点
	dl.Prev.Next = dl.Next
	dl.Next.Prev = dl.Prev
}

func (this *LRUCache) AddToHead(dl *DLinkList) {
	// node 节点
	dl.Next = this.Head.Next
	dl.Prev = this.Head

	// node 后面节点
	this.Head.Next.Prev = dl

	// head 节点
	this.Head.Next = dl
}

func (this *LRUCache) RemoveTail() int {
	tailNode := this.Tail.Prev
	this.removeNode(tailNode)
	return tailNode.Key
}

三、自我感受

1、算法真的逃避不了,面试在哪里都需要刷

2、什么难就问什么,多了解了解原理性东西

3、总结项目亮点、需要凝练、另外需要增强表述与表达

三、复盘

1、一定要下个录屏软件,把和面试官面试过程录下来,进行反复观看和复盘

2、加强弱点补充,我的在于深入原理和算法

3、寻找项目亮点、增加语言表述能力,从业务思考优化难点