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;
原文地址:https://www.cnblogs.com/code2one/p/10100179.html