字符串练习

字符串中的变位词

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的某个变位词。
换句话说,第一个字符串的排列之一是第二个字符串的 子串 。

示例 1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).

示例 2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False

提示:
1 <= s1.length, s2.length <= 10^4
s1 和 s2 仅包含小写字母

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/MPnaiL

方法一:深度优先搜索
利用深度优先搜索,将字符串s1的各种排列方式求出来,每得到一种排列方式,就判断在s2中是否存在这样一个子串(利用indexOf(String str)即可实现。但是我们看一下字符串的大小,10^4,显然时间复杂度极其高,因此,利用深度优先搜索解决的话就会导致超时。

方法二:滑动窗口
题目中明确说明了只要字符串s1的任意一种排列方式是s2的子串,那么s1就是s2的变位词。那么我们来想一下,如果s2和s1长度相等的情况下,我们应该怎样做呢?很显然,我们可以比较两个字符串中各个字符出现的次数是否相同即可,如果各个字符出现相同,那么s1就是s2的变位词,否则不是。
所以明白这一点之后,本题的解题思路就有了。

  • 如果s1的长度大于s2的长度,那么不管s1怎么排列,都不可能找到s1,因为长度不满足,因此直接返回false。
  • 否则,s1的长度小于等于s2的长度时,那么我们假设s1的长度为n,我们在s2中每n个字符构成的子字符串进行判断,这时候就变成了判断s1是否为s2长度为n的子字符串的变位词,因为长度相同,只需要比较这其中的字符个数即可。

对应代码:

class Solution {
    /*
    因为数据过大,所以不可以使用深度优先搜索,来获取s1的排列后的各种情况。
    
    我们假设一下,如果s1和s2的长度相同,那么只需要判断两者中各个字符出现的
    次数是否相同,就可以判断是否为变位词。

    所以,根据这个原则,我们在知道s1的长度为n,那么我们只需要对s2的每n个字符构成的子串
    然后判断出现的字符次数是否和s1的相同即可,如果相同,说明s1是s2的变位词子串,否则不是
    */
    public boolean checkInclusion(String s1, String s2) { 
        if(s1.length() > s2.length())
           return false;//s1的长度大于s2,那么直接返回false      
        char[] chars1 = s1.toCharArray();
        char[] chars2 = s2.toCharArray();
        int[] count1 = new int[26],count2 = new int[26];
        int i;
        for(i = 0; i < chars1.length; ++i){
            //统计s1中所有字符出现的次数,统计s2中[0,chars1.length - 1]各字符出现的次数
             count1[chars1[i] - 'a']++;
             count2[chars2[i] - 'a']++;
        }
        //判断能不能相等,如果出现的次数一样,说明s1的排列之一可以成为第二个字符串的子串
        if(equalsCount(count1,count2))
           return true;
        for(i = chars1.length; i < chars2.length; ++i){
            /*
            统计s2中长度为chars1.length的并且以chars2[i]字符结尾,chars2[i - chars1.length + 1]
            作为起点的子串
            因为之前已经统计过了s2中[i - chars1.length + 1,i - 1]范围的字符个数,所以这时候我们
            只需要在统计下标为i的字符个数即可
            */
            count2[chars2[i - chars1.length] - 'a']--;//更新起点为i - chars1.length + 1
            count2[chars2[i] - 'a']++;
            if(equalsCount(count1,count2))
               return true;
        }
        return false;
       
    }
    public boolean equalsCount(int[] count1,int[] count2){
        for(int i = 0; i < count1.length; ++i){
            if(count1[i] != count2[i])
               return false;
        }
        return true;
    }
    /*
    使用深度优先搜索进行排列明显不合理,因为字符串的长度过长,这样的话就会导致超时
    public void dfs(char[] chars,int step,String s2){
        if(step == chars.length){
            String str = new String(route);
            //System.out.println(str);
            if(s2.indexOf(str) != -1)
               flag = true;
            return;
        }
        for(int i = 0; i < chars.length; ++i){
            if(chars[i] != '0'){
                route[step] = chars[i];
                chars[i] = '0';
                dfs(chars,step + 1,s2);
                chars[i] = route[step];
                if(flag)
                   break;
                
            }
        }
    }
    */

}

运行结果:
在这里插入图片描述

字符串中所有变位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 变位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。变位词 指字母相同,但排列不同的字符串.

示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的变位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的变位词。

示例 2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的变位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的变位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的变位词。

提示:
1 <= s.length, p.length <= 3 * 10^4
s 和 p 仅包含小写字母

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/VabMRr

方法:滑动窗口
明白上面的思路之后,这道题就变得很简单了。这里就不重复说了。
对应代码:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> list = new ArrayList<Integer>();
        if(p.length() > s.length())
           return list;//p的长度大于s的长度,说明不是变位词,直接返回list
        char[] chars1 = s.toCharArray();
        char[] chars2 = p.toCharArray();
        int[] count1 = new int[26],count2 = new int[26];
        int i;
        for(i = 0; i < chars2.length; ++i){
            //统计字符串p出现的各个字符次数,然后统计s中[0,chars2.length - 1]的各个字符出现的次数
            count1[chars1[i] - 'a']++;
            count2[chars2[i] - 'a']++;
        }
        if(equalsCount(count1,count2))//p是长度为chars2.length,并且起点为0的s字符串的变位词,可以直接返回下标0,也可以是i - chars2.length
           list.add(i - chars2.length);
        for(i = chars2.length; i < chars1.length; ++i){
            //统计[i - chars2.length + 1,i]中各个字符出现的次数,但是之前已经有了
            //i - chars2.length + 1的字符,而且已经count2中还统计了i - chars2.length的
            //字符的次数,我们要做的就是将i - chars2.length这个字符删掉
            count1[chars1[i - chars2.length] - 'a']--;
            count1[chars1[i] - 'a']++;
            if(equalsCount(count1,count2)){
            /*
            这时候的i是长度为chars2.length的子串中最后一个字符的下标,所以要求这个
            子串的起点,就是起点和终点存在这样关系:i - x + 1 = chars2.length,得x 为 i - 
            chars2.length + 1
            */
                list.add(i - chars2.length + 1);
            }
        }
        return list;
    }

    public boolean equalsCount(int[] count1,int[] count2){
        for(int i = 0; i < count1.length; ++i){
            if(count1[i] != count2[i])
              return false;
        }
        return true;
    }
}

运行结果:
在这里插入图片描述

不含重复字符的最长子字符串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长连续子字符串 的长度

示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子字符串是 “abc”,所以其长度为 3。

示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子字符串是 “b”,所以其长度为 1。

示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

示例 4:
输入: s = “”
输出: 0

提示:

0 <= s.length <= 5 * 10^4
s 由英文字母、数字、符号和空格组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/wtcaE1

方法:滑动窗口
当我们遍历到一个字符chars[ i ]的时候,我们需要判断[ j, i - 1]构成的子串中是否已经存在了这个字符chars[ i ],如果已经存在了,记录它的下标为k,那么更新j为k + 1,此时[ j , i ]才是不包含chars[ i ]的连续子字符串,我们就要获取这些子字符串的最大长度即可。

对应代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() == 0) //如果字符串长度为0,直接返回0
          return 0;
        char[] chars = s.toCharArray();
        int ans = 1,i,j,k;
        String str;
        for(i = 1,j = 0; i < chars.length; ++i){
            str = s.substring(j,i);//获取从j开始,i- 1结束(即范围为[j,i - 1]的子串,判断这个子串中是否含有chars[i]
            k = str.indexOf(chars[i]);//获取子串中字符chars[i]的下标,如果出现了,需要更新j
            if(k != -1)
               j += k + 1;//不可以是j = k + 1,因为k是子串str的下标,对应的是s中的下标为k + j的字符
            ans = Math.max(ans,i - j + 1); //[j,i]是没有含重复字符的子字符串,那么对应的长度为i - j + 1
        }
        return ans;
    }
}

运行结果:
在这里插入图片描述

通过上面,我们一定需要通过String的indexOf方法来判断chars[ i ]是否已经出现了吗?答案是否定的,我们可以通过哈希表来实现,哈希表中的值存放的是chars[ i ]在字符串s[0, i - 1]中的最后一次出现的下标,这样我们就能够很快找到k的值,然后直接更新j为k + 1即可,因为这时候的k对应的就是字符串s的下标,而不是子字符串的下标,如果使用的上面的方法,必须务必注意k的含义

对应代码:

class Solution {
    /*
    public int lengthOfLongestSubstring(String s) {

        if(s.length() == 0) //如果字符串为空,或者长度为0,直接返回0
          return 0;
        char[] chars = s.toCharArray();
        int ans = 1,i,j,k;
        String str;
        for(i = 1,j = 0; i < chars.length; ++i){
            str = s.substring(j,i);//获取从j开始,i- 1结束的子串,判断这个子串中是否含有chars[i]
            k = str.indexOf(chars[i]);//获取子串中字符chars[i]的下标,如果出现了,需要更新j
            if(k != -1)
               j += k + 1;//不可以是j = k + 1,因为k是子串str的下标,对应的是s中的下标位k + j的字符
            ans = Math.max(ans,i - j + 1); //[j,i]是没有含重复字符的子字符串的长度
        }
        return ans;
    }
    */
    public int lengthOfLongestSubstring(String s) {

        if(s.length() == 0) //如果字符串为空,或者长度为0,直接返回0
          return 0;
        char[] chars = s.toCharArray();
        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        map.put(chars[0],0);
        int ans = 1,i,j,k;
        for(i = 1,j = 0; i < chars.length; ++i){
            if(map.containsKey(chars[i])){
                /*
                哈希表中存放的是[j,i - 1]子串中各个字符的最后一次出现的下标
                这时候的[j,i - 1]自身就是一个不包含重复字符的子串,所以我们在
                遍历到一个字符chars[i],要做的就是判断chars[i]是否出现在[j,i - 1]子串中
                所以才有了k >= j,如果k >= j,那么说明chars[i]已经出现在了[j,i - 1]子串
                中,所以需要更新j,否则,如果k小于j,说明当前字符没有出现在[j,i - 1]子串中
                比如例子"abba"中的第二个a就是这样情况
                */
                k = map.get(chars[i]);
                if(k >= j) //这一步十分关键,因为存在类似"abba"这样的情况
                   j = k + 1;
            }
            ans = Math.max(ans,i - j + 1); //[j,i]是没有含重复字符的子字符串的长度
            map.put(chars[i],i);
        }
        return ans;
    }
}

运行结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值