End

Kotlin 朱涛 实战 刷题 LeetCode

本文地址


目录

春节刷题计划 | Kotlin + LeetCode

新手快速掌握 Kotlin:

  • 第一步,去 Google 搜索一些语言特性对比的文章。比如 from Java to Kotlin,建立起 Java 与 Kotlin 的语法联系
  • 第二步,打开 Kotlin 官方文档,花几个小时的时间粗略看一遍,对 Kotlin 的语法有个大致印象
  • 最后一步,打开 LeetCode 之类的网站,开始用 Kotlin 刷题

注意:LeetCode 使用的 Kotlin 版本是 1.3.10,比较旧了,一些新特性、新 API 可能不兼容

移除字符

移除字符串中的所有元音字母 a、e、i、o、u。

public String removeVowels(String s) {
    return s.replaceAll("[aeiou]", "");
}
fun String.removeVowels(): String = replace("[aeiou]".toRegex(), "")
fun String.removeVowels(): String = filter { it !in setOf('a', 'e', 'i', 'o', 'u') }

冒泡排序

public static int[] bubbleSort(int[] array) {
    int size = array.length;
    for (int i = 0; i < size; ++i) {
        for (int j = 0; j < size - i - 1; ++j) {
            if (array[j] > array[j + 1]) {
                int tmp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = tmp;
            }
        }
    }
    return array;
}
fun sort(array: IntArray): IntArray {
    for (i in array.indices) {
        for (j in 0 until array.size - i - 1) {
            if (array[j] > array[j + 1]) {
                val tem = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tem
            }
        }
    }
    return array
}

最常见的单词

LeetCode-819:给定一个段落 (paragraph) 和一个禁用单词列表 (banned)。返回出现次数最多,同时不在禁用列表中的单词。

题目保证至少有一个词不在禁用列表中,而且答案唯一。禁用列表中的单词用小写字母表示,不含标点符号。段落中的单词不区分大小写。答案都是小写字母。

Java

public static String mostCommonWord(String paragraph, String[] banned) {
    HashMap<String, Integer> map = new HashMap<>();
    String[] strings = paragraph.toLowerCase().replaceAll("[^A-Za-z]", " ").split(" ");
    for (String s : banned) {
        map.put(s, Integer.MIN_VALUE);
    }
    map.put("", Integer.MIN_VALUE);
    for (String s : strings) {
        Integer count = map.get(s);
        map.put(s, count == null ? 1 : count + 1);
    }

    String text = null;
    int max = 0;
    for (String next : map.keySet()) {
        Integer count = map.get(next);
        if (count != null && count > max) {
            text = next;
            max = count;
        }
    }
    return text;
}

Kotlin

fun mostCommonWord(paragraph: String, banned: Array<String>): String =
    paragraph.toLowerCase()
        .replace("[^A-Za-z]".toRegex(), " ")
        .split(" ")
        .asSequence()
        .filter { it !in banned }
        .filter { it != "" }
        .groupBy { it }
        .map { Pair(it.key, it.value.size) }
        .sortedByDescending { it.second }
        .toList()[0]
        .first

比较版本号

LeetCode-165:比较版本号

  • 版本号仅包含数字和 .
  • 1.0 小于 1.1
  • 2.0 大于 1.5.6
  • 1.1 等于 1.001 等于 1.1.0

Java

public class Test {
    public static void main(String[] args) {
        System.out.println(compareVersion("3.10.0101.0", "3.10.0102."));
    }

    public static int compareVersion(String version1, String version2) {
        int[] v1 = getVersions(version1);
        int[] v2 = getVersions(version2);
        boolean isV1LengthLong = v1.length > v2.length;
        int minLength = isV1LengthLong ? v2.length : v1.length;
        for (int i = 0; i < minLength; i++) {
            if (v1[i] != v2[i]) {
                return v1[i] > v2[i] ? 1 : -1;
            }
        }

        int maxLength = isV1LengthLong ? v1.length : v2.length;
        int[] maxLengthVersion = isV1LengthLong ? v1 : v2;
        for (int i = minLength; i < maxLength; i++) {
            if (maxLengthVersion[i] != 0) {
                return isV1LengthLong ? 1 : -1;
            }
        }

        return 0;
    }

    private static int[] getVersions(String version) {
        String[] strings = version.split("\\.");
        int[] versions = new int[strings.length];
        for (int i = 0; i < strings.length; i++) {
            String newString = strings[i];
            while (newString.length() > 1 && newString.startsWith("0")) {
                newString = newString.substring(1);
            }
            versions[i] = Integer.parseInt(newString);
        }

        return versions;
    }
}

Kotlin - 1

fun compareVersion(version1: String, version2: String): Int {
    val list1 = version1.split(".")
    val list2 = version2.split(".")

    var i = 0
    while (i < list1.size || i < list2.size) {    // 只要有一个没遍历完,就要继续遍历
        val v1 = list1.getOrNull(i)?.toInt() ?: 0 // 类型转换,当数组越界时,转换为 0
        val v2 = list2.getOrNull(i)?.toInt() ?: 0
        if (v1 != v2) {
            return v1.compareTo(v2) // Int.compareTo() 的返回值,刚好符合题目的要求
        }
        i++
    }
    return 0
}

核心代码:

val v1 = list1.getOrNull(i)?.toInt() ?: 0

// 等价于以下三步
val versionStr: String? = list1.getOrNull(i) // 当数组越界时,返回 null
val versionInt: Int? = versionStr?.toInt()   // 当变量不为空时,才调用
val v1: Int = versionInt ?: 0                // 当变量为空时,才赋值

Kotlin - 2

前面的思路,我们是使用的 Kotlin 的库函数 split() 进行分割,然后对列表进行遍历来判断的版本号。其实,我们可以自己遍历字符串,来模拟 split 的过程,并在遍历时把比对的工作也一起做完。

fun compareVersion(version1: String, version2: String): Int {
    val v1 = Version(0, 0, version1)
    val v2 = Version(0, 0, version2)

    while (v1.index < version1.length || v2.index < version2.length) {
        v1.dealSubVersionValue()
        v2.dealSubVersionValue()
        if (v1.subValue != v2.subValue) {
            return v1.subValue.compareTo(v2.subValue)
        }
    }
    return 0
}

data class Version(var index: Int, var subValue: Int, val versionStr: String) {
    val baseValue = '0'.toInt()
    fun dealSubVersionValue() {
        subValue = 0
        while (index < versionStr.length && versionStr[index] != '.') {
            subValue = subValue * 10 + versionStr[index].toInt() - baseValue
            index++
        }
        index++
    }
}

求解一元一次方程

LeetCode-640:输入一个方程,以 x=value 的形式返回该方程的解

  • 方程仅包含 +-= 符号、变量 x 和其对应系数
  • 如果方程没有解,请返回 No solution
  • 如果方程有无限解,请返回 Infinite solutions
  • 如果方程中只有一个解,要保证返回值是一个整数
输入: "x+5-3+x=6+x-2"
输出: "x=2"

输入: "x=x"
输出: "Infinite solutions"

输入: "x=x+1"
输出: "No solution"

Java

public static String solveEquation(String equation) { // x+5-3+2x=6+x-2
    int xCount = 0;   // 移到左边的 x 系数之和
    int addValue = 0; // 移到右边的数字之和
    int equalIndex = equation.indexOf('='); // 等号的位置

    for (int i = 0; i < equation.length(); i++) {
        int fromIndex = i;
        if (i == 0) {
            i++;
        }
        for (int j = i; j < equation.length(); j++) {
            char c = equation.charAt(j);
            if (c == '+' || c == '-' || c == '=') {
                break;
            }
            i++;
        }

        String subString = equation.substring(fromIndex == 0 ? fromIndex : fromIndex - 1, i);
        subString = subString.startsWith("=") ? subString.substring(1) : subString;
        //System.out.println("值为:" + subString);
        if (subString.endsWith("x")) {
            subString = subString.substring(0, subString.length() - 1);
            int tempCount;
            if (subString.length() == 0) { // x
                tempCount = 1;
            } else {
                if (subString.length() == 1 && (subString.startsWith("+") || subString.startsWith("-"))) { // +x 或 -x
                    tempCount = subString.charAt(0) == '+' ? 1 : -1;
                } else { // +5x 或 5x 或 53x 或 -2x
                    tempCount = Integer.parseInt(subString);
                }
            }
            xCount = i > equalIndex ? xCount - tempCount : xCount + tempCount; // 左正右负
        } else if (subString.length() > 0) { // 过滤掉 = 产生的一个空字符串
            int tempValue = Integer.parseInt(subString);
            addValue = i > equalIndex ? addValue + tempValue : addValue - tempValue; // 左负右正
        }

    //System.out.println("结果:" + xCount + "x = " + addValue);
    if (xCount == 0) {
        return addValue == 0 ? "Infinite solutions" : "No solution";
    } else {
        return "x=" + (addValue / xCount);
    }
}

Kotlin

fun solveEquation(equation: String): String { // x+5-3+2x=6+x-2
    var xCount = 0   // 移到左边的 x 系数之和
    var addValue = 0 // 移到右边的数字之和
    val equalIndex = equation.indexOf('=') // 等号的位置
    var i = 0
    while (i < equation.length) {
        val fromIndex = i
        if (i == 0) {
            i++
        }
        for (j in i until equation.length) {
            val c: Char = equation[j]
            if (c == '+' || c == '-' || c == '=') {
                break
            }
            i++
        }
        var subString = equation.substring(if (fromIndex == 0) fromIndex else fromIndex - 1, i)
        subString = if (subString.startsWith("=")) subString.substring(1) else subString
        println("值为:$subString")
        if (subString.endsWith("x")) {
            subString = subString.substring(0, subString.length - 1)
            val tempCount = if (subString.isEmpty()) 1 // x
            else {
                if (subString.length == 1 && (subString.startsWith("+") || subString.startsWith("-"))) { // +x 或 -x
                    if (subString[0] == '+') 1 else -1
                } else subString.toInt() // +5x 或 5x 或 53x 或 -2x
            }
            xCount = if (i > equalIndex) xCount - tempCount else xCount + tempCount // 左正右负
        } else if (subString.isNotEmpty()) { // 过滤掉 = 产生的一个空字符串
            val tempValue = subString.toInt()
            addValue = if (i > equalIndex) addValue + tempValue else addValue - tempValue // 左负右正
        }
        i++
    }
    println("结果:${xCount}x = $addValue")
    return if (xCount == 0) if (addValue == 0) "Infinite solutions" else "No solution"
    else "x=" + addValue / xCount
}

分数加减运算

LeetCode-640:给定一个表示分数加减运算的字符串,返回计算结果。

  • 输入、输出字符串只包含 0-9 的数字,以及 /+- 符号(不会有空格)
  • 输入、输出分数格式均为 (-)分子/分母,正数前面的 + 会被省略
  • 输入只包含合法的最简分数,分子、分母的范围是[1,10](整数也会用分数来表示)
  • 输入分数的个数不超过 10 个,最终结果的分子与分母不超过 32 位整数(不会溢出)
  • 输出的分数需要是最简分数形式(整数也需要用分数来表示)

主要步骤:

  • 分割、解析分数
  • 计算分母的最小公倍数
  • 将分数进行通分
  • 计算分子加减运算的结果
  • 将计算结果约分

最小公倍数 & 最大公约数

最小公倍数 = a * b /最大公约数
最大公约数计算公式如下

20 36 -- 16
16 20 -- 4
4  16 -- 0  --> 最大公约数是 4

3 8 -- 5
3 5 -- 2
2 3 -- 1
1 2 -- 0    --> 最大公约数是 1

22 36 -- 14
14 22 -- 8
8  14 -- 6
6  8  -- 2
2  6  -- 0  --> 最大公约数是 2
// 求两个数的最小公倍数,Least Common Multiple
private fun lcm(a: Int, b: Int) = a * b / gcd(a, b)

// 求两个数的最大公约数,Greatest Common Divisor
private fun gcd(a: Int, b: Int): Int {
    var (big, small) = if (a > b) a to b else b to a

    while (small != 0) {
        val temp = small
        small = big % small
        big = temp
    }
    return big
}

Kotlin

fun fractionAddition(expression: String): String {
    var lcm: Int // 分母的最小公倍数
    val addValue = expression.replace("-", "+-") // 分子加减运算的结果
        .split("+")
        .filter { it.trim() != "" }
        .map { Expression(it) } // 将 String 集合转换为 Expression 集合
        .also { list -> lcm = list.map { it.denominator }.reduce(::lcm) } // 最小公倍数 ①
        .map { it.numerator * lcm / it.denominator } // 分子通分
        .reduce { a, b -> a + b } //将所有的分子相加
    val gcd = gcd(abs(addValue), lcm) // 分子和分母的最大公约数

    println("$lcm $addValue $gcd")
    return "${addValue / gcd}/${lcm / gcd}" // 简化分数
}

data class Expression(val exp: String, var numerator: Int = 0, var denominator: Int = 1) {
    init {
        exp.trim()
            .split("/")
            .takeIf { it.size == 2 }
            ?.let { numerator = it[0].toInt(); denominator = it[1].toInt() }
            ?: throw IllegalArgumentException()
    }
}

性能优化

上述代码标注 ① 的地方,是一次性计算所有分母的最小公倍数,我们在 lcm() 中加一些日志:

private fun lcm(a: Int, b: Int): Int {
    val v = a * b / gcd(a, b)
    println("$a  $b  $v")
    return v
}

则代码 listOf(3, 8, 20, 22, 36).reduce(::lcm) 的执行结果为:

3  8  24
24  20  120
120  22  1320
1320  36  3960

分数很多,并且分母很大的情况下,计算出的最小公倍数会非常大,这就会导致计算性能较差,甚至导致溢出。

优化的思路是:

  • 先计算两个相邻分数的最小公倍数
  • 得到结果以后立即做加法运算
  • 然后将计算结果转换为最简分数
  • 再拿最简分数和下一个分数做运算

代码就不写了,没啥大的区别。

2017-10-20

posted @ 2017-10-20 11:36  白乾涛  阅读(3572)  评论(0编辑  收藏  举报