Swift 数据结构与算法( ) + Leetcode
概念
在 Swift 语言中:
-
( 49
\%10 ) 会得到 ( 9 )。这是因为 49 除以 10 的余数是 9。 -
( 49
/=10 ) 这是一个复合赋值操作,它会将 49 除以 10 的结果(也就是 4)赋值回给原变量。所以,执行此操作后,变量的值会变为 4。 -
( 49
\%10 = 9 ) -
( 49
/=10 ) 之后,值变为 4。 -
舍去 n 的个位数,更新 n:
/=10 ⟹ 456/10 =45
n/=10 ⟹ n = 456/10 = 45
/10 等于, 取出 除了个位数以外数字. 然后, 除10.
456/10 =45
\% 10 ) 会得到 ( 9 )。这是因为 49 除以 10 的余数是 9。
因为个位数已经取出了.
使用场景与应用
学习的关键概念:
- 循环检测:使用快慢指针技巧来检测潜在的无限循环。
- 数字操作:如何取一个数字的各位,并计算它们的平方和。
实际应用场景:
-
链表循环检测:在链表中,快慢指针技巧常被用来检测循环。如果存在循环,快指针会追上慢指针。
技术点:快慢指针的初始化和移动;循环条件的检测。
-
数据流分析:在处理实时数据流时,可能需要检测模式或循环。快慢指针可以帮助在大量数据中检测这些模式。
技术点:数据流的实时处理;模式识别;快慢指针的应用。
iOS app 开发的实际应用场景:
-
滚动视图优化:在一个滚动视图中,如果有无限滚动的功能(如新闻feed),可以使用类似于快慢指针的技巧预加载内容,使得用户在滚动时总能看到内容而不是空白。
技术点:预加载策略;视图缓冲区的管理;滚动视图的事件处理。
-
动画渲染:在执行某些复杂动画时,可能需要在不同的速度下渲染不同的对象。快慢指针的概念可以帮助同步这些动画,确保它们在正确的时间点渲染。
技术点:动画的时间同步;渲染优化;动画的事件响应。
-
数字处理应用:例如,一个iOS应用可能需要处理用户输入的大量数字,如金融或健康应用。知道如何有效地处理和分析这些数字(例如,取其每一位)可以提高应用的性能和准确性。
技术点:数字输入的验证;数字的处理和分析;用户界面的响应和更新。
错误与反思
之前的代码版本,错误主要是在 getNext 函数中返回了 number 而不是 totalSum。
分析:
- 代码编写时的注意力分散:当编写
getNext函数时,可能过于关注循环和数字的处理,而忽略了最终的返回值。返回number而不是totalSum是一个典型的注意力不集中导致的错误。 - 没有完整测试:如果代码经过了全面的测试,特别是使用一些基本的测试用例,这个错误很可能会被发现。
改进建议:
- 代码审查:编写完代码后,进行一次代码审查。这不仅仅是查找语法错误,还要确保代码逻辑与你的初衷相符。
- 使用单元测试:为关键函数(如
getNext)编写单元测试。这样,即使你犯了一个小错误,测试也可能会揭示它。 - 逐步验证:在实现完整的算法之前,先验证其关键部分。例如,你可以先单独测试
getNext函数,确保它为给定的输入返回了正确的输出。 - 伪代码或注释:在开始编写实际代码之前,先用伪代码或注
while number > 0 {
let digital = number % 10
sum += digital * digital
number /= 10
}
这个循环会结束吗?
这个循环会结束。
原因在于每次循环中,number都会被整除10,这意味着它的值会逐渐减小。例如,如果初始number是456,经过第一次循环后,number会变成45;第二次循环后,变成4;第三次循环后,变成0。
当number的值为0时,while number > 0这个条件就不再满足,因此循环会结束。
所以,这个循环会逐位处理number的每个数字,直到number变成0为止。
当 number 只剩下一个个位数时,例如 number=7,我们进行如下操作:
- 通过
number % 10,我们得到该位数字,即 7。 - 将该位数字的平方(在这个例子中没有这一步,但在一般情况下会进行此操作)加到
totalSum。 - 使用
number /= 10进行整除操作。此时,710107 的整数部分是 0。
因此,number 更新为 0,while 循环的条件 number > 0 不再满足,循环结束。
题目
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入: n = 19
输出: true
解释: 12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入: n = 2
输出: false
提示:
1 <= n <= 231 - 1
解题思路🙋🏻 ♀️
-
循环检测:当我们不断地进行某种计算,并且这种计算可能导致永无止境的循环时,我们需要有方法来检测并中断这种循环。在本题中,对于某些数字,计算其数字的平方和可能会陷入无限循环。
-
快慢指针技巧:这是一个常用于链表中检测循环的技巧。通过使用两个速度不同的指针(一个快,一个慢),如果存在循环,那么快指针最终会追上慢指针。在这个问题中,我们使用相同的技巧来检测数字计算的循环。
-
数字操作:如何拆分一个数字以获得其各个位上的数字,以及如何计算这些数字的平方和。
-
递归与迭代:虽然本解答中我们使用了迭代的方法,但递归也是一个可能的解决方案。需要理解两者的区别和各自的优势。
使用 n = 19 作为示例来演示这个过程。
Start
|
|
V
Initialize: n = 19, slow = 19, fast = getNext(19)
|
|
V
Compute: 1^2 + 9^2 --> fast = 82
|
|
V
Enter while loop since fast != 1 and slow != fast
|
|
V
Update: slow = 82, fast = getNext(getNext(82))
|
|
V
Compute for getNext(82): 8^2 + 2^2 --> 68
|
|
V
Compute for getNext(68): 6^2 + 8^2 --> fast = 100
|
|
V
Continue in while loop
|
|
V
Update: slow = 68, fast = getNext(getNext(100))
|
|
V
Compute for getNext(100): 1^2 + 0^2 + 0^2 --> fast = 1
|
|
V
Exit while loop since fast = 1
|
|
V
Return true
|
|
End
边界思考🤔
代码
class Solution {
// 主要函数,判断一个数是否是快乐数
func isHappy(_ n: Int) -> Bool {
// 初始化慢指针为输入的数字 n
var slow = n
// 初始化快指针为 n 的下一个数值(即 n 的每位数字的平方和)
var fast = getNext(number: n)
// 如果快指针不为 1 且慢指针不等于快指针,则继续循环
while fast != 1 && slow != fast {
// 更新慢指针为其下一个数值
slow = getNext(number: slow)
// 更新快指针为其后两个数值(这是为了使快指针移动速度为慢指针的两倍)
fast = getNext(number: getNext(number: fast))
}
// 如果快指针为 1,则返回 true,表示 n 是一个快乐数;否则返回 false
return fast == 1 ? true : false
}
//MARK -
// 辅助函数,计算并返回一个数字的每位数的平方和
func getNext(number: Int) -> Int {
// 初始化局部变量 number 为传入的数字
var number = number
// 初始化 totalSum 为 0,用于存储平方和
var totalSum = 0
// 当 number 大于 0 时,继续循环
while number > 0 {
// 计算 number 的个位数字
let digit = number % 10
// 将该位数字的平方加到 totalSum
totalSum += digit * digit
// 用 10 整除 number,以移除其个位数字
number /= 10 //整除
}
// 返回计算得到的平方和
return totalSum
}
}
时空复杂度分析
O(d) 接近于 O(logn)
1. )O(d) 解释
考虑数字 �=12345n=12345。这个数字有 5 位。当我们计算这个数字的每位的平方和时,我们实际上是对这个数字的每一位进行了操作。因此,操作的次数与数字的位数 d 成正比。对于数字 n=1234567890,我们有 10 位,所以我们需要10次操作。
2. 为什么 Od 可以看作是 O(logn)
这是因为对于十进制数字来说,当数字 n 增加一个数量级时(例如从 9 到 10,或从 99 到 100),其位数 d 只增加1。换句话说,当 n 增大10倍时,d 只增加1。这种关系可以用对数来描述,所以 d 是 logn 的函数。
例如:
- n=10 的时候,d=2,因为 10 的对数是 1。
- n=100 的时候,d=3,因为 100 的对数是 2。
- n=1000 的时候,d=4,因为 1000 的对数是 3。
3. 循环长度为常数
当我们说到快慢指针在循环中相遇时,虽然我们不能确切知道这个循环有多长,但我们知道这个循环的长度是有限的,并且是常数。为什么呢?
考虑数字的平方和。对于三位数来说,最大的平方和是 92+92+92=24392+92+92=243。这意味着,无论我们开始于多大的数字,迭代几次后,我们都会得到一个不超过243的数字。因此,循环的可能性被限制在了一个非常小的数字范围内,这也就是为什么我们说循环长度是常数。
结合上述所有点,我们的时间复杂度可以描述为 O(d),并且由于 Od 和 Ologn 之间的关系,它也可以近似为 O(logn)。
### 引用
本系列文章部分概念内容引用 www.hello-algo.com/
解题思路参考了 abuladong 的算法小抄, 代码随想录... 等等
Youtube 博主: huahua 酱, 山景城一姐,