Swift 数据结构与算法( 38) + Leetcode128. 最长连续序列(哈希表)

119 阅读6分钟

Swift 数据结构与算法( ) + Leetcode 掘金 #日新计划更文活动

题目

128. 最长连续序列

思路 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) **的算法解决此问题。

 

示例 1:

输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入: nums = [0,3,7,2,5,8,4,6,0,1]
输出: 9

 

提示:

  • 0 <= nums.length <= 105

  • -109 <= nums[i] <= 109

class Solution {
 func longestConsecutive(_ nums: [Int]) -> Int {

 }
}

解题思路🙋🏻‍ ♀️

题目分析

题目给定了一个未排序的整数数组 nums,要求找出其中数字连续的最长序列的长度。这里的连续序列不要求在原数组中物理上连续,只要数字连续即可。

要求回答的是什么: 函数的返回值应该是一个整数,表示数字连续的最长序列的长度。

题目类型: 这是一个数组处理和查找的问题,它涉及到查找连续的数字序列。

解题思路

  1. 使用 Set

    • 首先,将所有数字放入一个 Set 中,这样我们可以在 O(1) 的时间内检查一个数字是否存在。
    • 然后,遍历每个数字,检查它是否是一个序列的开始。这可以通过检查 num - 1 是否存在来实现。如果 num - 1 不存在,那么 num 是序列的开始。
    • num 开始,我们可以继续检查 num + 1num + 2 等是否存在,直到找不到连续的数字为止,这样我们就可以找到从 num 开始的连续序列的长度。
    • 最后,返回最长的连续序列长度。
  2. 时间复杂度为 O(n)

    • 将所有数字放入 Set 的时间复杂度为 O(n)。
    • 由于我们只从每个连续序列的开始数字进行查找,所以虽然内部有一个循环,但所有数字只会被遍历两次(一次放入 Set,一次从 Set 中移除)。因此,总的时间复杂度仍然是 O(n)。

示例演示

考虑一个简单的例子,nums = [100, 4, 200, 1, 3, 2]。我们希望找到最长的连续序列长度。


初始化阶段

  • numSet = [100, 4, 200, 1, 3, 2]
  • longestStreak = 0

开始遍历 numSet

第一次迭代:num = 100

  • 检查 99 是否在 numSet 中 -> 不在
  • 开始寻找连续序列:
    • currentNum = 100
    • currentStreak = 1
    • 检查 101 是否在 numSet 中 -> 不在
  • longestStreak = max(0, 1) = 1

第二次迭代:num = 4

  • 检查 3 是否在 numSet 中 -> 在
  • 继续下一个迭代

第三次迭代:num = 200

  • 检查 199 是否在 numSet 中 -> 不在
  • 开始寻找连续序列:
    • currentNum = 200
    • currentStreak = 1
    • 检查 201 是否在 numSet 中 -> 不在
  • longestStreak = max(1, 1) = 1

第四次迭代:num = 1

  • 检查 0 是否在 numSet 中 -> 不在
  • 开始寻找连续序列:
    • currentNum = 1
    • currentStreak = 1
    • 检查 2 是否在 numSet 中 -> 在
      • currentNum = 2
      • currentStreak = 2
      • 检查 3 是否在 numSet 中 -> 在
        • currentNum = 3
        • currentStreak = 3
        • 检查 4 是否在 numSet 中 -> 在
          • currentNum = 4
          • currentStreak = 4
          • 检查 5 是否在 numSet 中 -> 不在
  • longestStreak = max(1, 4) = 4

第五次和第六次迭代:

num = 3num = 2 都是连续序列的中间数字,所以我们直接跳过它们。


结果

最长的连续序列长度为 4,即 [1, 2, 3, 4]

边界思考🤔

代码

基础版本

class Solution {
func longestConsecutive(_ nums: [Int]) -> Int {

 
 var numsSet = Set(nums)
 
 //current
 
 var maxNumber = 0
 

 for item in nums {
     // only 1
     if numsSet.contains(item - 1) {
        continue
     }
     
     var currentN = item
     var number = 0
     
     while numsSet.contains(currentN)  {
           number += 1
           numsSet.remove(currentN)  // 从 Set 中移除已找到的元素
           currentN += 1
           maxNumber = max(number, maxNumber)
     }
 }
 return maxNumber
}
}

class Solution {
    func longestConsecutive(_ nums: [Int]) -> Int {
        // 创建一个可变的 Set,便于后续删除元素
        var numsSet = Set(nums)
        var maxNumber = 0

        for num in nums {
            // 只处理还在 Set 中的元素
            if numsSet.contains(num) {
                numsSet.remove(num) // 从 Set 中移除当前元素

                // 向左和向右扩展连续序列
                var left = num - 1
                var right = num + 1
                while numsSet.contains(left) {
                    numsSet.remove(left)
                    left -= 1
                }
                while numsSet.contains(right) {
                    numsSet.remove(right)
                    right += 1
                }

                // 更新最长连续序列长度
                maxNumber = max(maxNumber, right - left - 1)
            }
        }

        return maxNumber
    }
}

时空复杂度分析

错误与反思优化

为了确保代码的时间效率达到最优,您可以考虑以下几点:

  1. 避免不必要的查找

    • 您已经做到了这一点,通过检查 item - 1 是否在 numsSet 中来确定是否开始查找连续序列。
  2. 减少数据结构转换

    • 您直接从数组转换为 Set,这是必要的,因为 Set 提供了 O(1) 的查找时间。确保不进行其他不必要的数据结构转换。
  3. 在找到连续序列后从 Set 中移除这些元素

    • 当您找到一个连续序列后,可以从 numsSet 中移除这些元素,以减少后续迭代中的查找次数。但这种优化可能并不总是带来性能提升,因为从 Set 中删除元素也需要时间。

以下是考虑上述优化建议后的代码:

class Solution {
    func longestConsecutive(_ nums: [Int]) -> Int {
        // 创建一个可变的 Set,便于后续删除元素
        var numsSet = Set(nums)
        var maxNumber = 0

        for item in nums {
            // 只从连续序列的开始查找
            if !numsSet.contains(item - 1) {
                var currentN = item
                var number = 0

                while numsSet.contains(currentN) {
                    number += 1
                    numsSet.remove(currentN)  // 从 Set 中移除已找到的元素
                    currentN += 1
                    maxNumber = max(number, maxNumber)
                }
            }
        }
        return maxNumber
    }
}

概念

使用场景与应用

核心概念:集合 (Set) 和哈希表 (Hash Table)

这个问题主要考察了我们如何使用集合来进行高效的数据查找和删除操作。集合和哈希表都提供了平均 O(1) 的时间复杂度,使我们能够在 O(n) 的时间内解决这个问题。

实际应用场景

  1. 数据库的去重操作

    • 技术点:在处理大量数据时,我们经常需要去除重复的数据。哈希表和集合都是进行去重操作的好方法。
  2. 社交网络中的好友推荐

    • 技术点:通过查找有共同好友但尚未连接的人,可以建议用户添加新的好友。哈希表可以帮助我们高效地找到这样的推荐。
  3. 网络安全与入侵检测

    • 技术点:通过跟踪连续的或异常的网络请求,可以检测到潜在的网络攻击。哈希表可以帮助我们高效地处理和跟踪这些请求。

iOS app 开发中的实际使用场景

  1. 缓存管理

    • 技术应用:当你需要缓存大量的数据,如图像或文档,哈希表可以帮助你快速查找缓存中的数据,确保不重复缓存相同的数据。
  2. 用户行为分析

    • 技术应用:为了提供个性化的内容,你可以跟踪用户的行为和偏好。哈希表可以帮助你高效地存储和分析这些数据。
  3. 快速搜索与自动完成功能

    • 技术应用:当用户在搜索框中输入时,应用可以使用哈希表来快速提供相关的搜索建议。
  4. 好友/联系人查找

    • 技术应用:在社交应用或通讯录中,当用户查找好友或联系人时,可以使用哈希表来加速搜索过程,尤其是在数据量很大时。