数组
数组是最基本的数据结构。在Swift 中,以前Obiective-C时代中将NSMutableArtay 和NSArray分开的做法,被统到了唯一 的数据结构Array. 虽然看上去就一种数据结构,其实它的实现有三种:
ContiguousArray
:效率最高,元素分配在连续的元素上。如果数组是值类型(栈上操作),则Swift会自动调用Array的这种实现,如果注重效率,那么推荐声明这种类型,尤其是在大量元素是类时,这样做效果会很好。 Array
: 会自动桥接到Objective-C 中的NSArray上,如果是值类型,则其性能与ContiguousArray无差别。 ArraySlice
: 它不是一一个新的数组,只是一一个片段,在内存上与原数组享用同一区域。
下面是数组一些最基本的运用。
声明一个不可修改的数组
let nums = [1, 2, 3]
let nums = [Int](repeating: 0,count: 5)
//声明一个可以修改的数组var nums=[3,1,2]//增加一个元素
nums.append(4)
//对原数组进行升序排序
nums.sort()
//对原数组进行降序排序
nums.sort(by: >)
//将原数组除最后一个外的所有元素赋值给另一-个数组
let anotherNums = Array(nums[0 ..< nums. count - 1])
不要小看这些简单的操作:数组可以依靠它们实现更多的数据结构。Swift 虽然不像Java;有现成的队列和栈,但完全可以用数组配合最简单的操作实现这些数据结构,下面就是用数组实现栈的示例代码。
//用数组实现栈
class Stack {
var stack: [AnyObject]
var isEmpty: Bool { return stack. isEmpty }
var peek: AnyObject? { return stack.last }
init() {
stack = [AnyObject]()
}
func push(object: AnyObject) {
stack. append(object)
}
func pop() -> Any0bject? {
if (!isEmpty) {
return stack. removeLast()
} else {
return nil
}
}
}
最后要特别强调一个操作: reserveCapacity()。 它用于为原数组预留空间,防止数组在出加或删除元素时反复申请内存空间或是创建新数组,特别适用于创建和removeAll()时进行调用,对于整段代码可以起到提高性能的作用。
字典和集合
字典和集合(这里专指HashSet)经常被使用的原因在于,查找数据的时间复杂度为0(1)。一般字典和集合要求它们的Key都必须遵守Hashable 协议,Cocoa中的基本数据类型都满足这一点:自定义的class需要实现Hashable,而又因为Hashable是对Equable的扩展,所以还要重载==运算符。
下面是关于字典和集合的一此实用操作:
let primeNums: Set = [3, 5, 7,11, 13]
let oddNums: Set = [1, 3, 5, 7, 9]
交集、并集、差集
let primeAndOddNum = primeNums. intersection( oddNums)
let prime0r0ddNum = primeNums.formUnion( oddNums )
let oddNotPrimNum = oddNums.subtracting(primeNums )
用字典和高阶函数计算字符串中每个字符的出现频率,结果为["h":1, "e":1, "l":2, "o":1]
let dict = Dictionary("hello".map { ($0,1) }, uniquingKeysWith: +)
集合和字典在实际中经常与数组配合使用,请看下面这道算法题:
给出一个整型数组和一个目标值,判断数组中是否有两个数之和等于目标值。
这道题是经典的“2Sum”,即已经有一个数组记为nums,也有一个目标值记为 target,最后要返回一个Bool值。
最粗暴的方法就是每次选中一个数,然后遍历整个数组,判断是否有另一个数使两者之和为target。这种方法的时间复杂度为0(n2)。
采用集合可以优化时间复杂度,即在遍历数组的过程中,用集合每次保存当前值。假如集合中已经有了一个数等于目标值减去当前值,则证明在之前的遍历中一定有一个数与当前值之和等于目标值。这种方法的时间复杂度为0(n),代码如下。
func twoSumSet(nums : [Int], target : Int) -> Bool
{
var set = Set()
for num in nums {
if set.contains(target - num)
{
return true
}else{
set.insert(num)
}
}
return false
}
如果把这道题目稍微修改一下,则变为:
给定一个整型数组中有且仅有两个数之和等于目标值,求这两个数在数组中的序号。
解决思路与上道题基本类似,但是为了方便得到序列号,这里使用字典,而此方法的时间复杂度依然是0(n)。代码如下。
func twoSumDict(nums : [Int],target : Int) -> [Int]
{
var dict = [Int:Int]()
for (index,num) in nums.enumerated() {
if let lastIndex = dict[target - num]
{
return [lastIndex,index]
}else{
dict[num] = index
}
}
fatalError("输入nums错误")
}
字符串
字符串在算法实战中极其常见。在Swift中,字符串不同于其他语言(包括Objctive-C),它是值类型,而非引用类型。首先列举一下字符串的通常用法。
字符串和数字之间的转换
let str= "3"
let num = Int(str)
let number = 3
字符串长度
let len = str.count
访问字符串中的单个字符,时间复杂度为0(1)
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())
}
关于字符串,下面先看一道以前的Google面试题目。
给出个字符串, 要求将其按照单词顺序进行反转
比如,如果是"the sky is blue",那么反转后的结果就是"blue is sky the".
这道题目乍一看很简单, 不就是类似反转字符串吗?但是这里存在以下两个问题:
- 每个单词长度不一样。
- 空格需要特殊处理。
这样一来代码写起来会很烦琐,而且容易出错。不如下面先实现一个字符串反转的方法。
fileprivate func _reverse(_ chars : inout [T] , _ start : Int, _ end : Int)
{
var start = start, end = end
while start < end {
swap(&chars, start, end)
start = start + 1
end = end - 1
}
}
fileprivate func swap(_ chars : inout [T] , _ p : Int, _ q : Int)
{
(chars[q],chars[p]) = (chars[p],chars[q])
}
有了这个方法,就可以实行下面两种字符串的反转:
整个字符串反转, "the sky is blue”一“eulb si yks eht”。
每个单词作为一个字符串单独反转,“ eulb si yks eht"” , blue is sky the” 。
整体思路有了,然后就可以解决这道题了。
func reverseWords(s : String?) -> String?
{
guard let s = s else {
return nil
}
var chars = Array(s.characters)
_reverse(&chars, 0, chars.count-1)
var start : Int = 0
for i in 0..