公众号:
在做一个难度标记为简单的力扣题时,本以为轻松能过,却遇到一点小问题,一时竟没能想到原因。
借助objdump分析汇编后,才恍然大悟。
汇编指令:
MOV 传送字或字节
CMP 比较.(两操作数作减法,仅修改标志位,不回送结果)
JLE 小于或等于转移
CALLQ 过程调用
ADD 加
题目
力扣题库:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
解题思想:遍历寻找最深
代码1:
class Solution {
public:
#define MAX_INT(x, y) ((x)>(y) ? (x) : (y)) /*获取Max*/
int maxDepth(TreeNode* root) {
if(NULL == root) {
return 0;
}
return MAX_INT(maxDepth(root->left), maxDepth(root->right)) + 1; /*向子树查找最大深度*/
}
};
运行结果:
一时没感觉哪里不正常,为何超时了呢
代码改为如下:
代码2:
class Solution {
public:
#define MAX_INT(x, y) ((x)>(y) ? (x) : (y))
int maxDepth(TreeNode* root) {
if(NULL == root) {
return 0;
}
int iLeft = maxDepth(root->left);/*左子树*/
int iRight = maxDepth(root->right);/*右子树*/
return MAX_INT(iLeft, iRight) + 1;
}
};
运行结果:
比较汇编
一时竟没想到原因,上objdump。
使用objdump -dlS 命令,输出汇编信息
-d:将代码段反汇编
-S:将代码段反汇编的同时,将反汇编代码和源代码交替显示,编译时需要给出-g,即需要调试信息。
-l:反汇编代码中插入源代码的文件名和行号。
编译源码时,添加-g参数
g++ treetest.cpp -o treetest -g
objdump -dlS treetest > treetest.asm
代码1的汇编:
return MAX_INT(maxDepth(root->left), maxDepth(root->right)) + 1;
400779: 48 8b 45 e0 mov -0x20(%rbp),%rax
40077d: 48 8b 50 08 mov 0x8(%rax),%rdx
400781: 48 8b 45 e8 mov -0x18(%rbp),%rax
400785: 48 89 d6 mov %rdx,%rsi
400788: 48 89 c7 mov %rax,%rdi
40078b: e8 ca ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
400790: 89 c3 mov %eax,%ebx
400792: 48 8b 45 e0 mov -0x20(%rbp),%rax
400796: 48 8b 50 10 mov 0x10(%rax),%rdx
40079a: 48 8b 45 e8 mov -0x18(%rbp),%rax
40079e: 48 89 d6 mov %rdx,%rsi
4007a1: 48 89 c7 mov %rax,%rdi
4007a4: e8 b1 ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
4007a9: 39 c3 cmp %eax,%ebx
4007ab: 7e 1c jle 4007c9 <_ZN8Solution8maxDepthEP8TreeNode+0x6f>
/home/wangzhaohui/treetest/treetest2.cpp:14 (discriminator 1)
4007ad: 48 8b 45 e0 mov -0x20(%rbp),%rax
4007b1: 48 8b 50 08 mov 0x8(%rax),%rdx
4007b5: 48 8b 45 e8 mov -0x18(%rbp),%rax
4007b9: 48 89 d6 mov %rdx,%rsi
4007bc: 48 89 c7 mov %rax,%rdi
4007bf: e8 96 ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
4007c4: 83 c0 01 add $0x1,%eax
4007c7: eb 1a jmp 4007e3 <_ZN8Solution8maxDepthEP8TreeNode+0x89>
/home/wangzhaohui/treetest/treetest2.cpp:14 (discriminator 2)
4007c9: 48 8b 45 e0 mov -0x20(%rbp),%rax
4007cd: 48 8b 50 10 mov 0x10(%rax),%rdx
4007d1: 48 8b 45 e8 mov -0x18(%rbp),%rax
4007d5: 48 89 d6 mov %rdx,%rsi
4007d8: 48 89 c7 mov %rax,%rdi
4007db: e8 7a ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
4007e0: 83 c0 01 add $0x1,%eax
代码2的汇编:
int iLeft = maxDepth(root->left);
400778: 48 8b 45 e0 mov -0x20(%rbp),%rax
40077c: 48 8b 50 08 mov 0x8(%rax),%rdx
400780: 48 8b 45 e8 mov -0x18(%rbp),%rax
400784: 48 89 d6 mov %rdx,%rsi
400787: 48 89 c7 mov %rax,%rdi
40078a: e8 cb ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
40078f: 89 45 fc mov %eax,-0x4(%rbp)
/home/wangzhaohui/treetest/treetest.cpp:17
int iRight = maxDepth(root->right);
400792: 48 8b 45 e0 mov -0x20(%rbp),%rax
400796: 48 8b 50 10 mov 0x10(%rax),%rdx
40079a: 48 8b 45 e8 mov -0x18(%rbp),%rax
40079e: 48 89 d6 mov %rdx,%rsi
4007a1: 48 89 c7 mov %rax,%rdi
4007a4: e8 b1 ff ff ff callq 40075a <_ZN8Solution8maxDepthEP8TreeNode>
4007a9: 89 45 f8 mov %eax,-0x8(%rbp)
/home/wangzhaohui/treetest/treetest.cpp:19
return MAX_INT(iLeft, iRight) + 1;
4007ac: 8b 45 fc mov -0x4(%rbp),%eax
4007af: 3b 45 f8 cmp -0x8(%rbp),%eax
4007b2: 7e 08 jle 4007bc <_ZN8Solution8maxDepthEP8TreeNode+0x62>
/home/wangzhaohui/treetest/treetest.cpp:19 (discriminator 1)
4007b4: 8b 45 fc mov -0x4(%rbp),%eax
4007b7: 83 c0 01 add $0x1,%eax
4007ba: eb 06 jmp 4007c2 <_ZN8Solution8maxDepthEP8TreeNode+0x68>
/home/wangzhaohui/treetest/treetest.cpp:19 (discriminator 2)
4007bc: 8b 45 f8 mov -0x8(%rbp),%eax
4007bf: 83 c0 01 add $0x1,%eax
对比一下汇编结果,可以顿时理解错在哪里了,错在对三元运算符的使用上了。
从代码1的汇编结果来看,可以看到有4次过程调用callq指令,callq调用的就是class Solution中的函数maxDepth。而代码2中,仅有两次callq指令。
这简单道理我竟然还用上了objdump去看汇编,预处理一下,就能明显看到问题。
预处理
g++ -E treetest.cpp
得到对三元运算符的预处理
int maxDepth(TreeNode* root) {
if(__null == root)
{
return 0;
}
return ((maxDepth(root->left))>(maxDepth(root->right)) ? (maxDepth(root->left)) : (maxDepth(root->right))) + 1;
}
可以看出,代码1中错误的使用了三元运算符,虽然能算出最终结果,行数少了,省了定义两个变量,但是return动作中,都要调用两次maxDepth,原本时间复杂度是O(n),现在成了O(2n)。
要吸取教训。。。