swift 数据结构与算法

725 阅读6分钟

1. 数组

数组是最基本的数据结构,在swift中以前Objcective-C中将NSMutableArray 和NSArray份额开的做法被统一到了唯一的数据结构Array。

// 声明不可变数组
let nums = [1,2,3]
let strs:[String] = ["1","2","3"];


// 声明可变数组
var nums = [Int](repeating: 0, count: 5)
nums.append(4)
nums.append(contentsOf: [1,2,3])
// 对原数组进行升序排序
nums.sort() 

let anotherNums = Array(nums[0..<nums.count - 1])

用数组实现栈

class Stack<Element> {
    var stack: [Element]
    var isEmpty:Bool { return stack.isEmpty}
    var peek:Element? {return stack.last}
    
    init(){
        stack = [Element]()
    }
    
    func push(object:Element) {
        stack.append(object)
    }
    
    func pop()->Element? {
        
        if(stack.isEmpty) {
            return nil
        } else {
            return stack.removeLast()
        }
    }
    
}

2. 字典和集合

集合

let primeNums: Set = [3,5,7,11,13]
let oddNums: Set = [1,3,5,7,9]

// 交并差集
let primeAddOddNums = primeNums.intersection(oddNums)
let primeOrOddNums = primeNums.union(oddNums)
let oddNotPrimNums = oddNums.subtracting(oddNums)
  1. 给出一个整形数组和一个目标值,判断数组中是否有两个数之和等于目标值
func twoSum1(nums:[Int],_ target: Int) -> Bool {
    var set = Set<Int>()
    for num in nums {
        if set.contains(target - num) {
            return true
        }
        set.insert(num)
    } 
    return false
}
  1. 给出一个整形数组有且仅有两个数之和等于目标值,求这两个数咋数组中的序号
func twoSum2(nums:[Int],_ target: Int) -> [Int] {
    var dict = Dictionary<Int,Int>()
    for (num, i) in nums.enumerated() {
        if let lastIndex = dict[target-num] {
            return [lastIndex,i]
        }
        dict[num] = i;
    }
    fatalError("No valid output!")
}

3. 字符串

字符串在算法中极其常见,在Swift中,字符串不同于其他语言,它是值类型,而非引用类型。

var str = "3"
let num = Int(str)
let number = 3
let string = String(describing: num)

// 字符串长度
let len = str.count

// 访问单个字符
//let char = str[str.index(str.startIndex, offsetBy: n)]
// 修改字符串
//str.remove(at: n)
str.append("c")
str += "hello world"

// 检测字符串是否由数字组成
func isStrNum(str:String) ->Bool {
    return Int(str) != nil;
}
// 将字符串按照字母顺序不考虑大小写
func sortStr(str:String) -> String {
    return String(str.sorted())
}

给出一个字符串,要求将其按照单词顺序进行反转。

func reverseString(_ s: inout [Character]) {
    if s.count == 0 {
        return
    }
    s = s.reversed()
}

func reverseString1(_ s: inout [Character]) {
    if s.count == 0 {
        return
    }
    var x = 0
    var y = s.count - 1
    while(x < y){
        s.swapAt(x, y)
        x += 1
        y -= 1
    }
}

3. 链表

常见的数据结构,不再赘述。

// 链表节点
class ListNode {
    var val:Int
    var next:ListNode?
    init(_ val:Int) {
        self.val = val
        self.next = nil
    }
}

class List {
    var head:ListNode?
    var tail:ListNode?
    //尾插法
    func appendToTail(_ val:Int) {
        if tail == nil {
            tail = ListNode(val)
        } else {
            tail!.next = ListNode(val)
            tail = tail!.next;
            
        }
    }
    // 头插法
    func appendToHead(_ val:Int) {
        if head == nil {
            head = ListNode(val)
        } else {
            let temp = ListNode(val)
            temp.next = head
            head = temp;
        }
    }
}
  1. 给出一个链表和一个值x,要求将链表中所有小于x的值放到左边,所有大于x的值放到右边,并且原链表的节点顺序不能变。 例如 1->5->3->2->4->2 给定2 返回1->2->2->5->3->4
func partition(_ head:ListNode? , _ x:Int) ->ListNode? {
    // 引入Dummy节点
    let preDummy = ListNode(1),postDummy = ListNode(0)
    var prev = preDummy, post = postDummy
    
    var node = head
    while node != nil {
        if (node!.val < x) {
            prev.next = node;
            prev = node!;
        }else {
            post.next = node;
            post = node!;
        }
        node = node!.next;
    }
    // 防止构成环
    post.next = nil
    // 左右拼接
    prev.next = postDummy.next;
    return preDummy.next
}
  1. 快慢指针,如何检测一个链表中是否有环?
func hasCycle(_ head:ListNode?)-> Bool {
    var slow = head,fast = head
    while fast != nil && fast?.next != nil {
        fast = fast!.next!.next;
        slow = slow!.next
        if slow === fast {
            return true
        }
        
    }
    return false
}

3.删除链表中倒数第k个节点 例: 1->2->3->4->5,n=2, 返回1->2->4->5 思路:快慢指针 但是速度一样, 一开始第一个指针落后第二个指针n个节点,接着二者同时移动,当第二个指针移动到尾结点时,第一个节点的下一个节点就是我们要删除的节点

func removeNthFromEnd(_ head: ListNode? ,_ n: Int)-> ListNode? {
    
    //安全处理
    guard let head = head else {
        return nil
    }
    
    let dummy = ListNode(0)
    dummy.next = head
    var pre:ListNode? = dummy
    var post:ListNode? = dummy
    
    
    // 1. 提前走N步
    for _ in 0..<n {
        if post == nil {
            break
        }
        post = post!.next
    }
    
    // 2. 同时移动快慢指针
    while post != nil && post?.next != nil {
        post = post!.next
        pre = pre!.next
    }
    
    // post== nil || 或者是最后一个节点
    // 删除对应节点
    pre!.next = pre!.next!.next
    return dummy.next
}
  1. 链表逆序 两种方法:
  2. 头插法(创建新节点,代表返回的链表,不断遍历链表,头插入这条链表)
  3. 直接置换法 (思路同方法一) 只不过是 没有创建新节点
func reverse(head:ListNode?) -> ListNode?{
    //实现思路
    // 1. 头插法
    let temp = ListNode()
    var cur = head

    var next = cur

    while(cur != nil) {
        next = cur!.next
        // 头插法
        cur!.next = temp.next
        temp.next = cur
        cur = next
    }

    return temp.next
    
}

直接替换法

func reverse(head:ListNode?) -> ListNode?{
    
    // 直接倒置法
    var pre:ListNode? = nil
    var cur = head
    var next = head
    
    while (cur != nil) {
        next = cur?.next
        cur?.next = pre
        
        pre = cur
        cur = next
    }
    
    return pre
}
  1. 以K为一组反转链表 1->2->3->4->5 k =2 输出 5->2->3->4->1 k =3 输出 4->5->1->2->3
// 以K个节点为一组进行链表反转
func reverseKthList(head :ListNode?, k : Int) -> ListNode? {
    // 实现思路:跟倒序是一样的,只不过是 以K为一组,保存下一个第K个位置防止断掉 保存下一个位置
    var cur = head
    var next = head
        
    let temp:ListNode = ListNode()
    
    while (cur != nil) {
        
        let (tail,hasMore) = nextKthNode(head: cur, k: k-1)
        next = tail?.next
        tail?.next = temp.next
        temp.next = cur
        cur = next;
        if (hasMore == false)  {
            break;
        }
    }
    return temp.next
}

func nextKthNode(head :ListNode? , k: Int) ->(ListNode? ,Bool){
    var times: Int = k
    var cur = head
    while (times > 0 && cur?.next != nil) {
        cur = cur?.next
        times -= 1
    }
    return (cur,times == 0)
}

直接替换法: 核心是保留链表前后节点,然后四步走.

// 以K个节点为一组进行链表反转
func reverseKthList(head :ListNode?, k : Int) -> ListNode? {        
    var cur = head
    var pre:ListNode? = nil
    while (cur != nil) {
        let temp = cur;
        let tail = nextKthNode(head: cur, k: k-1).0
        let next = tail?.next
        tail?.next = pre
        pre = temp
        cur = next
    }
    return pre
}

func nextKthNode(head :ListNode? , k: Int) ->(ListNode? ,Bool){
    var times: Int = k
    var cur = head
    while (times > 0 && cur?.next != nil) {
        cur = cur?.next
        times -= 1
    }
    return (cur,times == 0)
}

4. 栈和队列

栈是一个非常重要的数据结构,比如对于一些递归算法,也是通过栈来完成,通过栈,也能模拟递归的调用过程。然而,swift中没有内设的栈和队列,很多扩展库中使用Generic Type 来实现栈和队列。

无论是在面试还是在写App时,主要关注栈的几个基本操作:push,pop,isEmpty,peek,size.

protocol Stack {
    // 持有的元素类型
    associatedtype Element
    
    // 是否为空
    var isEmpty:Bool {get}
    var size:Int {get}
    var peek:Element? {get}
    // 进栈
    mutating func push(_ newElement:Element)
    
    // 出栈
    mutating func pop() -> Element?
}


struct IntegerStack:Stack {
    typealias Element = Int

    var isEmpty: Bool { return stack.isEmpty }
    
    var size: Int { return stack.count }
    
    var peek: Int? { return stack.last}
    
    private var stack = [Element]()
    
    mutating func push(_ newElement: Int) {
        stack.append(newElement)
    }
    
    mutating func pop() -> Int? {
        return stack.popLast()
    }
}

主要关注队列的几个基本操作:enqueue,dequeue,isEmpty,peek,size.

protocol Queue {
    // 持有元素类型
    associatedtype Element
    
    // 是否为空
    var isEmpty:Bool {get}
    var size:Int {get}
    // 队首元素
    var peek:Element? {get}
    // 入队
    mutating func enqueue(_ newElement:Element)
    // 出队
    mutating func dequeue() -> Element?
}

struct IntegerQueue: Queue {
    typealias Element = Int

    var isEmpty: Bool { return left.isEmpty && right.isEmpty }
    
    var size: Int { return left.count + right.count }
     
    var peek: Int? { return left.isEmpty ? right.first : left.last}
    
    private var left = [Element]()
    private var right = [Element]()
    
    mutating func enqueue(_ newElement: Int) {
        right.append(newElement)
    }
    
    mutating func dequeue() -> Int? {
        if left.isEmpty {
            left = right.reversed()
            right.removeAll()
        }
        return left.popLast()
    }
}
  1. 给出一个文件的绝对路径,要求将其简化,举一个例子,路径是”/home/“简化后为"/home" 路径是"/a/./b/../../c" 简化后为"/c" 思路根据"/" 拆分成String数组,对于".."相当于做pop操作,其他情况不做处理
func simplifyPath(path:String) -> String {
    var pathStack = [String]()
    let paths = path.components(separatedBy: "/")
    for path in paths {
        guard path != "." else {
            continue
        }
        if path == ".." {
            if (pathStack.count > 0) {
                pathStack.removeLast()
            }
        } else if path != "" {
            pathStack.append(path)
        }
    }
    // 将栈中的内容转化为优化后的新路径
    let res = pathStack.reduce("") { total, dir in "\(total)\(dir)"}
    
    // 注意空路径结果是 "/"
    return res.isEmpty ? "/" : res
}

5. 二叉树

节点定义

public class TreeNode {
    var left:TreeNode?
    var right:TreeNode?
    var val: Int
    init(_ val:Int) {
        self.val = val
    }
}
  1. 递归计算树的最大深度
func maxDepth(root:TreeNode?) -> Int {
    guard let root = root else {
        return 0
    }
    return 1+max(maxDepth(root: root.left),maxDepth(root: root.right))
}
  1. 栈实现二叉树前序遍历
func preorderTraversal(root: TreeNode?) -> [Int] {
    
    var res = [Int]()
    var stack = [TreeNode]()
    var node = root
    while !stack.isEmpty || node != nil {
        if node != nil {
            // 先遍历自己
            res.append(node!.val)
            stack.append(node!)
            // node 左子树节点遍历
            node = node!.left
        } else {
            node = stack.removeLast().right
        }
    }
    return res
}
  1. 队列实现二叉树层序遍历
func levelOrder(root: TreeNode?) -> [[Int]] {
    var res = [[Int]]()
    var queue = [TreeNode]()
    if let root = root {
        queue.append(root)
    }
    
    while queue.count > 0 {
        let size = queue.count
        var level = [Int]()
        for _ in 0..<size {
            // 出队 
            let node = queue.removeFirst()
            level.append(node.val)
             // 入队 
            if let left = node.left {
                queue.append(left)
            }
            if let right = node.right {
                queue.append(right)
            }
        }
        res.append(level)
    }
    return res
}

算法

1. 排序与搜索

  1. 归并排序
func mergeSort(_ array:[Int]) -> [Int] {
    var helper = [Int](repeating: 0, count: array.count),array = array
    mergeSort(&array, &helper, 0, array.count-1)
    return array
    
}

func mergeSort(_ array: inout [Int],_ helper : inout [Int], _ low: Int ,_ high:Int) {
    guard low < high else {
        return
    }
    let middle = (high - low)/2 + low
    mergeSort(&array, &helper, low, middle)
    mergeSort(&array, &helper, middle+1, high)
    

}


func merge(_ array: inout [Int],_ helper : inout [Int], _ low: Int , _ middle:Int, _ high:Int) {
    // copy both halves int a helper array
    for i in low ... high {
        helper[i] = array[i]
    }
    
    var helperLeft = low, helperRight = middle+1,current = low
    //iterate through helper array and copy the right one to orignial array
    while helperLeft<=middle && helperRight <= high {
        if helper[helperLeft] <= helper[helperRight] {
            array[current] = helper[helperLeft];
            helperLeft += 1
        } else {
            array[current] = helper[helperRight];
            helperRight += 1
        }
        current += 1
    }
    // handle the rest
    guard middle - helperLeft >= 0 else {
        return
    }
    for i in 0 ... middle - helperLeft {
        array[current+i] = helper[helperLeft + i]
    }
    
}
  1. 快速排序
func quickSort(_ array:[Int]) -> [Int] {
    guard array.count > 1 else {
        return array
    }
    let priot = array[array.count/2]
    let left = array.filter{ $0 < priot }
    let middle  = array.filter{ $0 == priot }
    let right  = array.filter{ $0 > priot }
    return quickSort(left) + middle + quickSort(right)
}