在牛客网上看到一个2018校招题,爱奇艺的,叫做 “字符串的价值”。感觉所有别人的答案都不满意,特此记录
题目描述:
有一种有趣的字符串价值计算方式 : 统计字符串中每种字符出现的次数,然后求所有字符次数的平方和作为字符串的价值
例如: 字符串"abacaba",里面包括4个’a’,2个’b’,1个’c’,于是这个字符串的价值为4 * 4 + 2 * 2 + 1 * 1 = 21
牛牛有一个字符串s,并且允许你从s中移除最多k个字符,你的目标是让得到的字符串的价值最小。
链接 https://www.nowcoder.com/questionTerminal/9240357eefcf4d938b90bdd5eec3712b
输入描述:
输入包括两行,第一行一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),其中只包含小写字母(‘a’-‘z’)。
第二行包含一个整数k(0 ≤ k ≤ length),即允许移除的字符个数。
输出描述:
输出一个整数,表示得到的最小价值
例如输入 aaabbc 2 输出 6
首先 无疑是用哈希法开一个大小26的整型数组counts,用 counts [ c - ‘a’ ]++ 来统计所有字符出现的数目。
不好的解法:
浏览了这个问题下的很多答案,发现他们寥寥几笔就解决了问题,核心思想都是通过不断的排序上面的26个整数,然后每次使最大的那个数减一,直到减k次。
时间复杂度,排序无论是直接排序还是使用堆排(算是优化,因为每次只需要最大的那个数),因为整数固定了是26个,所以排序的时间复杂度可以说是O(1),但是他们都是每次使 k 减一,所以时间复杂度和k有关,O(k)。
思考:
为什么他们都是每次使k减一呢,多减一些不是就可以优化时间复杂度吗,但实现起来不是特别的简单。复杂度为O(1)。
首先还是一个counts数组记录了所有字母出现的次数,然后排序。
虽然也使用了一个While循环,但是当 y到0 时或者k被减到0时跳出。
y相当于一条水平的悬线。起始值为最大的counts[0],就是图中的10了,图中只有4个柱状,实际是26个,而且便于理解选择了循环的中间步骤,更一般化容易理解。
由于之前排序过,所以肯定是非严格递减的,首先要计算有多少个柱形的高度是相同的,例如图中是2个,那么计算一下它与第二高的差距,这里是 10 - 7 = 3。
所以图中深蓝色部分的面积是 2 * 3 = 6。所以本次需要削减的面积就从深蓝色里面取。
分两种情况:
- 此时k的大于深蓝色的面积,那么说明这部分可以全部削除。更新counts数组,以及k和y的值,继续循环。
- 否则,说明需要部分削除,但不用每次减1.例如这里k是5,那么除以刚才得到的相同柱形的个数2,得到2,说明每个矩形可以都先一次性削去2。那么5/2得到的余数1,说明这两个矩形其中有1个还需要再削去一个高度。至此循环要跳出结束。
重要的是两个步骤,每次都大量削去了k的值,思考一下即使是最坏的情况,它的时间复杂度仍然和K无关,而是和数组的大小有关,这里是26,所以是O(1)。
下面是代码,数组大小是27的原因加一个0,防止边界情况。
java 竟然没有 基本元素数组逆序的api,懒得弄,所以下标是倒序,很难看。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static int fun(String string, int k) {
int[] counts = new int[27]; //多一个一定是0的
for (int i = 0; i < string.length(); i++)
counts[string.charAt(i) - 'a']++;
// 先排序一下
Arrays.sort(counts);
int y = counts[counts.length-1];
// y 相当于水平线,从最高的往下降
while( y > 0 && k > 0) {
//先统计有多少高度一样的
int sameHeigth = 1;
for(int i = counts.length - 1; i > 0; i--) {
if(counts[i] == counts[i-1]) sameHeigth++;
else break;
}
if(sameHeigth * (y - counts[counts.length - sameHeigth - 1]) <= k) {
//如果矩形面积 小于 k ,全部削矮
for(int i = counts.length - 1; i >= counts.length - sameHeigth; i--) {
counts[i] = counts[counts.length - sameHeigth - 1];
}
// 改变k 和 y的值
k -= sameHeigth * (y - counts[counts.length - sameHeigth - 1]);
y = counts[counts.length - 1];
}
//不小于k
else {
int yushu = k % sameHeigth;
int shang = k / sameHeigth;
//先一次削弱所有的
for(int i = counts.length - 1; i >= counts.length - sameHeigth; i--) {
counts[i] -= shang;
}
// 再考虑余数
for(int i = counts.length - 1; i >= counts.length - sameHeigth && yushu > 0; i--) {
counts[i] -= 1;
yushu--;
}
// 结束跳出
break;
}
}
int res = 0;
for(int i = 0; i < counts.length; i++)
res += counts[i] * counts[i];
return res;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String string = scanner.nextLine();
int k = scanner.nextInt();
System.out.println(fun(string, k));
}
scanner.close();
}
}