校招算法题:字符串价值

在牛客网上看到一个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。所以本次需要削减的面积就从深蓝色里面取。
分两种情况:

  1. 此时k的大于深蓝色的面积,那么说明这部分可以全部削除。更新counts数组,以及k和y的值,继续循环。
  2. 否则,说明需要部分削除,但不用每次减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();

	}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值