算法日记

324 阅读10分钟

1. 两数之和

//空间换时间,利用map降低时间复杂度
fun twoSum(nums: IntArray, target: Int): IntArray {
        val map = HashMap<Int, Int>()
        nums.forEachIndexed { index, i ->
            if (map.containsKey(target - i)) {
                return intArrayOf(map[target - i]!!, index)
            }
            map[i] = index
        }
        return intArrayOf()
    }

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.

fun addTwoNumbers(l1: ListNode?, l2: ListNode?): ListNode? {
    var node1 = l1
    var node2 = l2

    var result: ListNode? = null
    var tmp: ListNode? = null

    var carry = 0 //进位

    while (node1 != null || node2 != null) {
        val value1 = node1?.`val` ?: 0
        val value2 = node2?.`val` ?: 0

        val sum = value1 + value2 + carry
        val value = sum % 10
        carry = sum / 10

        if (result == null) {
            tmp = ListNode(value)
            result = tmp
        } else {
            tmp?.next = ListNode(value)
            tmp = tmp?.next
        }

        node1 = node1?.next
        node2 = node2?.next
    }

    if (carry > 0) {
        tmp?.next = ListNode(carry)
    }

    return result
}

3. 无重复字符的最长子串

fun lengthOfLongestSubstring(content: String): Int {
    val map = HashMap<Char, Int>()
    var max = 0
    var start = 0

    content.forEachIndexed { index, c ->
        if (map.containsKey(c)) {
            start = maxOf(start, map[c]!! + 1) //更新起点位置,用max abba
        }
        max = maxOf(max, index - start + 1)
        map[c] = index
    }
    return max
}

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

fun threeSum(nums: IntArray): List<List<Int>> {
    // 过滤
    if (nums.size < 3) return emptyList()
    Arrays.sort(nums) //排序
    if (nums[0] > 0) return emptyList()

    val list = ArrayList<List<Int>>()
    var left: Int
    var right: Int
    for (i in nums.indices) {
        if (nums[i] > 0) break 
        if (i > 0 && nums[i] == nums[i - 1]) continue // 过滤重复元素

        left = i + 1
        right = nums.size - 1

        while (left < right) {
            val sum = nums[i] + nums[left] + nums[right]
            when {
                sum > 0 -> right--  // sum大于0,右指针左移,右元素变小
                sum < 0 -> left++  // sum小于0,左指针右移,左元素变大
                else -> {
                    list.add(listOf(nums[i], nums[left], nums[right]))

                    while (left < right && nums[left] == nums[left + 1]) left++  // 过滤左右指针重复元素

                    while (left < right && nums[right] == nums[right - 1]) right--  // 过滤左右指针重复元素

                    left++
                    right--
                }
            }
        }
    }
    return list
}

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode temp = new ListNode(0, head);
        ListNode slow = temp;
        ListNode fast = temp;

        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }

        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next;

        return temp.next;
    }

20. 有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

fun isValid(s: String): Boolean {
    if (s.length % 2 != 0) return false

    val map = mapOf(')' to '(', ']' to '[', '}' to '{')

    val stack = Stack<Char>()
    s.forEach { c ->
        if (map.containsKey(c)) {
            if (stack.isEmpty() || stack.peek() != map[c]) {
                return false
            }
            stack.pop()
        } else {
            stack.push(c)
        }
    }

    return stack.isEmpty()
}

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

public int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }

    //快慢指针,记录有效的index和当前遍历的index
    int slow = 0, fast = 1;
    while (fast < nums.length) {
        if (nums[slow] != nums[fast]) {
            slow++;
            nums[slow] = nums[fast];
        }
        fast++;
    }

    return slow + 1; //有效index + 1,数组长度
}

83. 删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

public ListNode deleteDuplicates(ListNode head) {
    ListNode cur = head;

    while (cur != null && cur.next != null) {
        if (cur.val == cur.next.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }

    return head;
}

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

fun searchRange(nums: IntArray, target: Int): IntArray {
    var left = 0
    var right = nums.size - 1
    while (left <= right) {
        val index = (left + right) / 2
        when {
            nums[index] > target -> right = index - 1

            nums[index] < target -> left = index + 1

            else -> {
                var l = index
                var r = index
                while (l > 0 && nums[l - 1] == nums[l]) l--
                while (r < nums.size - 1 && nums[r + 1] == nums[r]) r++
                return intArrayOf(l, r)
            }
        }
    }

    return intArrayOf(-1, -1)
}

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

fun searchInsert(nums: IntArray, target: Int): Int {
    var left = 0
    var right = nums.size - 1
    while (left <= right) {
        val mid = (left + right) / 2
        when {
            nums[mid] < target -> left = mid + 1

            nums[mid] > target -> right = mid - 1

            else -> return mid
        }
    }
    return left
}

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
fun maxSubArray(nums: IntArray): Int {
    if (nums.isEmpty()) return 0

    var pre = nums[0]
    var maxSum = nums[0]
    for (i in 1 until nums.size) {
        // 前面的结果如果对后面没有贡献,则丢弃
        pre = if (pre > 0) pre + nums[i] else nums[i]
        maxSum = Math.max(maxSum, pre)
    }

    return maxSum
}

101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

image.png

输入: root = [1,2,2,3,4,4,3]
输出: true
public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return true;
    }

    return isSymmetric(root.left, root.right);
}

public boolean isSymmetric(TreeNode left, TreeNode right) {
    if (left == null && right == null) {
        return true;
    }

    if (left == null || right == null) {
        return false;
    }

    return left.val == right.val && isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left);
}

102. 二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

fun levelOrder(root: TreeNode?): List<List<Int>> {
    val result = ArrayList<ArrayList<Int>>()
    if (root == null) return result

    val queue = ArrayDeque<TreeNode>()
    queue.add(root)

    while (queue.isNotEmpty()) {
        val innerList = ArrayList<Int>()
        //这里是要一次性把这一层遍历完
        for (i in 0 until queue.size) {
            val node = queue.removeFirst()
            innerList.add(node.`val`)

            if (node.left != null) queue.add(node.left!!) //添加下一层

            if (node.right != null) queue.add(node.right!!) //添加下一层
        }

        result.add(innerList)
    }

    return result
}

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

使用位运算特点

/**
 * and 全1为1
 * or  有1为1
 * xor 不同为1
 */
fun singleNumber(nums: IntArray): Int {
    var num = 0
    nums.forEach { num = num xor it }
    return num
}

140、141 环形链表

//链表是否有环
public boolean hasCycle(ListNode head) {
    ListNode fast = head, slow = head;

    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            return true;
        }
    }

    return false;
}

//链表环的入口
public ListNode detectCycle(ListNode head) {
    ListNode fast = head, slow = head;

    while (true) {
        if (fast == null || fast.next == null) {
            return null;
        }

        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            break;
        }
    }

    slow = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }

    return slow;
}

143. 重排链表

给定一个单链表 L **的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

fun reorderList(head: ListNode?) {
    head ?: return

    val stack = Stack<ListNode>()
    var node = head
    while (node != null) {
        stack.push(node)
        node = node.next
    }

    var current = head
    repeat(stack.size / 2) {
        current ?: return

        val pop = stack.pop()
        val next = current!!.next

        current!!.next = pop
        pop.next = next

        current = next
    }

    current?.next = null
}

160. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    ListNode nodeA = headA, nodeB = headB;

    while (nodeA != nodeB) {
        //一条链表遍历结束后,指向另一条链表的头节点开始遍历
        //走过的路相同,最终在相交点相遇
        nodeA = nodeA == null ? headB : nodeA.next;
        nodeB = nodeB == null ? headA : nodeB.next;
    }

    return nodeA;
}

179. 最大数

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

fun largestNumber(nums: IntArray): String {
    val comparator = Comparator<Int> { a, b ->
        var sa = 10  // a = 24, sa = 100
        while (sa <= a) sa *= 10

        var sb = 10  // b = 5, sb = 10
        while (sb <= b) sb *= 10
        // 计算拼接 524 或 245
        return@Comparator (b * sa + a) - (a * sb + b)

        //return@Comparator -"$a$b".compareTo("$b$a")
    }

    val array = nums.toTypedArray()
    Arrays.sort(array, comparator)
    if (array[0] == 0) return "0"

    return StringBuilder().apply { array.forEach { append(it) } }.toString()
}

206. 反转链表

fun reverseList(head: ListNode?): ListNode? {
            if (head?.next == null) {
                return head
            }

            var pre: ListNode? = null
            var cur = head
            while (cur != null) {
                val next = cur.next
                cur.next = pre //每次将当前next指向pre
                pre = cur //向右
                cur = next //平移
            }
            return pre
        }

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

fun findKthLargest(nums: IntArray, k: Int): Int {
    var left = 0
    var right = nums.size - 1
    val position = nums.size - k

    while (true) {
    	// 循环一次后,index左边都比index小,index右边都比index大
        val index = partition(nums, left, right)
        when {
            index > position -> right = index - 1
            index < position -> left = index + 1
            else -> return nums[index]
        }
    }
}

//利用快速排序的分治思想
fun partition(nums: IntArray, left: Int, right: Int): Int {
    var start = left
    var end = right
    val temp = nums[left]

    while (start < end) {
        while (nums[end] >= temp && start < end) end--

        while (nums[start] <= temp && start < end) start++

        if (start < end) {
            val swap = nums[start]
            nums[start] = nums[end]
            nums[end] = swap
        }
    }

    nums[left] = nums[start]
    nums[start] = temp

    return start
}

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

fun findDuplicate(nums: IntArray): Int {
    var min = 1
    var max = nums.size - 1

    while (min < max) {
        val mid = (min + max) / 2
        val count = nums.count { it <= mid }

        if (count > mid) { //表示重复数在1..mid中
            max = mid
        } else {
            min = mid + 1
        }
    }
    return min
}

448. 找到所有数组中消失的数字

给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入: [4,3,2,7,8,2,3,1]
输出: [5,6]

fun findDisappearedNumbers(nums: IntArray): List<Int> {
    for (i in nums.indices) {
        val newIndex = Math.abs(nums[i]) - 1
        if (nums[newIndex] > 0) {
            nums[newIndex] = nums[newIndex] * -1
        }
    }

    val list = ArrayList<Int>()
    for (i in nums.indices) {
        if (nums[i] > 0) {
            list.add(i + 1)
        }
    }
    return list
}

724. 寻找数组的中心下标

给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。

fun pivotIndex(nums: IntArray): Int {
    if (nums.isNotEmpty()) {
        for (index in nums.indices) {
            var left = 0
            var right = 0
            nums.forEachIndexed { i, num ->
                if (index < i) left += num
                if (index > i) right += num
            }

            if (left == right) {
                return index
            }
        }
    }
    return -1
}

fun pivotIndex(nums: IntArray): Int {
    if (nums.isNotEmpty()) {
        val sum = nums.sum()
        var left = 0
        nums.forEachIndexed { index, num ->
            if (left + num + left == sum) {
                return index
            }
            left += num
        }
    }
    return -1
}

912. 排序数组

//快速排序
fun quickSort(nums: IntArray, start: Int, end: Int) {
    if (start >= end) return
    var left = start
    var right = end
    val temp = nums[left] // 默认左边第一位是基准数

    while (left < right) {
        // 从右开始找小于基准数的
        while (nums[right] >= temp && left < right) right--

        // 从左开始找大于基准数的
        while (nums[left] <= temp && left < right) left++

        // 交换左右目标数位置
        if (left < right) {
            val swap = nums[left]
            nums[left] = nums[right]
            nums[right] = swap
        }
    }

    // 交换基准数位置,左边全部小于基准数,右边全部大于基准数
    nums[start] = nums[left]
    nums[left] = temp

    quickSort(nums, start, left - 1)
    quickSort(nums, left + 1, end)
}

/**
 *原理:比较相邻的两个元素,将较大的元素交换至右端。
 *思路:依次比较相邻的两个元素,将小数放前边,大数放后边;
 *第一趟比较完成后,最后一个数一定是数组中最大的一个数,所以第二趟比较的时候最后一个数不参与比较,依次类推,每一趟比较次数-1。
 */
//冒泡排序
fun popSort(nums: IntArray) {
    for (i in nums.indices) {
        for (j in 0 until nums.size - 1 - i) {
            if (nums[j] > nums[j + 1]) {
                val temp = nums[j]
                nums[j] = nums[j + 1]
                nums[j + 1] = temp
            }
        }
        println(nums.contentToString())
    }
}

//归并排序
fun mergeSort(arr: IntArray) {
    if (arr.isEmpty() || arr.size == 1) {
        return
    }

    mergeSort(arr, 0, arr.size - 1)
}

private fun mergeSort(arr: IntArray, left: Int, right: Int) {
    if (left >= right) {
        return
    }

    val mid = (left + right) / 2

    mergeSort(arr, left, mid)
    mergeSort(arr, mid + 1, right)

    merge(arr, left, mid, right) //合并两个数组
}

private fun merge(arr: IntArray, left: Int, mid: Int, right: Int) {
    val leftArr = IntArray(mid - left + 1)
    val rightArr = IntArray(right - mid)

    for (i in left..mid) {
        leftArr[i - left] = arr[i] //填充数据
    }

    for (i in (mid + 1)..right) {
        rightArr[i - (mid + 1)] = arr[i] //填充数据
    }

    var lIndex = 0
    var rIndex = 0
    var arrIndex = left //原数组指针

    //比较两个小数组元素,填充原数字
    while (lIndex < leftArr.size && rIndex < rightArr.size) {
        if (leftArr[lIndex] < rightArr[rIndex]) {
            arr[arrIndex++] = leftArr[lIndex++]
        } else {
            arr[arrIndex++] = rightArr[rIndex++]
        }
    }

    //左边数字未比较完毕,复制到后边
    while (lIndex < leftArr.size) {
        arr[arrIndex++] = leftArr[lIndex++]
    }

    //右边数字未比较完毕,复制到后边
    while (rIndex < rightArr.size) {
        arr[arrIndex++] = rightArr[rIndex++]
    }
}

插入排序

private static void sort(int[] arr) {
    int length = arr.length;
    if (length <= 1) {
        return;
    }

    //默认左边第一个是有序的,依次从第二个开始,插入到前边有序的合适位置
    for (int i = 1; i < length; i++) {
        int j = i - 1; //已排序
        
        int temp = arr[i];
        while (j >= 0 && arr[j] > temp) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = temp;

        /*while (j >= 0 && arr[j] > arr[j + 1]) {
            int temp = arr[j + 1];
            arr[j + 1] = arr[j];
            arr[j] = temp;
            j--;
        }*/
    }
}

堆排序

public static void sort(int[] arr) {  
    int n = arr.length;  
  
    // 构建大根堆  
    for (int i = n / 2 - 1; i >= 0; i--)  
        heapify(arr, n, i);  
  
    // 依次取出堆顶元素,放到数组末尾  
    for (int i = n - 1; i >= 0; i--) {  
        int temp = arr[0];  
        arr[0] = arr[i];  
        arr[i] = temp;  
  
        // 重新构建大根堆  
        heapify(arr, i, 0);  
    }  
}  
  
private static void heapify(int[] arr, int n, int i) {  
    int largest = i; // 初始化最大值为根节点  
    int left = 2 * i + 1; // 左子节点  
    int right = 2 * i + 2; // 右子节点  
  
    // 找到最大值  
    if (left < n && arr[left] > arr[largest])  
        largest = left;  
    if (right < n && arr[right] > arr[largest])  
        largest = right;  
  
    // 如果最大值不是根节点,则交换根节点和最大值,并继续构建大根堆  
    if (largest != i) {  
        int temp = arr[i];  
        arr[i] = arr[largest];  
        arr[largest] = temp;  
  
        heapify(arr, n, largest);  
    }  
}

剑指 Offer 15. 二进制中1的个数

请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

public static int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            n &= n - 1; //消除最低位的1
            count++;
        }
        return count;
    }

动态规划0-1背包问题

0-1背包算法是一种常见的动态规划算法,其解决的问题是:给定一组有价值、有重量的物品和一个固定大小的背包,如何在不超过背包容量的前提下,使得装入背包中的物品总价值最大。

具体来说,该算法可以分为以下步骤:

  1. 创建一个二维数组dp,其中dp[i][j]表示在前i个物品中,背包容量为j时的最大价值。
  2. 初始化第一行和第一列,即当物品数量为0或背包容量为0时,最大价值都为0。
  3. 遍历所有物品,计算是否能将该物品放入背包中,从而更新dp数组。
  4. 最终,dp数组的最后一个元素即为所求的最大价值。
public static int knapsack(int[] w, int[] v, int c) {
    int n = w.length;
    int[][] dp = new int[n + 1][c + 1]; //背包矩阵

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= c; j++) {
            if (j < w[i - 1]) { //背包容量 < 当前物品重量
                dp[i][j] = dp[i - 1][j]; //上次最优解
            } else {
                //                   不放当前物品    放当前物品(减当前物品重量后的最优解 + 当前物品重量)
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1]);
            }

            print(dp);
            System.out.println();
        }
    }
    return dp[n][c];
}

递归求解N皇后

public class NQueens {  
  
    public static void main(String[] args) {  
        solveNQueens(4);  
    }  
  
    public static void solveNQueens(int n) {  
        int[] cols = new int[n];  
        placeQueen(0, n, cols);  
    }  
  
    public static void placeQueen(int row, int n, int[] cols) {  
        if (row == n) { //摆放完成  
            System.out.println(Arrays.toString(cols));  
            for (int i = 0; i < n; i++) {  
                for (int j = 0; j < n; j++) {  
                    System.out.print(((cols[i] == j) ? "Q" : ".") + " ");  
                }  
                System.out.println();  
            }  
            return;  
        }  
  
        for (int i = 0; i < n; i++) {  
            cols[row] = i; //尝试摆放第row行皇后在第i列  
            if (isValid(row, cols)) { //检查同列,对角  
                placeQueen(row + 1, n, cols);  
            }  
        }  
    }  
  
    public static boolean isValid(int row, int[] cols) {  
        for (int i = 0; i < row; i++) {  
            if (cols[i] == cols[row] || Math.abs(i - row) == Math.abs(cols[i] - cols[row])) {  
                return false;  
            }  
        }  
        return true;  
    }  
}