版本号比较 | 豆包MarsCode AI刷题

121 阅读8分钟

问题描述

在某个项目中,每个版本都用版本号标记,由一个或多个修订号组成,修订号之间由点号.分隔。每个修订号可能有多位数字,并且可能会包含前导零。你需要根据两个版本号 version1 和 version2,判断哪个版本更新,或者它们是否相同。

例如,2.5.33 和 0.1 都是有效的版本号。

当比较两个版本时,从左到右依次比较它们的修订号。忽略每个修订号的前导零,直接比较修订号对应的整数值。如果其中一个版本没有足够的修订号,缺失部分默认补为0

你需要根据以下规则返回比较结果:

  • 如果 version1 > version2,返回 1
  • 如果 version1 < version2,返回 -1
  • 如果两个版本相等,返回 0

思路

你需要比较两个版本号 version1 和 version2,判断哪个版本更新,或者它们是否相同。版本号由一个或多个修订号组成,修订号之间由点号 . 分隔。每个修订号可能有多位数字,并且可能会包含前导零。通过动态规划(背包思想)的思路来解决这个问题,思路如下:

我们可以把每次升级操作看作是往 “背包”(总共 k 次升级机会这个限制)里放物品,每个英雄有不同的升级收益(达到目标值后获得的奖励),目标是在不超过 k 次操作的情况下获得最大的奖励总和。这里使用一个二维数组 dp[i][j] 表示在前 i 个英雄中,使用 j 次升级操作能获得的最大奖励总和。

数据结构选择

由于版本号是由多个修订号组成的,我们可以将每个版本号拆分成一个整数列表,这样便于逐个比较修订号。

算法步骤

  1. 拆分版本号:将 version1 和 version2 分别拆分成整数列表 v1_list 和 v2_list

  2. 补齐修订号:比较两个列表的长度,如果其中一个列表较短,则在末尾补 0,使得两个列表长度相同。

  3. 逐个比较修订号:从左到右依次比较两个列表中的修订号。

    • 如果 v1_list[i] > v2_list[i],则 version1 更新,返回 1
    • 如果 v1_list[i] < v2_list[i],则 version2 更新,返回 -1
    • 如果所有修订号都相等,则两个版本相同,返回 0

图解

以下是使用流程图来解释上述比较两个版本号大小的代码逻辑的过程:

st=>start: 开始
input=>inputoutput: 输入version1和version2
split1=>operation: 将version1按.分割并转换为整数列表v1_list
split2=>operation: 将version2按.分割并转换为整数列表v2_list
getLen=>operation: 获取len1(v1_list长度)、len2(v2_list长度),计算max_len(两者最大长度)
iLoop=>operation: i = 0
compareLoop=>condition: i < max_len?
getV1=>operation: num1 = v1_list[i] if i < len1 else 0
getV2=>operation: num2 = v2_list[i] if i < len2 else 0
compare1=>condition: num1 > num2?
return1=>inputoutput: 返回1(version1 > version2)
compare2=>condition: num1 < num2?
return2=>inputoutput: 返回 -1(version1 < version2)
iInc=>operation: i++
endLoop=>operation: 循环结束
return0=>inputoutput: 返回0(version1 = version2)
end=>end: 结束

st->input->split1->split2->getLen->iLoop->compareLoop
compareLoop(yes)->getV1->getV2->compare1
compare1(yes)->return1->end
compare1(no)->compare2
compare2(yes)->return2->end
compare2(no)->iInc->compareLoop
compareLoop(no)->endLoop->return0->end

以下是对这个流程图各部分的详细解释:

开始阶段

  • 开始(st) :整个流程的起始节点,表示开始进行版本号比较的流程。
  • 输入(input) :接收要比较的两个版本号 version1 和 version2,这是后续操作的数据基础。

数据准备阶段

  • 将version1按.分割并转换为整数列表v1_list(split1) :使用字符串的 split(".") 方法把 version1 按照点号 . 分割成字符串列表,再通过 map(int,...) 等操作将这些字符串元素转换为整数类型,最终得到整数列表 v1_list,方便后续按修订号进行比较。
  • 将version2按.分割并转换为整数列表v2_list(split2) :与上述对 version1 的操作类似,对 version2 进行同样的处理,得到 v2_list,用于后续比较。
  • 获取len1(v1_list长度)、len2(v2_list长度),计算max_len(两者最大长度)(getLen) :分别获取 v1_list 和 v2_list 的长度,然后找出其中较大的长度值,记为 max_len,后续将按照这个最大长度来循环比较两个版本号对应位置的修订号。

比较循环阶段

  • i = 0(iLoop) :初始化一个循环变量 i,用于指示当前比较的修订号位置,从位置0开始比较。
  • i < max_len?(compareLoop) :判断循环变量 i 是否小于 max_len,如果是,则进入比较的核心操作;如果否,表示已经比较完所有需要对比的修订号位置,进入后续的结果判断阶段。
  • num1 = v1_list[i] if i < len1 else 0(getV1) :根据当前位置 i 和 v1_list 的长度 len1,获取版本号 version1 当前位置对应的修订号数值,如果 i 超出了 len1 的范围(即版本号 version1 的修订号个数没那么多),则将该位置的修订号值设为0。
  • num2 = v2_list[i] if i < len2 else 0(getV2) :与获取 num1 的操作类似,根据当前位置 i 和 v2_list 的长度 len2,获取版本号 version2 当前位置对应的修订号数值,若超出范围则设为0。
  • num1 > num2?(compare1) :比较获取到的两个版本号当前位置的修订号数值 num1 和 num2,如果 num1 大于 num2,说明 version1 在这个位置上更大,按照规则应返回1,表示 version1 大于 version2,流程跳转到相应的返回节点。
  • num1 < num2?(compare2) :如果 num1 不大于 num2,则进一步判断 num1 是否小于 num2,若小于,说明 version1 在这个位置上更小,按照规则应返回 -1,表示 version1 小于 version2,流程跳转到对应的返回节点。
  • i++(iInc) :如果 num1 既不大于也不小于 num2(即两者相等),则将循环变量 i 的值增加1,准备比较下一个位置的修订号,然后流程回到 i < max_len?(compareLoop) 继续循环比较。

结束阶段

  • 循环结束(endLoop) :当循环变量 i 不再小于 max_len 时,说明已经比较完所有对应位置的修订号,且都相等,按照规则应返回0,表示两个版本号相等,流程跳转到相应的返回节点,最终到达 结束(end)  节点,整个比较流程结束。

通过这样的流程图,可以清晰地展示比较两个版本号大小的代码逻辑流程,方便理解每一步的操作和判断依据。

代码详解

  1. 初始化动态规划数组 dp
    创建一个二维数组 dp,其大小为 (n + 1) * (k + 1),用于存储中间状态的最大奖励总和,其中 dp[i][j] 表示在前 i 个英雄中,使用 j 次升级操作能获得的最大奖励总和,初始值都设为 0。

  2. 动态规划的状态转移

    • 外层循环 for i in range(1, n + 1) 遍历每个英雄,从第 1 个英雄到第 n 个英雄。

    • 内层循环 for j in range(k + 1) 遍历升级次数,从 0 次到 k 次。

    • 对于每个 dp[i][j],首先考虑不选择当前英雄进行升级的情况,即直接继承前 i - 1 个英雄使用 j 次升级操作的最大奖励总和,也就是 dp[i][j] = dp[i - 1][j]

    • 然后,尝试选择当前英雄进行升级,通过内层的 for x in range(1, j + 1) 循环来枚举选择的正整数 x(每次升级选择的参数),模拟对当前英雄进行升级的过程。

      • 在循环内部,先初始化当前英雄的能力值 cur_value 为初始值 1,记录已经使用的升级次数 cur_used 为 0。
      • 接着通过 while 循环不断根据规则更新 cur_valuecur_value += cur_value // x),同时更新已使用的升级次数 cur_used,直到当前英雄的能力值达到或超过目标值 b[i - 1] 或者升级次数超过了当前允许的次数 j(通过 if cur_used > j 判断)。
      • 如果最终使用的升级次数 cur_used 没有超过允许的 j 次,就可以尝试更新 dp[i][j],取之前的值和选择当前英雄升级后的奖励总和(dp[i - 1][j - cur_used] + (c[i - 1] if cur_value >= b[i - 1] else 0))中的较大值作为新的 dp[i][j],这里的条件判断 if cur_value >= b[i - 1] 是为了确定当前英雄是否达到目标值从而可以获得对应的奖励 c[i - 1]
  3. 最终结果返回
    最后返回 dp[n][k],即考虑完所有 n 个英雄,使用 k 次升级操作后能获得的最大奖励总和。

通过这样的动态规划过程,我们就能在给定的升级次数限制下,找到选择英雄和升级方式的最优组合,以获得最大的奖励总和。