来源 | 程序媛山楂(ID:shanzhacoder)刚刷到这个吐槽,给我看乐了
大群里突然被艾特,说你别直接 git pull,这样会把提交记录搞得一坨,后面排查问题像考古。那一刻估计人都懵了:我不就拉个代码吗,怎么还拉出职场事故了。
很多人其实都是这么过来的。刚进公司没人细讲,老员工也默认你懂,平时能跑就行。直到哪天分支历史变成麻花,大家开始找锅,才发现原来 pull 里面还藏着 merge 这回事。
老鬼我只能说,Git 这玩意儿真不是会提交就算会了,很多坑都是挨骂后才补上的。下次默默 pull --rebase 吧,先别吭声。
1000 / 100 / 10 / 2 这题,第一眼看过去像表达式枚举。
真按枚举写,基本就绕远了。括号一多,人脑还能撑两层,代码很快就开始变丑。
最优除法这题的关键点不在“怎么暴力试括号”,而在于你要先看清楚一件事:
除法后面的数,放到分母里会让结果变小;如果能把分母里的除法变成“除以一个更小的数”,结果就会变大。
比如:
1000 / 100 / 10 / 2
默认从左往右算:
((1000 / 100) / 10) / 2 = 0.5
但如果写成:
1000 / (100 / 10 / 2)
里面的 100 / 10 / 2 = 5,最后就是:
1000 / 5 = 200
差距直接拉开。
这里我一般不会上来就写动态规划。不是不能写,是没必要。这个题有个很明显的贪心味道:第一个数一定在分子上,第二个数开始,能塞进同一个括号里就塞进去。
因为:
a / b / c / d
想让结果最大,就应该变成:
a / (b / c / d)
这样 c、d 实际上都被“翻”到了分子方向,它们会把整体结果放大。
要注意两个边界:
数组只有一个数,直接返回它。
数组只有两个数,直接 a/b,别加括号,加了也没意义。
代码我会这么写,别搞一堆递归表,题目要的是表达式,不是让你展示肌肉:
class Solution {
public String optimalDivision(int[] nums) {
if (nums == null || nums.length == 0) {
return"";
}
if (nums.length == 1) {
return String.valueOf(nums[0]);
}
if (nums.length == 2) {
return nums[0] + "/" + nums[1];
}
StringBuilder expr = new StringBuilder();
expr.append(nums[0]).append("/(");
for (int i = 1; i < nums.length; i++) {
if (i > 1) {
expr.append("/");
}
expr.append(nums[i]);
}
expr.append(")");
return expr.toString();
}
}
这段代码没什么花活,核心就是从第二个数字开始统一包进括号。
拿一组数跑一下:
nums = [1000, 100, 10, 2]
输出:
1000/(100/10/2)
再看一个容易误判的:
nums = [6, 2, 3]
如果按默认顺序:
6 / 2 / 3 = 1
如果按最优写法:
6 / (2 / 3) = 9
这地方很多人会下意识觉得“括号只是改变顺序”,但除法不是加法,顺序一变,数值方向也变了。
这题最忌讳写成全排列、全括号枚举。能过是能过,但属于把小刀活干成了挖掘机项目。
真正该抓的是这个结构:
nums[0] / (nums[1] / nums[2] / ... / nums[n - 1])
只要数组长度大于 2,这个形式就是最优表达式。
所以它的时间复杂度也很干净,就是扫一遍数组:
时间复杂度:O(n)
空间复杂度:O(n)
空间这里主要是结果字符串本身占用,不算额外算法结构。
这题看着是数学题,其实更像一次代码审查:看到除法和括号,先别急着递归。先问一句,哪些数被放到了分子,哪些数被压到了分母。想明白这个,代码就剩拼字符串了。