• leetcode String相关


    3无重复字符的最长子串

    思路:

    • 新建数组int[128],用来记录各个字符处在字符串中的最新位置。注意位置从1开始,一方面区别默认的0,另一方面,下面调整left的值时,可以直接移动到刚好去掉重复字符的位置。
    • 设置左右边界,右为遍历中的i
      • 每遇到新的字符,看是否需要调整left位置。取当前left位置和在record中记录的当前遍历到的字符的位置的最大值。如果该字符的位置大于left,说明新字符与窗口中的某个值重复了。所以left更新为该字符前一次出现的位置。
      • 记录/更新当前字符的位置,记得是right+1
      • 调整窗口最大值
    // 新建数组存放各个字符的最新位置
    int[] record = new int[128];
    // 设置左
    int left = 0, res = 0, n = s.length();
    
    // 遍历
    for (int right = 0; right < n; right++) {
        // 每遇到新的字符,看是否需要调整left位置
        left = Math.max(left, record[s.charAt(right)]);
        // 记录当前字符的位置
        record[s.charAt(right)] = right - 1;
        // 判断新窗口的长度
        res = Math.max(res, right - left + 1);
    }
    return res;
    

    5最长回文子串

    思路:

    • 少于两个节点的情况
    • 设置maxLen和start记录最大值及最大值位置
    • 遍历,优化条件n - i > maxLen / 2
      • 设置左右指针
      • 右指针去重
      • i调整到右指针
      • 向两边扩展
      • 检查max是否需要更新
    • 返回结果s.substring(start, start + maxLen)
    if (s.length() < 2) return s;
    
    int n = s.length();
    int maxLen = 0;
    int start = 0;
    
    for (int i = 0; i < n && n-i > maxLen/2; i++){
        int left = i, right = i;
        
        while (right < n - 1 && s.charAt(right) == s.charAt(right + 1)) right ++;
        
        i = right;
        
        while (right < n - 1 && left > 0 && s.charAt(right+1) == s.charAt(left-1)){
            right++;
            left--;
        }
        
        if (maxLen < right - left + 1){
            maxLen = right - left + 1;
            start = left;
        }
    }
    
    return s.substring(start, start + maxLen);
    

    8字符串转换整数(atoi), 9回文数,7整数反转

    思路:

    • 变量设置:结果变量、遍历坐标、符号变量、长度、界限
    • 去开头的空格,去完后检查是否已到尽头(全为空的情况)
    • 判断第一个非空字符是否为正负号,并记录在sign变量中。这里用else if结束
    • 循环,只要没有超过范围而且是数字
      • 添加数字前检查是否会溢出
      • 数字拼接res = res * 10 + (s.charAt(i++) - '0');
    • return res * sign
    int i = 0, n = s.length(), sign = 1, res = 0, limit = Integer.MAX_VALUE / 10;
    
    while(i < n && s.charAt(i) == ' ') i++;
    if (i >= n) return 0;
    
    if (s.charAt(i) == '+') i++;
    else if (s.charAt(i) == '-') {
        i++;
        sign = -1;
    }
    
    while (i < n && s.charAt(i) >= '0' && s.charAt(i) <= '9'){
        if (res > limit || (res == limit && s.charAt(i) > '7')){
            return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        }
        res = res * 10 + (s.charAt(i++) - '0');
    }
    
    return res * sign;
    

    相关:回文数字/反转数字

    int reversedNum = 0;
    // 只需反转到一半,或者一半多一个字
    while (x > reversedNum) {
        // 下面用于反转数字
    // while (x != 0) {
    //     if (Math.abs(res) > limit) return 0;
        reversedNum = reversedNum * 10 + x % 10;
        x /= 10;
    }
    
    // 判断刚好一半,还是一半多一个字
    return x == reversedNum || x == reversedNum / 10;
    

    28实现strStr(), 459重复的子字符串(KMP)

    Java的 indexOf()。

    思路:

    • 根据haystack和needle的长度判断可能性:如果needle为空,返回0,如果haystack为空或者haystack的长度小于needle的,返回-1;
    • 遍历haystack,从每个字符开始遍历needle,只要出现不同就break。如果顺利完成needle的遍历,那就返回当前haystack的index
    • 如果遍历完haystack,说明没有找到,返回-1
    int m = hs.length();
    int n = nd.length();
    if (n == 0) return 0;
    if (m == 0 || m < n) return -1;
    
    // 注意到达m-n+1时,说明刚好能够容纳一个needle,所以用小于
    for (int i = 0; i < m - n + 1; i++){
        int j = 0;
        for (j = 0; j < n; j++){
            if (hs.charAt(i+j) != nd.charAt(j)) break;
        }
        if (j == n) return i;
    }
    
    return -1;
    

    更好的方法是KMP,可参考下题的方法。

    459重复的子字符串

    // "abcabc"的dp数组为[-1 0 0 0 1 2 3],dp数组长度要比原字符串长度多一个。
    // 那么我们看最后一个位置数字为3,就表示重复的字符串的字符数有3个。
    // 如果是"abcabcabc",那么dp数组为[-1 0 0 0 1 2 3 4 5 6]
    int j = 0, k = -1, n = s.length();
    int[] dp = new int[n+1];
    dp[0] = -1;
    while (j < n) {
        // 过程:k=-1时,会给数组添加0,下一次k不为-1了,就要比较j和k的字是否相同。
        // 如果相同,k和j就可以一同前进,数组开始添加为1,即有重复字符串有一个了。
        // 如果不同,k变回-1,j不动。
        // 如果出现了重复字符串,k会与j相隔这个重复字符串的长度。
        // 如果发现当前重复的字符串不对,通过k = dp[k],就能让k回到前面找是否有匹配的。如果没有就从新在-1开始
        if (k == -1 || s.charAt(j) == s.charAt(k)) {
            dp[++j] = ++k;
        } else {
            k = dp[k];
        }
    }
    
    // dp[n] != 0 表示最后一个字符也配对了。否则第二个条件中0 % 任何数(除了0)都为0,就肯定返回true了
    // (dp[n] % (n - dp[n])) == 0 中 (n - dp[n])得到重复子字符串的长度,即上面的abc
    return dp[n] != 0 && (dp[n] % (n - dp[n])) == 0;
    

    上面可以用来求next数组,dp大小只需n,最后返回dp即可。

    返回的next数组可用于上面的strStr。代码与上面几乎一样,就多加了 k < m和 (k == m) ? j - k : -1;

    int n = haystack.length(), m = needle.length(), j = 0, k = 0;
    int[] next;
    next = getNext(needle);
    while (j < n && k < m) {
        if (k == -1 || haystack.charAt(j) == needle.charAt(k)) {
            ++j;
            ++k;
        } else {
            k = next[k];
        }
    }
    return (k == m) ? j - k : -1;
    

    43字符串相乘

    思路:

    • 0的情况
    • 用一个数组记录每对乘积的结果(相乘结果最多m+n位),遍历更新数组,从后往前记录
    • 定义一个carry,遍历将数组的值转化为结果值中的单个数。
    • 去掉多余的0,同时确定string开始合并的位置。
    • 利用stringbuilder合并
    // 设置变量:长度、数组开始更新的位置
    int m = num1.length(), n = num2.length(), k = m + n - 2;
    
    // 0的情况
    if ((m == 1 && num1.equals("0")) || (n == 1 && num2.equals("0"))) return "0";
    
    // 存储两个单数字乘积结果的数组
    // 遍历更新数组
    int[] record = new int[m+n];
    for (int i = 0; i < m; i++){
        for (int j = 0; j < n; j++){
            // 注意这里要 +=,因为有i+j两次等于相同的数。
            // 另外,最后一个位置此时并不会用上。例如99 * 99中,数组里的数分别是[81, 162, 81, 0]
            record[k-i-j] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
        }
    }
    
    // 将数组的值转化为结果值
    // 加总,将各乘积整理成单个数。此处用上最后一个位置,[1 0 8 9]
    int carry = 0;
    for (int i = 0; i < m + n; i++){
        record[i] += carry;
        carry = record[i] / 10;
        record[i] %= 10;
    }
    
    // 去掉多余的0,同时确定string开始合并的位置
    int i = m + n - 1;
    if (record[i] == 0) i--;
    
    // 利用stringbuilder合并
    StringBuilder res = new StringBuilder();
    while (i >= 0) res.append(record[i--]);
    
    // 返回结果
    return res.toString();
    

    71简化路径

    思路:

    • split

    • 遍历,如果不是空和".",那么就判断添加还是删除了

      • 如果不等于"..",添加
      • 否则判断listPath.size()是否大于0,是就去掉最后一个文件
    • 如果遍历后是空的话返回"/",如果有多个"/"只保留一个。

    String[] paths = path.split("/");
    List<String> res = new ArrayList<>();
    
    for (String str : paths) {
        if (!str.isEmpty() && !str.equals(".")){
            if (!str.equals("..")) res.add(str);
            else {
                // 如果是"..",且size() > 0,要去掉最后一个文件
                if (res.size() > 0) res.remove(res.size() - 1);
            }
        }
    }
    
    if (res.size() == 0) return "/";
    
    StringBuilder builder = new StringBuilder();
    for (String re : res) {
        builder.append("/").append(re);
    }
    
    return builder.toString();
    

    93复原IP地址

    思路:

    • 新建ArrayList存储结果

    • 创建函数递归寻找四个数字的组合(函数中包含:待检验的string、已确定数量的n,例如1表示已经得出255.xx、目前拼接到的)

      • 遍历1到4,由于每位最多3个数字。如果s剩余的长度不及遍历的数字k,就结束循环。用一位,两位,三位来尝试,判断是否合法val > 255 || k != String.valueOf(val).length()。合法的话,说明配置了一个,递归调用helper时n+1,out + 配置好的值val,另外如果n==3,则要加"."

      • 如果n达到了4,还要检查s是否为空,否则会出现"2.5.5.2"

    public static List<String> restoreIpAddresses(String s) {
        List<String> res = new ArrayList<String>();
        helper(s, 0, "", res);
        return res;
    }
    
    /**
     * @param n 已确定的位数,例如 255. 就确定了一位
     * @param out 当前累积的结果,如 255. 或 255.1
     */
    public static void helper(String s, int n, String out, List<String> res) {
        if (n == 4) {
            // 保存结果,s.isEmpty()是最后一段刚好3个数的情况
            // 要检查s是否已经为空,否则会出现"2.5.5.2"
            if (s.isEmpty()) res.add(out);
            return;
        }
    
        for (int k = 1; k < 4; ++k) {
            // 剩余的数可能组不成3个数,如23,此时就没必要尝试k = 3了,否则s.substring(0, k)会outOfIndex
            if (s.length() < k) break;
            // 用一位,两位,三位来尝试,分别判断其合不合法,如果合法,则调用递归继续分剩下的字符串,最终和求出所有合法组合
            int val = Integer.parseInt(s.substring(0, k));
            // 比如当k=3时,说明应该是个三位数,但如果字符是"010",那么转为整型val=10,再转回字符串就是"10",此时的长度和k值不同了
            if (val > 255 || k != String.valueOf(val).length()) continue;
            helper(s.substring(k), n + 1, out + val + (n == 3 ? "" : "."), res);
        }
    }
    

    60第k个排列

    给定 nk,返回第 k 个排列。

    思路:

    • 新建一个ArrayList,存储[1, ..., n]
    • 新建一个数组存储[1, 1!, 2!, 3!, ..., n-1!]阶乘
    • k—,把排名转换为下标
    StringBuilder sb = new StringBuilder();
    for (int i = n; i >= 1; i--) {
        // 除以 n - 1 的阶乘,即16/3! = 2,在第三组全排序中
        int idx = k / f[i - 1];
        // nums.get(idx)取得3
        sb.append(nums.get(idx));
        // 去掉已选的数字
        nums.remove(idx);
        // 取余数,更新k在新组中的排名,即第三组中的第16%3! = 4个(0开始)
        k %= f[i - 1];
    }
    return sb.toString();
    

    151翻转字符串里的单词(344反转字符串、557反转字符串中的单词 III)

    思路:

    • split
    • 判断是否为空
    • 遍历,从后往前,只要不为空,就添加,并加上空格
    • 返回结果,记得trim
    151翻转字符串里的单词
    输入: "the sky is blue",
    输出: "blue is sky the".
    
    344反转字符串
    输入: "A man, a plan, a canal: Panama"
    输出: "amanaP :lanac a ,nalp a ,nam A"
    
    557反转字符串中的单词 
    输入: "Let's take LeetCode contest"
    输出: "s'teL ekat edoCteeL tsetnoc" 
    

    14最长公共前缀

    思路:

    • 取第一个string作为prefix
    • 遍历,从第二个开始
      • 循环,只要str.indexOf(prefix) != 0,prefix去掉最后一个字符
    • 返回prefix

    20有效的括号

    思路:

    • 新建stack
    • 遍历
      • 遇到做括号入栈
      • 否则,先判断stack是否为空,为空返回false,否则顶层和各个有括号匹配,不匹配返回false
    • 返回stack.isEmpty()

    567字符串的排列(了解)

    输入: s1 = "ab" s2 = "eidbaooo"
    输出: True
    解释: s2 包含 s1 的排列之一 ("ba").
    

    思路:

    • 把s1都放进int[128]中,放一个,record[i]++;
    • 遍历s2找匹配,遍历到的都-1
    • 当cnt减到0时,开始判断窗口大小是否刚好等于s1大小,否则缩小left,如果缩小时减去了s1中的值,则cnt+1,继续上一步的遍历。
    // 创建数组计数s1的字符
    int[] record = new int[128];
    for (char i: s1.toCharArray()){
        record[i]++;
    }
    // 遍历,从s2中寻找配对的
    for (int right = 0; right < n2; right++){
        if (record[s2.charAt(right)]-- > 0) cnt--;
    
        // 如果匹配数等于s1的长度,遍历
        while(cnt == 0){
            // 如果窗口大小刚好与s1的一样,则找到了
            if (right - left + 1 == n1) return true;
            // 收缩左边界,如果去掉了s1中的字符,那么s1的匹配数+1
            if (++record[s2.charAt(left++)] > 0) cnt++;
        }
    }
    return false;
    
  • 相关阅读:
    在线工具TOOL收藏
    HtmlDocument [代码碎片笔记]
    ChromiumWebBrowser [链接]
    PHP [开发汇总]
    Discuz[技术文献]
    [jvm] -- 监控和调优常用命令工具篇
    [jvm] -- 常用内存参数配置篇
    [日常摘要] -- 事务的隔离级别篇
    [日常摘要] -- ThreadLocal篇
    [日常摘要] -- zookeeper篇
  • 原文地址:https://www.cnblogs.com/code2one/p/10100179.html
Copyright © 2020-2023  润新知