菊厂笔试1
考点
题型 | 建议掌握内容 |
---|---|
字符串 | 双指针、哈希、KMP |
数组 | 前缀和、滑动窗口、二分查找、排序 |
栈与队列 | 单调栈、括号匹配、双端队列技巧 |
链表 | 快慢指针、翻转链表、合并链表 |
哈希表 | 判重、频次统计、哈希加滑窗技巧 |
二叉树 | 递归遍历、层序遍历、构造树 |
图 | BFS、DFS、并查集、最短路(Dijkstra) |
贪心 | 区间调度、背包类问题简化版 |
动态规划 | 背包问题、最长子序列/子数组 |
模拟题 | 比如“文件系统”、“装配线调度” |
华为题目喜欢“工程场景模拟题”——既考算法又有业务背景。要熟练将题意转化为数据结构建模。
✅ 二、笔试时的实战建议:
1. 分配时间:
-
一般是 90 分钟 3 道题。
-
第 1 题通常较简单(字符串/哈希/数组遍历),不要超过 15~20 分钟。
-
第 2 题中等偏难,第 3 题较难(涉及DP/图),留足时间逐步完成。
2. 调试技巧:
-
写自测样例,别只靠系统给的 case。
-
提交前做边界值检查(空串、极大值、负数等)。
-
注意输出格式,别因为“多了个空格”而 WA。
3. 善用函数封装、注释清晰:
class Solution:def maxSubArray(self, nums: List[int]) -> int:# 动态规划:f(i)表示以nums[i]结尾的最大子数组和pass
✅ 三、刷题技巧(目标清晰,练重点)
推荐平台:
-
LeetCode 热题 100 + Top Interview Questions
-
牛客网 华为笔试真题专区
-
力扣每日一题(用自己语言快速写)
刷题方法:
-
反复刷而不是广度刷:做过的题二刷、三刷,尤其是你错过/不会的。
-
总结题型套路,而不是每题从零开始。
-
建议你建一个错题本/代码本子:写下你不会的点、易错的细节。
✅ 四、考前其他的基础准备
-
✅ 一份简洁、清晰、无错误的 简历(PDF格式)
-
✅ 简历中的项目尽量能答出:你做了什么、为什么这么做、遇到什么问题
-
✅ 对华为业务线有一定了解,比如你投的是“云计算”、“终端”、“ICT”哪一块
代码风格转换
首先我们要从传统算竞中的 io 风代码转到工程上的面向对象封装风
什么是面向对象编程(OOP)?
将 数据&功能打包成一个对象,通过类对其进行描述
什么是类 可以理解为一种模版,比如猫类包含了所有猫的共同特征
什么是对象 从类而来的具体实例,比如一只叫“小豆泥”的暹罗猫就是猫类里面的一个对象
面向对象的三大特性
封装 将数据和方法放在一起,对外只暴露接口 class + def 组合,通过__init__构造
继承 子类拥有父类的属性和方法,代码复用 class B( A ) 表示B继承A
多态 不同对象同样的方法调用
class Cat: # 定义一个类def __init__(self, name): # 构造函数(初始化)self.name = namedef meow(self): # 类的行为print(f"{self.name} 喵喵叫")douni = Cat("小豆泥") # 创建一个对象
douni.meow() # 调用对象的方法 => 输出:咪咪 喵喵叫
在这个例子里面 meow 就是一个方法
self 指代的是实例自身,是面向对象编程中类方法的第一个参数,通过 self 可以访问对象的属性和其他方法
变化
一、结构上的改变
OI风格 | 面向对象风格 |
---|---|
全部写在 main() 或直接写在全局 | 封装为 class Solution 中的方法 |
输入用 input() | 输入由函数参数传入 |
输出用 print() | 输出用 return 返回,由调用者决定是否打印 |
一般直接运行 | 用 if __name__ == "__main__": 测试调用 |
二、必须封装成函数
你不仅要用 return,还要把算法逻辑写成函数形式,这才符合面向对象的结构。
例如:
# 原始 OI 风格
a, b = map(int, input().split())
print(a + b)
转为面向对象 + 函数封装是:
class Solution:def add(self, a: int, b: int) -> int:return a + bif __name__ == "__main__":a, b = map(int, input().split())sol = Solution()print(sol.add(a, b))
三、输入不再写在函数内部,而是作为“参数”传入
这是最大区别之一:
-
OI风格:函数内直接 input()
-
OOP风格:函数内不写 input(),而是从参数接收数据
示例对比
OI 风格:
def solve():a, b = map(int, input().split())print(a + b)
solve()
面向对象风格:
class Solution:def add(self, a: int, b: int) -> int:return a + bif __name__ == "__main__":a, b = map(int, input().split())sol = Solution()print(sol.add(a, b))
小结:过渡时需要做的 3 件事
步骤 | 说明 |
---|---|
1. 把主逻辑写成函数 | 用类封装为 def 函数(self, 参数) |
2. 用 return 代替 print | 函数输出用 return,主程序再决定是否 print |
3. 输入变成参数传入 | 函数内部不要再 input(),由主程序读取传入 |
技巧
类中多个子函数封装逻辑,结构更清晰
类型标注 def func(self , a : int ) -> int : 提高可读性
写完函数写主程序调用测试 if __name__=='__main__'
比如gcd
class Solution:def gcd(self, a: int, b: int) -> int:# 辗转相除法while b != 0:a, b = b, a % breturn a# 本地测试用的主函数
if __name__ == "__main__":a,b=map(int,input().split())sol = Solution() # 创建类的实例result = sol.gcd(a,b) # 调用函数print("最大公约数是:", result) # 输出结果
OK,下面我们开始刷题
3.无重复字符的最长子串
我的第一个思路是set去重
但是很明显不行,因为他必须是子串
那么双指针滑动?
答案是可以的
我们需要维护四个变量:
l 各个字符上次位置
vis 当前窗口里面包含的字符
lmax 答案目标
lnow 当前窗口的长度
class Solution:'''def __init__:'''def lengest(self,s:str)->int:l={}vis=''lmax=0lnow=0for i in range(len(s)):if s[i] not in vis:vis+=s[i]lnow+=1l[s[i]]=ielse:#如果出现过:结算,更新lmax=max(lmax,lnow)last=l[s[i]]lnow=i-lastl[s[i]]=i#记得更新lvis=s[last+1:i+1]lmax=max(lmax,lnow)#最后一次清算return lmaxif __name__=='__main__':s=input()sol=Solution() #先创建类对象res=sol.lengest(s) #再通过对象调用方法print(res)
146.LRU缓存
146. LRU 缓存
很显然是字典
而且还需要一个变量存储最先进来的,方便后面逐出(队列)
可以直接用这个变量的长度来判断是否应该逐出
但还是用个变量存储吧,否则每次都要len()
而且注意题意“最久未使用”,那么不只是存储,读取、覆盖的话也得更新他的位置
更新的话先remove再append
from collections import deque
class LRUCache:def __init__(self,capacity:int):self.lmax=capacityself.lnow=0#self.last=0self.deque=deque()self.d={}#defaultdict(int)def get(self,key:int)->int:if key in self.d:self.deque.remove(key)self.deque.append(key)return self.d[key]else:return -1def put(self,key:int,value:int)->None:if self.lnow<self.lmax:#未形成窗口#低级错误:竟然先放进去了self.d[key]=valueif key not in self.d:self.d[key]=valueself.deque.append(key)self.lnow+=1else: #"最久未使用“self.d[key]=valueself.deque.remove(key)#用remove删除后重新加self.deque.append(key)else:#形成窗口if key in self.d:self.d[key]=valueself.deque.remove(key)#用remove删除后重新加self.deque.append(key)self.deque.append(key)else:s=self.deque.popleft()del self.d[s] #self.deque.append(key)self.d[key]=value
不难发现其实每次输入key“使用”都得更新他的地位
但是这样只有5%用时击败和80%用存击败
正确的做法是用 OrderedDict
OrderedDict 是 Python collections 模块中的一个 有序字典,它和普通 dict 基本功能相同,但能记住键的插入顺序(或访问顺序)
导入
from collections import OrderedDict
主要特性
特性 | 说明 |
---|---|
保留顺序 | 记录元素插入的顺序(或访问顺序) |
支持 move_to_end(key) | 可以将某个键移动到字典的开头或末尾 |
支持 popitem() | 可以按顺序弹出最前面或最后面的元素 |
兼容 dict | 用法和普通字典几乎一致 |
常用方法举例
1. 插入顺序保留
from collections import OrderedDictod = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3print(od) # 输出:OrderedDict([('a', 1), ('b', 2), ('c', 3)])
2. move_to_end(key, last=True)
-
默认 last=True:将键移动到末尾(表示最近使用)
-
last=False:将键移到开头
od.move_to_end('a') # 'a' 移到最后
od.move_to_end('b', last=False) # 'b' 移到最前
3. popitem(last=True)
-
last=True(默认):弹出最后一个键值对(表示最近使用)
-
last=False:弹出最前的键值对(表示最久未使用)
od.popitem() # 弹出最后一个
od.popitem(last=False) # 弹出第一个
因此我们可以用:
-
move_to_end(key) 在使用时将更新其地位
-
popitem(last=False) “删除不是last最新的”从左边删除最久未使用的项;
-
OrderedDict 本身就是 O(1) 操作,配合 dict 的查找速度,非常高效。
class LRUCache:def __init__(self, capacity: int):self.cache = OrderedDict()self.capacity = capacitydef get(self, key: int) -> int:if key not in self.cache:return -1# 把访问的 key 提到最前(代表最近访问)self.cache.move_to_end(key)return self.cache[key]def put(self, key: int, value: int) -> None:if key in self.cache:# 更新值并将其移到末尾self.cache.move_to_end(key)self.cache[key] = valueif len(self.cache) > self.capacity:# 弹出最旧的项(先进的)self.cache.popitem(last=False)
5.最长回文子串
经典马拉车Malacher
class Solution:def longestPalindrome(self, s: str) -> str:t='#'+'#'.join(s)+'#'n=len(t)p=[0]*nC=R=maxlen=center=0for i in range(n):p[i]=min(p[2*C-i],R-i) if R-i>0 else 0while i+p[i]+1<n and i-p[i]-1>=0\and t[i+p[i]+1]==t[i-p[i]-1]:p[i]+=1if i+p[i]>R:C,R=i,i+p[i]if p[i]>maxlen:maxlen,center=p[i],ireturn s[(center-maxlen)//2:(center+maxlen)//2]
我明天写一章讲讲马拉车,今天先休息了