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

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

串联所有单词得字串



题目描述:


给定一个字符串 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。这里就有个教训:


20200921161859357.png


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


普通哈希法(滑动窗口)


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


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


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


20200921170323630.png


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


20200921180920807.png


用两个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;
}


20200921182701622.png

Hash滑动窗口优化


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


20200921195226927.png


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


2020092120042270.png


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


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


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


20200921201219949.png


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


2020092120162062.png


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


20200921201815783.png


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


20200921202224920.png


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


  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;
}

20200921202406893.png

下一个排列



题目描述:


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


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


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


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


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;
   }
  }


20200921205622320.png

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

目录
相关文章
|
22天前
|
算法 C++ 容器
Leetcode第三十一题(下一个排列)
这篇文章介绍了LeetCode第31题“下一个排列”的C++解决方案,该算法通过原地修改数组来找到下一个字典序更大的排列,如果不存在则重排为字典序最小的排列。
27 0
Leetcode第三十一题(下一个排列)
|
22天前
Leetcode(最后一个单词长度)
这篇文章介绍了两种解决LeetCode第58题的方法,即计算给定字符串中最后一个单词的长度,方法包括翻转字符串和逆向遍历统计。
15 0
|
22天前
【LeetCode 20】151.反转字符串里的单词
【LeetCode 20】151.反转字符串里的单词
15 0
|
3月前
|
算法
LeetCode第58题最后一个单词的长度
LeetCode第58题"最后一个单词的长度"的解题方法,通过从字符串末尾向前遍历并计数非空格字符,直接得出最后一个单词的长度。
LeetCode第58题最后一个单词的长度
|
3月前
|
算法 JavaScript Python
【Leetcode刷题Python】79. 单词搜索和剑指 Offer 12. 矩阵中的路径
Leetcode第79题"单词搜索"的Python解决方案,使用回溯算法在给定的二维字符网格中搜索单词,判断单词是否存在于网格中。
35 4
|
3月前
|
Python
【Leetcode刷题Python】生词本单词整理
文章提供了一个Python程序,用于帮助用户整理和排版生词本上的单词,包括去除重复单词、按字典序排序,并按照特定的格式要求进行打印排版。
36 3
|
3月前
|
算法 Java
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
51 0
|
3月前
|
Python
【Leetcode刷题Python】318. 最大单词长度乘积
本文提供了LeetCode题目318的Python编程解决方案,题目要求在一个字符串数组中找出两个不含有公共字母的单词,且这两个单词的长度乘积最大,如果不存在这样的两个单词,则返回0。
17 0
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
3月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
102 2