陣列
陣列是最基本的資料結構。Swift中改變了以前Objective-C時代NSMutableArray和NSArray分開的做法,統一到了Array唯一的資料結構。下面是最基本的一些實現。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 宣告一個不可修改的陣列 let nums = [1, 2, 3] let nums = [Int](count: 5, repeatedValue: 0) // 宣告一個可以修改的陣列 var nums = [3, 1, 2] // 增加一個元素 nums.append(4) // 對原陣列進行升序排序 nums.sortInPlace({$0 < $1}) // 將原陣列除了最後一個以外的所有元素賦值給另一個陣列 let anotherNums = Array(nums[0 ..< nums.count - 1]) |
不要小看這些簡單的操作:陣列可以依靠它們實現更多的資料結構。Swift雖然不像Java中有現成的佇列和棧,但我們完全可以用陣列配合最簡單的操作實現這些資料結構,下面就是用陣列實現棧的示例程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 用陣列實現棧 class Stack { var stack: [AnyObject] init() { stack = [AnyObject]() } func push(object: AnyObject) { stack.append(object) } func pop() -> AnyObject? { if (!isEmpty()) { return stack.removeLast() } else { return nil } } func isEmpty() -> Bool { return stack.isEmpty } func peek() -> AnyObject? { return stack.last } } |
集合和字典
這兩個資料結構經常使用的原因在於,查詢資料的時間複雜度為O(1)。這兩個在實戰中經常與陣列配合使用,請看下面這道題:
給一個整型陣列和一個目標值,判斷陣列中是否有兩個數字之和等於目標值
這道題是傳說中經典的2Sum,我們已經有一個陣列記為nums,也有一個目標值記為target,最後要返回一個Bool值。
最粗暴的方法就是每次選中一個數,然後遍歷整個陣列,判斷是否有另一個數使兩者之和為target。這種做法時間複雜度為O(n^2)。
採用集合可以優化時間複雜度。在遍歷陣列的過程中,用集合每次儲存當前值。假如集合中已經有了目標值減去當前值,則證明在之前的遍歷中一定有一個數與當前值之和等於目標值。這種做法時間複雜度為O(n),程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func twoSum(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 } |
如果把題目稍微修改下,變為
給定一個整型陣列中有且僅有兩個數字之和等於目標值,求兩個數字在陣列中的序號
思路與上題基本類似,但是為了方便拿到序列號,我們採用字典,時間複雜度依然是O(n)。程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func twoSum(nums: [Int], _ target: Int) -> [Int] { var res = [Int]() var dict = [Int: Int]() for i in 0 ..< nums.count { guard let lastIndex = dict[target - nums[i]] else { dict[nums[i]] = i continue } res.append(lastIndex) res.append(i) break } return res } |
字串
字串在演算法實戰中極其常見。首先還是列舉一下字串的通常用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 字串和數字之間的轉換 let str = "3" let num = Int(str) let number = 3 let string = String(num) // 字串長度 let len = str.characters.count // 訪問字串中的單個字元,時間複雜度為O(n) let char = str[str.startIndex.advancedBy(n)] // 修改字串 str.removeAtIndex(n) str.append("c") str += "hello world" // 檢測字串是否是由數字構成 func isStrNum(str: String) -> Bool { return Int(str) != nil } // 將字串按字母排序(不考慮大小寫) func sortStr(str: String) -> String { var chars = [Character](str.characters) chars.sortInPlace({$0 < $1}) return String(chars) } |
下面是本篇的精華所在,我們來一起看一道以前的Google面試題。
Given an input string, reverse the string word by word.
A word is defined as a sequence of non-space characters.
The input string does not contain leading or trailing spaces and the words are always separated by a single space.
For example,
Given s = “the sky is blue”,
return “blue is sky the”.
Could you do it in-place without allocating extra space?
這道題目一看好簡單,不就是翻轉字串的翻版嗎?這種方法有以下兩個問題
- 每個單詞長度不一樣
- 空格需要特殊處理
這樣一來程式碼寫起來會很繁瑣而且容易出錯。不如我們先實現一個字串翻轉的方法。
1 2 3 4 5 |
private func _swap(inout chars:[Character], _ p: Int, _ q: Int) { let temp = chars[p] chars[p] = chars[q] chars[q] = temp } |
有了這個方法,我們就可以實行下面兩種字串翻轉:
- 整個字串翻轉,”the sky is blue” -> “eulb si yks eht”
- 每個單詞作為一個字串單獨翻轉,”eulb si yks eht” -> “blue is sky the”
整體思路有了,我們就可以解決這道問題了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func reverseWords(s: String) -> String { var chars = [Character](s.characters) _reverse(&chars, 0, chars.count - 1) var start = 0 for i in 0 ..< chars.count { if i == chars.count - 1 || chars[i + 1] == " " { _reverse(&chars, start, i) start = i + 2 } } return String(chars) } private func _reverse(inout chars: [Character], _ start: Int, _ end: Int) { var start = start var end = end while start < end { _swap(&chars, start, end) start += 1 end -= 1 } } |
時間複雜度還是O(n),整體思路和程式碼簡單很多。
總結
Swift中陣列、字串、集合以及字典是最基本的資料結構,但是圍繞這些資料結構的問題層出不窮。幸運的是解決方法也並不是千變萬化、高深莫測,大家做好相應的積累即可。下期我們講連結串列、棧、佇列這三種資料結構。