Leetcode --- 字符串系列3(面试篇)

1.比较版本号(165-中)

题目描述:给你两个版本号 version1 和 version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。

比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。

返回规则如下:

  • 如果 version1 > version2 返回 1,
  • 如果 version1 < version2 返回 -1,
  • 除此之外返回 0。

示例

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

输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有指定下标为 2 的修订号,即视为 "0"

输入:version1 = "0.1", version2 = "1.1"
输出:-1
解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1" 。0 < 1,所以 version1 < version2

思路:双指针求解

  • 遇到 . 略过,每一次比较后,num1和num2重新进行赋值0。
  • 注意:前缀0在计算过程中被过滤

代码

public int compareVersion(String version1, String version2) {
    int l1 = 0, l2 = 0;
    int num1 = 0, num2 = 0;
    int n1 = version1.length(), n2 = version2.length();
    while (l1 < n1 || l2 < n2) {
        num1 = 0;
        num2 = 0;
        if (l1 < n1 && version1.charAt(l1) == '.') l1++; 
        if (l2 < n2 && version2.charAt(l2) == '.') l2++; 

        while (l1 < n1 && version1.charAt(l1) != '.') num1 = num1 * 10 + (version1.charAt(l1++) - '0');
        while (l2 < n2 && version2.charAt(l2) != '.') num2 = num2 * 10 + (version2.charAt(l2++) - '0');

        if (num1 != num2) {
            return num1 > num2 ? 1 : -1;
        } 
    }
    return 0;
}

2.逆波兰表达式求值(165-中)

题目描述:根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

逆波兰表达式的特点是:没有括号,运算符总是放在和它相关的操作数之后。因此,逆波兰表达式也称后缀表达式。

示例

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

思路:题外话:我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。

例如:4 + 13 / 5,这就是中缀表达式,计算机从左到右去扫描的话,扫到13,还要判断13后面是什么运算法,还要比较一下优先级,然后13还和后面的5做运算,做完运算之后,还要向前回退到 4 的位置,继续做加法,你说麻不麻烦!

那么将中缀表达式,转化为后缀表达式之后:["4", "13", "5", "/", "+"] ,就不一样了,计算机可以利用栈里顺序处理,不需要考虑优先级了。也不用回退了, 所以后缀表达式对计算机来说是非常友好的。

综上所述:逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:

  • 如果遇到操作数,则将操作数入栈;
  • 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。

整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。

代码

public int evalRPN(String[] tokens) {
    Deque stack = new LinkedList();
    int n = tokens.length;
    for (int i = 0; i < n; i++) {
        String token = tokens[i];
        if (isNumber(token)) {
            stack.push(Integer.parseInt(token));
        } else {
            int num2 = stack.pop();
            int num1 = stack.pop();
            switch (token) {
                case "+":
                    stack.push(num1 + num2);
                    break;
                case "-":
                    stack.push(num1 - num2);
                    break;
                case "*":
                    stack.push(num1 * num2);
                    break;
                case "/":
                    stack.push(num1 / num2);
                    break;
                default:
            }
        }
    }
    return stack.pop();
}

public boolean isNumber(String token) {
    return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
}

3.删除字符串中的所有相邻重复项(1047-易)

题目描述:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

思路:本题可以使用栈,类似上题,左边相同元素类似于左括号(压栈),否则右边相同元素比较(相同则出栈),所以最终栈中剩余字符就是结果,需要出栈反转。代码如下。

当然可以拿字符串直接作为栈,这样省去了栈还要转为字符串的操作,不过只有c++提供。

这里提供使用将字符数组进行重排,核心是数组双指针去重算法(多了删除重复元素)。

代码

// 使用栈
public String removeDuplicates(String s) {
    Deque stack = new LinkedList<>();
    if (s == null || s.length() < 2) {
        return s;
    }
    for (Character c : s.toCharArray()) {
        if (stack.isEmpty() || stack.peek() != c) {
            stack.push(c);
        } else {
            stack.pop();
        }
    }
    StringBuilder sb = new StringBuilder();
    while (!stack.isEmpty()) {
        sb.append(stack.pop());
    }
    return sb.reverse().toString();
}

// 字符数组去重
public String removeDuplicates(String s) {
    char[] chs = s.toCharArray();
    int top = -1;
    for (int i = 0; i < chs.length; i++) {
        if (top == -1 || chs[i] != chs[top]) {
            chs[++top] = chs[i];
        } else {
            top--;  // 删除重复元素
        }
    }
    // 开始取 count 个元素 转换成字符串 
    return String.valueOf(chs, 0, top + 1);
}

4.基本计算器(224-难)

题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

  • s 由数字、'+''-''('')'、和 ' ' 组成
  • s 表示一个有效的表达式

示例

输入:s = " 2-1 + 2 "
输出:3

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

思路:待补充。。。

代码


5.基本计算器II(227-中)

题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。

示例

输入:s = "3+2*2"
输出:7

输入:s = " 3/ 2 "
输出:1

思路:思路是先乘除后加减(优先级),用栈保存中间结果。遍历数组,当不是数字(空格或者运算符)或者最后一个运算符:

  • 加法:将数字入栈(字符转化为整型)
  • 减法:将数字的相反数入栈
  • 乘除:取出栈顶元素与当前数字运算,将当前结果替换为栈顶元素

累加取出栈中元素。

代码

public int calculate(String s) {
    Deque stack = new LinkedList<>();
    char preSign = '+'; 
    int n = s.length();
    int num = 0, ans = 0;
    for (int i = 0; i < n; i++) {
        if (Character.isDigit(s.charAt(i))) {
            num = num * 10 + s.charAt(i) - '0';
        }
        if (!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == n - 1) {
            switch (preSign) {
                case '+':
                    stack.push(num);
                    break;
                case '-':
                    stack.push(-num);
                    break;
                case '*':
                    stack.push(stack.pop() * num);
                    break;
                default:
                    stack.push(stack.pop() / num);
            }
            num = 0;
            preSign = s.charAt(i);
        }
    }
    while (!stack.isEmpty()) {
        ans += stack.pop();
    }
    return ans;
}

6.压缩字符串(443-中)

题目描述:给定一组字符,使用原地算法将其压缩。压缩后的长度必须始终小于或等于原数组长度。

数组的每个元素应该是长度为1 的字符(不是 int 整数类型)。在完成原地修改输入数组后,返回数组的新长度。

进阶:你能否仅使用O(1) 空间解决问题!

示例

输入:
["a","a","b","b","c","c","c"]
输出:
返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"]

说明:
"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。

输入:
["a","b","b","b","b","b","b","b","b","b","b","b","b"
输出:
返回 4 ,输入数组的前4个字符应该是:["a","b","1","2"]。

解释:
由于字符 "a" 不重复,所以不会被压缩。"bbbbbbbbbbbb" 被 “b12” 替代。
注意每个数字在数组中都有它自己的位置。

思路:类似数组去重算法,三指针,一个指针用于遍历。另外定义两个指针:

  • 一个以用于记录写入结果位置
  • 另一个记录重复串的开始位置,便于返回重复字符的个数

代码

public int compress(char[] chars) {
    int index = 0, start = 0;
    for (int i = 0; i < chars.length; i++) {
        if (i + 1 == chars.length || chars[i + 1] != chars[i]) {
            chars[index++] = chars[i];
            // 存在重复字符, 需要压缩
            if (i > start) {
                for (char c : ("" + (i - start + 1)).toCharArray()) {
                    chars[index++] = c;
                }
            }
            // 更新重复元素开始位置
            start = i + 1; 
        }
    }
    return index;
}

你可能感兴趣的:(Leetcode --- 字符串系列3(面试篇))