《剑指Offer》阅读笔记(swift):数组篇(11题)

1,150 阅读4分钟

我的Github地址

小码哥《恋上数据结构与算法》笔记

极客时间《iOS开发高手课》笔记

iOS大厂面试高频算法题总结

iOS面试资料汇总

  • 数组(11道):
    • 剑指Offer(3):数组中重复的数字
    • 剑指Offer(4):二维数组中的查找
    • 剑指Offer(11):旋转数组的最小数字
    • 剑指Offer(21):调整数组顺序使奇数位于偶数前面
    • 剑指Offer(39):数组中出现次数超过一半的数字
    • 剑指Offer(42):连续子数组的最大和
    • 剑指Offer(45):把数组排成最小的数
    • 剑指Offer(51):数组中的逆序对
    • 剑指Offer(56):数字在排序数组中出现的次数
    • 剑指Offer(56):数组中只出现一次的数字
    • 剑指Offer(66):构建乘积数组

面试题3:数组中重复的数字

题目一:

思路一:

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

代码

func findRepeatNumber(_ nums: [Int]) -> Int {
    let newNums = nums.sorted()
    for (i, v) in newNums.enumerated() {
        if v == newNums[i + 1] {
            return v
        }
    }
    return 0
}

思路二:哈希表

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码

func findRepeatNumber(_ nums: [Int]) -> Int {
    var map = [Int: Int]()
    for item in nums{
        if map.keys.contains(item){
            map.updateValue(map[item]! + 1, forKey: item)
        }else{
            map.updateValue(1, forKey: item)
        }
    }
    
    var num: Int = 0
    for (key, value) in map{
        if value > 1{
            num = key
        }
    }
    return num
}

思路三:原地交换

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码

func findRepeatNumber(_ nums: [Int]) -> Int {
    guard nums.count > 0 else {
        return -1
    }
    var nums = nums
    for i in 0..<nums.count {
        while i != nums[i] {
            if nums[i] == nums[nums[i]] {
                return nums[i]
            }
            nums.swapAt(i, nums[i])
        }
    }
    return -1
}

题目二:

思路一:辅助数组

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码

func findRepeatNumber(_ nums: [Int]) -> Int {
    var arr = Array.init(repeating: -1, count: nums.count)
    for item in nums {
        if arr[item] != -1 {
            return item
        } else {
            arr[item] = item
        }
    }
    return -1
}

思路二:类似二分查找

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)

代码

func duplicate(_ nums: [Int]) -> Int? {
    // 所有数字都在1到 nums.count-1 大小范围内, min 和 max 表示这个范围
    var start = 1
    var end = nums.count - 1
    
    while start <= end {
        let middle = (end - start) / 2 + min
        let countAtRange = nums.filter { $0 >= start && $0 <= middle }.count
        
        //退出条件
        if start == end {
            if countAtRange > 1 {
                return start
            } else {
                break
            }
        }
        
        //范围缩小
        if countAtRange > (middle - start + 1) {
            end = middle
        } else {
            start = middle + 1
        }
    }
    return -1
}

面试题4:二维数组中的查找

题目一:

思路一

代码

    func findNumberIn2DArray(_ matrix: [[Int]], _ target: Int) -> Bool {
        if matrix.count == 0 {
            return false
        }
        
        let rows = matrix.count
        let columns = matrix.first!.count
        
        var row = 0
        var column = columns - 1
        
        while row < rows && column >= 0 {
            let number = matrix[row][column]
            if number == target {
                return true
            } else if target > number {
                row = row + 1
            } else if target < number {
                column = column - 1
            }
        }
        return false
    }

面试题11:旋转数组的最小数字

题目一:

思路一

思路二

代码

    func minArray(_ numbers: [Int]) -> Int {
        var min = 0
        var max = numbers.count - 1

        while min < max {
            let mid = (max + min) / 2
            
            if numbers[mid] > numbers[max] {
                min = mid + 1;
            }else if numbers[mid] < numbers[max] {
                max = mid;
            } else {
                max -= 1
            }
        }
        return numbers[min]
    }

面试题21:调整数组顺序使奇数位于偶数前面

题目一:

思路一

思路二

代码

    func exchange(_ nums: [Int]) -> [Int] {
        
        if nums.count == 0 { return [] }
        
        var nums = nums
        var l = 0
        var r = nums.count - 1

        while l < r {
            while l < r && nums[l] % 2 != 0 {
                l += 1
            }

            while l < r && nums[r] % 2 == 0 {
                r -= 1
            }

            if l < r {
                let temp = nums[r]
                nums[r] = nums[l]
                nums[l] = temp
            }
        }
        return nums
    }

面试题39:数组中出现次数超过一半的数字

题目一:

思路一

思路二:投票法

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码

  func majorityElement(_ nums: [Int]) -> Int {
    
    var count = 0
    var candidate = nums[0]
    
    for num in nums {
      if count == 0 {
        candidate = num
      }
      count += (num==candidate) ? 1:-1
    }
    return candidate
  }

思路三:排序

  • 多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。也就是说 多数元素至少占总数组的一半以上,那么在排序好的数组中,多数元素肯定在数组中间也有出现

代码

func majorityElement(_ nums: [Int]) -> Int {
      return nums.sorted()[nums.count / 2]
}

思路四:哈希表

  • 使用哈希表存储元素出现的次数, 因为多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。所以在进行统计时,可以优化操作

代码

  func majorityElement(_ nums: [Int]) -> Int {
    //! 初始化操作
    var hashMap = [Int:Int]()
    for num in nums {
      if hashMap[num] == nil {
        //! 说明从未保存过,
        hashMap[num] = 1
      } else {
        //! 否则,代表之前已经保存过了,如果是多数元素,那么在+1前,本身出现的次数已经 = nums.count/2
        if hashMap[num]! == nums.count/2 {
          return num
        } else {
          hashMap[num] = hashMap[num]!+1
        }
      }
    }
    //! 没有找到,则默认输出第一个元素
    return nums[0]
  }![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2463f639c068494ea224fe847be274ca~tplv-k3u1fbpfcp-watermark.image)![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/67c2c918da45400fbf7875ef81963643~tplv-k3u1fbpfcp-watermark.image)

面试题42:连续子数组的最大和

题目一

思路一

思路二:分析数组的规律

代码

func maxSubArray(_ nums: [Int]) -> Int {
    
    // 注意:result的初始值为第一个元素。
    // 1. 创建变量
    var result = nums[0] // result:连续子数组的最大和
    var sum = 0 // sum:遍历数组元素和
    
    for i in nums {
        // 2. 如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
        if sum > 0 {
            sum += i
        }
        // 3. 如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
        else {
            sum = i
        }
        // 3. 每次比较 sum 和 ans的大小,将最大值置为result,遍历结束返回结果
        result = result >= sum ? result : sum
    }
    return result
}

思路三: 动态规划

代码

    func maxSubArray(_ nums: [Int]) -> Int {
        var numArray = nums
        var res = numArray[0]
        for index in 1 ..< numArray.count {
            numArray[index] += max(0, numArray[index-1])
            res = max(res, numArray[index])
        }
        return res
    }

面试题45:把数组排成最小的数

题目一

思路一

代码

    func minNumber(_ nums: [Int]) -> String {
        // transfer to [String] type
        var nums = nums.compactMap{"\($0)"} 
        // sort by compare combinations; O(nLogn)
        nums.sort(by: {$0 + $1 < $1 + $0})
        return nums.joined()
    }

面试题51:数组中的逆序对

题目一

思路一

代码

class Solution {
    func merge(_ nums: inout [Int], _ left: Int, _ mid: Int, _ right: Int, _ temp: inout [Int]) -> Int  {
        var i = left, j = mid + 1, k = 0
        var count = 0
        while i <= mid && j <= right {
            if nums[i] <= nums[j] {
                temp[k] = nums[i]
                i+=1
                count+=(j-mid-1)
            } else {
                temp[k] = nums[j]
                j+=1
            }
            k+=1
        }
        while i <= mid {
            temp[k] = nums[i]
            i+=1
            k+=1
            count+=(j-mid-1)
        }
        while j <= right {
            temp[k] = nums[j]
            j+=1
            k+=1
        }
        var l = left, t = 0
        while l <= right {
            nums[l] = temp[t]
            l+=1
            t+=1
        }
        return count
    }

    func reversePairs1(_ nums: inout [Int], _ left: Int, _ right: Int, _ temp: inout [Int]) -> Int {
        guard left < right else { return 0 }
        let mid = (left + right) / 2
        let leftPairs = reversePairs1(&nums, left, mid, &temp)
        let rightPairs = reversePairs1(&nums, mid+1, right, &temp)
        if nums[mid] <= nums[mid+1] {
            return leftPairs + rightPairs
        }
        let corssPairs = merge(&nums, left, mid, right, &temp)
        return leftPairs + rightPairs + corssPairs
    }

    func reversePairs(_ nums: [Int]) -> Int {
        var nums = nums
        var temp = Array(repeating: 0, count: nums.count)
        return reversePairs1(&nums, 0, nums.count - 1, &temp)
    }
}

面试题56:数组中数字出现的次数

题目一

思路一

代码

    func singleNumbers(_ nums: [Int]) -> [Int] {
        var tmp = 0
        for i in 0..<nums.count {
            tmp ^= nums[i]
        }
        var n = 1
        while (tmp & n) == 0 {  //求最低位的1
            n <<= 1
        }
        var a = 0
        var b = 0
        for i in 0..<nums.count {
            if (nums[i] & n) != 0 {
                a ^= nums[i]
            }
            else {
                b ^= nums[i]
            }
        }
        return [a, b]
    }

题目二

思路一

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

思路二:哈希表

  • 用一个哈希表来记录数组中每个数字出现的次数,但这个哈希表需要O(n)的空间。
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码

    func singleNumber(_ nums: [Int]) -> Int {
        // 1. 建立一个[Int:Int]字典,
        var dict: [Int:Int] = [:]

        // 2. 对数组进行遍历,有一个数就增加一个计数
        for num in nums {
            dict[num, default: 0] += 1
        }

        // 3. 最后遍历字典,返回找出计数为1的
        for e in dict{
            if e.value == 1 {
                return e.key
            }
        }
        return -1
    }

面试题66:构建乘机数组

题目一

思路一

代码

    func constructArr(_ a: [Int]) -> [Int] {
        guard a.count > 0 else {
            return []
        }
        let count = a.count
        var left = 1
        var result = [Int]()
        for i in 0..<count {
            result.append(left)
            left *= a[i]
        }
        var right = 1
        for i in (0..<count).reversed() {
            result[i] *= right
            right *= a[i]
        }
        return result
    }