String、StringBuilder的基本用法

StringBuilder

  1. append(e)//e表示各种数据类型的参数
  2. insert(int offset, e)
  3. 删: StringBuilder delete(int start, int end)
  4. 删除此序列的子字符串中的字符。
  5. StringBuilder deleteCharAt(int index)
  6. 删除 char在这个序列中的指定位置。
  7. 改: StringBuilder replace(int start, int end, String str)
  8. 用指定的String中的字符替换此序列的子字符串中的 String
  9. setCharAt(int index, char ch)///指定索引处的字符设置为 ch 。
  10. charAt(int index)
  11. indexOf(String str)
  12. indexOf(String str, int fromIndex)
  13. lastIndexOf(String str)
  14. lastIndexOf(String str, int fromIndex)//返回指定子字符串最后一次出现的字符串中的索引。
  15. 其他用法:
  16. StringBuilder reverse()
  17. String substring(int start, int end)
  18. toString()//返回表示此顺序中的数据的字符串。

String

  1. :无
  2. 删:
  3. 查:
  4. int indexOf(int ch)
  5. indexOf(String str)//返回指定子字符串第一次出现的字符串内的索引。
  6. indexOf(String str, int fromIndex)
  7. lastIndexOf(int ch)
  8. startsWith(String prefix)
  9. endsWith(String suffix)
  10. trim()
  11. startWith()
  12. endsWith()
  13. toLowerCase()
  14. toUpperCase()
  15. 改:
  16. String replace(char oldChar, char newChar)
  17. String replace(CharSequence target, CharSequence replacement)
  18. replaceAll(String regex, String replacement)
  19. 其他:
  20. compareTo()
  21. concat(String str)
  22. contentEquals(StringBuffer sb)

16. 替换空格 - AcWing题库

熟悉基本的语法

增 append, insert

删 deleteCharAt, delete(int start, int end)

改 replace(int start, int end, String str), setCharAt()

查 length(), indexOf, charAt(), lastIndexOf(), subString(), toString()
class Solution {
    public String replaceSpaces(StringBuffer str) {
        for (int i = 0; i < str.length(); i ++){
            if (str.charAt(i) == ' '){
                str.deleteCharAt(i);
                str.insert(i, "%20"); 
            }
        }
        return str.toString();
    }
}

各种逻辑判断31. 表示数值的字符串 - AcWing题库

class Solution {
    public boolean isNumber(String s) {
        if (s.length() == 0) return false; 
        //取出字符串两端的空格(有空格不是数值)" ."
        for (;s.length() > 0 && s.charAt(0) == ' ';)
            s = s.substring(1);
        for (int i = s.length() - 1;i >= 0 &&  s.charAt(i) == ' '; i --)
            s = s.substring(0, i);

        if (s.length() == 0) return false; // ""

        if (s.charAt(0) == '+' || s.charAt(0) == '-') s = s.substring(1, s.length());
        if (s.length() == 0) return false; // "+" "-"

        int hasE = 0, hasDot = 0;
        for (int i = 0; i < s.length(); i ++){
            if (s.charAt(i) >= '0' && s.charAt(i) <= '9') ;

            else if (s.charAt(i) == 'e' || s.charAt(i) == 'E'){
                hasE ++;
                // "123e" "e123" "12e132e" ".e1"
                if (hasE > 1 || i == 0 || i == s.length() - 1 || i - 1 == 0 && s.charAt(i - 1) == '.') return false; 
            }

            else if (s.charAt(i) == '.'){
                hasDot++;
                 // "123.213.3" "1e123.3" "."
                if (hasE > 0 || hasDot > 1 || s.length() == 1) return false;
            }

            //"1e+"
            else if (i > 0 && (s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E') && 
            (s.charAt(i) == '+' || s.charAt(i) == '-') ){

                if (i == s.length() - 1) return false;  
            }

            else return false;//其他字符
        }
        return true;
    }
}

逻辑判断87. 把字符串转换成整数

class Solution {
    public int strToInt(String str) {
        //忽略行首空格
        // i扫描下标
        int len = str.length();
        int i = 0;
        while (i < len && str.charAt(i) == ' '){i++;}
        //跳过+-
        boolean isNeg = false;
        if(i < len && str.charAt(i) == '+')i++;
        if(i < len && str.charAt(i) == '-'){i++;isNeg = true;}
        //"+" "-" "" 
        if (i == len) return 0;
        //开始整数的读取
        long res = 0;
        while (i < len && str.charAt(i) >= '0' && str.charAt(i) < '9'){
            res = res * 10 + (str.charAt(i++) - '0');
        }
        //">2^31" "<-2^31-1"
        if (!isNeg && res > Integer.MAX_VALUE) return Integer.MAX_VALUE;
        if (-res < Integer.MIN_VALUE) return Integer.MIN_VALUE;
        return isNeg ? -(int)res : (int) res;
    }
}

线性扫描+判断 165. 比较版本号 - 力扣

class Solution {
    public int compareVersion(String version1, String version2) {
        // 遍历每一个版本号,版本号中去除前导零,比较大小
        int i = 0, j = 0, m = version1.length(), n = version2.length();
        while (i < m || j < n){
            int res1 = 0, res2 = 0, idx1 = i, idx2 = j, im = i, in = j;
            while (im != m && version1.charAt(im) != '.') im++;
            while (in != n && version2.charAt(in) != '.') in++;
            while (idx1 < im && version1.charAt(idx1) == '0') idx1++;
            while (idx1 < im) res1 = res1 * 10 + version1.charAt(idx1++) - '0';
            while (idx2 < in && version2.charAt(idx2) == '0') idx2++;
            while (idx2 < in) res2 = res2 * 10 + version2.charAt(idx2++) - '0';
           // System.out.println(res1 + " " + res2 + ' ' + im + " " + in);
            if (res1 > res2) return 1;
            else if (res1 < res2) return -1;
            if (im == m) i = im; else  i = im + 1; 
            if (in == n) j = in; else j = in + 1;
        }    
        return 0;   
    }
}

多情况讨论 56. 从1到n整数中1出现的次数

思路1

从前到后遍历每个数,把其转换为字符串,再遍历字符串,如果遇到字符’1’,res++

这种方法显然是TLE的

class Solution {
    public int numberOf1Between1AndN_Solution(int n) {
        //从前到后遍历每个数,把其转换为字符串,再遍历字符串,如果遇到字符'1',res++
        int res = 0;
        for (int i = 1; i <= n; i ++){
            String s = String.valueOf(i);
            for (int j = 0; j < s.length(); j++ ){
                if (s.charAt(j) == '1')
                    res++;
            }
        }
        return res;
    }
}

思路2

按照位数来枚举,假设当前位数是 c的数位 ,数为 abcdef

计算c数位上1的个数

  • c前面的位数是 00 ~ ab-1,有 ab * 1000个1
  • c前面的位数是 ab或者c是第一位

    • c == 0, 有0个1
    • c == 1, 有def+1个1
    • c >1, 有 1000个1
class Solution {
    public int numberOf1Between1AndN_Solution(int n) {
       //先把数字转换成字符串,用于枚举位数
       String s = String.valueOf(n);
       int res = 0;
       for (int i = 0; i < s.length(); i++ ){
           //计算当前数位左边,右边的字符数字和10^(右边数位)
            int left = 0, right = 0, t = 1;
            int j = 0;
            while (j < i) {left = left * 10 + s.charAt(j)-'0'; j++;}
            j = i + 1;
            while (j < s.length()){right = right * 10 + s.charAt(j) - '0';t = 10*t; j++; }
           // 001def~ab1def +ab*1
            res += left * t;
            // c == 1, 0~def 有def+1个1
            if (s.charAt(i) == '1')res += right + 1;
            // c >1, 1000~1999 有t个1
            if (s.charAt(i) > '1') res += t;
            //System.out.println(i + " " + left + " " + right + " " + t + " " + res);
       }
       return res;
    }
}

HashMap的使用63. 字符串中第一个只出现一次的字符

hashmap存放出现的字符和字符次数。

第一次将每个字符串都加入到HashMap中。然后再扫描一遍,如果有value > 1,则赋值给res,否则输出#

class Solution {
    public char firstNotRepeatingChar(String s) {
        HashMap<Character, Integer> hash = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++ ){
            char c = s.charAt(i);
            hash.put(c, hash.getOrDefault(c, 0) + 1);
        }
        for (int i = 0; i < s.length(); i++ ){
            if (hash.get(s.charAt(i)) == 1) return s.charAt(i);
        }
        return '#';
    }
}

队列+哈希表 64. 字符流中第一个只出现一次的字符

定义双指针i,j。 队列中:i代表起始位置,j代表中止位置。

遍历一个字符串的每一个字符。

插入操作:

  • 插入队列中,j++

  • 如果当前字符不存在于哈希表中,加入哈希表,统计个数。

  • 如果存在哈希表中,个数+1

查询:

  • 判断队首的哈希值,如果 > 1,i++
  • 返回i指向的元素

判断队列中存在特定值的复杂度o(n)

class Solution {    
    List<Character> list = new ArrayList<Character>();
    HashMap<Character, Integer> hash = new HashMap<Character, Integer>();
    int i = 0, j = -1;
    //Insert one char from stringstream   
    public void insert(char ch){
        list.add(ch); j++;
        hash.put(ch, hash.getOrDefault(ch, 0) + 1);
    }
    //return the first appearence once char in current stringstream
    public char firstAppearingOnce(){
        while (i <= j && hash.get(list.get(i)) > 1){
            i++;
        }
        return i > j ? '#' : list.get(i);
    }
}

旋转字符串 77. 翻转单词顺序

  1. 翻转整个句子
  2. 翻转每个单词
class Solution {
    public String reverseWords(String s) {
        //翻转两次
        StringBuffer s1 = new StringBuffer(s).reverse();
        //System.out.println(s1);
        int i = 0, j = 0;
        for (j = 0; j < s1.length(); j++){
            if (s1.charAt(j) == ' ' || j == s1.length() - 1) {
                int k = j;
                if (s1.charAt(j) == ' ')j--;
                while (i < j){
                   // System.out.println(s1.substring(i, i + 1) +" " +s1.substring(j, j +1));
                    String t = s1.substring(i, i + 1);
                    s1.replace(i, i + 1, s1.substring(j, j+1));
                    s1.replace(j, j + 1, t);
                    i++;j--;
                }
                j = k +1; i = k +1;   
            }
        }
        return s1.toString();
    }
}

旋转字符串 78. 左旋转字符串 - AcWing题库

  1. 翻转整个句子
  2. 翻转指定单词
class Solution {
    public String leftRotateString(String str,int n) {
        //把字符串翻转
        //再从制定位置[0, len-n-1],[len-n,len-1]翻转字符串
        int len = str.length();
        StringBuffer sb = new StringBuffer(str).reverse();
        int i = 0, j = len - n - 1;
        while (i < j){
            String t = sb.substring(i,i + 1);
            sb.replace(i, i + 1,sb.substring(j, j +1));
            sb.replace(j, j +1, t);
            i++; j--;
        }
        i = len - n; j = len-1;
        while (i < j){
            String t = sb.substring(i,i + 1);
            sb.replace(i, i + 1,sb.substring(j, j +1));
            sb.replace(j, j +1, t);
            i++; j--;
        }
        return sb.toString();
    }
}

KMP

求子字符串P在字符串S中出现的次数

next[i]数组的定义是p数组中前缀p[1~j+1]和后缀p[xx~i]相等的最大长度(前缀末尾下标)。

在字符串s中,p[1~j+1]代表S的子串,p[xx~i]代表S

每次移动p的前缀,如果和p的后缀相等,记录下前缀的末尾坐标。这样当需要移动前缀字符串的时候,直接从前缀的末尾匹配(不需要再匹配前缀的中的字符了,因为前缀的字符串中后缀也有)。

如果p[i] == p[j + 1]

以i为结尾的子字符串中前缀字符串p[1 ~ j +1] 和后缀字符串p[XXXX~i]是相等的

赋值 next[i] = ++j

如果p[i] != p[j + 1],说明就要把前缀字符串移动一定距离,直到重新匹配上p的后缀

因此j = next[j]

  1. 为什么是p[i]p[j + 1]比较?

因为建立p的next数组的时候,将p的第一个和第二个字符作为起始点开始比较

class Solution {
    public int strStr(String haystack, String needle) {
        //KMP
        // 首先计算子串的前缀1和后缀2匹配上的最长下标
        // 如果子串的前缀和后缀没有匹配上,此时仍然要计算后缀对应的前缀下标
        //我们不需要把前缀1末尾下标每次前移一位来匹配2,
        // 直接把前缀1中的前缀3(这个前缀3和前缀1的后缀4匹配), 这个新的前缀3一定是和2匹配的
        // 中间的步骤都是可以节省的,因为前缀3 = 4 = 以2末尾字符结尾的后缀子串。
        int m = haystack.length(), n = needle.length();
        // 每次把后缀坐标i和前缀的下一个坐标j+1匹配, 如果没有匹配上就把前缀j缩小到前缀的前缀,缩小后再进行比较
        if (n == 0) return 0;
        haystack = " " + haystack; needle = " " + needle;
        int[] next = new int[n + 1];
        // 后缀坐标i从第二个字符开始
        for (int i = 2, j = 0; i <= n; i ++){
            while (j > 0 && needle.charAt(i) != needle.charAt(j + 1)) j = next[j];
            // 如果i和j+1匹配,增加前缀长度
            if (needle.charAt(i) == needle.charAt(j + 1)) j ++;
            // 更新后缀的next数组为新的前缀下标
            next[i] = j;
        }
        // 进行父串和子串的匹配
        for (int i = 1, j = 0; i <= m; i ++){
            // j > 0 -> 如果第一个字符就不匹配, i++移动父子符
            while (j > 0 && needle.charAt(j + 1) != haystack.charAt(i)) j = next[j];
            if (haystack.charAt(i) == needle.charAt(j + 1)) j++;
            if (j == n)return i - j;
        }
        return -1;
    }
}

KMP 28. 实现 strStr()

class Solution {
    public int strStr(String p, String s) {
        //记录s 的 next数组
        int m = p.length(), n = s.length();
        if (n == 0) return 0;
        p = " " + p; s = " " + s;
        int[] next = new int[n + 1];
        for (int i = 2, j = 0; i <= n; i ++){
            //如果不相同,将s的前缀字符串移动,直到和后缀字符串匹配上
           while (j > 0 && s.charAt(i) != s.charAt(j + 1)) j = next[j];
           //如果匹配上,j++代表前缀字符串长度增加
           if (s.charAt(i) == s.charAt(j + 1)) j++;
           //后缀s[xx~i] 匹配上前缀s[1~j+1]
           next[i] = j;
        }
        //进行p和s的匹配
        for (int i = 1, j =0; i <= m; i ++){
            while (j > 0 && p.charAt(i) != s.charAt(j + 1)) j = next[j];
            if (p.charAt(i) == s.charAt(j + 1)) j++;
            //如果最终子串匹配上了,此时p的子串下标是i, s是j
            if (j == n) return i - j;
        }
        return -1;
    }
}