昨天刚做的笔试,只过了25%,当时没想懂哪里有问题,现在想明白了,改了代码发一下。
时刻表:aabbcddc,字母a、b、c、d为目的地,同一个目的地的车辆必须分到同一组,车序不能打乱,对时刻表尽可能多的分组,输出各个分组的长度,逗号分隔
样例:
输入:aabbcddc
输出:2,2,4
说明:aa为一组,长度2,bb为一组,长度2,cddc为一组,长度4。因为两个c是同一个目的地,必须分到同一组。
由于这个样例坑了我,哎,所以code出错了,接下来我用另一个样例来说明:
输入:babacddc
输出:4,4
对于这道题,我们要知道以下几个信息:
1、每一个字母(车站)第一次出现的顺序,样例顺序为b、a、c、d;
2、每个字母第一次出现的位置和最后一次出现的位置,样例为:
b:[0,2], a:[1,3], c:[4,7], d:[5,6]
ok,这个时候我们就可以分割了,0~2一定要分在一组,1~3一定要分在一组,4~7一定要分在一组,5~6一定要分在一组
合并:有交集的分在一组,没有交集的分开,结果就是两组:0~3,4~7,答案输出的是长度,返回4,4。
逻辑巨简单无比,那怎么实现呢?
1、map< Character,Integer>,存字母Character的出现的顺序Integer.
样例中,map={b:0,a:1,c:2,d:3}
2、lastpos数组,存字母的最后一次出现的位置,因为大小写最多就58个,所以lastpos[58]就够了(Python的话就不用定义长度啦,更方便)
#敲重点#
这里的 lastpos 是按字母顺序ABC...来存放每个字母的最后一次出现的位置吗?当然不是,怎么可能,如果这样存的话,字母的出场顺序就被拉乱,当然不允许他们乱插队啦,所以我们上面用一个map存Character出现的顺序,然后我们就可以很方便的知道,lastpos[0]代表b最后一次出现的位置,lastpos[1]代表a最后一次出现的位置,lastpos[2]代表c最后一次出现的位置,lastpos[3]代表d最后一次出现的位置。
遍历的过程lastpos的变化如下:
①遍历arr[0]=b:
b加入map中,map={b:0}
lastpos:
0 |
②遍历arr[1]到a:
b加入map中,map={b:0,a:1}
lastpos:
0 | 1 |
③遍历arr[2]到b:
map={b:0,a:1}
lastpos[map.get('b')]=lastpos[0]=2:
2 | 1 |
④遍历arr[3]到a:
map={b:0,a:1}
lastpos[map.get('a')]=lastpos[1]=3:
2 | 3 |
⑤遍历arr[4]到c:
c加入map中,map={b:0,a:1,c:2}
lastpos[map.get('c')]=lastpos[2]=4:
2 | 3 | 4 |
...
继续直到遍历完,后面就不写出来了,最后:
map={b:0,a:1,c:2,d:3}
lastpos:
2 | 3 | 7 | 6 |
3、合并:
啊啊,这个也是重点,我昨天的笔试就是错在了这里!!!ok,fine,不重要,继续讲。
其实,与其说是合并区间,不如说是区间右扩,哈哈哈,看过左神的课的是不是有点懂懂的呢~没错,只右扩(连左缩都没有哦),所以复杂度是O(n)的。具体怎么做呢?来,前排小板凳。
再一次遍历输入的字母序列(代码中的input[]),
①遍历input[0]=b:
map.get['b'] = 0,所以在lastpos[0] 中存放着b最后一次出现的位置为2,ok,用一个max存放,我这个分组的区间,最少要到2。
②遍历input[1]=a:
map.get['a'] = 1,所以在lastpos[a] 中存放着a最后一次出现的位置为3,#重点来了#,这个时候,区间要右扩,max=3。
为什么呢?本来max=2,我至少要把0~2分为一组,才能保证位置0的b和位置2的b分到同一组,这个时候,第一个分组至少要包含所有的字母b,现在位置1多了一个a,那个我第一个分组就至少应该包含所有的a、b字母,而a最后结束的位置是3,所以位置3的a也应该在这个组中,所以,右扩咯~max=3.
③遍历input[2]=b: max=3,继续
④遍历input[3]=a: max=3,这个时候,遍历的位置3(遍历到的input的位置) 和max的位置是一样的,证明什么?前面的区间里的字母,都是纯纯的啦,后面再也没有前面出现的字母哈哈哈,太棒了,那么我就可以输出了,第一个区间是[0,3],那么长度就是4咯~
记录一下,last = 3, [0,3]的区间之后我就可以不用管了~~现在的max应该是什么,恩,下一个字母c最后出现的位置7,so,max=7.
⑤遍历input[4]=c:max=7,继续...
一直继续到结束就完事了~不要忘记输出间隔符‘,’哦~
最后结果就是:4,4
代码如下:
import java.util.HashMap;
import java.util.Scanner;
public class Main {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
//输入的字母序列
char[] input= sc.nextLine().toCharArray();
HashMap< Character,Integer> map = new HashMap();
int[] lastpos = new int[58];
//index作为标记,代表下一个新出现的字母是第几个出现的,从0开始
int index = 0;
//map和lastpos是同时更新的
for(int i=0;i
呃,是有个聊胜于无的优化方案的,就是当第一次碰到input的长度,即max = input.length-1时,就可以不用继续遍历了,因为他一定是最后一个区间了,都到最末了,输出该区间长度就可以pass了。不过我上面没写,更新max后加一个判断就可以了。因为时间复杂度不变,时间未必会短,甚至可能会变长,要看数据哈哈哈哈哈。
突然想到一个很蠢的事情,如果觉得用lastpos数组,要转换下标和字符的对应关系很麻烦怕出错,其实用两个map,一个存字母第一次出现的位置,一个存字母最后一次出现的位置,也是可以的,而且逻辑清晰很多,代码我就不改了,小伙伴可以自己实现哦~