外包离职后,把核心代码上传到GitHub,项目组已退场,大概率找不到人了
刚看到个贴子,说有外包离职后,竟然把营销系统的核心代码和全年营收数据一股脑扔到 GitHub,上面只留个神秘网名,人也找不到了。
项目组早就散了,线索断得比断网还彻底。
网友们骂得挺狠的,有说外包“报复社会”的,也有吐槽企业管理像筛子一样漏风的。
但怎么说呢,单看结果确实离谱——代码泄露,这不是整顿职场,这是违法乱来。你再苦再累,也不能把自己变成安全事故的起点。
不过换个角度想,外包流动性高、强度大,这类岗位最容易埋雷。
一边压着期限赶项目,一边人来人往没人真正负责到底,权限给得乱,管理跟不上,最后风险像雪球一样越滚越大。
我的看法是,企业要反思的不是“怎么抓人”,而是“为什么会出现这种人才能轻易搞出事”。
权限分级、代码审计、交接制度,这些该补的都得补。
员工再情绪化、再不爽,也得有机制把风险挡在外面【备注:文末可领最新资料】
面试题:我能赢吗
有这么个小游戏:
桌上有一堆数字:1, 2, 3, ..., maxChoosableInteger 你和对手轮流拿数字,每次只能拿一个,而且拿过的不能再拿 大家把自己拿过的数字加在一起,谁先让总和 ≥ desiredTotal,谁就赢 你是先手,题目问:在双方都足够聪明的前提下,你有没有办法保证自己赢?这就是经典的「我能赢吗」(Can I Win)那道题。
函数长这样:
defcanIWin(maxChoosableInteger: int, desiredTotal: int) -> bool:
...
返回 True 就是“我一定能赢”,False 就是“再怎么下也赢不了”。
别急着上来就回溯,我们先算两步账:
如果
desiredTotal <= 0,那还玩啥,先手一上来就算赢了,直接True如果 1 加到
maxChoosableInteger的和都 小于desiredTotal也就是:if (1 + maxChoosableInteger) * maxChoosableInteger // 2 < desiredTotal:
returnFalse意思就是:所有数字都拿完,总和都达不到目标,那不管多聪明也赢不了,直接
False
这两步剪枝能挡掉一大堆无意义的搜索。
博弈论的核心:当前局面是“必胜”还是“必输”?
关键想法就一句话:
如果我能找到一个数字,让对手在后续局面必输,那当前局面就是必胜。
换成状态的说法就是:
定义
win(state):轮到当前玩家,在这个状态下能不能必胜枚举当前能选的每一个数字
i:如果我选了 i之后,对手处在的新状态是必输,那我就赢了
用公式写就是那句经典的:
存在选择,使得对手在后续状态
win(next_state) == False,那当前就是True
状态怎么表示?用 bitmask 压一下
难点不在逻辑,而在“怎么唯一地表示一个局面”。
只看“哪些数字已经用过”就够了,当前的总和可以通过参数或减法算出来。 数字最大只到 20(题目原本约束),所以可以直接用一个整数的二进制位来表示:
第 i位是 1:说明数字i已经被用过第 i位是 0:说明数字i还可以选
比如 used = 0b00101 表示:1 和 3 已经被拿走了,2 还在桌上。
这样我们就可以:
用 used作为记忆化搜索的 key当前还差多少才能够到目标: remain = desiredTotal - current_sum
常见写法是递归函数里传这俩信息中的一个,比如我这里选“还差多少”这条线会比较直观。
递归+记忆化的大致逻辑
用话说一遍递归逻辑:
dfs(used, remain):
如果 remain <= 0:说明上一个人已经凑够了,我这边已经晚了,返回 False
枚举每个还没用过的数字 i:
如果选 i 后,让对手处在一个必输状态:
也就是 dfs(used | (1 << i), remain - i) == False
那我就能赢,直接返回 True
如果所有选择都不能让我把对手送进必输局面,那我就是必输,返回 False
记忆化就是:同一个 used 状态,结果肯定一样,就别反复算了,用字典或者 functools.lru_cache 缓一下。
用 Python 写出来
下面是比较完整的一版,带一点点注释:
from functools import lru_cache
defcanIWin(maxChoosableInteger: int, desiredTotal: int) -> bool:
# 剪枝 1:目标 <= 0,先手不用动就赢了
if desiredTotal <= 0:
returnTrue
# 剪枝 2:所有数加起来都不够,那谁也赢不了
max_sum = (1 + maxChoosableInteger) * maxChoosableInteger // 2
if max_sum < desiredTotal:
returnFalse
# 记忆化搜索,used 用 bitmask 表示哪些数已经被选过了
@lru_cache(None)
defdfs(used_mask: int, remain: int) -> bool:
# remain <= 0 说明上一个人已经把总和凑到了,这一轮的玩家已经输了
if remain <= 0:
returnFalse
# 从 1 到 maxChoosableInteger 枚举还能选的数
for x in range(1, maxChoosableInteger + 1):
bit = 1 << (x - 1)
# 这个数已经被用过了,跳过
if used_mask & bit:
continue
# 我选了 x 之后:
# - 新的 used_mask 是 used_mask | bit
# - 还差 remain - x
# 如果能让对手处在「必输」状态,那我就是必胜
ifnot dfs(used_mask | bit, remain - x):
returnTrue
# 所有选择都不能让对手必输,那就是我必输
returnFalse
# 初始状态:一个数都没用,目标是 desiredTotal
return dfs(0, desiredTotal)
简单测两下举个例子,你可以自己在本地跑:
print(canIWin(10, 11)) # 常见测试用例
print(canIWin(10, 40)) # 通常是 False
复杂度大概什么水平?
状态是「哪些数被用过」,最多是 2^maxChoosableInteger个状态每个状态里最多枚举 maxChoosableInteger个数字
所以时间复杂度粗略看是 O(2^n * n),n 最大 20,乘一乘也还能接受。 这也是为啥题目会把 maxChoosableInteger 限制在 20 以内,再大就直接爆炸了。
大概就这样,这题本质就是“用 bitmask 把博弈类搜索压到能跑”,思路顺一次,后面很多类似的“我先手是不是必胜”题都可以照这个套路来写。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB