Leetcode刷题笔记——字符串篇

518 阅读21分钟

字符

520.检测大写字母(简单)

我们定义,在以下情况时,单词的大写用法是正确的:

全部字母都是大写,比如 "USA" 。
单词中所有字母都不是大写,比如 "leetcode" 。
如果单词不只含有一个字母,只有首字母大写, 比如 "Google"

给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false 。

class Solution {
    //整理规则
    //不管首字母是大小写,其余的字母需要与第二个字母相同
    //若首字母小写,需要额外判断第二个字母为小写
    public boolean detectCapitalUse(String word) {
        int n = word.length();
        if(n == 1) return true;
        if(Character.isLowerCase(word.charAt(0)) && Character.isUpperCase(word.charAt(1))){
            return false;
        }
        //判断其他字母与第二个字母相同
        for(int i = 1; i < n; i++){
            if(Character.isLowerCase(word.charAt(1)) ^ Character.isLowerCase(word.charAt(i))){
                return false;
            }
        }
        return true;
    }
}

回文串的定义

125.验证回文串(简单)

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

输入: "A man, a plan, a canal: Panama"
输出: true
解释: "amanaplanacanalpanama" 是回文串
class Solution {
    //使用双指针法,分别指向头和尾,逐个对比
    //注意Character的各种方法
    public boolean isPalindrome(String s) {
        int len = s.length();
        int left = 0;
        int right = len - 1;
        while(left < right){
            while(left < right && (!Character.isLetterOrDigit(s.charAt(left)))){
                left++;
            }
            while(left < right && (!Character.isLetterOrDigit(s.charAt(right)))){
                right--;
            }
            if(left < right){
                if(Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))){
                    return false;
                }
                left++;
                right--;
            }   
        }
        return true;
    }
}
//也可以过滤一遍所有元素,把有效字符放在一个StringBuffer中
//使用reverse()将其逆序,字符串与逆序字符串相等时即为回文串

公共前缀

14.最长公共前缀(简单)

写一个函数来查找字符串数组中的最长公共前缀。若不存在,返回空字符串 ""

输入: strs = ["flower","flow","flight"]
输出: "fl"
class Solution {
    //找到最短字符串,然后对数组中的字符串逐一比较
    public String longestCommonPrefix(String[] strs) {
        int len = strs.length;
        int minLen = strs[0].length();
        String minStr = strs[0];
        for(int i = 1; i < len; i++){           //找到最短的字符串
            if(strs[i].length() < minLen){
                minLen = strs[i].length();
                minStr = strs[i];
            }
        }
        for(int i = 0; i < minLen; i++){        //遍历最短字符串的每一个字符
            char ch = minStr.charAt(i);        
            for(int j = 0; j < len; j++){       //遍历数组中所有字符串
                if(strs[j].charAt(i) != ch){
                    return minStr.substring(0, i);
                }
            }
        }
        return minStr;
    }
}
class Solution {
    //逐个字符串比较,更新最长公共前缀
    public String longestCommonPrefix(String[] strs) {
        int len = strs.length;
        if(len == 0) return "";
        if(len == 1) return strs[0];
        String prefix = strs[0];
        for(int i = 1; i < len; i++){
            prefix = longestCommonPrefix(prefix, strs[i]);      //每次比较两个字符串时更新prefix
            if(prefix == ""){
                break;
            }
        }
        return prefix;
    }

    public String longestCommonPrefix(String s1, String s2){
        int n = Math.min(s1.length(), s2.length());
        int index = 0;
        while(index < n && s1.charAt(index) == s2.charAt(index)){
            index++;
        }
        return s1.substring(0, index);
    }
}

单词

434.字符串中的单词数(简单)

统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。

class Solution {
    //当前字符不是字母,但前一个字符是字母时,算作一个单词(第一个字母另外判断)
    //但是题目里面的意思是,前一个字符不是空格,后一个字符是空格就可以算作一个单词
    public int countSegments(String s) {
        int count = 0;
        s = s.trim();           //去除字符串前后空格
        for(int i = 0; i < s.length(); i++){
            if(i == 0 || (s.charAt(i-1) != ' ' && s.charAt(i) == ' ')){
                count++;
            }
        }
        return count;
    }
}

58.最后一个单词的长度(简单)

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。s仅由字母和空格构成。

class Solution {
    //从后往前遍历,找到第一个字母与非字母的分界处
    public int lengthOfLastWord(String s) {
        s = s.trim();
        int len = s.length();
        int count = 0;  //记录单词的长度
        for(int i = len - 1; i >= 0; i--){
            if(!Character.isLetter(s.charAt(i))){       //当遇到第一个非字母时
                count = len - 1 - i;
                break;
            }else if(i == 0){           //遍历到首个字符了,还是字母
                count = len;
            }
        }
        return count;
    }
}

字符串的反转

344.反转字符串(简单)

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。必须原地反转。

class Solution {
    //使用双指针法,交换字符
    public void reverseString(char[] s) {
        int n = s.length;
        int i = 0;
        int j = n-1;
        while(i < j){
            char temp = s[i];
            s[i] = s[j];
            s[j] = temp;
            i++;
            j--;
        }
    }
}

541.反转字符串2(简单)

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
输入: s = "abcdefg", k = 2
输出: "bacdfeg"
class Solution {
    //整理一下规则,对于字符串s分为k段,第一段反转,第二段不变...直到最后不够k个且属于反转段,全部反转
    public String reverseStr(String s, int k) {
        int len = s.length();
        int count = len / k;                //把字符串按照k分段
        char[] ch = s.toCharArray();        //字符串转换为字符数组,方便操作
        for(int i = 0; i < count; i++){     //对每段进行处理
            if(i%2 == 1){                   //奇数号的段不处理
                continue;
            }else{
                reverse(ch, i*k, (i+1)*k-1);//偶数号的段反转
            }
        }
        if((len % k) > 0 && (count % 2) == 0){ //当剩余字符少于k个时且属于反转段时,全部反转
            reverse(ch, count * k, len-1);
        }
        return String.valueOf(ch);
    }

    public void reverse(char[] ch, int begin, int end){
        while(begin < end){
            char temp = ch[begin];
            ch[begin] = ch[end];
            ch[end] = temp;
            begin++;
            end--;
        }
    }
}
//简单写法
//每个反转下标从2k的倍数开始,长度为k的子串。若该子串长度不足k,则反转整个子串。
class Solution {
    public String reverseStr(String s, int k) {
        int n = s.length();
        char[] arr = s.toCharArray();
        for (int i = 0; i < n; i += 2 * k) {
            reverse(arr, i, Math.min(i + k, n) - 1);
        }
        return new String(arr);
    }

    public void reverse(char[] arr, int left, int right) {
        while (left < right) {
            char temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            left++;
            right--;
        }
    }
}

557.反转字符串中的单词3(简单)

给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。开头结尾不含空格,所有单词都用一个空格隔开。

class Solution {
    //使用双指针法记录单词的开始和结束,逐个单词进行反转
    public String reverseWords(String s) {
        int len = s.length();
        int begin = 0;
        int end = 0;
        char[] ch = s.toCharArray();
        while(end < len){
            if(ch[end] == ' '){
                reverse(ch, begin, end-1);
                begin = end+1;
            }
            if(end == len-1){
                reverse(ch, begin, end);
            }
            end++;
        }
        return new String(ch);
    }

    public void reverse(char[] ch, int begin, int end){
        while(begin < end){
            char temp = ch[begin];
            ch[begin] = ch[end];
            ch[end] = temp;
            begin++;
            end--;
        }
    }
}

151.颠倒字符串中的单词(中等)

给你一个字符串 s ,颠倒字符串中单词的顺序。单词是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的单词分隔开。

返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

输入: s = "the sky is blue"
输出: "blue is sky the"
class Solution {
    //从后往前遍历,记录单词首字母和长度,然后放到StringBuffer中
    public String reverseWords(String s) {
        s = s.trim();
        int len = s.length();
        int begin = len-1;      //记录单词首字母
        int count = 0;          //记录单词长度
        StringBuffer sb = new StringBuffer();
        while(begin >= 0){
            if(s.charAt(begin) == ' ' && s.charAt(begin+1) != ' '){//当前字符是空格且上一个字符不是空格
                sb.append(s.substring(begin+1, begin+count+1) + " ");
                count = 0;
            }
            if(begin == 0){     //当到达最后一个字符时
                sb.append(s.substring(begin, begin+count+1));
            }
            if(s.charAt(begin) != ' '){         //当前位置不是空格时才累加单词长度
                count++;
            }
            begin--; 
        }
        return sb.toString();
    }
}
class Solution {
    //API解法,先trim,再split,然后reverse,最后join
    public String reverseWords(String s) {
        s = s.trim();
        List<String> list = Arrays.asList(s.split("\\s+"));
        Collections.reverse(list);
        return String.join(" ", list);
    }
}

字符的统计

387.字符串中第一个唯一字符(简单)★

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

class Solution {
    //使用HashMap存储每个字符出现的次数,再遍历一次字符串,找到第一个只出现一次的字符
    public int firstUniqChar(String s) {
        int len = s.length();
        Map<Character, Integer> map = new HashMap<>();
        for(int i = 0; i < len; i++){
            char c = s.charAt(i);
            map.put(c, map.getOrDefault(c, 0)+1);
        }
        for(int i = 0; i < len; i++){
            if(map.get(s.charAt(i)) == 1){
                return i;
            }
        }
        return -1;
    }
}
//由于本题只需要知道出现一次的字符,故可以把重复出现的字符的hashmap对应的值改为-1
//只出现一次的值设为索引,这样后面只需要再遍历一次hashmap即可。

//由于字符都为字母,故可以使用一个26长度的数组作为存储字母出现频次的容器

389.找不同(简单)★

给定两个字符串 s 和 t ,它们只包含小写字母。 字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。 请找出在 t 中被添加的字母。

class Solution {
    //由于只有小写字母,故使用一个26长度的数组作为map,存储元素与出现次数
    public char findTheDifference(String s, String t) {
        int[] arr = new int[26];
        for(int i = 0; i < s.length(); i++){
            arr[s.charAt(i) - 'a'] += 1;
        }
        for(int i = 0; i < t.length(); i++){
            int j = t.charAt(i) - 'a';
            arr[j] -= 1;
            if(arr[j] == -1){
                return (char)(97+j);        //97为a的ASCII码
            }
        }
        return ' ';
    }
}
class Solution {
    //s数组所有数之和减去t数组所有数之和即为所求
    //由于t与s的长度只差1,故可以同时遍历t与s
    public char findTheDifference(String s, String t) {
        int res = 0;
        for(int i = 0; i < s.length(); i++){
            res += t.charAt(i) - s.charAt(i);
        }
        res += (int)t.charAt(t.length() - 1);
        return (char)res;
    }
}
class Solution {
    //相当于求s+t这个字符串中出现次数为奇数次的数,使用异或
    public char findTheDifference(String s, String t) {
        int res = 0;
        for(int i = 0; i < s.length(); i++){
            res ^= s.charAt(i);
            res ^= t.charAt(i);
        }
        res ^= t.charAt(t.length() - 1);
        return (char)res;
    }
}

383.赎金信(简单)

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。

class Solution {
    //使用数组记录magazine中的字符出现的次数,再遍历ransomNote,减掉对应的字符出现次数
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] arr = new int[26];
        for(int i = 0; i < magazine.length(); i++){
            arr[magazine.charAt(i) - 'a'] += 1;
        }
        for(int i = 0; i < ransomNote.length(); i++){
            int j = ransomNote.charAt(i) - 'a';
            arr[j] -= 1;
            if(arr[j] < 0){
                return false;
            }
        }
        return true;
    }
}

242.有效的字母异位词(简单)

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

class Solution {
    //本题不能用异或解决,因为可能出现"aa"和"bb"这样的情况
    //可以先排序,排序后两个字符一致即可
    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()) return false;
        char[] sch = s.toCharArray();
        char[] tch = t.toCharArray();
        Arrays.sort(sch);
        Arrays.sort(tch);
        return Arrays.equals(sch, tch);
    }
}

49.字母异位词分组(中等)★

给你一个字符串数组,请你将字母异位词组合在一起。可以按任意顺序返回结果列表。

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
class Solution {
    //使用Hashmap存储每组的标志和每个组,使用排序来判断是否是字母异位词
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();
        for(String s : strs){
            char[] ch = s.toCharArray();
            Arrays.sort(ch);
            String key = new String(ch);        //获取标志为hashmap的key
            List<String> list = map.getOrDefault(key, new ArrayList<String>()); 
            list.add(s);        //把当前字符串添加到对应list中
            map.put(key, list); //更新key对应的list
        }
        return new ArrayList<List<String>>(map.values()); //获取map中所有的list并返回
    }
}

451.根据字符出现频率排序(中等)

给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。注意大小写为不同字符,字符中有字母和数字

输入: s = "tree"
输出: "eert"
class Solution {
    //先映射到hashmap,存储每个字符出现的次数,然后把key放到列表中,对列表进行排序
    //注意这道题不直接对map进行排序,而是转化为对list排序的思路
    public String frequencySort(String s) {
        StringBuffer sb = new StringBuffer();
        Map<Character, Integer> map = new HashMap<>();
        for(int i = 0; i < s.length(); i++){
            map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
        }
        List<Character> list = new ArrayList<>(map.keySet());   //list只存储字符
        Collections.sort(list, (a,b) -> map.get(b) - map.get(a));   //对list按存储在map中的出现次数排序
        int len = list.size();
        for(int i = 0; i < len; i++){
            char key = list.get(i);
            for(int j = map.get(key); j > 0; j--){
                sb.append(key);
            }
        }
        return sb.toString();
    }
}

423.从英文中重建数字(中等)

给你一个字符串 s ,其中包含字母顺序打乱的用英文单词表示的若干数字(0-9)。按 升序 返回原始的数字。

输入: s = "owoztneoer"
输出: "012"
class Solution {
    //本题可以计算所有字母在哪些数字单词中出现,从字母出现的次数中推导出出现了什么数字(详细推理见leetcode题解)
    public String originalDigits(String s) {
        Map<Character, Integer> c = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); ++i) {
            char ch = s.charAt(i);
            c.put(ch, c.getOrDefault(ch, 0) + 1);
        }

        int[] cnt = new int[10];
        cnt[0] = c.getOrDefault('z', 0);
        cnt[2] = c.getOrDefault('w', 0);
        cnt[4] = c.getOrDefault('u', 0);
        cnt[6] = c.getOrDefault('x', 0);
        cnt[8] = c.getOrDefault('g', 0);

        cnt[3] = c.getOrDefault('h', 0) - cnt[8];
        cnt[5] = c.getOrDefault('f', 0) - cnt[4];
        cnt[7] = c.getOrDefault('s', 0) - cnt[6];

        cnt[1] = c.getOrDefault('o', 0) - cnt[0] - cnt[2] - cnt[4];

        cnt[9] = c.getOrDefault('i', 0) - cnt[5] - cnt[6] - cnt[8];

        StringBuffer ans = new StringBuffer();
        for (int i = 0; i < 10; ++i) {
            for (int j = 0; j < cnt[i]; ++j) {
                ans.append((char) (i + '0'));
            }
        }
        return ans.toString();
    }
}

657.机器人能否返回原点(简单)

在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。

移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。移动方向与面朝的方向无关。

class Solution {
    public boolean judgeCircle(String moves) {
        int Vcount = 0;
        int Hcount = 0;
        for(int i = 0; i < moves.length(); i++){
            char ch = moves.charAt(i);
            switch(ch){
                case 'U':
                    Vcount++;
                    break;
                case 'D':
                    Vcount--;
                    break;
                case 'L':
                    Hcount++;
                    break;
                case 'R':
                    Hcount--;
                    break;
                default:
                    break;
            }
        }
        if(Vcount == 0 && Hcount == 0){
            return true;
        }
        return false;
    }
}

551.学生出勤记录1(简单)

给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

'A':Absent,缺勤
'L':Late,迟到
'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

按 总出勤 计,学生缺勤('A')严格 少于两天。
学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。

如果学生可以获得出勤奖励,返回 true ;否则,返回 false

class Solution {
    public boolean checkRecord(String s) {
        int Acount = 0; //记录A出现的次数
        int Lcount = 0; //记录连续的L的出现次数
        for(int i = 0; i < s.length(); i++){
            char ch = s.charAt(i);
            if(ch == 'L'){
                Lcount++;
                if(Lcount >= 3){
                    return false;
                }
            }else{
                Lcount = 0;
                if(ch == 'A'){
                    Acount++;
                    if(Acount >= 2){
                        return false;
                    }
                }
            }  
        }
        return true;
    }
}

696.计数二进制字符串(简单)★

给定一个字符串 s,统计并返回具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是成组连续的。 重复出现(不同位置)的子串也要统计它们出现的次数。

输入:s = "00110011"
输出:6
解释:6 个子串满足具有相同数量的连续 10"0011""01""1100""10""0011""01" 。
注意,一些重复出现的子串(不同位置)要统计它们出现的次数。
另外,"00110011" 不是有效的子串,因为所有的 0(还有 1 )没有组合在一起。
class Solution {
    //记录每段连续出现的字符的长度,相邻的两段出现的字符不同,这两段贡献的子串个数就是他们中的较小值
    //可以用list来存储每段,若对“0011000”,list=[2,2,3],故0011这段的贡献为min(2,2)=2,11000这段的贡献为min(2,3)=2
    //但可知当前段贡献的子串数只与自己和上一段有关,故可以只记录上一段的长度
    public int countBinarySubstrings(String s) {
        int len = s.length(), pre = 0, ans = 0, i = 0;//pre记录上一段长度
        while(i < len){
            char ch = s.charAt(i);    //当前段的字符
            int count = 0;  //当前段的长度
            while(i < len && s.charAt(i) == ch){
                i++;
                count++;
            }
            ans += Math.min(pre, count);
            pre = count;
        }
        return ans;
    }
}

467.环绕字符串中唯一的子字符串(中等)★

找出p中非空连续子串数量。(包含单个字符)

输入: p = "zab"
输出: 6
解释: 在字符串 s 中有 p 的六个子串 ("z", "a", "b", "za", "ab", "zab") 。
class Solution {
    //题目本质就是要求最长连续子串,使用动态规划
    //设dp[a]表示以a结尾的最长连续串的长度,则题目所求为dp[a]+...+dp[z]
    public int findSubstringInWraproundString(String p) {
        int[] dp = new int[26];
        int len = 0;
        for(int i = 0; i < p.length(); i++){
            if(i > 0 && (p.charAt(i) - p.charAt(i-1) + 26) % 26 == 1){  //与前一个字符连续时
                len++;
            }else{
                len = 1;    //非连续字符时
            }
            dp[p.charAt(i) - 'a'] = Math.max(dp[p.charAt(i) - 'a'], len);
        } 
        return Arrays.stream(dp).sum();
    }
}

535.TinyURL的加密和解密(中等)

TinyURL 是一种 URL 简化服务, 比如:当你输入一个 URL leetcode.com/problems/de… 时,它将返回一个简化的URL tinyurl.com/id 。请你设计一个类来加密与解密 TinyURL 。

public class Codec {
    //使用一个HashMap来模拟dataBase存储id与longurl的映射
    //shorturl由固定字段和id组成
    //关键在于id要如何生成,1.逐个计数累加,2.自定义实现hashcode,3.随机生成
    //此处使用逐个计数累加
    Map<Integer, String> dataBase = new HashMap<Integer, String>();
    int id = 0;
    // Encodes a URL to a shortened URL.
    public String encode(String longUrl) {
        id++;
        dataBase.put(id, longUrl);
        return "http://tinyurl.com/" + id;
    }

    // Decodes a shortened URL to its original URL.
    public String decode(String shortUrl) {
        int index = shortUrl.lastIndexOf('/') + 1;
        int key = Integer.parseInt(shortUrl.substring(index));
        return dataBase.get(key);
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.decode(codec.encode(url));

数字与字符串间转换

299.猜数字游戏(中等)

写一个数字secret,猜测数字guess,对于guess中的每位数,若位置和数字都和secret一样,则算一个A,若数字包含在secret中但是位置不对应,则算一个B。

输入: secret = "1807", guess = "7810"
输出: "1A3B"
class Solution {
    //遍历字符串,先计算出公牛个数,然后用两个数组记录两个字符串的非公牛数出现的次数
    //由于出现次数多余的数无法匹配,故奶牛数为两字符串中对应数字出现次数少者
    public String getHint(String secret, String guess) {
        int[] mapS = new int[10];
        int[] mapG = new int[10];
        int countA = 0; //公牛数
        int countB = 0; //奶牛数
        int len = secret.length();
        for(int i = 0; i < len; i++){
            if(secret.charAt(i) == guess.charAt(i)){
                countA++;
            }else{
                mapS[secret.charAt(i) - '0'] += 1;
                mapG[guess.charAt(i) - '0'] += 1;
            }
        }
        for(int i = 0; i < 10; i++){
            countB += Math.min(mapS[i], mapG[i]);
        }
        return countA + "A" + countB + "B";
    }
}

412.FizzBuzz(简单)

给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:

answer[i] == "FizzBuzz" 如果 i 同时是 35 的倍数。
answer[i] == "Fizz" 如果 i3 的倍数。
answer[i] == "Buzz" 如果 i5 的倍数。
answer[i] == i (以字符串形式)如果上述条件全不满足。
class Solution {
    public List<String> fizzBuzz(int n) {
        List<String> res = new ArrayList<>();
        for(int i = 1; i <= n; i++){
            if(i % 15 == 0){
                res.add("FizzBuzz");
            }else if(i % 3 == 0){
                res.add("Fizz");
            }else if(i % 5 == 0){
                res.add("Buzz");
            }else{
                res.add(String.valueOf(i));
            }
        }
        return res;
    }
}

506.相对名次(简单)

给你一个长度为 n 的整数数组 score ,其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同 。

运动员将根据得分 决定名次 ,其中名次第 1 的运动员得分最高,名次第 2 的运动员得分第 2 高,依此类推。运动员的名次决定了他们的获奖情况:

名次第 1 的运动员获金牌 "Gold Medal" 。
名次第 2 的运动员获银牌 "Silver Medal" 。
名次第 3 的运动员获铜牌 "Bronze Medal" 。
从名次第 4 到第 n 的运动员,只能获得他们的名次编号(即,名次第 x 的运动员获得编号 "x")。

使用长度为n的数组answer返回获奖,其中 answer[i] 是第 i 位运动员的获奖情况。

输入: score = [5,4,3,2,1]
输出: ["Gold Medal","Silver Medal","Bronze Medal","4","5"]
class Solution {
    //使用一个数组记录分数和名次
    public String[] findRelativeRanks(int[] score) {
        int len = score.length;
        int[] sort = new int[len];
        for(int i = 0; i < len; i++){
            for(int j = 0; j < len; j++){
                if(score[i] <= score[j]){   //遇到一个得分比自己高(或等于)的人,名次就+1
                    sort[i] += 1;
                }
            }
        }
        String[] res = new String[len];
        for(int i = 0; i < len; i++){
            int index = sort[i];
            if(index == 1){
                res[i] = "Gold Medal";
            }else if(index == 2){
                res[i] = "Silver Medal";
            }else if(index == 3){
                res[i] = "Bronze Medal";
            }else{
                res[i] = String.valueOf(index);
            }
        }
        return res;
    }
}
class Solution {
    //问题的本质是在对得分数组进行排序之后,要保存排序之前的数据的下标
    //使用一个二维数组,记录得分和下标
    public String[] findRelativeRanks(int[] score) {
        int len = score.length;
        int[][] arr = new int[len][2];
        String[] str = {"Gold Medal","Silver Medal","Bronze Medal"};
        for(int i = 0; i < len; i++){
            arr[i][0] = score[i];   //记录得分
            arr[i][1] = i;          //记录原数据下标
        }
        Arrays.sort(arr, (a, b) -> b[0] - a[0]);
        String[] res = new String[len];
        for(int i = 0; i < len; i++){
            if(i >= 3){
                res[arr[i][1]] = String.valueOf(i+1);
            }else{
                res[arr[i][1]] = str[i];
            }
        }
        return res;
    }
}

539.最小时间差(中等)★

给定一个 24 小时制(小时:分钟 "HH:MM" )的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。

输入: timePoints = ["23:59","00:00"]
输出: 1
class Solution {
    //将列表进行排序,最小时间差必然出现在相邻的两个元素或首尾两个元素
    public int findMinDifference(List<String> timePoints) {
        int len = timePoints.size();
        if(len > 1440) return 0;    //总共有24*60=1440个时间点,若长度大于1440,必然有两个相同的时间点
        Collections.sort(timePoints);
        int ans = Integer.MAX_VALUE;
        int con = getMin(timePoints.get(0));    //getMin将单位都转化为分钟
        int pre = con;
        for(int i = 1; i < len; i++){
            con = getMin(timePoints.get(i));
            ans = Math.min(ans, con - pre);
            pre = con;
        }
        return Math.min(ans, getMin(timePoints.get(0)) + 1440 - pre);
    }

    public int getMin(String time){
        return ((time.charAt(0)-'0') * 10 + (time.charAt(1)-'0')) * 60 +
         ((time.charAt(3)-'0') * 10 + (time.charAt(4)-'0'));
    }
}

553.最优解法(中等)

给定一组正整数,相邻的整数之间将会进行浮点除法操作。例如,[2,3,4] -> 2/3/4

但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,才能得到最大的结果,并且返回相应的字符串格式的表达式。你的表达式不应该含有冗余的括号。

输入: [1000,100,10,2]
输出: "1000/(100/10/2)"
class Solution {
    //要使结果最大应该使分子最大,分母最小,由于是除法,故无论如何加括号,最大的分子一定是首个数
    //分母最小一定是从第二个一直除到最后一个数
    //也就是说,结果一定是a/(b/c/d)
    public String optimalDivision(int[] nums) {
        int len = nums.length;
        if(len == 1) return String.valueOf(nums[0]);
        if(len == 2) return String.valueOf(nums[0]) + "/" + String.valueOf(nums[1]);
        StringBuffer sb = new StringBuffer();
        sb.append(String.valueOf(nums[0]) + "/(" + String.valueOf(nums[1]));
        for(int i = 2; i < len; i++){
            sb.append("/" + String.valueOf(nums[i]));
        }
        sb.append(")");
        return sb.toString();
    }
}

537.复数乘法(中等)

复数 可以用字符串表示,遵循 "实部+虚部i" 的形式,并满足下述条件:

实部 是一个整数,取值范围是 [-100, 100]
虚部 也是一个整数,取值范围是 [-100, 100]
i² == -1

给你两个字符串表示的复数 num1 和 num2 ,请你遵循复数表示形式,返回表示它们乘积的字符串。

class Solution {
    public String complexNumberMultiply(String num1, String num2) {
        String[] spnum1 = num1.split("\\+|i");      //使用正则表达式
        String[] spnum2 = num2.split("\\+|i");

        int num1Real = Integer.parseInt(spnum1[0]);  //num1的实部
        int num1Virtual = Integer.parseInt(spnum1[1]);//num1的虚部
        int num2Real = Integer.parseInt(spnum2[0]);  //num2的实部
        int num2Virtual = Integer.parseInt(spnum2[1]);//num2的虚部

        return (num1Real * num2Real - num1Virtual * num2Virtual) + "+" 
                + (num1Real * num2Virtual + num2Real * num1Virtual) + "i";
    }
}

592.分数加减运算(中等)★

给定一个表示分数加减运算的字符串 expression ,你需要返回一个字符串形式的计算结果。

这个结果应该是不可约分的分数,即最简分数。 如果最终结果是一个整数,例如 2,你需要将它转换成分数形式,其分母为 1。所以在上述例子中, 2 应该被转换为 2/1。

输入: expression = "-1/2+1/2"
输出: "0/1"
class Solution {
    //模拟,遍历字符串得到分子与分母,两个分数相加即(top1 * bottom2 + top2 * bottom1)/(bottom1 * bottom2)
    //最后再找出分子和分母的最大公约数,进行化简
    public String fractionAddition(String expression) {
        int len = expression.length();
        long top = 0;    //分子初始值为0,保存的是上一次计算的结果
        long bottom = 1; //分母初始值为1
        int index = 0;
        while(index < len){
            long top1 = 0;  //存储的是当前扫描到的分子
            long bottom1 = 0;   //注意这里的分母跟bottom不一样,这里需要遍历每一位得到一个数,故初始为0
            int sign = 1;   //记录符号
            if(expression.charAt(index) == '-' || expression.charAt(index) == '+'){
                sign = expression.charAt(index) == '-' ? -1 : 1;
                index++;
            }
            //读取分子
            while(index < len && Character.isDigit(expression.charAt(index))){
                top1 = top1 * 10 + (expression.charAt(index) - '0');
                index++;
            }
            top1 *= sign;
            index++;

            //读取分母
            while(index < len && Character.isDigit(expression.charAt(index))){
                bottom1 = bottom1 * 10 + (expression.charAt(index) - '0');
                index++;
            }
            top = top * bottom1 + top1 * bottom;
            bottom *= bottom1;
        }
        if(top == 0) return "0/1";
        long g = gcd(Math.abs(top), bottom);
        return top/g + "/" + bottom/g;
    }

    public long gcd(long a, long b){
        long temp = a % b;
        while(temp != 0){
            a = b;
            b = temp;
            temp = a%b;
        }
        return b;
    }
}

640.求解方程(中等)★

求解一个给定的方程,将x以字符串 "x=#value" 的形式返回。该方程仅包含 '+' , '-' 操作,变量 x 和其对应系数。 如果方程没有解,请返回 "No solution" 。如果方程有无限解,则返回 “Infinite solutions” 。

题目保证,如果方程中只有一个解,则 'x' 的值是一个整数。

输入: equation = "x+5-3+x=6+x-2"
输出: "x=2"
class Solution {
    //将全部项移到左边,故等号右边式子的系数都乘上-1,遍历字符串,记录x的系数和整数值
    //无解的情况和无限解的情况都是x的系数为0,数值为0时无限解,数值不为0时无解
    public String solveEquation(String equation) {
        int len = equation.length();
        int flag = 1;           //等号左边的符号为1,当遇到=号时变为-1
        int factor = 0;         //x的系数
        int val = 0;            //常数项数值
        int index = 0;
        while(index < len){
            if(equation.charAt(index) == '='){
                flag = -1;
                index++;
                continue;
            }
            int sign = flag;           //当前项的符号
            int num = 0;            //当前项数值
            boolean vaild = false;  //当前项的系数是否有效,因为x前面可能没有数值
            if(equation.charAt(index) == '-' || equation.charAt(index) == '+'){
                sign = equation.charAt(index) == '-' ? -sign : sign;
                index++;
            }
            while(index < len && Character.isDigit(equation.charAt(index))){
                num = num * 10 + (equation.charAt(index) - '0');
                vaild = true;
                index++;
            }
            if(index < len && equation.charAt(index) == 'x'){
                factor += vaild ? sign * num : sign;
                index++;        //当前字符是x,需要继续指向下一个字符
            }else{
                val += sign*num;    //当前字符是+-号,或者到达字符串末尾,不需要指向下一个字符
            }
        }
        if(factor == 0){
            return val == 0 ? "Infinite solutions" : "No solution";
        }else{
            return "x=" + (-val/factor);
        }
    }
}

38.外观数组(中等)

给定一个正整数 n ,输出外观数列的第 n 项。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。 你可以将其视作是由递归公式定义的数字字符串序列:

countAndSay(1) = "1"
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。
前五项如下:
1.     1
2.     11
3.     21
4.     1211
5.     111221
第一项是数字 1 
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
class Solution {
    //递归解法
    public String countAndSay(int n) {
        if(n == 1) return "1";
        String str = countAndSay(n-1);
        int len = str.length();
        StringBuffer sb = new StringBuffer();
        int index = 0;
        while(index < len){
            int count = 0;
            char pre = str.charAt(index);
            while(index < len && str.charAt(index) == pre){
                count++;
                index++;
            }
            sb.append(String.valueOf(count) + pre);
        }
        return sb.toString();
    }
}
class Solution {
    //遍历解法
    public String countAndSay(int n) {
        String str = "1";
        for(int i = 2; i <= n; i++){
            int len = str.length();
            StringBuffer sb = new StringBuffer();
            int start = 0;
            int con = 0;
            while(con < len){
                while(con < len && str.charAt(start) == str.charAt(con)){
                    con++;
                }
                sb.append(Integer.toString(con-start) + str.charAt(start));
                start = con;
            }
            str = sb.toString();
        }
        return str;
    }
}

443.压缩字符串(中等)★

输入:chars = ["a","a","b","b","c","c","c"]
输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"]
注意长度为10时应该设为"1","0"
对字符数组进行操作,并返回新数组的长度,且原地计算。
class Solution {
    //使用一个读指针和一个写指针,对字符数组边遍历边修改
    public int compress(char[] chars) {
        int len = chars.length;
        if(len == 1) return 1;
        int write = 0;
        int start = 0;  //记录连续字符的开始下标
        for(int read = 0; read < len; read++){
            if(read == len-1 || chars[read] != chars[read+1]){  //最后一个字符或非连续字符时才需要操作
                chars[write++] = chars[read];
                int num = read - start + 1;
                start = read + 1;
                if(num > 1){        //将num转化为字符串依次写入数组
                    String str = String.valueOf(num);
                    for(int i = 0; i < str.length(); i++){
                        chars[write++] = str.charAt(i);
                    }
                }
            }
        }
        return write;
    }
}

8.字符串转化为整数(中等)★

题目的意思是要把第一个数字转化为int类型输出,但是有几个注意点:

1.去除前后空格
2.去除空格后第一个字符必须是数字或正负号,否则返回0
3.只返回第一个整数,返回值为32位整数范围,即[−2^31,  2^31 − 1],越界时截断
普通解法,代码臃肿,边界条件多
class Solution {
    public int myAtoi(String s) {
        s = s.trim();
        int len = s.length();
        if(len == 0) return 0;
        if(!Character.isDigit(s.charAt(0)) && s.charAt(0) != '-' && s.charAt(0) != '+'){
            return 0;
        }
        int sign = 1;
        if(s.charAt(0) == '-'){
            sign = -1;
        }
        int index = Character.isDigit(s.charAt(0)) ? 0 : 1;
        int num = 0;
        while(index < len && Character.isDigit(s.charAt(index))){
            if(sign == 1 && (num > Integer.MAX_VALUE / 10 || (num == Integer.MAX_VALUE / 10 && 
            (s.charAt(index) - '0') > 7))){
                return Integer.MAX_VALUE;
            }
            if(sign == -1 && (num > Integer.MAX_VALUE / 10 || (num == Integer.MAX_VALUE / 10 &&
            (s.charAt(index) - '0') > 8))){
                return Integer.MIN_VALUE;
            }
            num = num * 10 + s.charAt(index) - '0';
            index++;
        }
        return sign * num;
    }
}
有限状态机解法★
class Solution {
    public int myAtoi(String str) {
        Automaton automaton = new Automaton();
        int length = str.length();
        for (int i = 0; i < length; ++i) {
            automaton.get(str.charAt(i));
        }
        return (int) (automaton.sign * automaton.ans);
    }
}

class Automaton {
    public int sign = 1;
    public long ans = 0;
    private String state = "start";
    private Map<String, String[]> table = new HashMap<String, String[]>() {{
        put("start", new String[]{"start", "signed", "in_number", "end"});
        put("signed", new String[]{"end", "end", "in_number", "end"});
        put("in_number", new String[]{"end", "end", "in_number", "end"});
        put("end", new String[]{"end", "end", "end", "end"});
    }};

    public void get(char c) {
        state = table.get(state)[get_col(c)];
        if ("in_number".equals(state)) {
            ans = ans * 10 + c - '0';
            ans = sign == 1 ? Math.min(ans, (long) Integer.MAX_VALUE) : Math.min(ans, -(long) Integer.MIN_VALUE);
        } else if ("signed".equals(state)) {
            sign = c == '+' ? 1 : -1;
        }
    }

    private int get_col(char c) {
        if (c == ' ') {
            return 0;
        }
        if (c == '+' || c == '-') {
            return 1;
        }
        if (Character.isDigit(c)) {
            return 2;
        }
        return 3;
    }
}

tip:charAt()会检查小标的合法性,故一般将字符串转换为字符数组进行操作,即str.toCharArray()

13.罗马数字转整数(简单)

罗马数字包含七种字符: I,V,X,L,C,D和M。分别表示1,5,10,50,100,500,1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 49。
X 可以放在 L (50) 和 C (100) 的左边,来表示 4090。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400900

给定一个罗马数字,将其转换成整数。

class Solution {
    //逐个字符遍历,遇到特殊情况时(前面数字比后面数字小)转化为前面数字取负号
    public int romanToInt(String s) {
        Map<Character, Integer> map = new HashMap<>(){{
            put('I', 1);
            put('V', 5);
            put('X', 10);
            put('L', 50);
            put('C', 100);
            put('D', 500);
            put('M', 1000);
        }};
        int len = s.length();
        if(len == 1) return map.get(s.charAt(0));
        int sum = 0;
        for(int i = 1; i < len; i++){
            int pre = map.get(s.charAt(i-1));
            int con = map.get(s.charAt(i));
            if(pre < con){
                sum -= pre;
            }else{
                sum += pre;
            }
        }
        sum += map.get(s.charAt(len-1));
        return sum;
    }
}

12.整数转罗马数字(中等)

与上题相反

class Solution {
    //模拟,把所有可能出现的罗马数字符号对应其数字
    public String intToRoman(int num) {
        int[] arr = {1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000};
        String[] str = {"I", "IV", "V", "IX", "X", "XL", "L", "XC", "C", "CD", "D", "CM", "M"};
        StringBuffer sb = new StringBuffer();
        for(int i = arr.length-1; i >= 0; i--){
            while(num >= arr[i]){
                sb.append(str[i]);
                num -= arr[i];
            }
        }
        return sb.toString();
    }
}

273.整数转换为英文表示(困难)

将非负整数 num 转换为其对应的英文表示。

输入:num = 1234567
输出:"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
class Solution {
    //将每三位做一次划分,读取出三位数然后加上倍数即可,如12345的12,为12+倍数thousand

    //声明预处理map
    public static Map<Integer, String> map = new HashMap<>(){{
            put(1, "One");put(2, "Two");put(3, "Three");put(4, "Four");
            put(5, "Five");put(6, "Six");put(7, "Seven");put(8, "Eight");
            put(9, "Nine");put(10, "Ten");put(11, "Eleven");put(12, "Twelve");
            put(13, "Thirteen");put(14, "Fourteen");put(15, "Fifteen");put(16, "Sixteen");
            put(17, "Seventeen");put(18, "Eighteen");put(19, "Nineteen");put(20, "Twenty");
            put(30, "Thirty");put(40, "Forty");put(50, "Fifty");put(60, "Sixty");
            put(70, "Seventy");put(80, "Eighty");put(90, "Ninety");
    }};

    public String numberToWords(int num) {
        if(num == 0) return "Zero";
        StringBuffer sb = new StringBuffer();
        if(num / 1000000000 != 0){
            sb.append(map.get(num/1000000000) + " Billion ");
            num %= 1000000000;
        }
        if(num / 1000000 != 0){
            sb.append(readThreeNum(num/1000000).trim() + " Million ");
            num %= 1000000;
        }
        if(num / 1000 != 0){
            sb.append(readThreeNum(num/1000).trim() + " Thousand ");
            num %= 1000;
        }
        if(num != 0){
            sb.append(readThreeNum(num));
        }
        return sb.toString().trim();
    }

    //实现读取三位数
    public String readThreeNum(int num){
        StringBuffer sb = new StringBuffer();
        if(num / 100 != 0){
            sb.append(map.get(num/100) + " Hundred ");
            num %= 100;
        }
        if(num / 10 != 0){
            if(num <= 20){
                sb.append(map.get(num));
                return sb.toString();
            }else{
                sb.append(map.get(num/10*10) + " ");
            }  
        }
        if(num%10 != 0){
            sb.append(map.get(num%10));
        }
        return sb.toString();
    }
}
更规范简洁的代码
class Solution {
    String[] singles = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
    String[] teens = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    String[] tens = {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    String[] thousands = {"", "Thousand", "Million", "Billion"};

    public String numberToWords(int num) {
        if (num == 0) {
            return "Zero";
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 3, unit = 1000000000; i >= 0; i--, unit /= 1000) {
            int curNum = num / unit;
            if (curNum != 0) {
                num -= curNum * unit;
                StringBuffer curr = new StringBuffer();
                recursion(curr, curNum);
                curr.append(thousands[i]).append(" ");
                sb.append(curr);
            }
        }
        return sb.toString().trim();
    }

    public void recursion(StringBuffer curr, int num) {
        if (num == 0) {
            return;
        } else if (num < 10) {
            curr.append(singles[num]).append(" ");
        } else if (num < 20) {
            curr.append(teens[num - 10]).append(" ");
        } else if (num < 100) {
            curr.append(tens[num / 10]).append(" ");
            recursion(curr, num % 10);
        } else {
            curr.append(singles[num / 100]).append(" Hundred ");
            recursion(curr, num % 100);
        }
    }
}

165.比较版本号(中等)★

输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,"01""001" 都表示相同的整数 "1"

输入:version1 = "0.1", version2 = "1.1"
输出:-1
解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1" 
0 < 1,所以 version1 < version2
class Solution {
    public int compareVersion(String version1, String version2) {
        String[] v1 = version1.split("\\.");
        String[] v2 = version2.split("\\.");
        for(int i = 0; i < v1.length || i < v2.length; i++){    //两个数组长度不一致时这样处理
            int x = 0;
            int y = 0;
            if(i < v1.length){
                x = Integer.parseInt(v1[i]);    //parseInt方法可以直接去掉前导0
            }
            if(i < v2.length){
                y = Integer.parseInt(v2[i]);
            }
            if(x > y) return 1;
            if(x < y) return -1;
        }
        return 0;
    }
}
//双指针法,不单纯按位数比较,而是转化为数字,比较大小
//使用双指针法的优势在于,不用花费额外两个数组存储切分好的修订号
class Solution {
    public int compareVersion(String version1, String version2) {
        int n = version1.length(), m = version2.length();
        int i = 0, j = 0;
        while (i < n || j < m) {
            int x = 0;
            for (; i < n && version1.charAt(i) != '.'; ++i) {
                x = x * 10 + version1.charAt(i) - '0';
            }
            ++i; // 跳过点号
            int y = 0;
            for (; j < m && version2.charAt(j) != '.'; ++j) {
                y = y * 10 + version2.charAt(j) - '0';
            }
            ++j; // 跳过点号
            if (x != y) {
                return x > y ? 1 : -1;
            }
        }
        return 0;
    }
}

481.神奇的字符串(中等)★

神奇字符串 s 仅由 '1' 和 '2' 组成,并需要遵守下面的规则:

s 的前几个元素是 s = "1221121221221121122……" 。如果将 s 中连续的若干 1 和 2 进行分组,可以得到 "1 22 11 2 1 22 1 22 11 2 11 22 ......" 。每组中 1 或者 2 的出现次数分别是 "1 2 2 1 1 2 1 2 2 1 2 2 ......" 。上面的出现次数正是 s 自身。

给你一个整数 n ,返回在神奇字符串 s 的前 n 个数字中 1 的数目。

class Solution {
    //神奇字符串就是在原字符串的末尾不断添加末尾对应的元素
    public int magicalString(int n) {
        if(n <= 3) return 1;
        StringBuffer str = new StringBuffer("122");
        int i = 2; //i指向当前字符增加的次数
        while(str.length() < n){
            char lastCh = str.charAt(str.length()-1);   //lastCh为当前末尾字符,增加的字符与其相反
            if(str.charAt(i) == '1'){
                if(lastCh == '1'){
                    str.append("2");
                }else{
                    str.append("1");
                }
            }else{
                if(lastCh == '1'){
                    str.append("22");
                }else{
                    str.append("11");
                }
            }
            i++;
        }
        int count = 0;
        for(int j = 0; j < n; j++){   //注意这里是j<n,因为实际的str长度可能超过n
            if(str.charAt(j) == '1'){
                count++;
            }
        }
        return count;
    }
}

子序列

392.判断子序列(简单)★

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

进阶: 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

class Solution {
    //双指针法,分别指向s与t
    public boolean isSubsequence(String s, String t) {
        int index_s = 0;  //指向s
        int index_t = 0;  //指向t
        while(index_s < s.length() && index_t < t.length()){
            if(s.charAt(index_s) == t.charAt(index_t)){
                index_s++;
                index_t++;
            }else{
                index_t++;
            }
        }
        if(index_s == s.length()) return true;
        return false;
    }
}
双指针法在t字符串中需要不断寻找下一个字符出现的位置,若使用动态规划
设dp[i][j]为字符串t中i位置后字符j出现的位置,即可迅速跳到下一个字符的位置
且动态规划数组只与t有关,当有大量s时,不需要总是遍历字符串t,效率更高
class Solution {
    //动态规划,dp[i][j]为字符串t第i个位置后出现字符j的下标
    //若当前字符就是字符j,则dp[i][j]=i,若当前字符不是字符j,则dp[i][j]=dp[i+1][j]
    //dp数组需要从后往前更新
    public boolean isSubsequence(String s, String t) {
        int n = s.length();
        int m = t.length();
        int[][] dp = new int[m+1][26];
        for(int i = 0; i < 26; i++){
            dp[m][i] = m;       //设置边界条件,当某字符没有出现时,设为m
        }
        for(int i = m-1; i >= 0; i--){      //从后往前更新
            for(int j = 0; j < 26; j++){
                if(t.charAt(i) == j + 'a'){ //若当前字符就是要找的字符
                    dp[i][j] = i;
                }else{
                    dp[i][j] = dp[i+1][j];
                }
            }
        }
        int begin = 0;      
        for(int i = 0; i < n; i++){     //从s的第一个字符开始
            if(dp[begin][s.charAt(i) - 'a'] == m){
                return false;
            }
            begin = dp[begin][s.charAt(i) - 'a'] + 1;   //begin跳到字符出现位置的下一个位置
        }
        return true;
    }
}

524.通过删除字母匹配到字典最长单词(中等)★

给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。

如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。

class Solution {
    //问题是找出s的最长子串,对s构建动态规划数组,dp[i][j]指s字符串i位置后j字符的位置
    //再对dictionary逐个判断是否为s的子串,是s的子串的情况下判断是否最长及字典序最小
    //动态数组的构建方式与上题相同
    public String findLongestWord(String s, List<String> dictionary) {
        int n = s.length();
        int[][] dp = new int[n+1][26];
        Arrays.fill(dp[n], n);
        for(int i = n-1; i >= 0; i--){
            for(int j = 0; j < 26; j++){
                if(s.charAt(i) == j + 'a'){
                    dp[i][j] = i;
                }else{
                    dp[i][j] = dp[i+1][j];
                }
            }
        }
        String res = "";
        for(String str : dictionary){
            int len = str.length();
            boolean flag = true;           //是否匹配失败
            int begin = 0;           
            for(int j = 0; j < len; j++){
                if(dp[begin][str.charAt(j) - 'a'] == n){
                    flag = false;   //匹配失败
                    break;
                }
                begin = dp[begin][str.charAt(j) - 'a'] + 1;
            }
            if(flag){
                if(len > res.length() || (len == res.length()) && str.compareTo(res) < 0){
                    res = str;
                }
            }
        }
        return res;
    }
}

521.最长特殊子串(简单)

给你两个字符串 a 和 b,请返回 这两个字符串中 最长的特殊序列 的长度。如果不存在,则返回 -1 。

「最长特殊序列」 定义如下:该序列为 某字符串独有的最长子序列(即不能是其他字符串的子序列) 。

字符串 s 的子序列是在从 s 中删除任意数量的字符后可以获得的字符串。例如,"abc" 是 "aebdc" 的子序列。

class Solution {
    public int findLUSlength(String a, String b) {
        int len1 = a.length();
        int len2 = b.length();
        if(len1 != len2) return len1 > len2 ? len1 : len2;//两字符串长度不等时,最长特殊子串是长的那个
        if(a.equals(b)) return -1;      //若两字符串长度相等,且a是b的子串,即a与b相同,则无特殊子串
        return len1;        //若两字符串长度相等,且a不是b的子串,则最长特殊子串为a
    }
}

522.最长特殊序列2(中等)★

给定字符串列表 strs ,返回其中最长的特殊序列的长度。如果最长特殊序列不存在,返回 -1 。

特殊序列定义如下:该序列为某字符串 独有的子序列(即不能是其他字符串的子序列)。

s的子序列可以通过删去字符串s中的某些字符实现。 例如,"abc" 是 "aebdc" 的子序列

class Solution {
    //使用双重循环,每次拿出一个字符串判断是否是其他字符串的子串
    //若不是,则他本身就是特殊字符串,取其长度与max进行比较
    //判断子串使用双指针法
    public int findLUSlength(String[] strs) {
        int len = strs.length;
        int res = -1;
        for(int i = 0; i < len; i++){   
            boolean flag = true;    //标记str[i]不是strs[j]的子串    
            for(int j = 0; j < len; j++){
                if(i != j && isSubstring(strs[i], strs[j])){
                    flag = false;
                    break;
                }
            }
            if(flag){
                res = Math.max(res, strs[i].length());
            }
        }
        return res;
    }

    //判断str1是否为str2的子串
    public boolean isSubstring(String str1, String str2){
        int index1 = 0;
        int index2 = 0;
        while(index1 < str1.length() && index2 < str2.length()){
            if(str1.charAt(index1) == str2.charAt(index2)){
                index1++;
            }
            index2++;
        }
        return index1 == str1.length();
    }
}

高精度运算

66.加一(简单)★

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外,这个整数不会以零开头。

class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length;
        List<Integer> res = new ArrayList<>();
        int temp = 1;       //进位,初始时为1表示+1
        for(int i = len - 1; i >= 0; i--){        //从低位往高位
            int num = digits[i];
            num = num + temp;
            temp = num / 10;
            num = num % 10;
            res.add(num);    //注意这里低位在前,要逆序才能得到答案
        }
        if(temp != 0){
            res.add(temp);
        }
        Collections.reverse(res);
        return res.stream().mapToInt(Integer::intValue).toArray();//注意如何将List<Integer>转化为int[]
    }
}

67.二进制求和(简单)

给你两个二进制字符串,返回它们的和(用二进制表示)。

输入为 非空 字符串且只包含数字 1 和 0

class Solution {
    //模拟
    public String addBinary(String a, String b) {
        StringBuffer res = new StringBuffer();
        int index1 = a.length() - 1;
        int index2 = b.length() - 1;
        int temp = 0;   //进位
        int con = 0;    //当前位
        while(index1 >= 0 || index2 >= 0){
            int num1 = index1 >= 0 ? a.charAt(index1) - '0' : 0;
            int num2 = index2 >= 0 ? b.charAt(index2) - '0' : 0;
            con = (num1 + num2 + temp) % 2;
            temp = (num1 + num2 + temp) / 2;
            res.append(String.valueOf(con));
            index1--;
            index2--;
        }
        if(temp != 0){
            res.append(String.valueOf(temp));
        }
        res.reverse();
        return res.toString();
    }
}

415.字符串相加(简单)

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

class Solution {
    public String addStrings(String num1, String num2) {
        int index1 = num1.length() - 1;
        int index2 = num2.length() - 1;
        int temp = 0;
        StringBuffer res = new StringBuffer();
        while(index1 >= 0 || index2 >= 0){
            int a = index1 >= 0 ? num1.charAt(index1) - '0' : 0;
            int b = index2 >= 0 ? num2.charAt(index2) - '0' : 0;
            int num = a+b+temp;
            res.append(String.valueOf(num%10));
            temp = num / 10;
            index1--;
            index2--;
        }
        if(temp != 0){
            res.append(String.valueOf(temp));
        }
        return res.reverse().toString();
    }
}

43.字符串相乘(中等)★★

实现非负整数字符串相乘

class Solution {
    //可以分解为单个字符乘字符串,得到结果再进行字符串加法,比较麻烦
    //实际上,可以把字符串转换为字符数组,方便计算,且满足规律:num1​[i]×num2​[j] 的结果位于 ansArr[i+j+1],如果 ansArr[i+j+1]≥10,则将进位部分加到 ansArr[i+j]
    public String multiply(String num1, String num2) {
        if(num1.equals("0") || num2.equals("0")) return "0";
        int len1 = num1.length();
        int len2 = num2.length();
        int[] res = new int[len1+len2];     //结果最长不超过len1+len2
        for(int i = len1 - 1; i >= 0; i--){
            int x = num1.charAt(i) - '0';
            for(int j = len2 - 1; j >= 0; j--){
                int y = num2.charAt(j) - '0';
                res[i+j+1] += x * y;        //注意累加
            }
        }
        for(int i = len1+len2-1; i > 0; i--){   //对进位进行处理
            res[i - 1] += res[i] / 10;
            res[i] %= 10;
        }
        int index = res[0] == 0 ? 1 : 0;
        StringBuffer sb = new StringBuffer();
        while(index < len1+len2){
            sb.append(res[index]);
            index++;
        }
        return sb.toString();
    }
}

306.累加数(中等)★

累加数 是一个字符串,组成它的数字可以形成累加序列。 一个有效的 累加序列 必须 至少 包含 3 个数。除了最开始的两个数以外,序列中的每个后续数字必须是它之前两个数字之和。

给你一个只包含数字 '0'-'9' 的字符串,编写一个算法来判断给定输入是否是 累加数 。如果是,返回 true ;否则,返回 false 。

说明:累加序列里的数,除数字 0 之外,不会 以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。

输入:"199100199"
输出:true 
解释:累加序列为: 1, 99, 100, 1991 + 99 = 100, 99 + 100 = 199
class Solution {
    //对于累加序列,只要第一个数和第二个数确定,整个序列也就可以确定了
    //故需要枚举第一二个数所有的可能,设置firstbegin=0,firstend=secondbegin-1
    //只需要遍历secondbegin和secondend即可
    //同时要采样字符串加法,防止溢出,遍历到有前导0的数时可以终止本轮循环
    public boolean isAdditiveNumber(String num) {
        int len = num.length();
        for(int secondbegin = 1; secondbegin < len/2 + 1; secondbegin++){//secondbegin不能超过len/2+1
            if(num.charAt(0) == '0' && secondbegin != 1){   //第一个数有前导0时
                break;
            }
            for(int secondend = secondbegin; secondend < len; secondend++){
                if(num.charAt(secondbegin) == '0' && secondend != secondbegin){ //第二个数有前导0时
                    break;
                }
                if(valid(num, secondbegin, secondend)){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean valid(String num, int secondbegin, int secondend){
        int firstbegin = 0;
        int firstend = secondbegin - 1;
        int len = num.length();
        while(secondend < len){
            String third = addString(num, firstbegin, firstend, secondbegin, secondend);
            int thirdbegin = secondend+1;
            int thirdend = secondend + third.length();
            if(thirdend >= len || !num.substring(thirdbegin, thirdend + 1).equals(third)){
                return false;   //若第三个数长度不能超过len,且与计算出来的要相等
            }
            if(thirdend == len-1) return true;  //达到末尾,没有返回false,即匹配成功
            firstbegin = secondbegin;   //第二个数变为第一个数
            firstend = secondend;
            secondbegin = thirdbegin;   //第三个数变为第二个数
            secondend = thirdend;
        }
        return false;
    }

    public String addString(String num, int firstbegin, int firstend, int secondbegin,int secondend){
        int index1 = firstend;
        int index2 = secondend;
        int con = 0;    //当前位
        int temp = 0;   //进位
        StringBuffer sb = new StringBuffer();
        while(index1 >= firstbegin || index2 >= secondbegin){
            int x = index1 >= firstbegin ? num.charAt(index1) - '0' : 0;
            int y = index2 >= secondbegin ? num.charAt(index2) - '0' : 0;
            con = (x+y+temp) % 10;
            temp = (x+y+temp) / 10;
            sb.append(con);
            index1--;
            index2--;
        }
        if(temp != 0){
            sb.append(temp);
        }
        return sb.reverse().toString();
    }
}

字符串变换

482.密钥格式化(简单)

给定一个许可密钥字符串 s,仅由字母、数字字符和破折号组成。字符串由 n 个破折号分成 n + 1 组。你也会得到一个整数 k 。

我们想要重新格式化字符串 s,使每一组包含 k 个字符,除了第一组,它可以比 k 短,但仍然必须包含至少一个字符。此外,两组之间必须插入破折号,并且应该将所有小写字母转换为大写字母。

输入: S = "2-5g-3-J", k = 2
输出: "2-5G-3J"
class Solution {
    //题意是要每个组长度都为k,而第一组的长度为s.length()%k
    //故从后向前遍历,同时计数
    public String licenseKeyFormatting(String s, int k) {
        int len = s.length();
        s = s.toUpperCase();
        int count = 0;
        StringBuffer sb = new StringBuffer();
        for(int i = len - 1; i >= 0; i--){
            if(s.charAt(i) != '-'){
                sb.append(s.charAt(i));
                count++;
                if(count % k == 0){
                    sb.append("-");
                }
            }
        }
        //解决最后一个组后带-的问题,sb.length()>0,防止数组越界
        if(sb.length() > 0 && sb.charAt(sb.length()-1) == '-'){ 
            sb.deleteCharAt(sb.length()-1);
        }
        return sb.reverse().toString();
    }
}

6.Z字形变换(中等)

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。

class Solution {
    //第一行和最后一行相邻元素下标相差2r-2
    //其他元素每个周期需要添加两个元素,第一个元素下标是当前元素位置+2(numRows - r)
    //第二个元素下标是当前元素位置+2(r-1)
    public String convert(String s, int numRows) {
        char[] array = s.toCharArray();
        int len = array.length;
        if(numRows == 1 || numRows >= len) return s;      //特殊情况处理
        int r = 1;  //当前处理的是第r行
        StringBuffer res = new StringBuffer();
        int index;  //当前处理行的第一个元素下标
        while(r <= numRows){
            index = r - 1;
            while(index < len){
                if(r == 1 || r == numRows){
                    res.append(array[index]);
                    index += 2 * numRows - 2;
                }else{
                    if(index < len){
                        res.append(array[index]);
                        index += 2 * (numRows - r);
                    }
                    if(index < len){
                        res.append(array[index]);
                        index += 2 * (r - 1);
                    }
                }
            }
            r++;
        }
        return res.toString();
    }
}
//这道题也可以用二维矩阵模拟,还可以将二维矩阵压缩为一维矩阵

68.左右文本对齐(困难)★★

给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。

你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。

要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。

文本的最后一行应为左对齐,且单词之间不插入额外的空格。

图片.png

class Solution {
    //首先计算出一行中可以填充的单词数量,由填充的单词长度可以计算出需要填充的空格数
    //若是最后一行,除了单词间的空格,其他空格全部在最后
    //若只有一个单词,则空格全部在最后
    //若有多个单词且不是最后一行,空格先平均分配avgSpaces=spacesNum / wordsNum-1
    //再将剩余空格分配给前spacesNum % wordsNum-1个单词,即每个单词后多加1个空格
    public List<String> fullJustify(String[] words, int maxWidth) {
        int len = words.length;
        List<String> res = new ArrayList<>();
        int right = 0;          //当前行的最后一个单词在words的下标
        while(true){
            int left = right;   //当前行的第一个单词在words的下标
            int rowWordsLen = 0;//当前行的单词长度
            //循环计算当前行可以放多少个单词
            while(right < len && rowWordsLen + words[right].length() + right - left <= maxWidth){
                rowWordsLen += words[right++].length();     //注意++,right会指向下一个新的单词
            }

            //若当前行是最后一行
            if(right == len){
                StringBuffer sb = join(words, left, right, " ");    //添加单词,每个单词由一个空格分割
                sb.append(blank(maxWidth - sb.length()));   //在末尾添加对应缺少数量的空格
                res.add(sb.toString());
                return res;
            }

            int wordsNum = right - left;    //当前行的单词书
            int spacesNum = maxWidth - rowWordsLen;     //当前行的空格数

            //若当前行只有一个单词,单词左对齐,空格全部在最后
            if(wordsNum == 1){
                StringBuffer sb = new StringBuffer(words[left]);
                sb.append(blank(spacesNum));
                res.add(sb.toString());
                continue;       //转下一行
            }

            //若当前行有多个单词且不是最后一行
            int avgSpaces = spacesNum / (wordsNum - 1);   //平均每个单词带的空格
            int etraSpaces = spacesNum % (wordsNum - 1);    //前extraSpaces需要多带一个空格
            StringBuffer sb = new StringBuffer();
            sb.append(join(words, left, left+etraSpaces+1, blank(avgSpaces+1)));//前extraSpaces个单词多带一个空格,注意join左闭右开,且最后一个单词后没有空格
            sb.append(blank(avgSpaces));   //补上第extraSpaces+1个单词后的空格
            sb.append(join(words, left+etraSpaces+1, right, blank(avgSpaces)));//添加后面单词的空格
            res.add(sb.toString());
        }
    }

    //join函数实现把下标为left和right-1的单词用sep拼接在一起并返回一个字符串
    public StringBuffer join(String[] words, int left, int right, String sep){
        StringBuffer sb = new StringBuffer(words[left]);
        for(int i = left+1; i < right; i++){
            sb.append(sep + words[i]);
        }
        return sb;
    }

    //返回指定长度的空格字符串
    public String blank(int spacesNum){
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < spacesNum; i++){
            sb.append(' ');
        }
        return sb.toString();
    }
}

字符串匹配

28.实现strStr()(简单)★

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

class Solution {
    //暴力匹配算法,每次取haystack中长度为m的子串进行匹配
    public int strStr(String haystack, String needle) {
        int n = haystack.length();
        int m = needle.length();
        boolean flag;
        for(int i = 0; i + m <= n; i++){
            flag = true;
            for(int j = 0; j < m; j++){
                if(haystack.charAt(i+j) != needle.charAt(j)){
                    flag = false;
                    break;
                }
            }
            if(flag){
                return i;
            }
        }
        return -1;
    }
}

KMP算法

暴力解法的问题在于每次匹配失败时原始字符串上的指针需要回溯,当匹配字符串有许多重复的前后缀时,KMP算法借助相同的前后缀这一信息,提前排除不可能匹配的位置,原始字符串上的指针不需要回溯。

KMP的核心在于对匹配字符串构建next数组,next数组记录了当前位置匹配失败时,匹配字符串的指针应该回到哪个位置,而原始字符串的指针不会回溯。

如何构建next数组?

  • 初始化next[0] = 0,匹配串为p,指针j从0开始,指针i从1开始,
  • 若p[j] == p[i], next[i] = j+1, i和j同时往后移
  • 若p[j] != p[i], j = next[j-1], 直到j==0
  • 若j == 0 且 p[j] != p[i], next[i] = 0, i后移,j不变
  • 注意第二步的next[i] = j+1实际上是指在每次匹配成功时,next都要基于前一个位置的值加一,第三步的j = next[j-1]指的是每次匹配失败则要回溯到前一个位置next数组的值,且字符串和next数组可能从0或1开始,对应表达式会比较乱,主要理解next数组构建和kmp的操作过程
class Solution {
    // KMP 算法,ss为原字符串,pp为匹配串
    public int strStr(String ss, String pp) {
        if (pp.isEmpty()) return 0;
        int n = ss.length(), m = pp.length();
        ss = " " + ss;      // 原串和匹配串前面都加空格,使其下标从 1 开始
        pp = " " + pp;
        char[] s = ss.toCharArray();    //转换为char数组,提高获取数据的效率
        char[] p = pp.toCharArray();

        // 构建 next 数组,长度与匹配串相同(从1开始)
        int[] next = new int[m + 1];    
        for (int i = 2, j = 0; i <= m; i++) {       //初始i指向第二个数,j从0开始,j+1指向第一个数
            while (j > 0 && p[i] != p[j + 1]) j = next[j]; //匹配失败,j = next(j)
            if (p[i] == p[j + 1]) j++;          // 匹配成功的话,先让 j++,再更新next[i],结束循环后i++
            next[i] = j;
        }

        // 匹配过程,i为指向原字符串的指针,j为指向匹配串的指针
        for (int i = 1, j = 0; i <= n; i++) {
            while (j > 0 && s[i] != p[j + 1]) j = next[j];  // 匹配不成功 j = next(j)
            if (s[i] == p[j + 1]) j++;  // 匹配成功,i,j同时后移,先让 j++,结束本次循环后 i++
            if (j == m) return i - m;   // j到达匹配串末尾,匹配成功,返回下标
        }
        return -1;
    }
}

686.重复叠加字符串匹配(中等)★

给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的字符串 a 的子串,如果不存在则返回 -1
注意:字符串 "abc" 重复叠加 0 次是 "",重复叠加 1 次是 "abc",重复叠加 2 次是 "abcabc"。

输入: a = "abcd", b = "cdabcdab"
输出: 3
解释: a 重复叠加三遍后为 "abcdabcdabcd", 此时 b 是其子串。
class Solution {
    //a的复制次数有上界和下界,下界是a的长度要大于等于b的长度
    //因为b的起始位置必然在第一个a中,且在下界的基础上b不会大于a,故上界为下界加一
    public int repeatedStringMatch(String a, String b) {
        StringBuilder sa = new StringBuilder();
        int n = 0;
        while(sa.length() < b.length()){    //得到下界
            sa.append(a);
            n++;
        }       
        sa.append(a);       //得到上界时的字符串
        int index = sa.indexOf(b);
        if(index != -1){
            return index + b.length() > a.length() * n ? n+1 : n;   //若超出下界字符串的长度,则返回上界
        }
        return -1;
    }
}
//将上述解法的indexOf函数换为KMP算法
class Solution {
    //a的复制次数有上界和下界,下界是a的长度要大于等于b的长度
    //因为b的起始位置必然在第一个a中,故上界为下界加一
    public int repeatedStringMatch(String a, String b) {
        StringBuilder sa = new StringBuilder();
        int n = 0;
        while(sa.length() < b.length()){    //得到下界
            sa.append(a);
            n++;
        }       
        sa.append(a);       //得到上界时的字符串
        int index = subStr(sa.toString(), b);
        if(index != -1){
            return index + b.length() > a.length() * n ? n+1 : n;   //若超出下界字符串的长度,则返回上界
        }
        return -1;
    }

    public int subStr(String ss, String pp){    //KMP算法
        if(pp.isEmpty()) return 0;
        int n = ss.length(), m = pp.length();
        ss = " " + ss;
        pp = " " + pp;
        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();

        //构建next数组
        int[] next = new int[m+1];
        for(int i = 2, j = 0; i <= m; i++){
            while(j > 0 && p[i] != p[j+1]){
                j = next[j];
            }
            if(p[i] == p[j+1]){
                j++;
            }
            next[i] = j;
        }

        //字符串匹配
        for(int i = 1, j = 0; i <= n; i++){
            while(j > 0 && s[i] != p[j+1]){
                j = next[j];
            }
            if(s[i] == p[j+1]){
                j++;
            }
            if(j == m){
                return i - m;
            }
        }
        return -1;
    }
}
//也可以使用字符串哈希
class Solution {
    //a的复制次数有上界和下界,下界是a的长度要大于等于b的长度
    //因为b的起始位置必然在第一个a中,故上界为下界加一
    public int repeatedStringMatch(String a, String b) {
        StringBuilder sa = new StringBuilder();
        int n = 0;
        while(sa.length() < b.length()){    //得到下界
            sa.append(a);
            n++;
        }       
        sa.append(a);       //得到上界时的字符串
        int index = strHash(sa.toString(), b);
        if(index != -1){
            return index + b.length() > a.length() * n ? n+1 : n;   //若超出下界字符串的长度,则返回上界
        }
        return -1;
    }

    //字符串hash使用一个数组记录字符串前缀对应的hash值,采样进制的方法将字符串转化为数字
    //一般使用进制为131或13331,能够最小化冲突
    int strHash(String ss, String b) {      
        int P = 131;        //进制数
        int n = ss.length(), m = b.length();
        String str = ss + b;        //两个字符串合并
        int len = str.length();
        int[] h = new int[len + 10], p = new int[len + 10]; //h数组存储字符串hash值
        p[0] = 1;       //P数组存储的是P的n次方
        for (int i = 0; i < len; i++) {
            p[i + 1] = p[i] * P;
            h[i + 1] = h[i] * P + str.charAt(i);    //当前值为前一个数字乘进制数加当前字符(对应数字)
        }
        int r = len, l = r - m + 1;     //子串b下标的范围
        int target = h[r] - h[l - 1] * p[r - l + 1]; // b 的哈希值
        for (int i = 1; i <= n; i++) {      //遍历有可能和b相同的子串
            int j = i + m - 1;
            int cur = h[j] - h[i - 1] * p[j - i + 1]; // 子串哈希值
            if (cur == target) return i - 1;    //两hash值相同说明两字符串相等
        }
        return -1;
    }
}

459.重复的子字符串(简单)★

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
class Solution {
    //枚举子串长度i(不超过n/2),当原串长度无法被子串长度整除时,匹配失败
    //可以整除的情况下,原串每个位置的字符要与比它前i个的字符相等
    public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
        char[] ch = s.toCharArray();
        for(int i = 1; i * 2 <= n; i++){    //i为子串的长度
            boolean flag = true;
            if(n % i != 0){                 //不能整除直接匹配失败
                flag = false;
            }
            for(int j = i; j < n; j++){
                if(ch[j] != ch[j-i]){       //与上一个重复序列对应位置不等,匹配失败
                    flag = false;
                    break;
                }
            }
            if(flag) return true;
        }
        return false;
    }
}
class Solution {
    //若s由xxxxx组成(x代表一段字符串),则把第一个x移动到末尾,字符串仍等于s
    //于是用两个s连接在一起,去掉第一个和最后一个字符(相当于破坏第一个和最后一个x)
    //若s仍是这个字符串的子串,则满足要求
    public boolean repeatedSubstringPattern(String s) {
        //从第二个位置开始找s,等价于去掉首元素,不等于s.length()等价于去掉最后一个字符
        return (s+s).indexOf(s, 1) != s.length();

    }
}
上个解法的indexOf也可以换成kmp算法
class Solution {
    //若s由xxxxx组成(x代表一段字符串),则把第一个x移动到末尾,字符串仍等于s
    //于是用两个s连接在一起,去掉第一个和最后一个字符(相当于破坏第一个和最后一个x)
    //若s仍是这个字符串的子串,则满足要求
    //indexOf函数也可以由kmp算法代替(在kmp中取出头尾字符)
    public boolean repeatedSubstringPattern(String s) {
        return kmp(s+s, s) == -1 ? false : true;
    }

    public int kmp(String ss, String pp){    //KMP算法
        if(pp.isEmpty()) return 0;
        int n = ss.length(), m = pp.length();
        ss = " " + ss;
        pp = " " + pp;
        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();

        //构建next数组
        int[] next = new int[m+1];
        for(int i = 2, j = 0; i <= m; i++){
            while(j > 0 && p[i] != p[j+1]){
                j = next[j];
            }
            if(p[i] == p[j+1]){
                j++;
            }
            next[i] = j;
        }

        //字符串匹配
        for(int i = 2, j = 0; i <= n-1; i++){
            while(j > 0 && s[i] != p[j+1]){
                j = next[j];
            }
            if(s[i] == p[j+1]){
                j++;
            }
            if(j == m){
                return i - m;
            }
        }
        return -1;
    }
}

214.最短回文串(困难)★

给定一个字符串s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。注意回文串指正逆序相同的字符串

输入: s = "aacecaaa"
输出: "aaacecaaa"

哈希字符串解法

class Solution {
    //将原字符串分为s1+s2,s1为回文串部分,s2为非回文串,此时需要在s1前加上s2的逆序,即可构成回文串
    //故原题转化为取得s的最长回文前缀s1
    //逐个遍历s1的结束位置,判断s1是否为回文串,同时使用字符串哈希算法,s1为回文串时正逆序相同,哈希值相同
    //根据s1结束位置从小到大计算哈希值比较容易编程(保留了上一次的hash值)
    //注意对结果取模,避免字符串过长导致数据溢出
    public String shortestPalindrome(String s) {
        int n = s.length();
        if(n==0 || n==1) return s;
        int base = 131;     
        int mod = 1000000007;
        int left = 0;       //s1的正序
        int right = 0;      //s1的逆序
        int mul = 1;        //逆序时高位需要乘的数
        int best = -1;      //取得最长回文前缀的s1结束位置
        for(int i = 0; i < n; i++){     //遍历结束位置
            left = (int)(((long)left * base + s.charAt(i)) % mod);      //正序时为前一个left乘进制base
            right = (int)(((long)s.charAt(i) * mul + right) % mod);     //逆序时为高位字符乘mul
            if(left == right) best = i;
            mul = (int)((long)mul * base % mod);    //每次乘进制base,相当于高位需要乘的进制
        }
        String add = best == n-1 ? "" : s.substring(best+1);
        StringBuffer sb = new StringBuffer(add).reverse();
        sb.append(s);
        return sb.toString();
    }
}
class Solution {
    //将原字符串分为s1+s2,s1为回文串部分,s2为非回文串,此时需要在s1前加上s2的逆序,即可构成回文串
    //故原题转化为取得s的最长回文前缀s1
    //也可以不用逐个遍历s1的结束位置,s的逆序为s',s1作为s的前缀,s1的逆序s1'为s'的后缀
    //而s1为回文串,故s1' = s1,于是把s'作为匹配串,s作为模式串
    //利用kmp算法找出模式串s与匹配串匹配的字符个数,即为s1的长度
    public String shortestPalindrome(String s) {
        int n = s.length();
        if(n==0 || n==1) return s;
        char[] str = s.toCharArray();
        int[] next = new int[n];
        Arrays.fill(next, -1);
        
        //构造next数组
        for(int i = 1; i < n; i++){
            int j = next[i-1];
            while(j != -1 && str[i] != str[j+1]){
                j = next[j];
            }
            if(str[j+1] == str[i]){
                next[i] = j+1;
            }
        }

        int best = -1;      //两个字符串匹配的最大长度
        for(int i = n-1; i >= 0; i--){      //匹配串从末尾开始计算,相当于逆序
            while(best != -1 && str[i] != str[best+1]){
                best = next[best];
            }
            if(str[best+1] == str[i]){
                best++;
            }
        }
        String add = best == n-1 ? "" : s.substring(best+1);
        StringBuffer sb = new StringBuffer(add).reverse();
        sb.append(s);
        return sb.toString();
    }
}

中心拓展法

5.最长回文子串(简单)

给你一个字符串 s,找到 s 中最长的回文子串。

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

中心扩散法

class Solution {
    //使用中心扩散的方法,枚举每个中心点(两种情况,一个中心或两个中心)
    //从中心点往外扩散直到头尾元素不等,记录长度
    public String longestPalindrome(String s) {
        if(s.length() <= 1) return s;
        int start = 0, end = 0;
        for(int i = 0; i < s.length(); i++){     //遍历每个中心点
            int len1 = reverseLength(s, i, i);      //一个中心的情况
            int len2 = reverseLength(s, i, i+1);    //两个中心的情况
            int len = Math.max(len1, len2);         //当前中心的回文串长度
            if(len > end - start){
                start = i - (len-1)/2;
                end = i + len/2;
            }
        }
        return s.substring(start, end+1);
    }

    public int reverseLength(String s, int left, int right){
        while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
            left--;
            right++;
        }
        return right - left - 1;    //注意此时rightleft已经更新,在最后一个匹配位置的下一个位置
    }
}

动态规划

class Solution {
    //动态规划,若一个子串是回文串,那给它前后加上相同字符后仍是回文串
    //设dp[i][j]为以i开头,j结尾的字符串是否为回文串
    //若dp[i+1][j-1]为true且s[i]=s[j],则dp[i][j]=true
    //边界条件为,dp[i][i]=true即只有一个字符的情况,dp[i][i+1]=(s[i]==s[i+1])即两个字符的情况
    public String longestPalindrome(String s) {
        int n = s.length();
        if(n < 2) return s;
        boolean[][] dp = new boolean[n][n];
        for(int i = 0; i < n; i++){
            dp[i][i] = true;
        }
        int maxlen = 1;     //注意maxlen不是从0开始,最少会有单个字符为回文串
        int begin = 0;

        char[] str = s.toCharArray();
        for(int len = 2; len <= n; len++){  //遍历长度
            for(int i = 0; i < n; i++){     //i为起始位置
                int j = i + len - 1;        //j为结束位置
                if(j >= n) break;

                if(str[i] != str[j]){
                    dp[i][j]=false;
                }else{
                    if(j-i < 3){            //处理两个字符的情况
                        dp[i][j] = true;
                    }else{
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
                if(dp[i][j] == true && j-i+1 > maxlen){
                    maxlen = j-i+1;
                    begin = i;
                }   
            }
        }
        return s.substring(begin, begin + maxlen);
    }
}

647.回文子串(中等)

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

class Solution {
    //遍历每个可能的回文串中心,向外扩散的同时计数,注意中心有两种情况,即单个字符和两个字符
    public int countSubstrings(String s) {
        int n = s.length();
        char[] str = s.toCharArray();
        int count = 0;
        int i = 0, j = 0;
        for(int k = 0; k < n; k++){     //枚举中心
            i = k;                      //一个中心的情况
            j = k;
            while(i >= 0 && j < n && str[i--] == str[j++]){
                count++;
            }
            i = k;                      //两个中心的情况
            j = k+1;
            while(i >= 0 && j < n && str[i--] == str[j++]){
                count++;
            }
        }
        return count;
    }
}