leetcode-763. 划分字母区间
贪心算法
划分字母区间
题目详情
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表
。
示例:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
我的代码:
C++
class Solution
{
public:
vector<int> partitionLabels(string s)
{
int last[26],len=s.size();
for(int i=0;i<len;++i)
{
last[s[i]-'a']=i;
}
int start=0,end=0;
vector<int> ans;
for(int i=0;i<len;++i)
{
end=max(end,last[s[i]-'a']);
if(i==end)
{
ans.push_back(end-start+1);
start=end+1;
}
}
return ans;
}
};
Java
class Solution {
public List<Integer> partitionLabels(String s) {
//利用last数组来存储26个字母的最后出现位置的下标
int last[] = new int[26];
char ss[] = s.toCharArray();
int len = s.length();
//遍历一次来统计每个字母的最后位置下标
for (int i = 0; i < len; ++i){
last[ss[i] - 'a'] = i;
}
//start:待切割片段的头 end:尾
int start = 0, end = 0;
List<Integer> ans = new LinkedList<>();
for (int i = 0; i < len; ++i){
/*
* 这里更新尾的思路是:
* 比较 已经决定好的尾 和 遍历到的字母的last位置
* 哪个更远,就将尾(切割位置)更新为它
*/
end = Math.max(end, last[ss[i]-'a']);
//遍历到i==end了,是时候切割了,为什么呢?因为贪心啊!
//越早切割,片段越多!
if (i == end){
ans.add(end - start + 1); //片段长度注意是end-start+1
start = end + 1; //更新切割头
}
}
return ans;
}
}
方法二:转化为区间问题
可以统计出现字母的头尾位置,存入一个二维数组,从而转变为不重叠区间问题,更能体现贪心算法
class Solution {
//将字母转换为一个个的区间
public int[][] findPartitions(String s){
int[][] hash = new int[26][2]; //26个字母,区间头尾2
for (int i = 0; i < s.length(); ++i){
//提取出i处的字符c来处理
char c = s.charAt(i);
//未出现过,则记录此处为c的头
if (hash[c - 'a'][0] == 0) hash[c - 'a'][0] = i;
hash[c - 'a'][1] = i; //出现一次就更新一次尾巴
//注意一定要单独处理第一个字母的头为0
//如果不处理,第一个字母的头肯定会弄成第二次出现的地方
hash[s.charAt(0) - 'a'][0] = 0;
}
//↓h来统计有多少个字母出现过,这样就不用记录没有出现的字母了,节省空间
//temp每次临时记录一个字母的头和尾
List<Integer> temp = new ArrayList<>();
//h存储所有字母的头尾
List<List<Integer>> h = new LinkedList<>();
//遍历,将所有字母的头尾存入h
for (int i = 0; i < 26; ++i){
temp.clear();
temp.add(hash[i][0]);
temp.add(hash[i][1]);
h.add(new ArrayList<>(temp));
}
//开辟出 出现过的字母数量的空间即可
//即过滤掉没出现的字母,就不用记录26个了
int[][] res = new int[h.size()][2];
for (int i = 0; i < h.size(); ++i){
List<Integer> list = h.get(i);
res[i][0] = list.get(0);
res[i][1] = list.get(1);
}
return res;
}
public List<Integer> partitionLabels(String s) {
//字符串转换成一个个的区间,从而转变成区间问题
int[][] partitions = findPartitions(s);
List<Integer> res = new ArrayList<>();
Arrays.sort(partitions, (a,b) -> a[0] < b[0]);//按头排序
int right = partitions[0][1]; //从第一个区间开始[0,partitions[0][1]]
int left = 0;
for (int i = 0; i < partitions.length; ++i){
//一旦下一区间的头小于当前区间尾了(两个区间无重叠)
//则可以切割一段
//这里i从0开始是为了记录上最左边的一段
if (partitions[i][0] > right){
res.add(right - left + 1);
left = partitions[i][0];
}
//每次更新为最大右边界(保证切割下来的段中字母所有都在)
right = Math.max(right, partitions[i][1]);
}
//记录上最右边剩余的一段
res.add(right - left + 1);
return res;
}
}
这个方法就和
leetcode-435.无重叠区间
leetcode-452.用最少数量的箭引爆气球
思想完全一样了
涉及知识点:
- 贪心算法
顾名思义,贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最
后得到的结果是全局最优的。
思路:
统计每一个字符最后出现的位置
从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点