###字符串相关的基本知识点:
1. 基本操作
let str = "Hello, World!"
// 查找
if str.contains("World") {
print("String contains 'World'")
}
// 替换
let replaced = str.replacingOccurrences(of: "World", with: "Universe")
print(replaced) // "Hello, Universe!"
// 添加
let appended = str + " How are you?"
print(appended) // "Hello, World! How are you?"
// 删除
let withoutHello = str.replacingOccurrences(of: "Hello,", with: "")
print(withoutHello) // " World!"
// 切割
let words = str.split(separator: " ")
print(words) // ["Hello,", "World!"]
// 组合
let combined = words.joined(separator: " - ")
print(combined) // "Hello, - World!"
//**插入字符到特定位置:**
var welcome = "Hello, World"
let index = welcome.index(welcome.startIndex, offsetBy: 7)
welcome.insert(contentsOf: "Beautiful ", at: index)
print(welcome) // "Hello, Beautiful World"
2. 字符编码
let char: Character = "A"
if let asciiValue = char.asciiValue {
print("ASCII value of \(char) is \(asciiValue)") // 65
}
let smile = "😀"
for codeUnit in smile.utf8 {
print("UTF-8: \(codeUnit)")
}
3. 字符串比较
let string1 = "apple"
let string2 = "banana"
if string1 < string2 {
print("\(string1) comes before \(string2) in alphabetical order.")
} else {
print("\(string2) comes before \(string1) in alphabetical order.")
}
4. 字符串搜索 (简单版)
// 使用 Swift 的内置方法
if str.range(of: "World") != nil {
print("String contains 'World'")
}
使用 KMP 算法进行字符串匹配: KMP 是一个相对复杂的算法,但其核心思想是利用已知信息避免从头开始搜索每个子字符串。
func KMPSearch(_ text: String, _ pattern: String) -> Int? {
let textChars = Array(text), patternChars = Array(pattern)
let lps = computeLPS(pattern: patternChars)
var i = 0, j = 0
while i < textChars.count {
if patternChars[j] == textChars[i] {
i += 1
j += 1
}
if j == patternChars.count {
return i - j
} else if i < textChars.count && patternChars[j] != textChars[i] {
if j != 0 {
j = lps[j - 1]
} else {
i += 1
}
}
}
return nil
}
func computeLPS(pattern: [Character]) -> [Int] {
var lps = Array(repeating: 0, count: pattern.count)
var length = 0, i = 1
while i < pattern.count {
if pattern[i] == pattern[length] {
length += 1
lps[i] = length
i += 1
} else if length != 0 {
length = lps[length - 1]
} else {
lps[i] = 0
i += 1
}
}
return lps
}
5. 回文
func isPalindrome(_ s: String) -> Bool {
let cleanedString = String(s.lowercased().filter { $0.isLetter })
return cleanedString == String(cleanedString.reversed())
}
print(isPalindrome("A man, a plan, a canal, Panama")) // true
5. Manacher 算法
Manacher 算法是一个用于在字符串中找到最长回文子串的线性时间复杂度算法。
核心思想:
- 预处理:首先对原始字符串进行预处理,例如将 "abba" 转换为 "^#a#b#b#a#$"。这样做的目的是使得回文子串的长度始终为奇数,从而简化处理过程。
- 利用已知的回文信息:当我们在字符串上从左到右移动时,可以利用已知的回文信息来避免不必要的计算。
- 中心点和右边界:我们总是试图利用之前计算的回文信息,所以我们会维护一个中心点和它的右边界。当我们的当前位置超过这个右边界时,我们需要重新计算回文长度。
func longestPalindrome(s: String) -> String {
guard s.count > 0 else { return "" }
// 1. 预处理字符串
var chars = "^"
for ch in s {
chars += "#\(ch)"
}
chars += "#$"
let n = chars.count
var P = [Int](repeating: 0, count: n)
var center = 0, right = 0
for i in 1..<n-1 {
let mirror = 2 * center - i
if right > i {
P[i] = min(right - i, P[mirror])
}
// 尝试扩展回文
while chars[i + 1 + P[i]] == chars[i - 1 - P[i]] {
P[i] += 1
}
// 如果回文超出了右边界,调整中心
if i + P[i] > right {
center = i
right = i + P[i]
}
}
// 找到最长的回文长度
let maxLength = P.max() ?? 0
let index = P.firstIndex(of: maxLength) ?? 0
let start = (index - maxLength) / 2
let end = start + maxLength
return String(s[s.index(s.startIndex, offsetBy: start)..<s.index(s.startIndex, offsetBy: end)])
}
6. 前缀、后缀和子串
let example = "swiftExample"
if example.hasPrefix("swift") {
print("String starts with 'swift'")
}
if example.hasSuffix("Example") {
print("String ends with 'Example'")
}
let indexStart = example.index(example.startIndex, offsetBy: 5)
let indexEnd = example.index(example.startIndex, offsetBy: 8)
let substring = example[indexStart...indexEnd]
print(substring) // "Exam"
Trie,也叫前缀树,是一种用于存储动态集合中字符串的树形数据结构。
核心思想:
- 节点结构:每个节点表示一个字符,并有指向下一个字符的子节点。根节点通常表示空字符。
- 共享前缀:Trie 的关键特性是共享前缀,这意味着具有相同前缀的字符串在 Trie 中共享同一路径。
以下是一个简单的 Trie 实现:
class TrieNode {
var value: Character?
var children: [Character: TrieNode] = [:]
var isEndOfWord: Bool = false
init(value: Character? = nil) {
self.value = value
}
}
class Trie {
var root: TrieNode
init() {
root = TrieNode()
}
func insert(word: String) {
var currentNode = root
for char in word {
if currentNode.children[char] == nil {
currentNode.children[char] = TrieNode(value: char)
}
currentNode = currentNode.children[char]!
}
currentNode.isEndOfWord = true
}
func search(word: String) -> Bool {
var currentNode = root
for char in word {
guard let nextNode = currentNode.children[char] else {
return false
}
currentNode = nextNode
}
return currentNode.isEndOfWord
}
func startsWith(prefix: String) -> Bool {
var currentNode = root
for char in prefix {
guard let nextNode = currentNode.children[char] else {
return false
}
currentNode = nextNode
}
return true
}
}
7. 正则表达式
let regexPattern = "[a-z]+"
if let regex = try? NSRegularExpression(pattern: regexPattern) {
let matches = regex.matches(in: str, options: [], range: NSRange(str.startIndex..., in: str))
for match in matches {
if let range = Range(match.range, in: str) {
print(str[range]) // 输出所有小写字母组成的词
}
}
}
8
let validChars = lowercased.filter { $0.isLetter || $0.isNumber }
这行代码是 Swift 语言中的一种简洁、函数式编程写法。它使用了 Swift 的 filter 方法,这是 String 和 Array 等集合类型的一个方法。filter 方法的目的是根据某个条件筛选集合中的元素。
让我们一步步地分解这行代码:
-
filter方法:filter接收一个闭包作为参数。这个闭包定义了筛选条件。- 对于集合中的每一个元素,这个闭包都会被调用,并传入当前的元素作为参数。
- 如果闭包返回
true,那么这个元素会被包含在筛选后的结果中。如果返回false,则不会。
-
闭包
{ $0.isLetter || $0.isNumber }:- 在这个闭包中,
$0代表当前正在被筛选的元素。 .isLetter是Character的一个属性,它返回一个布尔值,表示这个字符是否是一个字母。.isNumber同样是Character的一个属性,它返回一个布尔值,表示这个字符是否是一个数字。||是一个逻辑"或"运算符,所以整个闭包的意思是:如果这个字符是一个字母或一个数字,返回true,否则返回false。
- 在这个闭包中,
所以,validChars 将会是一个只包含字母和数字的新字符串,所有其他字符都会被过滤掉。
例如,如果 lowercased 是 "a man, a plan, a canal: panama",那么 validChars 将会是 "amanaplanacanalpanama"。