找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2172

积分

0

好友

303

主题
发表于 昨天 14:40 | 查看: 6| 回复: 0

刚看到一个帖子,讨论的是关于面试薪资谈判的博弈。一位求职者面试拿到了40K的offer,但HR要求提供银行流水,结果发现他上一家的薪资是25K每月。HR看了流水后,立刻改口说最多只能给到30K。这位朋友的做法很干脆,直接拒绝了这份offer。

社交媒体关于面试薪资谈判的帖子截图

在我看来,这件事的关键不在于40K还是30K,而在于规则的透明度。既然前期沟通明确了是40K,后面再用流水来压价,本质上就是把薪酬谈判变成了一场翻旧账的算计。站在求职者的角度,拒绝并不丢人,这是在为自己的价值预期正确定价。

不过,HR要求流水、公司需要控制人力成本,也是现实的考量。更成熟的做法是尽早明确薪酬评估的逻辑,避免让对方产生不必要的期待。

归根结底,职场中清楚自己的底线,远比纠结那一两万的薪资差额更重要。姿态端正,路反而走得更稳。

从面试博弈到算法难题:旅行销售员问题

这让我想起前几天晚上九点多,在公司楼下的便利店买咖啡时,我们组的小李一边排队一边跟我吐槽:“我下周要出差跑好几个城市,老板还问我能不能把总路费压到最低,这简直就是在出算法题啊!”我愣了一下,这不就是经典的“旅行销售员问题”(Traveling Salesman Problem, TSP)吗?当时就在餐巾纸上给他推演了一遍解题思路。

我们来脑补一下题目:有若干个城市,一个销售员需要从某个城市出发,访问所有城市各一次,最后再回到起点城市。每两个城市之间都有确定的路费(或距离),问题是:如何规划访问顺序,使得总花费最小?这就是一个非常典型的业务需求。

人脑的第一反应通常是:既然访问顺序影响总路费,那就把所有的可能顺序都列出来,挨个算一遍,选总花费最小的那个。

换成代码思路就是:除了起点城市,对其他所有城市进行全排列。对每一个排列,计算按照这个顺序走一遍的总距离,最后取最小值。

Python 写一个最暴力的解法,大概是下面这样:

import itertools

def tsp_bruteforce(dist, start=0):
    n = len(dist)
    cities = [i for i in range(n) if i != start]
    ans = float('inf')

    for perm in itertools.permutations(cities):
        cost = 0
        cur = start
        # 按顺序访问城市
        for nxt in perm:
            cost += dist[cur][nxt]
            cur = nxt
        # 最后回到起点
        cost += dist[cur][start]
        ans = min(ans, cost)

    return ans

问题来了,这个方法的计算复杂度是 O(n!)。城市数量稍多一点,计算就直接卡死了。10个城市勉强还能算算,15个城市你的电脑基本就开始“思考人生”了。所以,暴力枚举属于“只能跑跑小样例,完全无法投入实战”的方案。

进阶思路:状态压缩动态规划(Bitmask DP)

那有没有更优雅的解法呢?我当时在便利店跟小李说:“别总想着把完整路径当作一个整体去计算。你可以尝试记住‘已经走过了哪些城市’以及‘当前正停在哪个城市门口’,然后一步步向后推导。”

这就是经典的状态压缩动态规划(Bitmask DP)。

核心思想用人话解释一遍:

  • 用一个二进制数 mask 来表示“已经走过哪些城市”。例如有4个城市,mask = 0b1011 就表示走过了编号为0、1、3的城市(从右往左数,第0、1、3位是1)。
  • 定义一个 DP 状态:dp[mask][i] 表示“走完 mask 所代表的所有城市,并且最后停在城市 i 的最小花费”。

知道了“之前访问过哪些点”以及“最后停在谁那儿”,那么再向下走到一个新城市 j 的代价,就是“从当前城市 i 走到 j 的路费”加上“之前走到 i 时的最小花费”。

对应的状态转移关系:

  • 初始化:只在起点的状态。例如起点是0,则 dp[1 << 0][0] = 0
  • 状态转移:对于每个状态 mask,枚举当前的终点城市 i,再枚举下一个未访问的城市 j (即 j 不在 mask 中)。新状态 new_mask = mask | (1 << j)。转移方程为:dp[new_mask][j] = min(dp[new_mask][j], dp[mask][i] + dist[i][j])
  • 最终答案:当所有城市都访问完后 (mask 的二进制位全为1),需要再加上“从最后一个城市回到起点”的路费,在所有可能的最终城市中取最小值。

完整的 Python 实现代码

下面是一个可以直接运行的 Python 实现,你可以修改输入的距离矩阵来测试自己的场景:

from functools import lru_cache

def tsp_dp(dist, start=0):
    """
    dist: 距离矩阵,dist[i][j] 表示从城市 i 到城市 j 的路费(或距离)
    start: 起点城市编号,默认为 0
    """
    n = len(dist)
    ALL = (1 << n) - 1  # 所有城市都已访问的 mask

    @lru_cache(maxsize=None)
    def dfs(mask, i):
        """
        当前已访问的城市集合是 mask,最后停在城市 i,
        返回达到这个状态的最小花费。
        """
        # 特例:只访问了起点,并且现在就在起点
        if mask == (1 << start) and i == start:
            return 0

        # 对于其他状态,mask 必须包含 i
        # 尝试从“之前某个城市 k”转移过来
        res = float('inf')
        prev_mask = mask & ~(1 << i)  # 去掉当前城市 i,得到之前访问的集合
        # 起点状态已特判,这里 prev_mask 必须非空
        for k in range(n):
            if prev_mask & (1 << k):  # k 必须在之前访问的集合里
                res = min(res, dfs(prev_mask, k) + dist[k][i])
        return res

    ans = float('inf')
    full_mask = ALL
    # 枚举最后停在哪个城市,然后再加上回起点的路费
    for last in range(n):
        if last == start:
            continue
        cost = dfs(full_mask, last) + dist[last][start]
        ans = min(ans, cost)

    return ans

if __name__ == "__main__":
    # 示例:4 个城市的距离矩阵
    dist = [
        [0, 10, 15, 20],
        [10, 0, 35, 25],
        [15, 35, 0, 30],
        [20, 25, 30, 0],
    ]
    print(tsp_dp(dist, start=0))

这段代码利用了 lru_cache 进行记忆化搜索,相当于用递归的方式实现了动态规划。如果你更喜欢用 for 循环的“正统” DP 数组写法,也可以将其改写成二维数组形式,核心逻辑是相同的。

方案评估与实际应用

这个动态规划方案的时间复杂度大约是 O(n² * 2ⁿ),虽然看起来依然很大,但比起 O(n!) 已经有了天壤之别。在实际工程中:

  • 城市数量在15个左右时,用 Python 实现的上述代码还可以应付。
  • 城市数量达到20个左右,就需要考虑机器性能和实现细节了。
  • 如果城市规模更大,就不要再强求精确的最优解了,应该转向启发式算法,如最近邻算法、局部搜索、模拟退火、遗传算法等。

但在面试或算法讨论中,当别人提到“旅行销售员问题”时,通常希望你能回答出:暴力解法是全排列,优化解法是状态压缩 DP,其复杂度为 O(n² * 2ⁿ),实现上可以使用位掩码(Bitmask)。

在实际业务中,如果真遇到类似“优化出差路线”的需求,首先要判断城市规模。如果规模很小,直接把上面的 Python 代码拿过去修改适配即可;如果规模很大,目标就应该调整为寻找一个“相对较好的路线”,而非绝对的全局最优解。

好了,关于这个问题的探讨就到这里。我得去催一下我们组小李的接口进度了,他现在追赶的可不是销售员的路线,而是 deadline…




上一篇:Spring Boot参数校验:避免@NotEmpty误用,详解与@NotBlank区别
下一篇:Python脚本监控利器:pushplus实现微信通知推送
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 20:16 , Processed in 0.444986 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表