算法:(三)字符串

3.1 双指针

面试题14:字符串中的变位词

题目:输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词?如果字符串s2中包含字符串s1的的某个变位词,则s1至少有一个变位词是s2的子字符串。假设两个字符串中只包含英文小写字母。例如,字符串s1为"ac",字符串s2为"dgcfa",由于字符串s2包含s1的变位词"ca",因此输出为true。如果字符串s1为"ab",字符串s2为"dgcaf",则输出为false。

思路:hash表+双指针

public boolean checkInclusion(String s1, String s2){
    /**
     * 双指针+hash表(用数组做hash表)
     * 先将s1的字符信息存入hash表,
     * hash表的键是字符(这里用数组下标表示各个字符,例如下标0表示字符'a',下标1表示字符'b'),hash表的值为字符出现的次数
     * 然后遍历s2,利用双指针同时位移,移进的字符,hash表对应的值就减1,移出的字符,hash表对应的值就加1
     * 每次位移都判断hash表是否全为0(这个操作时间复杂度为O(1),每次执行固定26步操作)
     * 直至双指针位移结束
     * 时间复杂度O(n)
     */
    if(s2.length() < s1.length()){
        return false;
    }
    int[] hash = new int[26];
    for (int i = 0; i < s1.length(); i++) {
        // 在将s1信息存入hash中时,已经开始对s2的遍历的初始化
        hash[s1.charAt(i) - 'a']++;
        hash[s2.charAt(i) - 'a']--;
    }
    if (ifAllZero(hash)){
        return true;
    }
    for (int right = s1.length(); right < s2.length(); right++){
        hash[s2.charAt(right) - 'a']--;
        hash[s2.charAt(right - s1.length())]++;
        if (ifAllZero(hash)){
            return true;
        }
    }
    return false;
}

public boolean ifAllZero(int[] array){
    for (int i : array) {
        if (i != 0){
            return false;
        }
    }
    return true;
}

面试题15:字符串中所有的变位词

题目:输入字符串s1和s2,如何找出字符串s2中所有的变位词在字符串s1中的起始下标?假设两个字符串中只包含英文小写字母。例如:字符串s1为"cbadabacg",字符串s2为"abc",字符串s2的两个变位词"cba"和"bac"是字符串s1中的子字符串,输出他们在字符串s1中的起始下标0和5。

思路:hash表+双指针,14题的变种

public List<Integer> checkInclusion(String s1, String s2){
    /**
     * 和14题基本一摸一样,只是返回值不一样,返回的是变位词其实下标,得right - s2.length() + 1
     */
    List<Integer> result = new LinkedList<>();
    if(s1.length() < s2.length()){
        return result;
    }

    int[] hash = new int[26];
    for (int i = 0; i < s2.length(); i++) {
        // 在将s1信息存入hash中时,已经开始对s2的遍历的初始化
        hash[s2.charAt(i) - 'a']++;
        hash[s1.charAt(i) - 'a']--;
    }
    if (ifAllZero(hash)){
        result.add(0);
    }
    for (int right = s2.length(); right < s1.length(); right++){
        hash[s1.charAt(right) - 'a']--;
        hash[s1.charAt(right - s2.length())]++;
        if (ifAllZero(hash)){
            result.add(right - s2.length() + 1);
        }
    }
    return result;
}
public boolean ifAllZero(int[] array){
    for (int i : array) {
        if (i != 0){
            return false;
        }
    }
    return true;
}

面试题16:不包含重复字符的最长子字符串

题目:输入一个字符串,求改字符串中不含重复字符的最长子字符串的长度。例如,输入字符串"babcca",其最长不含重复字符的子字符串"abc",长度为3。

思路:hash表+双指针

public int lengthOfLongestSubstring(String s){
        /**
         * 用双指针left和right来确定一个子字符串,用hash表记录该子字符串中所有字符出现的次数
         * 如果hash表中所有字符出现的次数不超过1,则说明该子字符串是不重复的,于是移动right指针,尝试向子字符串中添加新的字符
         * 添加新的字符后要判断hash表中所有字符出现的次数是否依然不超过1,若不超过1则更新最长子串长度,继续右移right
         * 若hash表中所有字符出现的次数是否依然超过1,则要移动left指针,从子串中删除字符,直至hash表中所有字符出现自述都不超过1
         * 如此循坏直至right指针遍历至字符串s最右端
         * 时间复杂度O(n)
         */
        if(s.length() < 1){
            return 0;
        }
        // ascii字符集
        int[] hash = new int[256];
        int right = 0;
        int left = -1;
        int longest = 0;
        while(right < s.length()){
            hash[s.charAt(right)]++;
            while (hasGreaterThan1(hash)){
                hash[s.charAt(++left)]--;
            }
            longest = Math.max(right - left, longest);
            right++;
        }
        return longest;
    }
    public boolean hasGreaterThan1(int[] array){
        for (int i : array) {
            if (i > 1){
                return true;
            }
        }
        return false;
    }
    public int lengthOfLongestSubstringPro(String s){
        /**
         * 上面的方法每次都要遍历hash数组,需要执行256步遍历
         * 虽然时间复杂度量级上还是O(1),但实际上非常耗时
         * 故优化算法,使用一个变量countDup来记录hash表中是否有字符出现次数超过1,每次字符移进和移除都要维护countDup
         */
        if(s.length() == 0){
            return 0;
        }
        // ascii字符集
        int[] hash = new int[256];
        int right = 0;
        int left = -1;
        int longest = 0;
        int countDup = 0;
        while(right < s.length()){
            hash[s.charAt(right)]++;
            if(hash[s.charAt(right)] == 2){
                countDup++;
            }
            while (countDup > 0){
                hash[s.charAt(++left)]--;
                if(hash[s.charAt(left)] == 1){
                    countDup--;
                }
            }
            longest = Math.max(right - left, longest);
            right++;
        }
        return longest;
    }

面试题17:包含所有字符的最短字符串

题目:输入两个字符串s和t,请找出字符串s中包含到的字符串t的所有字符的最短子字符串。例如,输入的字符串s为"ADDBANCAD",字符串t为"ABC",则字符串s中包含字符‘“A”、“B”、“C"的最短子字符串是"BANC”。如果不存在符合条件的子字符串,则返回空字符串""。如果存在多个符合条件的子字符串,则返回任意一个。

思路:hash表+双指针

public String minWindow(String s, String t){
    /**
     * 还是用hash表+双指针
     * hash用于存放t的所有字符,双指针用来从s中寻找子字符串、
     * 时间复杂度为O(n),空间复杂度为O(n)
     */
    HashMap<Character,Integer> hash = new HashMap<>();
    for (char c : t.toCharArray()) {
        // 统计t中所有出现的字符及其出现的次数
        hash.put(c, hash.getOrDefault(c, 0) + 1);
    }
    int minLength = Integer.MAX_VALUE;
    // count代表还未出现在s的子字符串中的t的字符,用于简化hash表的遍历
    int count = hash.size();
    // 双指针索引
    int start = 0, end = 0;
    // 记录最短字符串出现的位置
    int minStart = 0, minEnd = 0;
    while(end < s.length() || (count == 0 && end == s.length())){
        if(count > 0){
            char endChar = s.charAt(end);
            if(hash.containsKey(endChar)){
                hash.put(endChar, hash.get(endChar) - 1);
                if(hash.get(endChar) == 0){
                    count--;
                }
            }
            // end永远指向当前判断字符的下一个未判断的字符
            end++;
        } else {
            // 当count为0时,更新最短长度的值
            if(end - start < minLength){
                minLength = end - start;
                // 其实minEnd应该是end - 1,但String.substring()是左闭右开。怎么写都可以
                minEnd = end;
                minStart = start;
            }

            char startChar = s.charAt(start);
            if(hash.containsKey(startChar)){
                hash.put(startChar, hash.get(startChar) + 1);
                if (hash.get(startChar) == 1){
                    count++;
                }
            }
            start++;
        }
    }
    return minLength == Integer.MAX_VALUE ? s.substring(minStart, minEnd) : "";
}

3.2 回文字符串

面试题18:有效的回文

题目:给定一个字符串,请判断它是不是回文。假设只需要考虑字母和数字字符,并忽略大小写。例如,"Was it a cat I saw?"是一个回文字符串,而"race a car"不是回文字符串。

思路:头尾双指针

public boolean isPalindrome(String str){
    int i = 0;
    int j = str.length() - 1;
    while(i < j){
        // 过滤非字母字符
        if(Character.isLetterOrDigit(str.charAt(i))) {
            i++;
        } else if(Character.isLetterOrDigit(str.charAt(j))) {
            j--;
        } else {
            // 忽略大小写
            char c1 = Character.toLowerCase(str.charAt(i));
            char c2 = Character.toLowerCase(str.charAt(j));
            if(c1 != c2){
                return false;
            }
            i++;
            j--;
        }
    }
    return true;
}

面试题19:最多删除一个字符得到回文

题目:给定一个字符串,请判断如果最多从字符串中删除一个字符能否得到一个回文字符串。例如,如果输入字符串"abca",由于删除字符’b’或’c’就能得到一个回文字符串,因此输出true。

思路:头尾双指针

public boolean validPalindrome(String str){
    int start = 0;
    int end = str.length() - 1;
    while(start < str.length() / 2){
        if(str.charAt(start++) != str.charAt(end--)){
            break;
        }
    }
    // 本身是否是回文 || 删除左边一个字符是否时候回文 || 删除右边一个字符是否是回文
    return start == str.length() / 2 || isPalindrome(str, start++, end) || isPalindrome(str, start, end--);
}

public boolean isPalindrome(String str, int start, int end){
    /**
     * 判断一个字符串的某个位置是否是回文字符串
     */
    while(start < end){
        if(str.charAt(start++) != str.charAt(end--)){
            return false;
        }
    }
    return true;
}

面试题20:回文子字符串的个数

题目:给定一个字符串,请问该字符串中有多少个回文连续子字符串?例如,字符串"abc"有三个回文子字符串,分别是"a"、“b"和"c”,“aaa"有6个回文子字符串,分别为"a”、“a”、“a”、“aa”、“aa"和"aaa”。

思路:中心双指针

public int countSubstring(String s){
	/**
     * 遍历字符串每个字符,判断从当前字符开始可以生成多少个回文字符串
     * 当前字符可以生成两种回文字符串:奇数回文和偶数回文
     */
    if(s == null || s.length() < 1){
        return 0;
    }
    int count = 0;
    for(int i = 0; i < s.length(); i++){
        // 奇数回文子字符串
        count += countPalindrome(s, i, i);
        // 偶数回文子字符串
        count += countPalindrome(s, i, i + 1);
    }
    return count;
}

private int countPalindrome(String s, int start, int end) {
    /**
     * 一个字符串从某个位置开始能产生的回文子字符串的个数
     */
    int count = 0;
    while(start >=0 && end < s.length() && s.charAt(start) == s.charAt(end)){
        count++;
        start--;
        end++;
    }
    return count;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值