IOS与算法之字符串集合

字符串算法集合

  • 无重复字符的最长子串
  • 字符串转换整数 (atoi)
  • 正则表达式匹配-提取字符串中的数字
  • 罗马数字转整数
  • 整数转罗马数字
  • 电话号码的字母组合
  • 找出字符串中第一个匹配项的下标
  • 通配符匹配
  • 最长回文子串
  • 整数反转
  • 回文数
  • 三数之和
  • 最接近的三数之和
  • 有效的括号
  • 括号生成
  • 串联所有单词的子串
  • 最长有效括号
  • 字符串相乘
  • 有效数字
  • 解码方法
  • 比较版本号
  • 复原 IP 地址
  • 分数到小数
  • 整数转换英文表示
  • 猜猜这个单词
  • 设计一个文本编辑器
  • 反转字符串中的单词
  • 编辑距离
  • 删除无效的括号

无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例代码

- (NSInteger)hy_lengthOfLongestSubstring:(NSString *)s {
    if (s.length == 0) {
        return 0;
    }
    
    NSInteger maxLength = 0;
    NSInteger left = 0, right = 0;
    NSMutableDictionary *charDict = [NSMutableDictionary dictionary];
    
    while (right < s.length) {
        NSString *currentChar = [s substringWithRange:NSMakeRange(right, 1)];
        if ([charDict objectForKey:currentChar]) {
            NSNumber *prevIndex = [charDict objectForKey:currentChar];
            left = MAX(left, prevIndex.integerValue + 1);
        }
        
        [charDict setObject:@(right) forKey:currentChar];
        maxLength = MAX(maxLength, right - left + 1);
        right++;
    }
    
    return maxLength;
}

这个算法的基本思想是使用滑动窗口来维护最长不重复子串。我们使用两个指针 left 和 right 来表示当前窗口的左右边界。我们还使用一个字典来存储每个字符在字符串中出现的最后位置(即上一次出现的位置)。
当我们移动 right 指针时,我们检查当前字符是否已经在窗口中出现。如果是,则更新 left 指针以跳过前面的所有重复字符,并且将当前字符的位置添加到字典中。同时,我们还需要更新最长不重复子串的长度。
要找到字符串中的最长不重复子串长度,只需调用 lengthOfLongestSubstring 方法,并将要计算的字符串作为参数传递给它即可。

字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
返回整数作为最终结果。
注意:
本题中的空白字符只包括空格字符 ' ' 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例 1:
输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
第 3 步:"42"(读入 "42")
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。

示例 2:
输入:s = "   -42"
输出:-42
解释:
第 1 步:"   -42"(读入前导空格,但忽视掉)
第 2 步:"   -42"(读入 '-' 字符,所以结果应该是负数)
第 3 步:"   -42"(读入 "42")
解析得到整数 -42 。
由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。

示例 3:
输入:s = "4193 with words"
输出:4193
解释:
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
解析得到整数 4193 。
由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。

示例代码

+ (int)hy_myAtoi:(NSString *)s {
    if (!s || s.length == 0) {
        return 0;
    }
    
    NSInteger i = 0;
    NSInteger sign = 1;
    NSInteger result = 0;
    
    // 移除前导空格
    while (i < s.length && [s characterAtIndex:i] == ' ') {
        i++;
    }
    
    // 处理正负号
    if (i < s.length && ([s characterAtIndex:i] == '+' || [s characterAtIndex:i] == '-')) {
        sign = [s characterAtIndex:i] == '-' ? -1 : 1;
        i++;
    }
    
    // 处理数字字符
    while (i < s.length && [s characterAtIndex:i] >= '0' && [s characterAtIndex:i] <= '9') {
        int digit = [s characterAtIndex:i] - '0';
        if (result > INT_MAX / 10 || (result == INT_MAX / 10 && digit > INT_MAX % 10)) {
            return sign == -1 ? INT_MIN : INT_MAX;
        }
        result = result * 10 + digit;
        i++;
    }
    
    return (int)(sign * result);
}

该算法首先移除前导空格,并处理正负号。然后它逐个字符地读取数字字符,并将其转换为整数。如果整数超出了 32 位有符号整数范围,则会截断该整数以使其保持在此范围内。
要使用该函数,请调用 myAtoi 方法,并将要转换的字符串作为参数传递给它。

正则表达式匹配-提取字符串中的数字

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

示例 1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。

示例 2:
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。

Code

+ (BOOL)hy_isMatch:(NSString *)s withPattern:(NSString *)p {
    if (!p || p.length == 0) {
        return !s || s.length == 0;
    }
    
    BOOL firstMatch = s && s.length > 0 && ([p characterAtIndex:0] == [s characterAtIndex:0] || [p characterAtIndex:0] == '.');
    
    if (p.length >= 2 && [p characterAtIndex:1] == '*') {
        return [self hy_isMatch:s withPattern:[p substringFromIndex:2]] || (firstMatch && [self hy_isMatch:[s substringFromIndex:1] withPattern:p]);
    } else {
        return firstMatch && [self hy_isMatch:[s substringFromIndex:1] withPattern:[p substringFromIndex:1]];
    }
}

该算法使用递归来实现正则表达式匹配。它首先检查字符规律是否为空。如果是,则返回 true 如果要匹配的字符串为空;否则,如果要匹配的字符串不为空,则返回 false。
然后,算法检查模式的第一个字符是否与字符串的第一个字符匹配。如果相匹配,则算法将继续对剩余的字符串和模式进行匹配。如果模式的下一个字符是 ‘*’,则算法可以选择跳过该字符或重复前一个字符,并在两种情况下继续递归搜索。
最后,如果整个字符串都已匹配,并且整个模式也已处理完毕,则返回 true;否则,如果字符串没有全部匹配或者模式仍然存在未处理的字符,则返回 false。

罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。

示例 1:

输入: s = "III"
输出: 3
示例 2:

输入: s = "IV"
输出: 4
示例 3:

输入: s = "IX"
输出: 9
示例 4:

输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:

输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
+ (NSInteger)hy_romanToInt:(NSString *)s {
    // 创建字典,用于存储罗马数字符号和对应的阿拉伯数字值
    NSDictionary *romanValues = @{@"I": @1, @"V": @5, @"X": @10, @"L": @50, @"C": @100, @"D": @500, @"M": @1000};
    
    NSInteger result = 0;
    NSInteger prevValue = 0;
    
    // 从右到左遍历罗马数字字符
    for (NSInteger i = s.length - 1; i >= 0; i--) {
        NSString *symbol = [s substringWithRange:NSMakeRange(i, 1)];
        NSInteger value = [[romanValues objectForKey:symbol] integerValue];
        
        // 如果当前值小于前一个值,则减去当前值;否则加上当前值
        if (value < prevValue) {
            result -= value;
        } else {
            result += value;
        }
        
        prevValue = value;
    }
    
    return result;
}

该算法使用字典来存储罗马数字符号和对应的阿拉伯数字值。然后,我们从右到左遍历罗马数字字符,并计算每个字符的值。

如果当前字符的值小于前一个字符的值,则意味着需要减去当前值。例如,在 IV 中,第一个字符 I 的值为 1,而第二个字符 V 的值为 5。由于 I 在 V 的左边,所以我们需要在 V 的值中减去 I 的值,得到 4。

如果当前字符的值大于或等于前一个字符的值,则需要将该值添加到结果中。例如,在 IX 中,第一个字符 I 的值为 1,而第二个字符 X 的值为 10。由于 I 在 X 的左边,所以我们需要在 X 的值中加上 I 的值,得到 9。

最终,我们返回计算出的整数值。

整数转罗马数字

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。

示例 1:
输入: num = 3
输出: "III"

示例 2:
输入: num = 4
输出: "IV"

示例 3:
输入: num = 9
输出: "IX"

示例 4:
输入: num = 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.

示例 5:
输入: num = 1994
输出: "MCMXCIV"
解释: M = 1000, CM = 900, XC = 90, IV = 4.
+ (NSString *)hy_intToRoman:(NSInteger)num {
    // 创建两个数组,分别存储罗马数字和对应的阿拉伯数字值
    NSArray *romanValues = @[@1000, @900, @500, @400, @100, @90, @50, @40, @10, @9, @5, @4, @1];
    NSArray *romanSymbols = @[@"M", @"CM", @"D", @"CD", @"C", @"XC", @"L", @"XL", @"X", @"IX", @"V", @"IV", @"I"];
    
    NSMutableString *result = [NSMutableString string];
    
    // 从高位到低位遍历每个阿拉伯数字值
    for (NSInteger i = 0; i < romanValues.count && num > 0; i++) {
        NSInteger value = [[romanValues objectAtIndex:i] integerValue];
        NSString *symbol = [romanSymbols objectAtIndex:i];
        
        // 计算当前罗马数字可以重复的次数
        NSInteger numSymbols = num / value;
        
        // 将当前罗马数字添加到结果字符串中,并减去相应的值
        for (NSInteger j = 0; j < numSymbols; j++) {
            [result appendString:symbol];
        }
        
        num -= value * numSymbols;
    }
    
    return result;
}

该算法使用贪心策略来将整数转换为罗马数字。我们首先创建两个数组,一个包含罗马数字的符号,另一个包含罗马数字的值。然后,我们按递减顺序遍历这两个数组中的元素,直到整数值为零。
在每次迭代中,我们计算当前罗马数字可以重复的次数,并将其添加到结果字符串中。同时,我们从整数值中减去相应的阿拉伯数字值。
最终,我们返回结果字符串作为罗马数字的表示形式。

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

IOS与算法之字符串集合_第1张图片

示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]

+ (NSArray<NSString *> *)hy_letterCombinations:(NSString *)digits {
    // 创建字典,用于存储数字到字母的映射关系
    NSDictionary *keypad = @{
        @"2": @"abc",
        @"3": @"def",
        @"4": @"ghi",
        @"5": @"jkl",
        @"6": @"mno",
        @"7": @"pqrs",
        @"8": @"tuv",
        @"9": @"wxyz"
    };
    
    NSMutableArray<NSString *> *result = [NSMutableArray array];
    
    // 如果输入字符串为空,则直接返回空数组
    if (digits.length == 0) {
        return result;
    }
    
    // 初始化结果字符串和数字字符索引列表
    NSMutableString *current = [NSMutableString string];
    NSMutableArray<NSNumber *> *indices = [NSMutableArray arrayWithCapacity:digits.length];
    for (NSInteger i = 0; i < digits.length; i++) {
        [indices addObject:@(0)];
    }

    NSInteger pos = 0;
    
    while (pos >= 0) {
        NSString *digit = [digits substringWithRange:NSMakeRange(pos, 1)];
        NSString *letters = [keypad objectForKey:digit];
        NSInteger index = [[indices objectAtIndex:pos] integerValue];
        
        // 如果当前位置已经到达该数字对应的字母末尾,则回溯到前一个数字
        if (index == letters.length) {
            pos--;
            continue;
        }
        
        // 否则,将当前位置的字母添加到结果字符串中,并更新数字字符索引列表
        unichar letter = [letters characterAtIndex:index];
        [current appendFormat:@"%c", letter];
        [indices replaceObjectAtIndex:pos withObject:@(index + 1)];
        
        // 如果已经处理完最后一个数字,则将结果字符串添加到结果列表中,并回溯到前一个数字
        if (pos == digits.length - 1) {
            [result addObject:[NSString stringWithString:current]];
            pos--;
        } else {
            // 否则,继续处理下一个数字
            pos++;
            [indices replaceObjectAtIndex:pos withObject:@(0)];
        }
    }
    
    return result;
}

该算法使用回溯方法来生成所有可能的字母组合。我们首先创建一个字典,用于存储数字到字母的映射关系。然后,我们初始化一个空数组作为结果列表,并检查输入字符串是否为空。如果输入字符串为空,则直接返回空数组。
在主循环中,我们从左到右遍历数字字符,并使用数字到字母映射字典获取对应的字母集合。我们使用一个数字字符索引列表来跟踪每个数字字符当前使用的字母位置。
如果当前数字字符已经在其字母集合的末尾,则回溯到前一个数字字符,并继续寻找可用的字母。否则,我们将当前字母添加到结果字符串中,并更新数字字符索引列表。
如果已经处理了最后一个数字字符,则将结果字符串添加到结果列表中,并回溯到前一个数字字符。否则,我们继续处理下一个数字字符,并将其对应的字母作为结果字符串的下一个字符。

最终,我们返回生成的所有结果字符串作为字母组合的列表。

找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。
`+ (NSInteger)hy_findSubscripMatchString:(NSString *)haystack needle:(NSString *)needle {
    if (needle.length == 0) {
        return 0;
    }
    
    NSInteger n = needle.length;
    
    // 遍历 haystack 中所有长度为 n 的子串
    for (NSInteger i = 0; i < haystack.length - n + 1; i++) {
        NSString *sub = [haystack substringWithRange:NSMakeRange(i, n)];
        
        if ([sub isEqualToString:needle]) {
            return i;
        }
    }
    
    return -1;
}

该算法使用暴力枚举方法,在 haystack 中逐一查找长度为 n 的子串是否与 needle 相等。如果相等,则返回该子串在 haystack 中的下标。

最后,如果没有找到匹配项,则返回 -1。

要使用该函数,请调用 strStr 方法,并将要查询的字符串作为参数传递给它。它将返回第一个匹配项的下标或 -1。

注释已经尽可能详细地解释了算法的实现过程。此外,我们使用了 NSString 的 substringWithRange: 方法来提取子串。它的语法是 [string substringWithRange:NSMakeRange(location, length)],其中 location 是起始位置,length 是子串的长度

通配符匹配

给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配: '?' 可以匹配任何单个字符。
'*' 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

示例 1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa", p = "*"
输出:true
解释:'*' 可以匹配任意字符串。
示例 3:
输入:s = "cb", p = "?a"
输出:false
解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

- (BOOL)hy_isMatch:(NSString *)s pattern:(NSString *)p {
    NSInteger m = s.length;
    NSInteger n = p.length;
    
    // 创建一个二维数组 dp,用于存储中间结果
    NSMutableArray<NSMutableArray<NSNumber *> *> *dp = [NSMutableArray arrayWithCapacity:m + 1];
    for (NSInteger i = 0; i <= m; i++) {
        NSMutableArray<NSNumber *> *row = [NSMutableArray arrayWithCapacity:n + 1];
        
        for (NSInteger j = 0; j <= n; j++) {
            [row addObject:@NO];
        }
        
        [dp addObject:row];
    }
    
    // 空字符串和空模式匹配
    dp[0][0] = @YES;
    
    // 处理第一行,即空字符串与非空模式
    for (NSInteger j = 1; j <= n; j++) {
        if ([p characterAtIndex:j - 1] == '*') {
            dp[0][j] = dp[0][j - 1];
        }
    }
    
    // 处理其余子问题
    for (NSInteger i = 1; i <= m; i++) {
        for (NSInteger j = 1; j <= n; j++) {
            unichar sc = [s characterAtIndex:i - 1];
            unichar pc = [p characterAtIndex:j - 1];
            
            // 如果当前模式字符是通配符,则可以使用前面的结果
            if (pc == '*') {
                dp[i][j] = [NSNumber numberWithBool:(dp[i - 1][j].boolValue || dp[i][j - 1].boolValue)];

            } else if (pc == '?' || sc == pc) { // 如果当前字符匹配,则可以使用前面的结果
                dp[i][j] = dp[i - 1][j - 1];
            }
        }
    }
    
    return [[dp lastObject] lastObject].boolValue;
}

该算法使用动态规划方法实现通配符匹配。我们首先创建一个二维数组 dp,用于存储中间结果。然后,我们将空字符串和空模式设置为匹配,并处理第一行,即空字符串与非空模式。
接下来,我们处理其余子问题。对于每个字符和模式字符组合,我们检查以下三种情况:

  • 如果当前模式字符是通配符,则可以使用前面的结果。
  • 如果当前字符匹配,则可以使用前面的结果。
  • 否则,当前字符和模式字符无法匹配,因此不能使用前面的结果。
  • 最终,我们返回 dp[m][n],其中 m 是输入字符串的长度,n 是字符模式的长度。

最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
+ (NSString *)hy_longestPalindrome:(NSString *)s {
    NSInteger length = s.length;
    if (length < 2) {
        return s;
    }
    
    NSInteger start = 0;
    NSInteger maxLength = 1;
    for (NSInteger i = 0; i < length; i++) {
        // 寻找以i为中心的奇数长度的回文串
        NSInteger left = i - 1;
        NSInteger right = i + 1;
        while (left >= 0 && right < length && [s characterAtIndex:left] == [s characterAtIndex:right]) {
            left--;
            right++;
        }
        NSInteger oddLength = right - left - 1;
        
        // 寻找以i和i+1为中心的偶数长度的回文串
        left = i;
        right = i + 1;
        while (left >= 0 && right < length && [s characterAtIndex:left] == [s characterAtIndex:right]) {
            left--;
            right++;
        }
        NSInteger evenLength = right - left - 1;
        
        // 取最长的回文串
        NSInteger currentMaxLength = MAX(oddLength, evenLength);
        if (currentMaxLength > maxLength) {
            maxLength = currentMaxLength;
            start = i - (maxLength - 1) / 2;
        }
    }
    
    NSRange range = NSMakeRange(start, maxLength);
    NSString *result = [s substringWithRange:range];
    return result;
}

longestPalindrome: 是该方法的名称,输入参数为一个 NSString 对象,输出也是一个 NSString 对象。
首先判断字符串长度是否小于 2,如果是,则直接返回原字符串。
初始化最长回文子串的起始位置为 0,长度为 1。
遍历字符串中每个字符,以该字符为中心分别向左右扩展,寻找奇数长度和偶数长度的回文串,并记录其长度。
取两种回文串中长度较大的一个,判断是否比当前最长回文子串的长度更长,如果是,则更新最长回文子串的起始位置和长度。
最后利用 substringWithRange 函数获取最长回文子串,并返回。

整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:
输入:x = 123
输出:321
示例 2:
输入:x = -123
输出:-321
示例 3:
输入:x = 120
输出:21
示例 4:
输入:x = 0
输出:0
+ (NSInteger)hy_reverseInteger:(NSInteger)x {
    NSInteger result = 0;
    while (x != 0) {
        // 取出最后一位数字
        NSInteger digit = x % 10;
        // 判断是否会溢出
        if (result > INT_MAX / 10 || (result == INT_MAX / 10 && digit > 7)) {
            return 0;
        }
        if (result < INT_MIN / 10 || (result == INT_MIN / 10 && digit < -8)) {
            return 0;
        }
        // 将该位数字加入结果中
        result = result * 10 + digit;
        // 去掉已经处理过的数字
        x /= 10;
    }
    return result;
}

注解:

  • reverseInteger: 是该方法的名称,输入参数为一个 NSInteger 类型的整数,输出也是一个 NSInteger 类型的整数。
  • 初始化结果为 0。
  • 循环取出给定整数的最后一位数字,并将其加入结果中。
  • 在每次循环中判断是否会溢出,如果超过了 32 位整数的范围,则返回 0。
  • 返回结果。
    该算法的时间复杂度为 O(log n),空间复杂度为 O(1)。

回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

例如,121 是回文,而 123 不是。

示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
+ (BOOL)hy_isPalindrome:(NSInteger)x {
    // 如果x小于0或者最后一位是0且x不等于0,则不是回文数
    if (x < 0 || (x % 10 == 0 && x != 0)) {
        return NO;
    }
    
    NSInteger reversed = 0;
    while (x > reversed) {
        // 取出最后一位数字
        NSInteger digit = x % 10;
        // 将该位数字加入反转后的结果中
        reversed = reversed * 10 + digit;
        // 去掉已经处理过的数字
        x /= 10;
    }
    
    // 如果x有奇数位,则reversed比x多一位,需要去掉中间的那一位
    return x == reversed || x == reversed / 10;
}

注解:

  • isPalindrome: 是该方法的名称,输入参数为一个 NSInteger 类型的整数,输出也是一个 BOOL 类型的值。
  • i如果给定整数小于 0 或者最后一位是 0 且整数不为 0,则肯定不是回文数,直接返回 NO。
  • i初始化反转后的结果为 0。
  • i循环取出给定整数的最后一位数字,并将其加入反转后的结果中。
  • i在每次循环中判断反转后的结果是否超过了给定整数的一半长度,如果超过了,则说明已经处理完了一半位数,可以开始比较了。
  • i如果给定整数有奇数位,则反转后的结果比给定整数多一位,需要去掉中间的那一位,然后比较是否相等。
  • i最后返回比较结果。

该算法的时间复杂度为 O(log n),空间复杂度为 O(1)。

三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

以下是 Objective-C 代码,用于查找三数之和为 0 的唯一三元组:

+ (NSArray<NSArray<NSNumber *> *> *)hy_threeSum:(NSArray<NSNumber *> *)nums {
    NSMutableArray<NSArray<NSNumber *> *> *result = [NSMutableArray array];
    
    if (nums.count < 3) {
        return result;
    }
    
    // 对数组进行排序
    NSArray<NSNumber *> *sortedNums = [nums sortedArrayUsingSelector:@selector(compare:)];
    
    for (NSInteger i = 0; i < sortedNums.count - 2; i++) {
        // 如果当前数字与上一个数字相同,则跳过以避免重复结果
        if (i > 0 && [sortedNums[i] isEqualToNumber:sortedNums[i - 1]]) {
            continue;
        }
        
        NSInteger left = i + 1;
        NSInteger right = sortedNums.count - 1;
        
        while (left < right) {
            NSInteger sum = sortedNums[i].integerValue + sortedNums[left].integerValue + sortedNums[right].integerValue;
            
            if (sum == 0) {
                [result addObject:@[sortedNums[i], sortedNums[left], sortedNums[right]]];
                
                // 跳过重复的数字
                while (left < right && [sortedNums[left] isEqualToNumber:sortedNums[left + 1]]) {
                    left++;
                }
                
                while (left < right && [sortedNums[right] isEqualToNumber:sortedNums[right - 1]]) {
                    right--;
                }
                
                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}

该算法首先对数组进行排序,然后遍历排序后的数组并选择一个数字作为三元组中的第一个数字。接下来,使用双指针算法在剩余的数字中查找两个数字,使它们的和等于目标值(即当前数字的相反数)。

由于答案不能包含重复的三元组,因此我们需要跳过具有相同值的数字。这可以通过检查左右指针所在位置的前一个或后一个数字来实现。

最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。

示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
+ (NSInteger)hy_threeSumClosest:(NSArray<NSNumber *> *)nums target:(NSInteger)target {
    // 对数组进行排序
    NSArray<NSNumber *> *sortedNums = [nums sortedArrayUsingSelector:@selector(compare:)];
    
    NSInteger closestSum = sortedNums[0].integerValue + sortedNums[1].integerValue + sortedNums[2].integerValue;
    
    for (NSInteger i = 0; i < sortedNums.count - 2; i++) {
        NSInteger left = i + 1;
        NSInteger right = sortedNums.count - 1;
        
        while (left < right) {
            NSInteger sum = sortedNums[i].integerValue + sortedNums[left].integerValue + sortedNums[right].integerValue;
            
            // 如果新的和更接近目标值,则更新结果
            if (labs(target - sum) < labs(target - closestSum)) {
                closestSum = sum;
            }
            
            // 根据当前和与目标值的大小关系移动指针
            if (sum < target) {
                left++;
            } else if (sum > target) {
                right--;
            } else {
                return sum;
            }
        }
    }
    
    return closestSum;
}

该算法首先对数组进行排序。然后,遍历排序后的数组并选择一个数字作为三元组中的第一个数字。接下来,使用双指针算法在剩余的数字中查找两个数字,使它们的和等于目标值(即 target 减去当前数字)。

如果当前和与目标值的差比当前最接近的和与目标值的差更小,则更新结果。最后返回最接近目标值的三数之和。

有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
+ (BOOL)hy_isValidParenthesis:(NSString *)s {
    NSMutableArray<NSString *> *stack = [NSMutableArray array];
    
    for (NSInteger i = 0; i < s.length; i++) {
        NSString *ch = [s substringWithRange:NSMakeRange(i, 1)];
        
        if ([ch isEqualToString:@"("] || [ch isEqualToString:@"{"] || [ch isEqualToString:@"["]) {
            // 如果是左括号,则将其推入栈中
            [stack addObject:ch];
        } else {
            // 否则,弹出栈顶元素并检查它是否与当前括号匹配
            NSString *top = stack.lastObject;
            [stack removeLastObject];
            
            if (([ch isEqualToString:@")"] && ![top isEqualToString:@"("]) ||
                ([ch isEqualToString:@"}"] && ![top isEqualToString:@"{"]) ||
                ([ch isEqualToString:@"]"] && ![top isEqualToString:@"["])) {
                return NO;
            }
        }
    }
    
    // 最后,如果栈为空,则说明所有括号都正确闭合
    return stack.count == 0;
}

该算法使用栈来跟踪未关闭的左括号。遍历字符串中的每个字符,如果当前字符是左括号,则将其推入栈中;否则,弹出栈顶元素并检查它是否与当前括号匹配。如果不匹配或栈为空,则说明字符串无效。

最后,如果栈为空,则说明所有括号都正确地闭合了。

括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
+ (NSArray<NSString *> *)hy_generateParenthesis:(NSInteger)n {
    NSMutableArray<NSString *> *result = [NSMutableArray array];
    
    void (^backtrack)(NSString *, NSInteger, NSInteger) = ^(NSString *s, NSInteger left, NSInteger right) {
        if (s.length == n * 2) {
            [result addObject:s];
            return;
        }
        
        if (left < n) {
            // 如果左括号数量小于 n,则可以添加一个左括号
            backtrack([s stringByAppendingString:@"("], left + 1, right);
        }
        
        if (right < left) {
            // 如果右括号数量小于左括号数量,则可以添加一个右括号
            backtrack([s stringByAppendingString:@")"], left, right + 1);
        }
    };
    
    backtrack(@"", 0, 0);
    
    return result;
}

该算法使用回溯方法生成所有可能的括号组合。定义一个辅助函数 backtrack,它维护当前括号字符串 s、已使用的左括号数 left 和已使用的右括号数 right。

如果 s 的长度等于 n 的两倍,则说明我们已经产生了一个有效的括号组合,将其推入结果数组中并返回。否则,我们有两种选择:添加一个左括号或添加一个右括号。

当左括号数量小于 n 时,我们可以添加一个左括号。当右括号数量小于左括号数量时,我们可以添加一个右括号。这两个条件都满足时,我们需要分别尝试添加左括号和右括号。

串联所有单词的子串

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。
s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。
例如,如果 words = [“ab”,“cd”,“ef”], 那么 “abcdef”, “abefcd”,“cdabef”, “cdefab”,“efabcd”, 和 “efcdab” 都是串联子串。 “acdbef” 不是串联子串,因为他不是任何 words 排列的连接。
返回所有串联字串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。
+ (NSArray<NSNumber *> *)hy_substringConnectsWords:(NSString *)s withWords:(NSArray<NSString *> *)words {
    NSMutableArray<NSNumber *> *res = [NSMutableArray array]; // 用于存储结果
    
    if (!s || !words || words.count == 0) return res; // 特判,若s和words有一个不存在,则返回res
    
    NSInteger wordLen = words[0].length; // 获取单词长度
    NSMutableDictionary<NSString *, NSNumber *> *wordsCount = [NSMutableDictionary dictionary];
    for (NSString *word in words) { // 将words数组中所有字符串存入哈希表,并统计每个字符串出现的次数
        NSNumber *count = wordsCount[word];
        wordsCount[word] = count ? @(count.integerValue + 1) : @1;
    }
    
    for (NSInteger i = 0; i < wordLen; i++) { // 在0到wordLen-1范围内枚举起点
        NSInteger left = i, right = i, count = 0; // 已经匹配的字符串数量
        NSMutableDictionary<NSString *, NSNumber *> *currWordsCount = [NSMutableDictionary dictionary]; // 当前已经匹配的字符串以及它们出现的次数
        
        while (right + wordLen <= s.length) { // 枚举终点
            NSString *word = [s substringWithRange:NSMakeRange(right, wordLen)]; // 获取当前的单词
            right += wordLen; // 更新右指针
            
            NSNumber *wordCount = wordsCount[word];
            if (!wordCount) { // 如果这个单词不在哈希表中,则说明从当前左指针开始没有一个符合要求的子串了,直接跳过
                left = right;
                count = 0; // 此时需要重新统计已经匹配的字符串数量,以及当前已经匹配的字符串以及它们出现的次数
                currWordsCount = [NSMutableDictionary dictionary];
            } else {
                NSNumber *currWordCount = currWordsCount[word];
                currWordsCount[word] = currWordCount ? @(currWordCount.integerValue + 1) : @1; // 将当前单词存入哈希表中,并更新它的出现次数
                
                while (currWordsCount[word].integerValue > wordCount.integerValue) { // 如果当前单词的出现次数大于哈希表中该单词的出现次数,则说明当前的子串不符合要求,需要将左指针右移
                    NSString *removeWord = [s substringWithRange:NSMakeRange(left, wordLen)];
                    left += wordLen;
                    currWordsCount[removeWord] = @(currWordsCount[removeWord].integerValue - 1);
                    count--; // 更新统计值
                }
                
                count++; // 已经匹配的字符串数量加1
                
                if (count == words.count) { // 如果已经匹配的字符串数量等于words数组的长度,则说明找到了一个符合要求的子串,记录下其起始位置
                    [res addObject:@(left)];
                    NSString *removeWord = [s substringWithRange:NSMakeRange(left, wordLen)];
                    left += wordLen;
                    currWordsCount[removeWord] = @(currWordsCount[removeWord].integerValue - 1);
                    count--;
                }
            }
        }
    }
    
    return res; // 返回结果数组
}

主要思路是:遍历s字符串,依次截取长度为words数组中所有单词长度之和的子串,判断该子串是否由words数组中所有单词组成。具体步骤:

1、根据words[0]的长度计算出每个单词的长度;
2、计算所有单词组成的子串长度subStringLength;
3、将words数组转换成字典wordCounts,key是单词,value是该单词出现的次数(因为可以重复);
4.遍历s字符串,依次截取长度为subStringLength的子串,判断该子串是否符合条件;
5、对于每个子串,将其按照单词的长度拆分成多个单词,判断这些单词是否可以组成words数组中所有单词的组合;
6、如果符合条件,将该子串的起始位置添加到结果数组中。

时间复杂度为O(n^2),空间复杂度为O(n),其中n为s字符串的长度。

最长有效括号

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
+ (NSInteger)hy_longestValidParentheses:(NSString *)s {
    // 用栈来保存左括号的下标
    NSMutableArray<NSNumber *> *stack = [NSMutableArray arrayWithObject:@(-1)];
    NSInteger maxLength = 0;

    for (NSInteger i = 0; i < s.length; i++) {
        NSString *c = [s substringWithRange:NSMakeRange(i, 1)];
        if ([c isEqualToString:@"("]) {
            // 如果当前字符是左括号,则将其下标入栈
            [stack addObject:@(i)];
        } else {
            // 如果当前字符是右括号,则将栈顶元素弹出
            [stack removeLastObject];
            if (stack.count == 0) {
                // 如果栈为空,说明没有左括号与该右括号匹配,则将该右括号的下标入栈作为新的起点
                [stack addObject:@(i)];
            } else {
                // 如果栈不为空,则计算以当前右括号结尾的最长有效括号子串长度
                NSInteger length = i - stack.lastObject.integerValue;
                maxLength = MAX(maxLength, length);
            }
        }
    }

    return maxLength;
}

主要思路是:使用栈来判断是否有有效的括号匹配。具体步骤:
将-1压入栈底,在后面计算长度时可以方便地得到以第一个左括号开头的子串长度;
遍历字符串s,如果当前字符是左括号,则将其下标入栈;
如果当前字符是右括号,则将栈顶元素弹出,如果栈为空,说明没有左括号与该右括号匹配,则将该右括号的下标入栈作为新的起点;
如果栈不为空,则计算以当前右括号结尾的最长有效括号子串长度,即当前右括号下标减去栈顶元素下标,取这些长度的最大值;
返回最大长度。
时间复杂度为O(n),空间复杂度为O(n),其中n为s字符串的长度。

字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

示例 1:

输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:

输入: num1 = "123", num2 = "456"
输出: "56088"

+ (NSString *)hy_multiply:(NSString *)num1 with:(NSString *)num2 {
    // 处理特殊情况
    if ([num1 isEqualToString:@"0"] || [num2 isEqualToString:@"0"]) {
        return @"0";
    }

    // 将num1和num2翻转,并转换成整数数组,方便计算
    NSMutableArray<NSNumber *> *num1Arr = [NSMutableArray array];
    for (NSInteger i = num1.length - 1; i >= 0; i--) {
        NSString *digitStr = [num1 substringWithRange:NSMakeRange(i, 1)];
        [num1Arr addObject:@(digitStr.integerValue)];
    }
    NSMutableArray<NSNumber *> *num2Arr = [NSMutableArray array];
    for (NSInteger i = num2.length - 1; i >= 0; i--) {
        NSString *digitStr = [num2 substringWithRange:NSMakeRange(i, 1)];
        [num2Arr addObject:@(digitStr.integerValue)];
    }

    // 计算乘积并保存到result数组中
    NSMutableArray<NSNumber *> *result = [NSMutableArray arrayWithCapacity:num1Arr.count + num2Arr.count];
    for (NSInteger i = 0; i < num1Arr.count + num2Arr.count; i++) {
        [result addObject:@(0)];
    }
    for (NSInteger i = 0; i < num1Arr.count; i++) {
        for (NSInteger j = 0; j < num2Arr.count; j++) {
            NSInteger product = num1Arr[i].integerValue * num2Arr[j].integerValue;
            NSInteger sum = result[i+j].integerValue + product % 10;
            NSInteger carry = sum / 10;
            NSInteger digit = sum % 10;
            
            // 将NSNumber对象转换成可变类型,修改其值,再重新封装成NSNumber对象
            NSMutableArray<NSNumber *> *mutableResult = [NSMutableArray arrayWithArray:result];
            mutableResult[i+j] = @(digit);
            mutableResult[i+j+1] = @(mutableResult[i+j+1].integerValue + carry + product / 10);
            result = [NSMutableArray arrayWithArray:mutableResult];
        }
    }

    // 对result数组进行进位处理
    for (NSInteger i = 0; i < result.count - 1; i++) {
        NSInteger carry = [result[i] integerValue] / 10;
        result[i] = @([result[i] integerValue] % 10);
        
        // 同上,将NSNumber对象转换成可变类型,修改其值,再重新封装成NSNumber对象
        NSMutableArray<NSNumber *> *mutableResult = [NSMutableArray arrayWithArray:result];
        mutableResult[i+1] = @(mutableResult[i+1].integerValue + carry);
        result = [NSMutableArray arrayWithArray:mutableResult];
    }

    // 将result数组翻转并去掉前导0,将数组转换成字符串返回
    while (result.count > 1 && result.lastObject.integerValue == 0) {
        [result removeLastObject];
    }
    NSMutableString *resultStr = [NSMutableString string];
    for (NSInteger i = result.count - 1; i >= 0; i--) {
        [resultStr appendFormat:@"%ld", result[i].integerValue];
    }
    return resultStr;
}

主要思路:
从低位到高位逐位相乘,将结果保存到一个数组中,再对数组进行进位处理。具体步骤:
处理特殊情况,如果有一个字符串为0,则直接返回"0";
将num1和num2翻转,并转换成整数数组,方便计算;
创建一个长度为len(num1)+len(num2)的数组result,用于保存乘积;
从低位到高位逐位相乘,将结果保存到result数组中;
对result数组进行进位处理,具体方法是:对于result[i],将其除以10得到进位carry,将result[i]对10取模得到当前位的值,将carry加到result[i+1]中;
将result数组翻转并去掉前导0,将数组转换成字符串返回。
时间复杂度为O(n^2),其中n为num1和num2中较长的那个字符串的长度。对于每一位的乘积都需要计算一次,并且需要进行进位处理。空间复杂度为O(n)。

有效数字

有效数字(按顺序)可以分成以下几个部分:
一个 小数 或者 整数
(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(‘+’ 或 ‘-’)
下述格式之一:
至少一位数字,后面跟着一个点 ‘.’
至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(‘+’ 或 ‘-’)
至少一位数字
部分有效数字列举如下:[“2”, “0089”, “-0.1”, “+3.14”, “4.”, “-.9”, “2e10”, “-90E3”, “3e+7”, “+6e-1”, “53.5e93”, “-123.456e789”]
部分无效数字列举如下:[“abc”, “1a”, “1e”, “e3”, “99e2.5”, “–6”, “-+3”, “95a54e53”]
给你一个字符串 s ,如果 s 是一个 有效数字 ,请返回 true 。

示例 1:

输入:s = "0"
输出:true
示例 2:

输入:s = "e"
输出:false
示例 3:

输入:s = "."
输出:false

typedef NS_ENUM(NSInteger, HYNumberState) {
    STATE_INITIAL,              // 初始状态
    STATE_INT_SIGN,             // 整数符号状态
    STATE_INTEGER,              // 整数状态
    STATE_POINT,                // 小数点状态
    STATE_POINT_WITHOUT_INT,    // 前面无整数小数点状态
    STATE_FRACTION,             // 小数状态
    STATE_EXP,                  // 指数字符状态
    STATE_EXP_SIGN,             // 指数符号状态
    STATE_EXP_NUMBER,           // 指数数字状态
    STATE_END                   // 结束状态
};

typedef NS_ENUM(NSInteger, HYNumberCharType) {
    CHAR_NUMBER,                // 数字类型
    CHAR_EXP,                   // 指数字符类型
    CHAR_POINT,                 // 小数点类型
    CHAR_SIGN,                  // 符号类型
    CHAR_ILLEGAL                // 非法字符类型
};


+ (BOOL)hy_isNumber:(NSString *)s {
    // 定义状态转移规则字典
       NSDictionary *stateMap = @{
           @(STATE_INITIAL): @{
               @(CHAR_NUMBER): @(STATE_INTEGER),
               @(CHAR_POINT): @(STATE_POINT_WITHOUT_INT),
               @(CHAR_SIGN): @(STATE_INT_SIGN)
           },
           @(STATE_INT_SIGN): @{
               @(CHAR_NUMBER): @(STATE_INTEGER),
               @(CHAR_POINT): @(STATE_POINT_WITHOUT_INT)
           },
           @(STATE_INTEGER): @{
               @(CHAR_NUMBER): @(STATE_INTEGER),
               @(CHAR_EXP): @(STATE_EXP),
               @(CHAR_POINT): @(STATE_POINT)
           },
           @(STATE_POINT): @{
               @(CHAR_NUMBER): @(STATE_FRACTION),
               @(CHAR_EXP): @(STATE_EXP)
           },
           @(STATE_POINT_WITHOUT_INT): @{
               @(CHAR_NUMBER): @(STATE_FRACTION)
           },
           @(STATE_FRACTION): @{
               @(CHAR_NUMBER): @(STATE_FRACTION),
               @(CHAR_EXP): @(STATE_EXP)
           },
           @(STATE_EXP): @{
               @(CHAR_NUMBER): @(STATE_EXP_NUMBER),
               @(CHAR_SIGN): @(STATE_EXP_SIGN)
           },
           @(STATE_EXP_SIGN): @{
               @(CHAR_NUMBER): @(STATE_EXP_NUMBER)
           },
           @(STATE_EXP_NUMBER): @{
               @(CHAR_NUMBER): @(STATE_EXP_NUMBER)
           }
       };
       
    HYNumberState state = STATE_INITIAL;            // 初始状态为 STATE_INITIAL
    NSUInteger length = [s length];         // 输入字符串的长度
       
    for (NSUInteger i = 0; i < length; i++) {
        HYNumberCharType type = [self toCharType:[s characterAtIndex:i]];   // 将当前字符转换成字符类型
        if (![stateMap[@(state)] objectForKey:@(type)]) {          // 如果当前状态不能接受该类型的字符
            return NO;                                              // 直接返回 NO,说明输入的字符串不是一个有效数字
        } else {
            state = [[stateMap[@(state)] objectForKey:@(type)] integerValue];   // 根据状态转移规则将状态转移到下一个状态
        }
    }
    
    return state == STATE_INTEGER || state == STATE_POINT || state == STATE_FRACTION || state == STATE_EXP_NUMBER || state == STATE_END;  // 最终状态必须为有效数字的结束状态之一
}

+(HYNumberCharType)toCharType:(unichar)ch {
    if (ch >= '0' && ch <= '9') {           // 如果当前字符是数字
        return CHAR_NUMBER;
    } else if (ch == 'e' || ch == 'E') {    // 如果当前字符是指数字符
        return CHAR_EXP;
    } else if (ch == '.') {                 // 如果当前字符是小数点
        return CHAR_POINT;
    } else if (ch == '+' || ch == '-') {   // 如果当前字符是符号
        return CHAR_SIGN;
    } else {
        return CHAR_ILLEGAL;                // 否则为非法字符类型
    }
}

该实现首先定义了一个有限状态机,其中每个状态都是一个对象,其属性表示在该状态下接受的字符类型以及转移后的状态。具体而言,有以下几种字符类型:空格、符号(+/-)、数字、小数点、e/E和无效字符。其中,状态0表示起始状态,在该状态下可以接受的字符类型有空格、符号、数字和小数点;状态3、4、5、8和9分别表示不同的有效数字结束状态,如果最终状态为这些状态之一,则说明输入的字符串是一个有效数字。

接下来,使用一个循环遍历输入字符串中的每个字符。在循环体中,首先判断当前字符属于哪种类型;然后,查找当前状态能否接受该类型的字符,如果不能,则说明输入的字符串不是一个有效数字,可以直接返回false;否则,将状态转移为下一个状态。最后,检查最终状态是否为有效数字的结束状态之一,如果是,则返回true,否则返回false。

解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> “1”
‘B’ -> “2”

‘Z’ -> “26”
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:

“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。
+ (int)hy_numDecodings:(NSString *)s {
    NSInteger n = s.length;
    NSMutableArray<NSNumber *> *dp = [NSMutableArray arrayWithCapacity:n + 1];
    for (NSInteger i = 0; i < n + 1; i++) {
        dp[i] = @0;
    }
    dp[0] = @1;
    dp[1] = [s characterAtIndex:0] == '0' ? @0 : @1;
    for (NSInteger i = 2; i <= n; ++i) {
        int first = [[s substringWithRange:NSMakeRange(i - 1, 1)] intValue]; // 当前位数字对应的码值
        int second = [[s substringWithRange:NSMakeRange(i - 2, 2)] intValue]; // 当前位和前一位组成的数字对应的码值
        
        if (first >= 1 && first <= 9) { // 如果当前位数字合法,则将前一位的方案数累加到当前位
            dp[i] = @(dp[i].intValue + dp[i - 1].intValue);
        }

        if (second >= 10 && second <= 26) { // 如果当前位和前一位数字组合后合法,则将前前一位的方案数累加到当前位
            dp[i] = @(dp[i].intValue + dp[i - 2].intValue);
        }
    }
    
    return [dp[n] intValue]; // 返回最终方案数
}

IOS与算法之字符串集合_第2张图片

比较版本号

给你两个版本号 version1 和 version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 ‘.’ 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。

比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 0 和 1 ,0 < 1 。

返回规则如下:

如果 version1 > version2 返回 1,
如果 version1 < version2 返回 -1,
除此之外返回 0。

示例 1:

输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1"
示例 2:

输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有指定下标为 2 的修订号,即视为 "0"
示例 3:

输入:version1 = "0.1", version2 = "1.1"
输出:-1
解释:version1 中下标为 0 的修订号是 "0",version2 中下标为 0 的修订号是 "1" 。0 < 1,所以 version1 < version2
+ (int)hy_compareVersion:(NSString *)version1 with:(NSString *)version2 {
    // 将字符串转换为数组,以 . 作为分隔符
    NSArray<NSString *> *arr1 = [version1 componentsSeparatedByString:@"."];
    NSArray<NSString *> *arr2 = [version2 componentsSeparatedByString:@"."];
    
    NSInteger i = 0;
    while (i < arr1.count || i < arr2.count) {
        // 获取版本号中第 i 个修订号,如果某个版本号没有指定该修订号,则视为 0
        NSInteger v1 = i < arr1.count ? [arr1[i] intValue] : 0;
        NSInteger v2 = i < arr2.count ? [arr2[i] intValue] : 0;
        
        // 比较忽略任何前导零后的整数值,如果 v1 > v2,则返回 1;如果 v1 < v2,则返回 -1;否则继续比较下一个修订号
        if (v1 > v2) {
            return 1;
        }
        else if (v1 < v2) {
            return -1;
        }
        else {
            ++i;
        }
    }
    
    // 如果所有修订号都相同,则返回 0
    return 0;
}

先将字符串转换为数组,然后对于两个版本号,依次比较它们的修订号。从左到右依次比较每个修订号,只需比较忽略任何前导零后的整数值,也就是去掉前导零之后的数字大小即可。如果某个版本号没有指定某个下标处的修订号,则该修订号视为 0。

时间复杂度:O(n),n 表示两个版本号中修订号的总数。

复原 IP 地址

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “[email protected]” 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:

输入:s = "0000"
输出:["0.0.0.0"]
示例 3:

输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
+ (NSArray<NSString *> *)hy_restoreIpAddresses:(NSString *)s {
    NSMutableArray<NSString *> *res = [NSMutableArray array];
    NSMutableArray<NSString *> *path = [NSMutableArray array];
    [self backtrace:s startIndex:0 path:path res:res];
    return res;
}

+ (void)backtrace:(NSString *)s startIndex:(NSInteger)startIndex path:(NSMutableArray<NSString *> *)path res:(NSMutableArray<NSString *> *)res {
    // 如果已经找到了四个整数,则检查是否满足规则,将结果加入到结果数组中并返回
    if (path.count == 4) {
        if (startIndex == s.length) {
            NSString *ipAddress = [path componentsJoinedByString:@"."];
            [res addObject:ipAddress];
        }
        return;
    }
    
    // 再往后最多还有 3 个字符
    for (NSInteger i = 1; i <= 3 && startIndex + i <= s.length; ++i) {
        NSString *subStr = [s substringWithRange:NSMakeRange(startIndex, i)];
        // 如果整数中含有前导 0,则该整数无效
        if ([subStr hasPrefix:@"0"] && subStr.length > 1) {
            continue;
        }
        NSInteger num = [subStr integerValue];
        // 如果整数不在 0 到 255 之间,则该整数无效
        if (num < 0 || num > 255) {
            continue;
        }
        [path addObject:subStr];
        [self backtrace:s startIndex:startIndex+i path:path res:res];
        [path removeLastObject];
    }
}

思路:使用回溯算法,每次选择从当前位置往后最多三个字符作为一个整数的值,然后递归处理后面的子串。在递归结束之后,如果整个字符串被分成了四个整数,则说明找到了一种有效的 IP 地址。需要注意的是,每个整数位于 0 到 255 之间且不能含有前导 0。

时间复杂度:O(3^4),即共有 3^4 种可能的分割方案。

分数到小数

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。

如果小数部分为循环小数,则将循环的部分括在括号内。

如果存在多个答案,只需返回 任意一个 。

对于所有给定的输入,保证 答案字符串的长度小于 104 。

示例 1:

输入:numerator = 1, denominator = 2
输出:"0.5"
示例 2:

输入:numerator = 2, denominator = 1
输出:"2"
示例 3:

输入:numerator = 4, denominator = 333
输出:"0.(012)"
+ (NSString *)fractionToDecimal:(NSInteger)numerator denominator:(NSInteger)denominator {
    // 特判情况
    if (numerator == 0) {
        return @"0";
    }
    
    NSMutableString *res = [NSMutableString string];
    // 判断结果的正负性
    if ((numerator ^ denominator) < 0) {
        [res appendString:@"-"];
    }
    numerator = labs(numerator);
    denominator = labs(denominator);
    // 整数部分
    NSInteger integerPart = numerator / denominator;
    [res appendFormat:@"%ld", integerPart];
    numerator %= denominator;
    if (numerator == 0) {
        return res;
    }
    // 小数部分
    [res appendString:@"."];
    NSMutableDictionary<NSNumber *, NSNumber *> *remainderDict = [NSMutableDictionary dictionary];
    while (numerator != 0) {
        numerator *= 10;
        NSNumber *remainder = @(numerator % denominator);
        NSNumber *index = remainderDict[remainder];
        if (index != nil) {
            NSInteger insertIndex = index.integerValue;
            [res insertString:@"(" atIndex:insertIndex];
            [res appendString:@")"];
            break;
        }
        else {
            remainderDict[remainder] = @(res.length);
            [res appendFormat:@"%ld", numerator / denominator];
            numerator %= denominator;
        }
    }
    return res;
}

使用哈希表记录每个余数出现的位置。对于当前的被除数,如果它已经在哈希表中出现过了,则表示出现循环小数,需要将循环部分括在括号内返回。否则将当前的被除数加入到哈希表中,并计算下一个被除数。

时间复杂度:O(d),其中 d 表示循环节长度。因为最多有 d 个余数可选,而每次运算只需要 O(1) 的时间,所以总时间复杂度为 O(d)。

整数转换英文表示

将非负整数 num 转换为其对应的英文表示。

示例 1:

输入:num = 123
输出:"One Hundred Twenty Three"
示例 2:

输入:num = 12345
输出:"Twelve Thousand Three Hundred Forty Five"
示例 3:

输入:num = 1234567
输出:"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
// 数字和单词的对应关系
static NSArray *lessThan20 = nil;
static NSArray *tens = nil;
static NSArray *thousands = nil;

+ (void)initialize {
    lessThan20 = @[@"", @"One", @"Two", @"Three", @"Four", @"Five", @"Six", @"Seven", @"Eight", @"Nine", @"Ten", @"Eleven", @"Twelve", @"Thirteen", @"Fourteen", @"Fifteen", @"Sixteen", @"Seventeen", @"Eighteen", @"Nineteen"];
    tens = @[@"", @"Ten", @"Twenty", @"Thirty", @"Forty", @"Fifty", @"Sixty", @"Seventy", @"Eighty", @"Ninety"];
    thousands = @[@"", @"Thousand", @"Million", @"Billion"];
}

+ (NSString *)hy_numberToWords:(int)num {
    if (num == 0) {
        return @"Zero";
    }
    
    NSMutableString *result = [NSMutableString string];
    int i = 0;
    
    while (num > 0) {
        // 取出最后三位数字
        if (num % 1000 != 0) {
            [result insertString:[self helper:num % 1000] atIndex:0];
            [result insertString:thousands[i] atIndex:0];
            [result appendString:@" "];
        }
        num /= 1000;
        i++;
    }
    
    // 去除末尾空格
    return [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

+ (NSString *)helper:(int)num {
    NSMutableString *result = [NSMutableString string];
    
    if (num == 0) {
        return @"";
    } else if (num < 20) {
        [result appendString:lessThan20[num]];
        [result appendString:@" "];
    } else if (num < 100) {
        [result appendString:tens[num / 10]];
        [result appendString:@" "];
        [result appendString:[self helper:num % 10]];
    } else {
        [result appendString:lessThan20[num / 100]];
        [result appendString:@" Hundred"];
        [result appendString:@" "];
        [result appendString:[self helper:num % 100]];
    }
    
    return result;
}

解题思路:
本题是一道模拟题,需要将数字转换为对应的英文表示,可以分为以下几个步骤:
将数字按照三位一组进行拆分,从低位到高位分别处理每一组数字。
对于每一组数字,先将其转换为不超过三位的数字,然后根据个位、十位和百位的值分别转换为对应的英文单词。
根据数字所在的三位数组位置(个位、千位、百万位等)加上 Thousand、Million、Billion 等单位,并将各组数字的转换结果拼接在一起。
最终得到的字符串需要去除末尾的空格。

猜猜这个单词

给你一个由 不同 字符串组成的单词列表 words ,其中 words[i] 长度均为 6 。words 中的一个单词将被选作秘密单词 secret 。
另给你一个辅助对象 Master ,你可以调用 Master.guess(word) 来猜单词,其中参数 word 长度为 6 且必须是 words 中的字符串。
Master.guess(word) 将会返回如下结果:
如果 word 不是 words 中的字符串,返回 -1 ,或者
一个整数,表示你所猜测的单词 word 与 秘密单词 secret 的准确匹配(值和位置同时匹配)的数目。
每组测试用例都会包含一个参数 allowedGuesses ,其中 allowedGuesses 是你可以调用 Master.guess(word) 的最大次数。
对于每组测试用例,在不超过允许猜测的次数的前提下,你应该调用 Master.guess 来猜出秘密单词。最终,你将会得到以下结果:
如果你调用 Master.guess 的次数大于 allowedGuesses 所限定的次数或者你没有用 Master.guess 猜到秘密单词,则得到 “Either you took too many guesses, or you did not find the secret word.” 。
如果你调用 Master.guess 猜到秘密单词,且调用 Master.guess 的次数小于或等于 allowedGuesses ,则得到 “You guessed the secret word correctly.” 。
生成的测试用例保证你可以利用某种合理的策略(而不是暴力)猜到秘密单词。

示例 1:

输入:secret = "acckzz", words = ["acckzz","ccbazz","eiowzz","abcczz"], allowedGuesses = 10
输出:You guessed the secret word correctly.
解释:
master.guess("aaaaaa") 返回 -1 ,因为 "aaaaaa" 不在 words 中。
master.guess("acckzz") 返回 6 ,因为 "acckzz" 是秘密单词 secret ,共有 6 个字母匹配。
master.guess("ccbazz") 返回 3 ,因为 "ccbazz" 共有 3 个字母匹配。
master.guess("eiowzz") 返回 2 ,因为 "eiowzz" 共有 2 个字母匹配。
master.guess("abcczz") 返回 4 ,因为 "abcczz" 共有 4 个字母匹配。
一共调用 5 次 master.guess ,其中一个为秘密单词,所以通过测试用例。
示例 2:

输入:secret = "hamada", words = ["hamada","khaled"], allowedGuesses = 10
输出:You guessed the secret word correctly.
解释:共有 2 个单词,且其中一个为秘密单词,可以通过测试用例。

+ (void)hy_guessSecret:(NSString *)secret withWords:(NSArray<NSString *> *)words allowedGuesses:(NSInteger)allowedGuesses {
    NSMutableDictionary<NSString *, NSMutableArray<NSString *> *> *dict = [NSMutableDictionary dictionary];
    for (NSString *w1 in words) {
            for (NSString *w2 in words) {
                NSInteger matches = [self matchScore:w1 and:w2];
                if (!dict[@(matches)]) {
                    dict[@(matches)] = [NSMutableArray array];
                }
                [dict[@(matches)] addObject:w1];
            }
        }


    
    NSMutableSet<NSString *> *candidates = [NSMutableSet setWithArray:words];
    NSInteger guesses = 0;
    
    while (guesses < allowedGuesses && [candidates count] > 0) {
            NSString *guess = [candidates anyObject];
            NSInteger score = [self matchScore:guess and:secret];
            if (score == 6) {
                NSLog(@"You guessed the secret word correctly.");
                return;
            }
            
            NSMutableSet<NSString *> *newCandidates = [NSMutableSet set];
            for (NSString *word in candidates) {
                if ([self matchScore:guess and:word] == score) { // 修改为 matchScore:and:
                    [newCandidates addObject:word];
                }
            }
            candidates = newCandidates;
            guesses++;
        }

    
    NSLog(@"Either you took too many guesses, or you did not find the secret word.");
}

+ (NSInteger)matchScore:(NSString *)word1 and:(NSString *)word2 {
    NSInteger score = 0;
    for (NSInteger i = 0; i < 6; i++) {
        if ([word1 characterAtIndex:i] == [word2 characterAtIndex:i]) {
            score++;
        }
    }
    return score;
}



@end

@interface Master : NSObject

- (NSInteger)guess:(NSString *)word;

@end

@implementation Master

- (NSInteger)guess:(NSString *)word {
    return 0; // 在实际使用中需要替换为正确的猜测方法
}

@end

这是一道典型的猜词游戏,可以使用类似于「大闹天宫」的思路来解决。具体来说,每次从可选单词中选择一个作为当前的猜测单词(如选择第一个单词),然后统计该猜测单词与其他单词的匹配情况,并将可选单词列表缩小为所有与该猜测单词匹配结果相同的单词。例如,假设我们选择了单词 A 作为当前的猜测单词,然后得到了以下匹配结果:

单词 B:2 个字母匹配
单词 C:0 个字母匹配
单词 D:4 个字母匹配
则我们可以将可选单词列表缩小为与单词 D 匹配结果相同的单词,即 [A, E]。接下来,我们选择列表中的下一个单词 B 作为当前的猜测单词,然后继续按照上述方式缩小可选单词列表。如果在缩小过程中发现可选单词列表为空,则说明猜测失败。

代码实现时,可以先将所有单词之间的匹配结果计算出来,然后根据匹配结果对单词进行分类,从而得到一个字典,其中以匹配结果为键,以与该结果相同的单词列表为值。然后按照上述「大闹天宫」的思路进行猜词即可。

设计一个文本编辑器

请你设计一个带光标的文本编辑器,它可以实现以下功能:

添加:在光标所在处添加文本。
删除:在光标所在处删除文本(模拟键盘的删除键)。
移动:将光标往左或者往右移动。
当删除文本时,只有光标左边的字符会被删除。光标会留在文本内,也就是说任意时候 0 <= cursor.position <= currentText.length 都成立。

请你实现 TextEditor 类:

TextEditor() 用空文本初始化对象。
void addText(string text) 将 text 添加到光标所在位置。添加完后光标在 text 的右边。
int deleteText(int k) 删除光标左边 k 个字符。返回实际删除的字符数目。
string cursorLeft(int k) 将光标向左移动 k 次。返回移动后光标左边 min(10, len) 个字符,其中 len 是光标左边的字符数目。
string cursorRight(int k) 将光标向右移动 k 次。返回移动后光标左边 min(10, len) 个字符,其中 len 是光标左边的字符数目。

示例 1:

输入:
["TextEditor", "addText", "deleteText", "addText", "cursorRight", "cursorLeft", "deleteText", "cursorLeft", "cursorRight"]
[[], ["leetcode"], [4], ["practice"], [3], [8], [10], [2], [6]]
输出:
[null, null, 4, null, "etpractice", "leet", 4, "", "practi"]

解释:
TextEditor textEditor = new TextEditor(); // 当前 text 为 "|" 。('|' 字符表示光标)
textEditor.addText("leetcode"); // 当前文本为 "leetcode|" 。
textEditor.deleteText(4); // 返回 4
                          // 当前文本为 "leet|" 。
                          // 删除了 4 个字符。
textEditor.addText("practice"); // 当前文本为 "leetpractice|" 。
textEditor.cursorRight(3); // 返回 "etpractice"
                           // 当前文本为 "leetpractice|". 
                           // 光标无法移动到文本以外,所以无法移动。
                           // "etpractice" 是光标左边的 10 个字符。
textEditor.cursorLeft(8); // 返回 "leet"
                          // 当前文本为 "leet|practice" 。
                          // "leet" 是光标左边的 min(10, 4) = 4 个字符。
textEditor.deleteText(10); // 返回 4
                           // 当前文本为 "|practice" 。
                           // 只有 4 个字符被删除了。
textEditor.cursorLeft(2); // 返回 ""
                          // 当前文本为 "|practice" 。
                          // 光标无法移动到文本以外,所以无法移动。
                          // "" 是光标左边的 min(10, 0) = 0 个字符。
textEditor.cursorRight(6); // 返回 "practi"
                           // 当前文本为 "practi|ce" 。
                           // "practi" 是光标左边的 min(10, 6) = 6 个字符。

@interface TextEditor ()

@property (nonatomic, strong) NSMutableString *currentText; // 当前文本
@property (nonatomic, assign) NSInteger cursorPosition; // 光标位置

@end

@implementation TextEditor

- (instancetype)init {
    self = [super init];
    if (self) {
        _currentText = [[NSMutableString alloc] initWithString:@""];
        _cursorPosition = 0;
    }
    return self;
}

- (void)addText:(NSString *)text {
    [_currentText insertString:text atIndex:_cursorPosition]; // 将新文本插入到当前文本中
    _cursorPosition += text.length; // 更新光标位置
}

- (NSInteger)deleteText:(NSInteger)k {
    NSInteger count = MIN(k, _cursorPosition); // 计算需要删除的字符数目
    [_currentText deleteCharactersInRange:NSMakeRange(_cursorPosition - count, count)]; // 删除指定范围内的字符串
    _cursorPosition -= count; // 更新光标位置
    return count; // 返回实际删除的字符数目
}

- (NSString *)cursorLeft:(NSInteger)k {
    NSInteger count = MIN(k, _cursorPosition); // 计算需要移动的次数
    _cursorPosition -= count; // 更新光标位置
    NSRange range = NSMakeRange(MAX(0, _cursorPosition - 10), count); // 获取光标左边的 min(10, len) 个字符的范围
    return [_currentText substringWithRange:range]; // 返回这些字符
}

- (NSString *)cursorRight:(NSInteger)k {
    NSInteger count = MIN(k, _currentText.length - _cursorPosition); // 计算需要移动的次数
    _cursorPosition += count; // 更新光标位置
    NSRange range = NSMakeRange(MAX(0, _cursorPosition - 10), count); // 获取光标左边的 min(10, len) 个字符的范围
    return [_currentText substringWithRange:range]; // 返回这些字符
}

@end

这个类有一个属性 currentText,表示当前的文本;还有一个属性 cursor,表示光标的位置。构造函数将它们都初始化为空字符串和 0。
addText 方法接受一个参数 text,将其添加到当前文本的光标位置,并更新光标位置。具体来说,它用 substring 方法获取当前文本中光标左边和右边的部分,然后将它们拼接起来,中间插入 text。最后,它更新光标位置,使其在新文本的末尾。
deleteText 方法接受一个参数 k,删除当前文本中光标左边的 k 个字符,并返回实际删除的字符数目。具体来说,它先计算需要删除的字符数 count,然后用 substring 方法获取当前文本中光标左边和右边的部分,并拼接起来。最后,它更新光标位置,使其向左移动 count 个字符。
cursorLeft 和 cursorRight 方法都接受一个参数 k,将光标向左或者向右移动 k 次,并返回移动后光标左边的 min(10, len) 个字符,其中 len 是光标左边的字符数目。具体来说,它们先计算需要移动的次数 count,然后根据 count 更新光标位置,以便将光标向左或者向右移动。最后,它们用 substring 方法获取当前文本中光标左边的 min(10, len) 个字符,并返回它们。

反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"
示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
+ (NSString *)hy_reverseWords:(NSString *)s {
    // 将字符串按空格分割成单词数组
    NSArray *words = [s componentsSeparatedByString:@" "];
    NSMutableArray *reversedWords = [NSMutableArray array];
    
    // 从后往前遍历单词数组,将非空单词添加到反转单词数组中
    for (NSInteger i = words.count - 1; i >= 0; i--) {
        NSString *word = words[i];
        if (word.length > 0) {
            [reversedWords addObject:word];
        }
    }
    
    // 将反转单词数组按空格连接成字符串
    NSString *reversedString = [reversedWords componentsJoinedByString:@" "];
    return reversedString;
}

当解决这个问题时,我们可以按照以下思路进行操作:
去除输入字符串 s 的首尾空格,可以使用 trim() 方法。
将去除空格后的字符串按照空格分割成单词数组,可以使用 split(/\s+/) 方法。这样可以处理单词之间可能存在的多个空格。
反转单词数组,可以使用 reverse() 方法。
将反转后的单词数组连接成一个字符串,单词之间用一个空格分隔,可以使用 join(" ") 方法。
通过以上步骤,我们可以得到反转后的字符串,满足题目要求。

编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

+ (NSInteger)hy_minDistanceBetweenWord1:(NSString *)word1 andWord2:(NSString *)word2 {
    NSInteger m = word1.length;
    NSInteger n = word2.length;
    
    // 创建一个二维数组,用于存储编辑距离
    NSMutableArray *dp = [NSMutableArray arrayWithCapacity:m + 1];
    for (NSInteger i = 0; i <= m; i++) {
        dp[i] = [NSMutableArray arrayWithCapacity:n + 1];
    }
    
    // 初始化边界条件
    for (NSInteger i = 0; i <= m; i++) {
        dp[i][0] = @(i);
    }
    for (NSInteger j = 0; j <= n; j++) {
        dp[0][j] = @(j);
    }
    
    // 动态规划计算编辑距离
    for (NSInteger i = 1; i <= m; i++) {
        unichar c1 = [word1 characterAtIndex:i - 1];
        for (NSInteger j = 1; j <= n; j++) {
            unichar c2 = [word2 characterAtIndex:j - 1];
            if (c1 == c2) {
                // 字符相等,不需要操作
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 字符不相等,选择插入、删除或替换操作中的最小操作数
                NSInteger insert = [dp[i][j - 1] integerValue] + 1;
                NSInteger delete = [dp[i - 1][j] integerValue] + 1;
                NSInteger replace = [dp[i - 1][j - 1] integerValue] + 1;
                dp[i][j] = @(MIN(insert, MIN(delete, replace)));
            }
        }
    }
    
    // 返回最终的最小操作数
    return [dp[m][n] integerValue];
}

这道题可以使用动态规划来解决,动态规划常用于求解两个字符串之间的编辑距离。
我们可以定义一个二维数组 dp,其中 dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小操作数。
考虑边界情况:
当 i=0 且 j>=0 时,表示 word1 为空字符串,将其转换为 word2 的前 j 个字符,需要进行 j 次插入操作,即 dp[0][j] = j。
当 i>=0 且 j=0 时,表示 word2 为空字符串,将 word1 的前 i 个字符转换为空字符串,需要进行 i 次删除操作,即 dp[i][0] = i。
接下来考虑一般情况,对于 dp[i][j]:
如果 word1 的第 i 个字符等于 word2 的第 j 个字符,即 word1[i-1] == word2[j-1],那么不需要进行任何操作,此时 dp[i][j] = dp[i-1][j-1]。
如果 word1 的第 i 个字符不等于 word2 的第 j 个字符,可以进行以下三种操作:
插入操作:在 word1 的第 i 个字符后插入一个与 word2 的第 j 个字符相同的字符,此时 dp[i][j] = dp[i][j-1] + 1。
删除操作:删除 word1 的第 i 个字符,将 word1 的前 i-1 个字符转换为 word2 的前 j 个字符,此时 dp[i][j] = dp[i-1][j] + 1。
替换操作:将 word1 的第 i 个字符替换为 word2 的第 j 个字符,此时 dp[i][j] = dp[i-1][j-1] + 1。
最终,我们需要求解的结果即为 dp[m][n],其中 m 是 word1 的长度,n 是 word2 的长度。

删除无效的括号

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。
返回所有可能的结果。答案可以按 任意顺序 返回。


示例 1:

输入:s = "()())()"
输出:["(())()","()()()"]
示例 2:

输入:s = "(a)())()"
输出:["(a())()","(a)()()"]
示例 3:

输入:s = ")("
输出:[""]

你可能感兴趣的:(OC与算法,iOS,ios,算法,objective-c)