标题

维护真的不易,如有帮助还请点赞关注,关注公众号​​bigsai​​​回复​​进群​​即可加入打卡。

串联所有单词得字串

题目描述:

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]

分析:
这题讲真还是挺有技巧和方案的,刷这道题也花了不少心思,需要考虑的点也稍微多一点。题意就是要找到字符串s的某个字串可以由words中所有单词组成。返回满足匹配s子串的首位编号。

递归法:

从处理的方式上理论是可以使用递归的,但是由于层数太多并且个别比较特殊的数据可能导致爆栈TL。这里就有个教训:

LeetCode 30串联所有单词的子串&31下一个排列_字符串


主要当时没仔细读题,没有在意每个单词长度都相同所以以为是搜索题,后来仔细看题之后才发现问题。

普通哈希法(滑动窗口)
对于有些人叫啥滑动窗口啥稀奇古怪的漂亮名称,这里我就简称为Hash法。如何去分析和处理这个问题呢?我们可以看看一些重要的条件:

  • words中所有单词长度都相同
  • 必须使用所有words中的单词一次

也就是说在进行匹配的时候可以根据单词进行匹配。

LeetCode 30串联所有单词的子串&31下一个排列_字符串_02

但每个字符串进行判断的时候,可以进行分割成若干单词数判断

LeetCode 30串联所有单词的子串&31下一个排列_i++_03


用两个HashMap储存单词数即可,存储途中进行判断如果有不满足直接停止。如果能跑到最后说明可以添加这个标记。

具体实现的代码为:

public List<Integer> findSubstring(String s, String[] words) 
{
List<Integer>value=new ArrayList<Integer>();
Map<String, Integer>map=new HashMap<String, Integer>();
for(int i=0;i<words.length;i++)
{
int num=map.getOrDefault(words[i], 0);
map.put(words[i], num+1);
}
int wordlen=words[0].length();
int len=words[0].length()*words.length;
StringBuilder sBuilder=new StringBuilder(" ");
sBuilder.append(s.substring(0, len-1));
for(int i=0;i<s.length()-len+1;i++)
{
sBuilder.deleteCharAt(0);
sBuilder.append(s.charAt(i+len-1));

int num=0;//统计总共满足的单词数量
Map<String, Integer>map2=new HashMap<String, Integer>();
//map2.putAll(map);
int index=0;
while (index<len) {
String team=sBuilder.substring(index,index+wordlen);
int number=map2.getOrDefault(team, 0);//次数
map2.put(team, number+1);
if(number+1>map.getOrDefault(team, 0))
break;

index+=wordlen;
}

if(index==len)
value.add(i);

}
return value;
}

LeetCode 30串联所有单词的子串&31下一个排列_leetcode_04

Hash滑动窗口优化

可以发现在上面所涉及的的滑动处理中每次都需要重新计算当前字符串的HashMap情况并重新匹配。这样就有很多已经匹配的情况就浪费了。

LeetCode 30串联所有单词的子串&31下一个排列_字符串_05


所以就需要优化来去掉重复的计算,首先要将字符串分成单词长度的组数来分别计算。

LeetCode 30串联所有单词的子串&31下一个排列_i++_06


然后每组在详细进行的时候需要两个指针动态向右表示区间匹配。而Map通常不需要每次都刷新可以重新利用。一个坐标j代表开头一个index表示当前结尾

你可能会遇到以下几种情况:

  • 遇到新单词不存在,此时j和index都移动到单词后重新开始,且储存的动态map需要clear;

LeetCode 30串联所有单词的子串&31下一个排列_i++_07

  • 遇到的单词存在,但是多了,需要将j右移一直到消除这个多余单词,同时修改动态map。

LeetCode 30串联所有单词的子串&31下一个排列_leetcode_08

  • 正常情况,叠加匹配更新index和map。

LeetCode 30串联所有单词的子串&31下一个排列_字符串_09

上面步骤完成之后,如果j+len==index那么就说明完成匹配,添加此个编号。但在此同时,可以判断下个单词是否与当前首单词相等,如果相等直接更新对应j和index顺便加入结果集中,不过不需要更新动态的map。

LeetCode 30串联所有单词的子串&31下一个排列_子串_10

有了上述优化的思路,就可以码代码了。注意细节实现,前开后闭等等,具体代码为:

public List<Integer> findSubstring(String s, String[] words) {
List<Integer>value=new ArrayList<Integer>();//返回的结果
Map<String, Integer>map=new HashMap<String, Integer>();//统计单词个数
for(String team:words)//进行统计
{
int num=map.getOrDefault(team, 0);
map.put(team, num+1);
}
int wordlen=words[0].length();//单个单词的长度
int len=words[0].length()*words.length;//总长度
for(int i=0;i<wordlen;i++)//分组分别进行
{
int j=i,index=j;
Map<String, Integer>map2=new HashMap<String, Integer>();
while (j<=s.length()-len&&index+wordlen<=s.length()) {
String word=s.substring(index,index+wordlen);

int num=map2.getOrDefault(word, 0);
map2.put(word, num+1);
if(!map.containsKey(word))//不包含该元素,直接跳过
{

j=index+wordlen;
map2.clear();
}
else if(map.get(word)<num+1)//元素存在但次数过多
{
String teamstr="";
while (!(teamstr=s.substring(j,j+wordlen)).equals(word)) {//找到第一个不相等得
map2.put(teamstr, map2.get(teamstr)-1);
j+=wordlen;
}
map2.put(teamstr, map2.get(teamstr)-1);
j+=wordlen;
}
index+=wordlen;
if(index==j+len)
{
value.add(j);
while (index+wordlen<=s.length()&&s.substring(j, j+wordlen).equals(s.substring(index, index+wordlen))) {
value.add(j+wordlen);
j+=wordlen;index+=wordlen;
}
String teamstr=s.substring(j,j+wordlen);
map2.put(teamstr, map2.get(teamstr)-1);
j+=wordlen;
}

}
}
return value;
}

LeetCode 30串联所有单词的子串&31下一个排列_字符串_11

下一个排列

题目描述:
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

分析:
是不是和全排列有点像,但是又不是全排列。是需要找到下一个字典序。
分析数列你会发现以下两个规律:

首先从右往左交换第一个正序:

  • 例如1 2 3 5 4 交换3和4成为1 2 4 5 3.

其次根据交换的区间内,从右向左(双重循环)交换逆序对:

  • 例如上述变成1 2 4 3 5;

具体实现的时候,注意位置编号等问题。具体代码为:

public void nextPermutation(int[] nums) {
boolean jud=false;
int i,j=0;
for( i=nums.length-2;i>=0;i--)
{
for( j=nums.length-1;j>i;j--)
{

if(!jud&&nums[i]<nums[j])
{
int team=nums[i];
nums[i]=nums[j];
nums[j]=team;
jud=true;
break;
}
}
if(jud)break;
}
if(jud)
for(int k=nums.length-1;k>i;k--)
{
for(int m=k-1;m>i;m--)
{
if(nums[k]<nums[m])
{
int team=nums[k];
nums[k]=nums[m];
nums[m]=team;
}
}
}
int team;
if(!jud)
for( i=0;i<nums.length/2;i++)
{
team=nums[i];
nums[i]=nums[nums.length-1-i];
nums[nums.length-1-i]=team;
}
}

LeetCode 30串联所有单词的子串&31下一个排列_字符串_12


好啦,本次打卡就到这里啦,更多精彩欢迎关注​​bigsai​​,回复进群加入打卡,回复bigsai获取珍藏pdf资源。

LeetCode 30串联所有单词的子串&31下一个排列_字符串_13