Skip to content

Latest commit

 

History

History
879 lines (718 loc) · 26.6 KB

腾讯精选50.md

File metadata and controls

879 lines (718 loc) · 26.6 KB

力扣题库:《腾讯精选练习 50 题》 Swift 版

🏓🏸🏒⚽️🏀🥎🏄‍♀️🏄🏄‍♂️

每道题目都经过亲自编写和测试,尽量保证执行效率双百 + 极简 + swifty,如有不足请各位道友多加指正


给定两个非空链表表示两个非负整数,其中,它们各自按照逆序方式存储,并且每个节点只能存储一位数字。将两个数相加,并使用链表返回它们的和(同样逆序)

差点看成了两数之和😅,本题难度为中等,没有啥捷径,借助哑节点进行遍历即可,注意进位的处理。

let dummy = ListNode(-1)
        var cur: ListNode? = dummy
        var node1 = l1
        var node2 = l2
        var carry = false // 记录是否有进位
        while node1 != nil || node2 != nil || carry { // 这里使用三个条件,将代码简化到一个循环中
            var sum = 0
            if let n1 = node1 {
                sum += n1.val
            }
            if let n2 = node2 {
                sum += n2.val
            }
            sum += carry ? 1 : 0
            carry = sum / 10 > 0
            let newNode = ListNode(sum % 10)
            cur?.next = newNode
            cur = cur?.next
            node1 = node1?.next
            node2 = node2?.next
        }
        return dummy.next

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。你可以假设 nums1 和 nums2 不会同时为空。

虽然有两个解法,但解法二为 hard 型题解,所以面试时尽量保一争二

解法一:合并有序数组 O(m + n) (与题目要求不符合,但算法较为简单)

class Solution {
    func findMedianSortedArrays(_ nums1: [Int], _ nums2: [Int]) -> Double {
      var nums1 = nums1
        func helper() {
            // 合并两个有序数组
            var l1 = nums1.count - 1
            var l2 = nums2.count - 1
            var l = nums1.count + nums2.count - 1
            nums1.append(contentsOf: [Int](repeating: 0, count: nums2.count))
            while l1 >= 0 && l2 >= 0 {
                if nums1[l1] > nums2[l2] {
                    nums1[l] = nums1[l1]
                    l1 -= 1
                } else {
                    nums1[l] = nums2[l2]
                    l2 -= 1
                }
                l -= 1
            }
            if l2 >= 0 {
                while l2 >= 0 {
                    nums1[l2] = nums2[l2]
                    l2 -= 1
                }
            }
        }
        helper()
        if nums1.isEmpty {
            return -1
        }
        if nums1.count % 2 == 1 {
            return Double(nums1[nums1.count / 2])
        } else {
            return Double(nums1[nums1.count / 2 - 1] + nums1[nums1.count / 2]) / 2
        }
    }
}

解法二:使用二分查找 O(log(m + n)) 💪

class Solution {
    func findMedianSortedArrays(_ nums1: [Int], _ nums2: [Int]) -> Double {
       let count1 = nums1.count, count2 = nums2.count
        let totalCount = count1 + count2
        if totalCount % 2 == 1 {
            let mid = totalCount / 2
            return Double(getKthElement(nums1, nums2, mid + 1))
        } else {
            let mid1 = totalCount / 2 - 1, mid2 = totalCount / 2
            return Double(getKthElement(nums1, nums2, mid1 + 1) + getKthElement(nums1, nums2, mid2 + 1)) / 2
        }
    }
    // 获取两个数组中第 K 小的数
    func getKthElement(_ nums1: [Int], _ nums2: [Int],_ k: Int) -> Int {
        var k = k
        let count1 = nums1.count, count2 = nums2.count
        var index1 = 0, index2 = 0
        
        while true {
            if index1 == count1 {
                return nums2[index2 + k - 1]
            }
            if index2 == count2 {
                return nums1[index1 + k - 1]
            }
            if k == 1 {
                return min(nums1[index1], nums2[index2])
            }
            let half = k / 2
            let newIndex1 = min(index1 + half, count1) - 1
            let newIndex2 = min(index2 + half, count2) - 1
            let pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]
            if pivot1 <= pivot2 {
                k -= newIndex1 - index1 + 1
                index1 = newIndex1 + 1
            } else {
                k -= newIndex2 - index2 + 1
                index2 = newIndex2 + 1
            }
        }
    }
}

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

暴力算法

Time:O(n^3) Space: O(1)

func longestPalindrome(_ s: String) -> String {
        // 暴力算法
        let n = s.count
        guard n >= 2 else { return s }
        
        var maxLength = 1
        var begin = 0
        let chars = Array(s)
        // 枚举所有长度严格大于 1 的子串 chars[i..j]
        for i in 0..<n - 1 {
            for j in i + 1..<n {
                if j - i + 1 > maxLength && validHelper(chars, i, j) {
                    maxLength = j - i + 1
                    begin = i
                }
            }
        }
        return String(chars[begin..<(begin + maxLength)])
}

func validHelper(_ arr: [Character], _ i: Int, _ j: Int) -> Bool {
  var i = i
  var j = j
  while i < j {
    if arr[i] != arr[j] {
      return false
    }
    i += 1
    j -= 1
  }
  return true
}

动态规划

Time: O(n^2) space: O(n^2)

func longestPalindrome(_ s: String) -> String {
        // 动态规划
        // dp(i, j) = s[i] == s[j] && dp(i + 1, j - 1)
        let n = s.count
        guard n >= 2 else {
            return s
        }
        var maxLength = 1
        var begin = 0
        var dp = [[Bool]](repeating: [Bool](repeating: false, count: n), count: n)
        // 对角线都为 true(因为单个字符必定为回文序列)
        for i in 0..<n {
            dp[i][i] = true
        }
        let chars = Array(s)
        for j in 1..<n { // 对角线已经处理,所以列从 1 开始
            for i in 0..<j { // 对角线以下不用管,所以只到 j 就结束了
                if chars[i] != chars[j] {
                    dp[i][j] = false
                } else {
                    dp[i][j] = j - i < 3 || dp[i + 1][j - 1] // 简化为 1 行,等同于下面 5 行
//                    if j - 1 - (i + 1) + 1 < 2 { // 表示 j - 1 到 i + 1 之间没有字符或只有一个字符,则肯定是回文串
//                        dp[i][j] = true
//                    } else {
//                        dp[i][j] = dp[i + 1][j - 1] // 状态转移,更里面的子串会优先计算出回文性质
//                    }
                }
                if dp[i][j] && j - i + 1 > maxLength { // 如果状态正确且长度大于原先的最大长度,则对 begin 和 maxLength 进行更新
                    maxLength = j - i + 1
                    begin = i
                }
            }
        }
        return String(chars[begin..<(begin+maxLength)])
    }

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转

	func reverse(_ x: Int) -> Int {
    var x = x
        var res = 0
        while x != 0 {
            let mod = x % 10
            x /= 10
            res = res * 10 + mod
        }
        return res < Int32.min || res > Int32.max ? 0 : res
  }

请你来实现一个 atoi 函数,使其能将字符串转换成整数。首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。 假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。 该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。 注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。在任何情况下,若函数不能进行有效的转换时,请返回 0 。

	func myAtoi(_ str: String) -> Int {
        let chars = Array(str)
        let n = chars.count
        var idx = 0
        while idx < n && chars[idx] == " " {
            idx += 1 // 去空格
        }
        if idx == n {
            return 0
        }
        // 处理符号
        var sign = 1
        if chars[idx] == "-" {
            sign = -1
            idx += 1
        } else if chars[idx] == "+" {
            idx += 1
        } else if !(chars[idx].isASCII && chars[idx].isWholeNumber) {
            return 0
        }
        var ans = 0
        while idx < n {
            if !(chars[idx].isASCII && chars[idx].isWholeNumber) {
                break
            }
            let digit = chars[idx].wholeNumberValue!
            if ans > (Int(Int32.max) - digit) / 10 {
                return Int(sign > 0 ? Int32.max : Int32.min)
            }
            ans = ans * 10 + digit
            idx += 1
        }
        return ans * sign
    }

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

	func isPalindrome(_ x: Int) -> Bool {
        if x < 0 || (x % 10 == 0 && x != 0) {
            return false
        }
        var reverseNumber = 0
        var x = x
        while reverseNumber < x {
            reverseNumber = reverseNumber * 10 + x % 10
            x /= 10
        }
        return reverseNumber == x || x == reverseNumber / 10
    }

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

双指针搞起

	func maxArea(_ height: [Int]) -> Int {
        var result = 0, leftBar = 0, rightBar = height.count - 1
        while leftBar < rightBar {
            let area = (rightBar - leftBar) * min(height[leftBar], height[rightBar])
            result = max(result, area)
            if height[leftBar] < height[rightBar] {
                leftBar += 1
            } else {
                rightBar -= 1
            }
        }
        return result
    }

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

	func longestCommonPrefix(_ strs: [String]) -> String {
        guard strs.count > 0 else { return "" }
        var ans = Array(strs[0])
        for i in 1..<strs.count {
            let chars = Array(strs[i])
            var count = 0
            for j in 0..<min(chars.count, ans.count) {
                if ans[j] != chars[j] {
                    break
                }
                count += 1
            }
            ans = Array(ans[0..<count])
            if ans.isEmpty {
                return ""
            }
        }
        return String(ans)
    }

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

注意:答案中不可以包含重复的三元组。

先对数组排序,方便使用双指针进行夹逼求值,然后左右边界不停调平

func threeSum(_ nums: [Int]) -> [[Int]] {
        var ans = [[Int]]()
        let n = nums.count
        guard n >= 3 else { return ans }
        var a = nums
        a.sort() // 一定记住要排序
        for i in 0..<n {
            guard a[i] <= 0 else { break } // 1. 第一个数一定小于等于0
            if i > 0 && a[i] == a[i - 1] { continue } // 2. 去重
            var lb = i + 1
            var rb = n - 1
            while lb < rb {
                let sum = a[i] + a[lb] + a[rb]
                if sum == 0 {
                    ans.append([a[i], a[lb], a[rb]])
                    while lb < rb && a[lb] == a[lb + 1] { lb += 1 } // 左边界去重
                    while lb < rb && a[rb] == a[rb - 1] { rb -= 1 } // 右边界去重
                    lb += 1
                    rb -= 1
                } else if sum < 0 {
                    lb += 1 // 左边界调平
                } else {
                    rb -= 1 // 右边界调平
                }
            }
        }
        return ans
    }

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

同15题,使用双指针向中间夹逼得出

	func threeSumClosest(_ nums: [Int], _ target: Int) -> Int {
        let n = nums.count
        guard n >= 3 else { return 0 }
        var a = nums
        a.sort()
        var res = a[0] + a[1] + a[2]
        for i in 0..<n {
            var l = i + 1
            var r = n - 1
            while l < r {
                let sum = a[i] + a[l] + a[r]
                if abs(sum - target) < abs(res - target) {
                    res = sum
                }
                if sum > target {
                    r -= 1
                } else if sum < target {
                    l += 1
                } else {
                    return sum
                }
            }
        }
        return res
    }

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

有效字符串需满足:

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

	func isValid(_ s: String) -> Bool {
        let map: [Character: Character] = ["{": "}", "[": "]", "(": ")"]
        var stack = [Character]()
        for char in s {
            if map[char] != nil {
                stack.append(char)
            } else {
                guard let top = stack.popLast(), map[top] == char else {
                    return false
                }
            }
        }
        return stack.isEmpty
    }

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

解法一:迭代 O(m + n)

	func mergeTwoLists(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        var l1 = l1, l2 = l2
        let dummy = ListNode(-1)
        var pre: ListNode? = dummy
        while l1 != nil && l2 != nil {
            if l1!.val < l2!.val {
                pre?.next = l1
                l1 = l1?.next
            } else {
                pre?.next = l2
                l2 = l2?.next
            }
            pre = pre?.next
        }
        pre?.next = l1 != nil ? l1 : l2
        return dummy.next
    }

解法二:递归 O(m + n)

	func mergeTwoLists(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        guard let l1 = l1 else { return l2 }
        guard let l2 = l2 else { return l1 }
        if l1.val < l2.val {
            l1.next = mergeTwoLists(l1.next, l2)
            return l1
        } else {
            l2.next = mergeTwoLists(l1, l2.next)
            return l2
        }
    }

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

方案一:O(NK)

思路:同合并两个有序链表

	func mergeKLists(_ lists: [ListNode?]) -> ListNode? {
        var lists = lists
        let dummy = ListNode(-1)
        var cur: ListNode? = dummy
        while true {
            var minNode: ListNode?
            var minPoint = -1
            for i in 0..<lists.count {
                if lists[i] == nil {
                    continue
                }
                if minNode == nil || lists[i]!.val < minNode!.val {
                    minNode = lists[i]
                    minPoint = i
                }
            }
            if minPoint == -1 {
                break
            }
            cur?.next = minNode
            cur = cur?.next
            lists[minPoint] = lists[minPoint]?.next
        }
        return dummy.next
    }

方案二:两两合并 O(N*LogK)

使用分治法求解

	func mergeKLists(_ lists: [ListNode?]) -> ListNode? {
        func merge2List(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
            guard let l1 = l1 else { return l2 }
            guard let l2 = l2 else { return l1 }
            if l1.val < l2.val {
                l1.next = merge2List(l1.next, l2)
                return l1
            } else {
                l2.next = merge2List(l1, l2.next)
                return l2
            }
        }
        func mergeHelper(_ lists: [ListNode?], _ l: Int, _ r: Int) -> ListNode? {
            if l == r {
                return lists[l]
            }
            if l > r {
                return nil
            }
            let mid = (l + r) >> 1
            let l1 = mergeHelper(lists, l, mid)
            let l2 = mergeHelper(lists, mid + 1, r)
            return merge2List(l1, l2)
        }
        return mergeHelper(lists, 0, lists.count - 1)
    }

给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

使用慢指针进行比较和赋值

	func removeDuplicates(_ nums: inout [Int]) -> Int {
        guard nums.count > 1 else { return nums.count }
        var slow = 0
        for i in 0..<nums.count {
            if nums[i] != nums[slow] {
                slow += 1
                nums[slow] = nums[i]
            }
        }
        return slow + 1
    }

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

	func search(_ nums: [Int], _ target: Int) -> Int {
        guard nums.count > 0 else { return -1 }
        var lb = 0, rb = nums.count - 1
        while lb <= rb {
            let mid = lb + (rb - lb) >> 1
            if nums[mid] == target {
                return mid
            }
            if nums[mid] >= nums[lb] {
                // mid 在左边
                if target >= nums[lb] && target < nums[mid] { // 在有序区间
                    rb = mid - 1
                } else {
                    lb = mid + 1
                }
            } else {
                // mid 在右边
                if target > nums[mid] && target <= nums[rb] { // 在有序区间
                    lb = mid + 1
                } else {
                    rb = mid - 1
                }
            }
        }
        return -1
    }

Easy

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

原地反转

	func reverseWords(_ s: String) -> String {
        var chars = [Character](s)
        var i = 0
        while i < chars.count {
            let start = i
            while i < chars.count && chars[i] != " " {
                i += 1
            }
            var left = start, right = i - 1
            while left < right {
                chars.swapAt(left, right)
                left += 1
                right -= 1
            }
            while i < chars.count && chars[i] == " " {
                i += 1
            }
        }
        return String(chars)
    }

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

	func reverseString(_ s: inout [Character]) {
        guard s.count > 1 else { return }
        var left = 0, right = s.count - 1
        while left < right {
            s.swapAt(left, right)
            left += 1
            right -= 1
        }
    }

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

  • 动态规划.

计算前面的和与当前的位置的和,如果前面的和大于0则相加,否则,直接覆盖,并一边计算最大结果

func maxSubArray(_ nums: [Int]) -> Int {
        guard nums.count > 0 else { return 0 }
        var sum = 0, maxAns = nums[0]
        for n in nums {
            if sum > 0 {
                sum += n
            } else {
                sum = n
            }
            maxAns = max(maxAns, sum)
        }
        return maxAns
    }

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

**注意:**给定 n 是一个正整数。

	func climbStairs(_ n: Int) -> Int {
        guard n > 2 else { return n }
        var first = 1
        var second = 2
        for _ in 3...n {
            (first, second) = (second, first + second)
        }
        return second
    }

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

	func merge(_ nums1: inout [Int], _ m: Int, _ nums2: [Int], _ n: Int) {
        // 双指针
        var l1 = m - 1
        var l2 = n - 1
        var l = m + n - 1
        
        while l1 >= 0 && l2 >= 0 {
            if nums1[l1] > nums2[l2] {
                nums1[l] = nums1[l1]
                l1 -= 1
            } else {
                nums1[l] = nums2[l2]
                l2 -= 1
            }
            l -= 1
        }
        if l2 >= 0 {
            nums1.replaceSubrange(0..<l2 + 1, with: nums2[0..<l2 + 1])  // 这里可以替换为 while 循环
            /*
            while l2 >= 0 {
                nums1[l2] = nums2[l2]
                l2 -= 1
            }
             */
        }
    }

求二叉树的最大深度,也就是根节点到叶子节点的最远路径的节点数。

推荐使用深度优先,递归即可快速解题,并且复杂度和广度优先相同

解法一:深度优先

class Solution {
    func maxDepth(_ root: TreeNode?) -> Int {
        guard let root = root else { return  0 }
        return max(maxDepth(root.left), maxDepth(root.right)) + 1
    }
}

解法二:广度优先

	func maxDepth(_ root: TreeNode?) -> Int {
        guard let root = root else { return 0 }
        var queue = [TreeNode]()
        queue.append(root)
        var depth = 0
        while !queue.isEmpty {
            depth += 1
            let width = queue.count
            for i in 0..<width {
                let node = queue.removeFirst()
                if let leftNode = node.left {
                    queue.append(leftNode)
                }
                if let rightNode = node.right {
                    queue.append(rightNode)
                }
            }
        }
        return depth
    }

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

低点买入,高点抛出,通俗易懂

	func maxProfit(_ prices: [Int]) -> Int {
        var minProfit = Int.max
        var maxProfit = 0
        for p in prices {
            if p < minProfit {
                minProfit = p
            } else {
                maxProfit = max(maxProfit, p - minProfit)
            }
        }
        return maxProfit
    }

给定一个 node 节点,将其在链表中删除。(比较 tricky,直接换 val 可还行?)

class Solution {
    func deleteNode(_ node: ListNode?) {
        guard let next = node?.next else { return }
        node?.val = next.val
        node?.next = next.next
    }
}