本题我首先分享我的思路,再在补充中说明贪心算法。
题目描述:
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec"
输出:[10]
提示:
1 <= s.length <= 500
s
仅由小写英文字母组成
解题准备:
1.了解可能存在的基础操作:首先,要求划分字符串,那么划分得到的字符串(就题意,不要求真正出新字符串,并返回),需要进行记录,所以可能有添加;;;其次,既然涉及数组,所以大概率涉及遍历(即查找);;;总之,可能有添加、查找两个基本操作。
2.了解字符串:字符串的本质是字符数组,对字符串的操作归根结底是对字符数组的操作,因此需要知道字符串转字符数组:str.toCharArray();
3.理顺题目要求:题目其实有三个要求:第一,划分得到的字符串段如S1、S2......SN,必须有S1+S2+......+SN==原字符串(不能缺、不能多、顺序不能乱变) === 第二,要求Si与Sj(i,j属于1到N,并且i!=j)之间,不能有相同字符,比如Si是"abcd"有字符a,Sj如果是"aef"则不满足,因为同样有字符'a' === 第三,要求划分后,字符串段尽可能多。
4.模拟操作:去掉所有条件,从原始开始组合,第一个条件,比较基础,第二个条件,引出以下疑问:如果我们只要求,划分出两个字符串段S1和S2,要求S1和S2中没有相同字符,怎么做?
解题难点1分析:
难点1:
在解题准备"4"中,如果只要求,划分两个字符串段S1、S2,且S1和S2中没有相同字符,可以怎么操作?
思路1分析:更简单的角度
如果我们提供两个不同的字符串S1、S2,要求找到S1、S2中是否有相同字符,如果有返回true,没有返回false,怎么做?【S1、S2中只有小写字符】
思路1解:
【思路1的题目用boolean数组即可,因为只判断有没有,本题用int数组是为接下来做准备】
第一,用一个长度为26的int[]数组A1,先遍历一次S1,记录每个字符出现的个数(有这个字符,对应的位置++)。
第二,同理,创建长度为26的int[]数组A2,遍历S2,记录字符个数,如果有对应字符,那么对应位置++。
第三,遍历数组A1,但凡有A1[i]!=0,判断A2[i]==0?如果等于0,继续遍历,不等于0,说明二者有重复,返回false。
思路2分析:从难点1角度出发
对于输入字符串S,如果已经划分为两部分,那么可以用思路1求解,目前问题是要求你划分。
所以,需要动态的变化,动态的记录。【使用一个指针(下标)ptr】
思路2解:
第一步,继续创建长度为26的int数组A1、A2,首先将S的字符记录进A1中。
第二步,移动指针ptr,将第一个字符记录进A2,从A1删除第一个字符(对应字符--)
第三步,通过A1、A2判断是否有重复字符,如果无重复,则将S划分。
(比如S是"abdb"),直接划分成【"a","bcb"】即可。
当然,很可能S是"abdafe"这种类型。
那么,第四步,进行迭代,每次,ptr向前移动一步,将S.charAt(ptr)记录进A2,同时删除A1数据。然后进行第三步判断,
直到1.找到无重复字符。2.遍历完整个字符串S,都没有符合题意。才返回数据。
解题难点2分析:
由"解题难点1分析",我们容易知道如何划分一个字符串S,使S1和S2中没有相同字符,并且使S1尽可能短【因为一旦遇到S1、S2无相同字符,就立刻返回S1,如果从0开始,那么S1一定是最短的】
不过,题目要求划分尽可能多的字符串段。
难点2:
对于一个字符串S,如何划分出尽可能多的、字符不重复的字符串段?
思路1分析:用迭代的思想
我们已经知道从S划分S1、S2,那么对于原始输入S,我们划分出S1、S2后,S1必定和S2无相同字符、并且但凡S1减少一位,就会有相同字符,所以,S1是最基础输出【也一定是第一个输出】。
那么,想找到第二个输出,必然要从S2中找。
不妨把S2看成S,从S2中拿到S1' 和 S2',
拿到第二个输出后,又得考虑S2'中是否存在......
就此迭代......直到遍历完整个字符数组,即可返回完整输出。
迭代结束条件:这里需要考虑指针ptr是否越界,一旦越界必然出错,所以ptr最多指向s.length-1,至此迭代结束。
代码:
class Solution {
public List<Integer> partitionLabels(String s) {
int[] temp=new int[26];
int[] data=new int[26];
char[] x=s.toCharArray(); // 用s.charAt(i)也一样;
int ptr=0; // 指针
List<Integer> res=new ArrayList<>();
for(char a:x){
temp[a-'a']++; // 首先记录A1
}
while(ptr<x.length){ // 只要不遍历结束,就一直遍历
// 这一部分属于初始化,否则一开始data中为null,必然与temp不相交
data[x[ptr]-'a']++; // A2++
temp[x[ptr]-'a']--; // A1--
ptr++; //ptr++
while(isFalse(temp, data)){
data[x[ptr]-'a']++;
temp[x[ptr]-'a']--;
ptr++;
}
// ttt用于确定前面已划分出的字符串长度
int ttt=0;
for(int n:res){
ttt+=n;
}
res.add(ptr-ttt);
// 重置A2
for(int i=0; i<26; i++){
data[i]=0;
}
}
return res;
}
// 判断是否有重复元素
private boolean isFalse(int[] temp, int[] data){
for(int i=0; i<26; i++){
if(data[i]!=0){
if(temp[i]!=0){
return true; // 有重复元素返回true
}
}
}
return false;
}
}
补充贪心算法:
本题的题解使用贪心算法,思路也比较简单。
1.利用HashMap哈希表,记录所有字符最晚出现的位置,key是字符,value是Integer,表示最晚的位置。比如"baabcbacadefegdehijhklij",记录a=8, b=5......
2.采用for循环遍历s的字符数组,拿到s.charAt(i)最晚出现的位置(hashMap.get(s.charAt(i)),如果最晚出现晚于目前最晚的位置(比如a=8,b=5,首先遍历到b,记录最晚等于5;然后遍历到a,记录最晚为8......)
3.如果一直遍历到8,没有出现更晚的数据,返回8,这个就是最小的满足题意的答案。
4.解释:第一个是b=5,也就是说,S1起码到5这个位置,否则不可能出现S1、S2没有相交字符。然后是a=8,也就是说,S1至少到8,否则必定有......(选取最大的即可)
以上内容即我想分享的关于力扣热题12的一些知识。
我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。