分享一个延长机械硬盘寿命的小技巧——少用Windows搜索

楔子
大家好,我是张桃狮。
我单位用的办公电脑还是十几年前的配置,只有安装WIN10系统的C盘是固态硬盘,D盘还是一块机械硬盘。
因为D盘比较大,我平时都是用D盘存文件。
日积月累,D盘存储了海量的文件。
每次我在D盘搜文件,硬盘灯都会亮上很久,让我担心硬盘会不会突然挂掉。
我估计Windows搜索是通过实时遍历磁盘目录来找文件的,这样做的好处是实时同步,缺点是太慢了。
从XP、WIN7到WIN10,Windows搜索似乎一直都是这么干的。
如果你也有这种烦恼,强烈建议试试Everything,这款神仙工具直接把找文件的痛苦煎熬变成秒出结果的快乐体验。
https://www.voidtools.com/zh-cn/
上面是Everything的官方网址,这个软件是免费使用的。
下面讲讲Everything是如何做到秒出结果的。
当Everything第一次启动时,会直接访问NTFS硬盘的主文件表(MFT)。
MFT是NTFS的核心结构,里面集中存储了该分区下所有文件和文件夹的关键元数据——包括文件名、存储路径、创建/修改时间,且这些数据是按固定结构有序存储的,不是分散在磁盘各处。Everything只提取这些元数据,完全不读取文件内容,然后将这些信息整理成一个轻量化的索引库,直接加载到电脑内存中。
这个过程速度极快,因为它是读取一个连续的系统结构,而非像Windows搜索那样逐个目录遍历文件,1TB的NTFS硬盘通常几十秒就能完成初始化。
电脑使用过程中,文件会不断被新建、删除、重命名,Everything不需要重新扫描整个硬盘来更新索引,而是靠监听系统的更新序列号码日志(USNJournal)。
USNJournal是NTFS系统自带的日志文件,会自动记录所有文件的变动操作。
Everything会实时读取这个日志,一旦检测到文件变动,就直接在内存里的索引库中做对应修改——新增文件就添加一条索引记录,删除文件就移除对应记录,全程只操作内存,不触发大规模磁盘读写。
当你在Everything搜索框输入关键词时,它不会去访问硬盘,而是直接在内存里的索引库中做匹配查询。
内存的读写速度比硬盘快上万倍,所以输入关键词的瞬间就能完成匹配,给出搜索结果,这也是为什么用Everything搜索时,硬盘灯基本不会闪烁——全程几乎没有磁盘IO操作。
俗话说硬盘有价,资料无价。
听说最近电脑硬件价格上涨的厉害,大家更应该注意自己电脑里机械硬盘的寿命。
我觉得用Everything替代Windows搜索,是一个零成本延长硬盘使用寿命的好方法。
大家觉得呢?
闲话讲完,咱们继续刷题学编程。
力扣202. 快乐数
编写一个算法来判断一个数 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 <= - 1
我的思路
写一个循环,如果计算结果为1,就返回True,很容易。
但是示例2中,n=2就无法测试通过了。
因为2的数字平方和结果为2、4、16、37、58、89、145、42、20、4、16……
当结果再次出现4后,后面的就是无限循环,永远也不会出现1了。
这时就应该返回False。
我新建了一个集合,将所有计算结果记录下来,并用if判断是否出现无限循环。
这样虽然可以通过大部分测试,但是我忽略了n=1的特殊情况。
还是用老办法,用if条件把特殊情况绕过去,最后提交通过了。
class Solution: def isHappy(self, n: int) -> bool: ret=0 ret_set={n} while ret != 1: ret=0 for i in str(n): ret += int(i)*int(i) if ret in ret_set and ret != 1: return False ret_set.add(ret) n=ret return True
力扣提交通过,时间复杂度和空间复杂度都是O (log n)。
官方题解
方法一:用哈希集合检测循环
class Solution: def isHappy(self, n: int) -> bool: def get_next(n): total_sum = 0 while n > 0: n, digit = divmod(n, 10) total_sum += digit ** 2 return total_sum seen = set() while n != 1 and n not in seen: seen.add(n) n = get_next(n) return n == 1
官方题解方法一和我的代码思路差不多,时间和空间复杂度也是O (log n)。
不过官方题解没有用字符串循环,而是对n除以10,取商和余数。
divmod(a, b) 会接收两个数字参数 a(被除数)和 b(除数),同时返回一个包含商和余数的元组 (商, 余数),等价于 (a // b, a % b),但只需要一次计算,效率更高。
发现一个小技巧,官方题解方法一的代码没有加class Solution:,我增加class Solution:后,全选下面的代码,按tab键瞬间就给所有行增加了4个空格。
这个技巧在VS Code中也有效,以前的我都是手动给每行加空格,后悔没有早点发现。
方法二:快慢指针法
class Solution: def isHappy(self, n: int) -> bool: def get_next(number): total_sum = 0 while number > 0: number, digit = divmod(number, 10) total_sum += digit ** 2 return total_sum slow_runner = n fast_runner = get_next(n) while fast_runner != 1 and slow_runner != fast_runner: slow_runner = get_next(slow_runner) fast_runner = get_next(get_next(fast_runner)) return fast_runner == 1
将无限循环想象为一个环形链表,如何判断链表有环,可以用弗洛伊德龟兔赛跑算法 / Floyd's Tortoise and Hare Algorithm。
让慢指针每次走一步,快指针每次走两步,如果有环,快慢指针肯定会相遇。
之前我一直以为这个算法只适合链表,现在看来我有点小瞧它了。
使用弗洛伊德龟兔赛跑算法来做这道题的好处是,不需要占用宝贵的内存空间来存储之前的计算结果,空间复杂度变为O(1)。
时间复杂度还是O (log n)。
方法三:数学
class Solution: def isHappy(self, n: int) -> bool: cycle_members = {4, 16, 37, 58, 89, 145, 42, 20} def get_next(number): total_sum = 0 while number > 0: number, digit = divmod(number, 10) total_sum += digit ** 2 return total_sum while n != 1 and n not in cycle_members: n = get_next(n) return n == 1
这个集合 {4, 16, 37, 58, 89, 145, 42, 20}是怎么来的,说实话我有点看不懂。
但是我想到一个证明的方法。
题目有提示1 <= n <= - 1
我只需要将1到 - 1的所有整数都按照方法一算一遍,并将非快乐数得到的计算结果集合求交集,说不定可以看出端倪。
class Solution: def isHappy(self, n: int) -> bool: def get_next(n): total_sum = 0 while n > 0: n, digit = divmod(n, 10) total_sum += digit ** 2 return total_sum seen = set() while n != 1 and n not in seen: seen.add(n) n = get_next(n) if n != 1: return seensolution=Solution()result_sets = []for i in range(1,2**31): if solution.isHappy(i) : result_sets.append(solution.isHappy(i))final_intersection = result_sets[0]for s in result_sets[1:]: final_intersection &= sprint(final_intersection)
结果发现算力不够,长时间得不到计算结果,只好将计算范围调小到range(1,2**11)。
在VS Code中运行得到的结果是{4, 37, 42, 16, 145, 20, 89, 58}
这8个数与力扣官方题解方法三中的cycle_members是一样的。
我尝试将计算范围调整到range(1,2**21),运行一段时间后,得到结果也是{4, 37, 42, 16, 145, 20, 89, 58}。
再大我就没尝试了,计算时间太久了,等不及。
我忽然发现我不用尝试这么大的数字。
因为经过第一次计算以后,数字长度就会几何级的变短。
print(len(str(2**31-1)))
结果是10
10位数最大的数是9999999999,经过第一轮计算后会得到810,它已经变成一个3位数了。
其他小于9999999999的数,经过第一轮计算后不可能大于810。
我只需要用1-810来计算结果交集就好了。
将range(1,2**11)修改为range(1,811),可以很快得到计算结果{4, 37, 42, 16, 145, 20, 89, 58}。
看来在本题的数字范围内,这8个数在所有非快乐数的计算结果中都会出现。
而且只要出现其中一个数,就会进入这8个数的循环。
比如4、16、37、58、89、145、42、20、4、16……,所以方法三的计算结果是正确的。
官方题解方法三的时间复杂度是O(logn),空间复杂度是O(1)。
我是个编程爱好者,小白级别的,如果你跟我一样希望通过力扣刷题,学习各种奇妙的算法,可以关注我,大家一起学习。


评论