问题描述
在某个项目中,每个版本都用版本号标记,由一个或多个修订号组成,修订号之间由点号.分隔。每个修订号可能有多位数字,并且可能会包含前导零。你需要根据两个版本号 version1 和 version2,判断哪个版本更新,或者它们是否相同。
例如,2.5.33 和 0.1 都是有效的版本号。
当比较两个版本时,从左到右依次比较它们的修订号。忽略每个修订号的前导零,直接比较修订号对应的整数值。如果其中一个版本没有足够的修订号,缺失部分默认补为0。
你需要根据以下规则返回比较结果:
- 如果
version1 > version2,返回1。 - 如果
version1 < version2,返回-1。 - 如果两个版本相等,返回
0。
思路
你需要比较两个版本号 version1 和 version2,判断哪个版本更新,或者它们是否相同。版本号由一个或多个修订号组成,修订号之间由点号 . 分隔。每个修订号可能有多位数字,并且可能会包含前导零。通过动态规划(背包思想)的思路来解决这个问题,思路如下:
我们可以把每次升级操作看作是往 “背包”(总共 k 次升级机会这个限制)里放物品,每个英雄有不同的升级收益(达到目标值后获得的奖励),目标是在不超过 k 次操作的情况下获得最大的奖励总和。这里使用一个二维数组 dp[i][j] 表示在前 i 个英雄中,使用 j 次升级操作能获得的最大奖励总和。
数据结构选择
由于版本号是由多个修订号组成的,我们可以将每个版本号拆分成一个整数列表,这样便于逐个比较修订号。
算法步骤
-
拆分版本号:将
version1和version2分别拆分成整数列表v1_list和v2_list。 -
补齐修订号:比较两个列表的长度,如果其中一个列表较短,则在末尾补
0,使得两个列表长度相同。 -
逐个比较修订号:从左到右依次比较两个列表中的修订号。
- 如果
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) 节点,整个比较流程结束。
通过这样的流程图,可以清晰地展示比较两个版本号大小的代码逻辑流程,方便理解每一步的操作和判断依据。
代码详解
-
初始化动态规划数组
dp:
创建一个二维数组dp,其大小为(n + 1) * (k + 1),用于存储中间状态的最大奖励总和,其中dp[i][j]表示在前i个英雄中,使用j次升级操作能获得的最大奖励总和,初始值都设为 0。 -
动态规划的状态转移:
-
外层循环
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_value(cur_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]。
- 在循环内部,先初始化当前英雄的能力值
-
-
最终结果返回:
最后返回dp[n][k],即考虑完所有n个英雄,使用k次升级操作后能获得的最大奖励总和。
通过这样的动态规划过程,我们就能在给定的升级次数限制下,找到选择英雄和升级方式的最优组合,以获得最大的奖励总和。