Python技术迷

被公司辞退了,领到了22万补偿金。结果准备入职下一家公司时,原公司HR电话叫我回去,涨薪7000,但要把赔偿金还回去

刚看到个贴子,说楼主被公司辞退拿了22万补偿,结果准备入职下一家公司时,原公司突然又喊他回去,直接给涨薪7000,但条件是——把补偿金还回去。

Image

这本质上就是典型的“用脚投票后,原公司才开始珍惜人”。

企业在你被辞退那一刻就已经做了选择,现在突然回头,是因为发现缺人、用人成本更高,不是因为突然良心发现。

换个角度讲,补偿金是合法合规给的,属于对你离开的补偿,现在让你退回去,说白了就是想两头占便宜。

7000涨薪看着心动,但真正的安全感不是涨一次,而是公司态度能不能稳定。

找工作看长期,别被一时的“回头是岸”晃了眼。做决定的时候,多为自己的职业稳定性想一想,心态稳了,路才走得更稳。【备注:文末可领最新资料】

面试题:两个字符串的删除操作

给你两个字符串 word1 和 word2,你每次操作只能“删掉一个字符”(可以删 word1 里的,也可以删 word2 里的),问最少要删几次,才能让两个字符串变成“完全一样”。

你可以想象有两个名字: 比如 sea 和 eat。

如果想让它们变一样,有很多做法:

  • 从 sea 删掉 s,变成 ea
  • 从 eat 删掉 t,也变成 ea

一共删了 2 次,就搞定了,那答案就是 2。

本质上其实就是:通过删字符,把俩串“对齐”成同一个串,删得越少越好。

有个特别好用的思路: 与其想着“删谁”,不如先想:这两个字符串里,能保留的“共同骨架”最多有多长?

这个“共同骨架”,就是两串的 最长公共子序列(LCS),注意是子序列,不要求连续,只要相对顺序不变就行。

比如 sea 和 eat:

  • 它们的最长公共子序列是 ea,长度是 2。

那有啥用呢?

如果最长能保留的共同部分长度是 L, 那剩下的都得删掉:

  • word1 里除了这 L 个共同字符,剩下 len(word1) - L 个,都得删
  • word2 里同理,删 len(word2) - L 个

所以总删除次数:

minDelete = (len(word1) - L) + (len(word2) - L)
          = len(word1) + len(word2) - 2 * L

所以整个题目就变成一句话:先求出两个字符串的 LCS 长度,再套上面这个公式。

用动态规划求 LCS(Python 实战)

老朋友 DP 出场。 我们搞一个二维数组 dp,定义是:

dp[i][j] = word1 前 i 个字符 和 word2 前 j 个字符 的最长公共子序列长度

注意这里前 i 个,是指子串 word1[:i],下标是从 1 开始算长度,对应字符串下标 i-1。

状态转移分两种情况:

  1. 如果当前最后一个字符相等:word1[i-1] == word2[j-1]那这俩字符可以一起加进公共子序列里:dp[i][j] = dp[i-1][j-1] + 1

  2. 如果不相等,那就只能“丢掉一个看”:

  • 不是丢字符串里的字符,而是让其中一个“不参与”当前匹配
  • 所以:dp[i][j] = max(dp[i-1][j], dp[i][j-1])

边界:只要有一个前缀长度是 0,那 LCS 长度肯定是 0,所以初始化整行整列为 0 就行(Python 默认就是 0)。

上代码:

defminDistance(word1: str, word2: str) -> int:
    m, n = len(word1), len(word2)
# dp[i][j] 表示 word1[:i] 和 word2[:j] 的最长公共子序列长度
    dp = [[0] * (n + 1) for _ in range(m + 1)]

for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
# 当前字符相等,可以一起加入公共子序列
                dp[i][j] = dp[i - 1][j - 1] + 1
else:
# 不相等,只能各自少看一个,取更优的那种
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    lcs = dp[m][n]
# 根据公式:总删除次数 = m + n - 2 * LCS
return m + n - 2 * lcs

随手测两下:

print(minDistance("sea", "eat"))   # 2
print(minDistance("leetcode", "etco"))  # 4

这就是 LeetCode 那道“两个字符串的删除操作”的标准写法之一。

时间复杂度是 O(m * n),空间也是 O(m * n),m 和 n 分别是两个字符串的长度。

如果你特别在意内存,其实还能把 dp 优化成一维滚动数组,但面试 / 刷题一般这样写就够用了。

直接算“最少删除次数”的 DP 写法(备个第二思路)

顺带说一句,这题还可以不用 LCS,直接 DP 最少删除次数,也挺直观的,思路是:

dp[i][j] = 把 word1[:i] 和 word2[:j] 变成一样,所需的最少删除次数

然后:

  • 如果 word1[i-1] == word2[j-1]:这俩字符保留就行,不用删dp[i][j] = dp[i-1][j-1]
  • 否则:要么删 word1[i-1],要么删 word2[j-1]所以:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1

边界:

  • dp[0][j] = j,因为 word1 为空,只能把 word2 前 j 个全删了
  • dp[i][0] = i,反之亦然

代码长这样:

defminDistance_direct(word1: str, word2: str) -> int:
    m, n = len(word1), len(word2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

# 处理边界:一个为空串时,只能把另一个全删了
for i in range(1, m + 1):
        dp[i][0] = i
for j in range(1, n + 1):
        dp[0][j] = j

for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
else:
# 删 word1[i-1] 或 删 word2[j-1],选更少的一边再 +1
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1

return dp[m][n]

这个方法和 LCS 那个,结果是一样的,只是思路的切入点不同。

整道题你脑子里记三件事就够了:

  1. 操作只有“删字符”,目标是把两个字符串变成一样;
  2. 可以先找它俩的“共同骨架”(最长公共子序列 LCS),答案是 len1 + len2 - 2 * LCS;
  3. LCS / 直接删除次数,都能用二维 DP 搞定,时间复杂度 O(m * n)。

你如果刷题,用第一种 LCS 写法就很好记; 如果想锻炼 DP 建模能力,可以再自己推一遍“直接算最少删除次数”的那个版本,多练几次就顺手了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB