A Jun takes you to use the Kotlin brush algorithm

Posted by xxtobirichter on Sat, 29 Jan 2022 17:49:34 +0100

This series uses Java and Kotlin to solve the above algorithm problems. Because I am a rookie in the algorithm, some problems may not be the optimal solution. I hope to discuss with you~

GitHub of the project: Algorithm

Two Sum

Difficulty: simple

Link: Two Sum

code

Java

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by TanJiaJun on 2021/6/4.
 * 1. Two Sum
 * Difficulty: simple
 *
 * @see <a href="https://leetcode-cn.com/problems/two-sum/">Two Sum</a>
 */
class TwoSum {

    public static void main(String[] args) {
        // Example 1
        System.out.println("Example 1");

        int[] firstNumbers = {2, 7, 11, 15};
        int firstTarget = 9;
        System.out.println(Arrays.toString(hashTwoSum(firstNumbers, firstTarget)));

        System.out.print("\n");

        // Example 2
        System.out.println("Example 2");

        int[] secondNumbers = {3, 2, 4};
        int secondTarget = 6;
        System.out.println(Arrays.toString(hashTwoSum(secondNumbers, secondTarget)));

        System.out.print("\n");

        // Example 3
        System.out.println("Example 3");
        int[] thirdNumbers = {3, 3};
        int thirdTarget = 6;
        System.out.println(Arrays.toString(hashTwoSum(thirdNumbers, thirdTarget)));
    }

    /**
     * Method 1: Enumeration
     * Time complexity: O(N^2), where N is the number of elements in the array
     * Space complexity: O(1)
     *
     * @param numbers int Array of types
     * @param target  target value
     * @return result
     */
    private static int[] twoSum(int[] numbers, int target) {
        for (int i = 0, length = numbers.length; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if (numbers[i] + numbers[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }

    /**
     * Method 2: hash table
     * Time complexity: O(N), where N is the number of elements of the array
     * Spatial complexity: O(N), where N is the number of elements of the array
     *
     * @param numbers int Array of types
     * @param target  target value
     * @return result
     */
    private static int[] hashTwoSum(int[] numbers, int target) {
        Map<Integer, Integer> hashMap = new HashMap<>();
        for (int i = 0, length = numbers.length; i < length; i++) {
            int other = target - numbers[i];
            if (hashMap.containsKey(other)) {
                return new int[]{hashMap.get(other), i};
            }
            hashMap.put(numbers[i], i);
        }
        return new int[0];
    }

}

Kotlin

/**
 * Created by TanJiaJun on 2021/6/5.
 * 1. Two Sum
 * Difficulty: simple
 *
 * @see <a href="https://leetcode-cn.com/problems/two-sum/">Two Sum</a>
 */
object TwoSumKotlin {

    @JvmStatic
    fun main(args: Array<String>) {
        // Example 1
        println("Example 1")

        val firstNumbers = intArrayOf(2, 7, 11, 15)
        val firstTarget = 9
        println(hashTwoSum(firstNumbers, firstTarget).contentToString())

        print("\n")

        // Example 2
        println("Example 2")

        val secondNumbers = intArrayOf(3, 2, 4)
        val secondTarget = 6
        println(hashTwoSum(secondNumbers, secondTarget).contentToString())

        print("\n")

        // Example 3
        println("Example 3")
        val thirdNumbers = intArrayOf(3, 3)
        val thirdTarget = 6
        println(hashTwoSum(thirdNumbers, thirdTarget).contentToString())
    }

    /**
     * Method 1: enumeration algorithm
     * Time complexity: O(N^2), where N is the number of elements in the array
     * Space complexity: O(1)
     *
     * @param numbers int Array of types
     * @param target  target value
     * @return result
     */
    private fun twoSum(numbers: IntArray, target: Int): IntArray {
        numbers.forEachIndexed { index, number ->
            for (i in index + 1 until numbers.size) {
                if (number + numbers[i] == target) {
                    return intArrayOf(index, i)
                }
            }
        }
        return intArrayOf()
    }

    /**
     * Method 2: hash table
     * Time complexity: O(N), where N is the number of elements of the array
     * Spatial complexity: O(N), where N is the number of elements of the array
     *
     * @param numbers int Array of types
     * @param target  target value
     * @return result
     */
    private fun hashTwoSum(numbers: IntArray, target: Int): IntArray {
        hashMapOf<Int, Int>().apply {
            numbers.forEachIndexed { index, number ->
                val other = target - number
                if (containsKey(other)) {
                    val otherIndex = get(other) ?: 0
                    return intArrayOf(otherIndex, index)
                }
                put(number, index)
            }
        }
        return intArrayOf()
    }

}

Problem solution

enumeration

/**
 * Method 1: Enumeration
 * Time complexity: O(N^2), where N is the number of elements in the array
 * Space complexity: O(1)
 *
 * @param numbers int Array of types
 * @param target  target value
 * @return result
 */
private fun twoSum(numbers: IntArray, target: Int): IntArray {
    numbers.forEachIndexed { index, number ->
        for (i in index + 1 until numbers.size) {
            if (number + numbers[i] == target) {
                return intArrayOf(index, i)
            }
        }
    }
    return intArrayOf()
}

Time complexity: O(N^2), where N is the number of elements in the array.

Space complexity: O(1).

Brute force solution: traverse every element in the numbers array and find any two elements whose addition is equal to target. Note that assuming that the index of the current element is i, the elements before i have actually matched i, so we don't need to match again.

Hashtable

/**
 * Method 2: hash table
 * Time complexity: O(N), where N is the number of elements of the array
 * Spatial complexity: O(N), where N is the number of elements of the array
 *
 * @param numbers int Array of types
 * @param target  target value
 * @return result
 */
private fun hashTwoSum(numbers: IntArray, target: Int): IntArray {
    hashMapOf<Int, Int>().apply {
        numbers.forEachIndexed { index, number ->
            val other = target - number
            if (containsKey(other)) {
                val otherIndex = get(other) ?: 0
                return intArrayOf(otherIndex, index)
            }
            put(number, index)
        }
    }
    return intArrayOf()
}

Time complexity: O(N), where N is the number of elements of the array.

Time complexity: O(N), where N is the number of elements of the array.

Assuming that the current element is number, method 1 takes too much time to find another element, that is, to find target - number. We can use HashMap storage, with element as key and index as value, so that we can quickly find the index corresponding to this element through containsKey method, As for why we use containsKey method instead of containsValue method, this is because HashMap uses chain address method to solve hash conflict. In short, it is the combination of array and linked list. Each array element is a linked list structure. First, call the hashCode method of key to get the hash value (this method is applicable to each Java object), Then, the corresponding storage location of the key value pair is located through the last two steps of the hash algorithm (high-order operation and modulo operation), so the efficiency of finding elements through key is higher than that through value. Oracle officials say that using these two methods to traverse all elements in HashMap, the time required by containsValue method is several orders of magnitude longer than that of containsKey method, So we use containsKey method to improve efficiency.

It should be noted that instead of using mutableMapOf method, we use hashMapOf method, because mutableMapOf method uses LinkedHashMap, while hashMapOf method uses HashMap. LinkedHashMap ensures the iterative order by maintaining an additional two-way linked list, but it is actually unnecessary in this topic, and there is no need to increase the cost of time and space, The source code is as follows:

// Maps.kt
/**
 * Returns an empty new [MutableMap].
 *
 * The returned map preserves the entry iteration order.
 * @sample samples.collections.Maps.Instantiation.emptyMutableMap
 */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <K, V> mutableMapOf(): MutableMap<K, V> = LinkedHashMap()

Add Two Numbers

Difficulty: medium

Link: Add Two Numbers

code

Java

/**
 * Created by TanJiaJun on 2021/6/5.
 * 2. Add Two Numbers
 * Difficulty: medium
 *
 * @see <a href="https://leetcode-cn.com/problems/add-two-numbers/">Add Two Numbers</a>
 */
class AddTwoNumbers {

    public static void main(String[] args) {
        // Example 1
        System.out.print("Example 1:");

        Node firstNode =
                new Node(2,
                        new Node(4,
                                new Node(3)));
        Node secondNode =
                new Node(5,
                        new Node(6,
                                new Node(4)));
        printNode(addTwoNumbers(firstNode, secondNode));

        System.out.print("\n");

        // Example 2
        System.out.print("Example 2:");

        Node thirdNode = new Node(0);
        Node fourthNode = new Node(0);
        printNode(addTwoNumbers(thirdNode, fourthNode));

        System.out.print("\n");

        // Example 3
        System.out.print("Example 3:");

        Node fifthNode =
                new Node(9,
                        new Node(9,
                                new Node(9,
                                        new Node(9,
                                                new Node(9,
                                                        new Node(9,
                                                                new Node(9)))))));
        Node sixthNode =
                new Node(9,
                        new Node(9,
                                new Node(9,
                                        new Node(9))));
        printNode(addTwoNumbers(fifthNode, sixthNode));

        System.out.print("\n");

        // Example 4
        System.out.print("Example 4:");

        Node seventhNode = new Node(2);
        Node eightNode = new Node(8);
        printNode(addTwoNumbers(seventhNode, eightNode));
    }

    /**
     * Time complexity: O(Max(m, n)), where m is the length of the first node and N is the length of the second node
     * Space complexity: O(1)
     *
     * @param firstNode  First node
     * @param secondNode Second node
     * @return result
     */
    private static Node addTwoNumbers(Node firstNode, Node secondNode) {
        Node dummyNode = new Node(-1);
        Node node = dummyNode;
        int carry = 0;
        // Traverse two linked lists
        while (firstNode != null || secondNode != null) {
            // If the length of the two linked lists is different, you can add several zeros after the short linked list
            int firstValue = firstNode != null ? firstNode.item : 0;
            int secondValue = secondNode != null ? secondNode.item : 0;
            int value = firstValue + secondValue + carry;
            int newItem = value % 10;
            // The added carry value is stored by carry
            carry = value / 10;
            Node newNode = new Node(newItem);
            if (firstNode != null) {
                firstNode = firstNode.next;
            }
            if (secondNode != null) {
                secondNode = secondNode.next;
            }
            // If the value of carry is greater than 0 after traversing the two linked lists, a new node should be added at the back of the linked list to store this value
            if (firstNode == null && secondNode == null && carry > 0) {
                newNode.next = new Node(carry);
            }
            node.next = newNode;
            node = node.next;
        }
        return dummyNode.next;
    }

    /**
     * Print node
     *
     * @param node node
     */
    private static void printNode(Node node) {
        System.out.print("[");
        while (node != null) {
            System.out.print(node.item);
            node = node.next;
            if (node != null) {
                System.out.print(",");
            }
        }
        System.out.print("]");
    }

    private static class Node {

        int item;
        Node next;

        Node(int item) {
            this.item = item;
        }

        Node(int item, Node next) {
            this.item = item;
            this.next = next;
        }

    }

}

Kotlin

/**
 * Created by TanJiaJun on 2021/6/6.
 * 2. Add Two Numbers
 * Difficulty: medium
 *
 * @see <a href="https://leetcode-cn.com/problems/add-two-numbers/">Add Two Numbers</a>
 */
object AddTwoNumbersKotlin {

    @JvmStatic
    fun main(args: Array<String>) {
        // Example 1
        print("Example 1:")
        val firstNode =
                Node(
                        2,
                        Node(
                                4,
                                Node(3)
                        )
                )
        val secondNode =
                Node(
                        5,
                        Node(
                                6,
                                Node(4)
                        )
                )
        printNode(addTwoNumbers(firstNode, secondNode))

        print("\n")

        // Example 2
        print("Example 2:")
        val thirdNode = Node(0)
        val fourthNode = Node(0)
        printNode(addTwoNumbers(thirdNode, fourthNode))

        print("\n")

        // Example 3
        print("Example 3:")
        val fifthNode =
                Node(
                        9,
                        Node(
                                9,
                                Node(
                                        9,
                                        Node(
                                                9,
                                                Node(
                                                        9,
                                                        Node(
                                                                9,
                                                                Node(9)
                                                        )
                                                )
                                        )
                                )
                        )
                )
        val sixthNode =
                Node(
                        9,
                        Node(
                                9,
                                Node(
                                        9,
                                        Node(9)
                                )
                        )
                )
        printNode(addTwoNumbers(fifthNode, sixthNode))

        print("\n")

        // Example 4
        print("Example 4:")
        val seventhNode = Node(2)
        val eightNode = Node(8)
        printNode(addTwoNumbers(seventhNode, eightNode))
    }

    /**
     * Time complexity: O(Max(m, n)), where m is the length of the first node and N is the length of the second node
     * Space complexity: O(1)
     *
     * @param first First node
     * @param second Second node
     * @return result
     */
    private fun addTwoNumbers(first: Node?, second: Node?): Node {
        var firstNode = first
        var secondNode = second
        val dummy = Node(-1)
        var node: Node = dummy
        var carry = 0
        // Traverse two linked lists
        while (firstNode != null || secondNode != null) {
            // If the length of the two linked lists is different, you can add several zeros after the short linked list
            val firstValue = firstNode?.item ?: 0
            val secondValue = secondNode?.item ?: 0
            val value = firstValue + secondValue + carry
            val newItem = value.rem(10)
            // The added carry value is stored by carry
            carry = value.div(10)
            val newNode = Node(newItem)
            firstNode?.let { firstNode = it.next }
            secondNode?.let { secondNode = it.next }
            // If the value of carry is greater than 0 after traversing the two linked lists, a new node should be added at the back of the linked list to store this value
            if (firstNode == null && secondNode == null && carry > 0) {
                newNode.next = Node(carry)
            }
            node.next = newNode
            node = node.next ?: dummy
        }
        return dummy.next ?: dummy
    }

    /**
     * Print node
     *
     * @param node node
     */
    private fun printNode(node: Node?) {
        print("[")
        var curNode: Node? = node
        while (curNode != null) {
            print(curNode.item)
            curNode = curNode.next
            if (curNode != null) {
                print(",")
            }
        }
        print("]")
    }

    private data class Node(
            var item: Int,
            var next: Node? = null
    )

}

Time complexity: O(Max(m, n)), where m is the length of the first node and N is the length of the second node.

Space complexity: O(1).

Problem solution

What we should pay attention to in this problem is that we find that the linked list to be input stores the relevant numbers in reverse order, and then the two linked lists can be added directly when they are in the same position. The added carry value is stored through carry. If the lengths of the two linked lists are different, we can add several zeros after the short linked list to make the length equal to the long linked list, It should be noted that if the value of carry is greater than 0 after traversing the two linked lists, a new node should be added after the linked list to store this value.

Longest Substring Without Repeating Characters

Difficulty: medium

Link: Longest Substring Without Repeating Characters

code

Java

import java.util.HashSet;
import java.util.Set;

/**
 * Created by TanJiaJun on 2021/6/6.
 * 3. Longest Substring Without Repeating Characters
 * Difficulty: medium
 *
 * @see <a href="https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/">Longest Substring Without Repeating Characters</a>
 */
class LongestSubstringWithoutRepeatingCharacters {

    public static void main(String[] args) {
        // Example 1
        System.out.print("Example 1:");

        String firstStr = "abcabcbb";
        System.out.println(lengthOfLongestSubstring(firstStr));

        System.out.print("\n");

        // Example 2
        System.out.print("Example 2:");

        String secondStr = "bbbbb";
        System.out.println(lengthOfLongestSubstring(secondStr));

        System.out.print("\n");

        // Example 3
        System.out.print("Example 3:");

        String thirdStr = "pwwkew";
        System.out.println(lengthOfLongestSubstring(thirdStr));

        System.out.print("\n");
    }

    /**
     * Method: sliding window
     * Time complexity: O(N), where N is the length of the string
     * Space complexity: O(∣) Σ ∣), where Σ Represents the set of characters that appear in the string, ∣ Σ ∣ indicates the size of the character set. The default is all characters within ASCII code [0128], that is, the size is 128
     *
     * @param str character string
     * @return result
     */
    private static int lengthOfLongestSubstring(String str) {
        // result
        int result = 0;
        // Right pointer
        int right = 0;
        // A hash set that records the characters that appear
        Set<Character> chars = new HashSet<>();
        // Traverse characters in string
        for (int i = 0, length = str.length(); i < length; i++) {
            if (i > 0) {
                // Remove a character and move the left pointer one space to the right
                chars.remove(str.charAt(i - 1));
            }
            while (right < length && !chars.contains(str.charAt(right))) {
                // If the index is less than the length of the string and the character does not appear, put the character into the hash set, and then move the right pointer one space to the right
                chars.add(str.charAt(right));
                right++;
            }
            // Maximum calculated length
            result = Math.max(result, right - i);
        }
        return result;
    }

}

Kotlin

import kotlin.math.max

/**
 * Created by TanJiaJun on 2021/6/6.
 * 3. Longest Substring Without Repeating Characters
 * Difficulty: medium
 *
 * @see <a href="https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/">Longest Substring Without Repeating Characters</a>
 */
object LongestSubstringWithoutRepeatingCharactersKotlin {

    @JvmStatic
    fun main(args: Array<String>) {
        // Example 1
        print("Example 1:")

        val firstStr = "abcabcbb"
        println(lengthOfLongestSubstring(firstStr))

        print("\n")

        // Example 2
        print("Example 2:")

        val secondStr = "bbbbb"
        println(lengthOfLongestSubstring(secondStr))

        print("\n")

        // Example 3
        print("Example 3:")

        val thirdStr = "pwwkew"
        println(lengthOfLongestSubstring(thirdStr))
    }

    /**
     * Method: sliding window
     * Time complexity: O(N), where N is the length of the string
     * Space complexity: O(∣) Σ ∣), where Σ Represents the set of characters that appear in the string, ∣ Σ ∣ indicates the size of the character set. The default is all characters within ASCII code [0128], that is, the size is 128
     *
     * @param str character string
     * @return result
     */
    private fun lengthOfLongestSubstring(str: String): Int {
        // result
        var result = 0
        // Right pointer
        var right = 0
        // A hash set that records the characters that appear
        val chars = hashSetOf<Char>()
        // Traverse characters in string
        str.forEachIndexed { index, _ ->
            // When the index is greater than 0, execute the following logic
            if (index > 0) {
                // Remove a character and move the left pointer one space to the right
                chars.remove(str[index - 1])
            }
            while (right < str.length && !chars.contains(str[right])) {
                // If the index is less than the length of the string and the character does not appear, put the character into the hash set, and then move the right pointer one space to the right
                chars.add(str[right])
                right++
            }
            // Maximum calculated length
            result = max(result, right - index)
        }
        return result
    }
}

Time complexity: O(N), where N is the length of the string.

Space complexity: O(∣) Σ ∣), where Σ Represents the set of characters that appear in the string, ∣ Σ ∣ indicates the size of the character set. The default is all characters within ASCII code [0128], that is, the size is 128.

Problem solution

We can use the sliding window to complete this problem. The sliding window is a method to calculate the logic related to the pointers by adjusting the left and right pointers. We take example 1 as an example. When the string is abcabcbb, we first put the left pointer and the right pointer on character A. at this time, the characters corresponding to the left pointer and the right pointer are the same, Then we move the right pointer one grid to the right. At this time, the right pointer is placed at the position of character b. at this time, judge whether the characters corresponding to the next two pointers are the same as just now. If they are the same, the left pointer moves one grid to the right, and the right pointer remains unchanged. If they are different, the left pointer remains unchanged, and the right pointer moves one grid to the right, and so on, The corresponding interval can be obtained, so as to obtain the longest substring without repeated characters.

It should be noted that, like the question of the sum of two, we use the Set set to record the characters, but we do not need to ensure the iterative order, so we can use the hashSetOf method to create the HashSet instead of the mutableSetOf method, because the mutableSetOf method creates a LinkedHashSet, It also ensures the iterative order through additional two-way linked list. The source code is as follows:

// Sets.kt
/**
 * Returns an empty new [MutableSet].
 *
 * The returned set preserves the element iteration order.
 * @sample samples.collections.Collections.Sets.emptyMutableSet
 */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableSetOf(): MutableSet<T> = LinkedHashSet()

My GitHub: TanJiaJunBeyond

Android general framework: Android general framework

My Nuggets: Tan Jiajun

My brief book: Tan Jiajun

My CSDN: Tan Jiajun

Topics: Java Algorithm kotlin