试题一
给出n个数字组成的字符串,移走k个字符,求剩下的字符组成的最小数字;
示例
- 字符串:132,移走1个字符, 输出:12
- 字符串:001032,移走2个字符,输出: 2
- 字符串: 132450, 移走2个字符,输出:1240
解答
移走k个字符,可以转换为移走1个字符,移k次,每一次都让剩下的字符组成的数字最小;
这样转换意味着,每次操作的最优解,就是多次操作同时进行后的最优解(局部最优累积成为全局最优),需要对此进行证明,之后我们会使用严格的数学推导方式得出操作的规律,从而证明局部最优累积为全局最优;
这可以理解为贪心算法的思想,但本题目是有严格解的,并不是绝对的贪心算法;
先看移走1个字符的情形:
设n个字符,排列顺序为
a
1
a
2
.
.
.
a
n
a_{1}a_{2}...a_{n}
a1a2...an,
a
1
≠
0
a_{1}\neq 0
a1=0, 那么该字符串表示的数字为
S
n
=
a
1
×
1
0
n
−
1
+
a
2
×
1
0
n
−
2
+
.
.
.
+
a
i
−
1
×
1
0
n
−
i
+
1
+
a
i
×
1
0
n
−
i
+
a
i
+
1
×
1
0
n
−
i
−
1
+
.
.
.
+
a
n
−
1
×
10
+
a
n
S_{n}=a_{1}\times 10^{n-1}+a_{2}\times 10^{n-2}+...+a_{i-1}\times 10^{n-i+1}+a_{i}\times 10^{n-i}+a_{i+1}\times 10^{n-i-1}+...+a_{n-1}\times 10+a_{n}
Sn=a1×10n−1+a2×10n−2+...+ai−1×10n−i+1+ai×10n−i+ai+1×10n−i−1+...+an−1×10+an
如果移走的字符是 a i a_{i} ai, 1 ≦ i ≦ n 1\leqq i\leqq n 1≦i≦n,则剩下的字符组成的数字为
f ( i ) = a 1 × 1 0 n − 2 + a 2 × 1 0 n − 3 + . . . + a i − 1 × 1 0 n − i + a i + 1 × 1 0 n − i − 1 + . . . + a n − 1 × 10 + a n {}f\left( i\right) =a_{1}\times 10^{n-2}+a_{2}\times 10^{n-3}+...+a_{i-1}\times 10^{n-i}+a_{i+1}\times 10^{n-i-1}+...+a_{n-1}\times 10+a_{n} f(i)=a1×10n−2+a2×10n−3+...+ai−1×10n−i+ai+1×10n−i−1+...+an−1×10+an
从f(i)的表达式不容易看出问题,但Sn和f(i)之间是存在关系的,且Sn是固定值,因此计算差值
Δ
S
i
=
S
n
−
f
(
i
)
=
a
1
×
1
0
n
−
1
+
(
a
2
−
a
1
)
×
1
0
n
−
2
+
.
.
.
+
(
a
i
−
a
i
−
1
)
×
1
0
n
−
i
\Delta S_{i}=\ S_{n}-f\left( i\right) =a_{1}\times 10^{n-1}+(a_{2}-a_{1})\times 10^{n-2}+...+(a_{i}-a_{i-1})\times 10^{n-i}
ΔSi= Sn−f(i)=a1×10n−1+(a2−a1)×10n−2+...+(ai−ai−1)×10n−i
如果要求f(i)最小,
Δ
S
i
\Delta S_{i}
ΔSi则要求最大;
所以,我们需要观察 Δ S i \Delta S_{i} ΔSi的表达式,找到跟i的规律;
Δ S 1 = a 1 × 1 0 n − 1 ; \Delta S_{1}=a_{1}\times 10^{n-1}; ΔS1=a1×10n−1;
Δ S 2 = a 1 × 1 0 n − 1 + ( a 2 − a 1 ) × 1 0 n − 2 \Delta S_{2}=a_{1}\times 10^{n-1}+\left( a_{2}-a_{1}\right) \times 10^{n-2} ΔS2=a1×10n−1+(a2−a1)×10n−2
Δ S 3 = a 1 × 1 0 n − 1 + ( a 2 − a 1 ) × 1 0 n − 2 + ( a 3 − a 2 ) × 1 0 n − 3 \Delta S_{3}=a_{1}\times 10^{n-1}+\left( a_{2}-a_{1}\right) \times 10^{n-2}+\left( a_{3}-a_{2}\right) \times 10^{n-3} ΔS3=a1×10n−1+(a2−a1)×10n−2+(a3−a2)×10n−3
-
从表达式来看,如果我们从高位到低位来移动字符(即从第一位到最后一位的顺序),每移动后一位字符时,就会多出一个 ( a i − a i − 1 ) × 1 0 n − i (a_{i}-a_{i-1})\times 10^{n-i} (ai−ai−1)×10n−i项,而对于未知的数字,并不能判断 ( a i − a i − 1 ) (a_{i}-a_{i-1}) (ai−ai−1)是否>0,所以应该分情况讨论;
- 如果 ( a i − a i − 1 ) > 0 (a_{i}-a_{i-1})>0 (ai−ai−1)>0,那么就继续向第i+1个字符的方向观察,因为此时多出来的那一项是正数,只会让 Δ S i \Delta S_{i} ΔSi越来越大;
- 如果 ( a i − a i − 1 ) < 0 (a_{i}-a_{i-1})<0 (ai−ai−1)<0,那么就要移走第i-1个字符了,防止出现负数;
-
另外,从表达式来看,高位的权重大(a1的权重是 1 0 n − 1 10^{n-1} 10n−1),应该从高位来开始判断;
- 如果a2 < a1,那么不能让(a2-a1)出现,则要移走a1即可;
- 如果a2 > a1, 那么继续看a3和a2的关系,重复本过程;
所以,综上所述,应该从最高位开始判断,一旦出现 a i < a i − 1 a_{i}<a_{i-1} ai<ai−1, 那么就移动 a i − 1 a_{i-1} ai−1,否则继续判断下一位;
再看移走k个字符
移走k个字符的情况,表达式中无非就是缺少了某些项,但跟移走1个字符的思路是一致的,规律是一致的,因此移走k个字符与移动k次,每次移走1个字符是等效的;
程序设计
/**
*
* @param {*} inputStr: string,满足/[0-9]{n}/正则规则, 例如132450
* @param {*} k: integer, k 应该小于inputStr去除开头为0的字符后的长度,例如inputStr为00132450, k < 6而不是8
*/
function minimize(inputStr, k) {
// 先去除字符串开头的0
let handledArr = inputStr.split('');
while(handledArr[0] === '0') {
handledArr = handledArr.slice(1);
}
// 开始取走字符
for(let i = 0; i < k; i ++) {
let j = 0;
while (handledArr[j] < handledArr[j + 1]) {
j ++;
}
handledArr.splice(j, 1);
}
while(handledArr[0] === '0') {
handledArr = handledArr.slice(1);
}
return handledArr.join('');
}