楔子
大家好,我是张桃狮。
最近,我发现了一个可以快速将公式粘贴为值的方法。
方法是在Excel中,Ctrl+V按两遍,第二次速度要慢下来。
第二次按Ctrl+V的时候,在Ctrl和V中间要有短暂停顿,按Ctrl后抬起按键,弹出一个粘贴菜单后再按V,就可以实现粘贴值了。
我在Excel2019和Excel2021上都可以用这招,不知道其他版本是否可以。
这个操作的原理和 Excel 的 “粘贴选项” 浮动工具栏有关,第二次按 Ctrl+V 时的 “慢下来”,本质是等待这个工具栏弹出后再操作,从而触发 “粘贴值” 的快捷方式,并非两次 Ctrl+V 本身的速度差异导致。
以下是核心原理详细拆解。
第一次 Ctrl+V:执行默认粘贴,会粘贴源内容的所有属性,包括值、公式、格式、批注等,粘贴完成后 Excel 会在目标区域右下角弹出 “粘贴选项” 浮动工具栏(通常是一个小方块图标,点击会展开选项)。
等待工具栏弹出:这就是第二次速度要慢下来的关键 —— 等这个工具栏出现后,可以看到工具栏上有一个(Ctrl)字样,按Ctrl并抬起,会展开粘贴选项菜单;再按一次V,此时 Excel 会识别为在粘贴选项上下文里选择 “粘贴值”,只保留数值而去除公式、格式等附加属性。
底层逻辑:粘贴选项工具栏是 Excel 的上下文交互机制,第二次 Ctrl+V 本质是触发了工具栏中的 “值” 选项,而非普通粘贴。
若不等待工具栏弹出,连续快速按两次 Ctrl+V 只会执行两次默认粘贴,不会触发粘贴值功能。
注意:若粘贴选项未弹出,可检查 Excel 设置-文件 - 选项 - 高级 - 剪切、复制和粘贴,确保粘贴内容时显示粘贴选项按钮选项已启用。
其实在目标单元格单击右键+V,也可以实现类似的效果。
不过我平时更喜欢用的快捷键是Ctrl+Shift+V,这个快捷键不是Excel自带的,我通过第三方软件PureText实现。
听说Microsoft 365 Excel已经默认启用 Ctrl+Shift+V 执行 “粘贴值 / 纯文本”,可惜我还没用过。
如果不想用第三方快捷键,也不想用鼠标,还可以通过快捷键 Alt+H+V+V实现粘贴值。
优点是纯键盘,快速稳定,缺点是需要两只手配合。
咱们闲话讲完,继续力扣刷题。
力扣222. 完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1
提示:
树中节点的数目范围是
题目数据保证输入的树是完全二叉树
进阶:遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗?
我的思路
我想到的方法是,用BFS广度优先搜索遍历完全二叉树,用deque双端队列存储节点。
class Solution: def countNodes(self, root: Optional[TreeNode]) -> int: n=0 q=deque() q.append(root) while q[0]: q.append(q[0].left) q.append(q[0].right) q.popleft() n+=1 return n
力扣提交通过,时间复杂度和空间复杂度都是O(n)。
更快的解法,我大概能想到,但是如何实现就搞不定了。还是先看官方题解吧。
力扣官方题解
方法一:二分查找 + 位运算
官方题解没有python代码,我让豆包转了一份。
# 首先定义二叉树节点类class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass Solution: def countNodes(self, root: TreeNode) -> int: if not root: return 0 # 计算树的高度(层数,从0开始) level = 0 node = root while node.left: level += 1 node = node.left # 二分查找范围:low是第level层最左节点编号,high是最右节点编号 low = 1 << level high = (1 << (level + 1)) - 1 while low < high: # 向上取整,避免死循环 mid = (high - low + 1) // 2 + low if self.exists(root, level, mid): low = mid else: high = mid - 1 return low def exists(self, root: TreeNode, level: int, k: int) -> bool: bits = 1 << (level - 1) node = root while node and bits > 0: if (bits & k) == 0: node = node.left else: node = node.right bits >>= 1 return node is not None
力扣提交通过,时间复杂度O(log²n),空间复杂度O(1)。
这个代码对我这样的小白来说,有点太复杂了。
不过它解决了一个我一直想不明白的问题——在完全二叉树中如何判断一个节点是否存在。
以示例1中的节点6举例,6的二进制为110。
从左向右数,第一个1代表第0层,没有方向。
第二个1代表第1层指针向右移动。
第三个0代表第2层指针向左移动。
很奇妙,有没有。
注意这个原理只适用于完全二叉树。
对于普通二叉树,因为节点中间有大量空节点,而空节点是没有左右子节点的,导致这个二进制求指针方向的方法完全失效。
exists函数代码中的掩码位运算,我结合举例勉强能看懂。
还是以示例1中的节点6举例,将6带入k节点,也就是中间值,节点6在第2层。
掩码bits = 1 << (level - 1) = 1 << (2 - 1) = 10
将掩码和节点6的值做按位与计算,就可以确定中间值的第1位是否为0。
注意对于二进制数来说,第1位是从右向左数第2位,相当于10进制的十位数。
按位与运算就像两个开关电路并联(实际上要更复杂),只有两个1做按位与计算,结果才是1,其他都是0。
6的二进制为110,第1位数为1,110与010做按位与计算结果不是0,所以根节点要先向右移动。
掩码bits通过位运算右移1位,从10变为1。
再次将掩码和节点6的值做按位与计算,就可以确定中间值的第0位是否为0。
注意对于二进制数来说,第0位是从右向左数第1位,相当于10进制的个位数。
按位与运算就像两个开关电路并联(实际上要更复杂),只有两个1做按位与计算,结果才是1,其他都是0。
6的二进制为110,第0位数为0,110与001做按位与计算结果是0,所以根节点要向左移动。
此时掩码bits为0,循环结束。
这个时候node节点已经从root节点移动到了k节点,也就是我们举例的节点6。
如果节点6是存在的,就返回True,否则就返回False。
在使用这个exists函数的时候,是将中间值mid节点带入k,这样就可以判断中间值mid节点是否真实存在了。
这个代码如果让我写,我大概率不会使用位运算,而是用二进制转字符串判断节点向左还是向右移动。
毕竟人脑不是电脑,我做不到用二进制位运算思考数学问题。
但是二进制位运算的速度与字符串循环相比,那是天上地下,根本不是一个级别的。
位运算是通过直接操作硬件电路实现的,没有任何中间层转换,所以计算速度极快。
据我所知,目前没有任何一款量产 CPU(含大型机)能稳定跑到10GHz;实验室极限超频也仅摸到9GHz出头,离10GHz仍有差距。
这种CPU频率无大幅进展的情况已持续约20年,核心因功耗、散热、量子隧穿等物理瓶颈。我忽然想到了三体中的智子,细思极恐。
在CPU硬件频率没有大幅提升的情况下,算法优化就显得非常重要。
而位运算就是算法优化的重要组成部分,尤其是在追求高性能、低资源消耗的场景中,位运算常被用来替代常规算术运算,实现效率的提升。
可惜的是,说起来天下无敌,做起来无能为力。
日常工作中,我还从来没有用位运算解决过问题。
我是个编程爱好者,小白级别的,如果你跟我一样希望通过力扣刷题,学习各种奇妙的算法,可以关注我,大家一起学习。