读c++ primer有感——stl性能leetcode刷题直观测试

xiaoxiao2021-02-28  68

用leetcode刷题对stl效率的感性认识。期望证实即使对stl(或某些c++特性)即使在不熟练的应用下也和裸写c没有性能代差(就是有差距也是小系数线性的),后面的文章会用string源码为例剖析下stl的实现细节和性能,而本文只是个简单的事实堆砌。 ps:stl网上有不少说法,这里就取standard template library的本意,用模板实现的那部分容器算法迭代器和各种trait都算,string也算,因为string也是基于模板的。 说明: 1.leetcode测试比较完备方便,即改即传即比; 2.刷leetcode的有不少有志之士,得到的效率分布数据相对靠谱; 3.刷的全是leetcode中easy的题,因为本文目的不是看算法效率,而是看代码优化效率; 4.是一口气全写完后,string源码看完后再写的博客,所以知道的有烂的地方没有改,因为目的是希望得到stl实现和纯c实现没有代差; 5.以前也做过不多的leetcode,和同学一起刷,发生过leetcode说某个代码效率低但一起分析和自测后发现该代码更优的情况,所以leetcode的结果是作参考用的; 6.到写的时候发现......!!!不同语言的效率对比没有了!!!好吧,和c++最优的比估计和c最优解比差不多,毕竟前者对后者的完全兼容。 415. Add Strings 题意:字符串模拟大数相加;string addStrings(string num1, string num2); 思路: 1.如果num2比num1长,swap二者,从而保证num1不比num2短; 2.num1和num2都和string("0")相加,string("0") + num1,从而进位不用特殊处理; 3.用std::transform,遍历num1和num2,把结果写回num1。加操作用lamda函数,进位与否用函数局部变量up表示; 4.最后看第一位是否进位(为1?),不是就把0给剪掉。 可能的败笔: 1.和string("0")相加导致string总体向后移位; 效率对比: 前85%,比最优速度差25%,如果在自己的实际工作中无论何时都可以忽略不计。 121. Best Time to Buy and Sell Stock 题意:int maxProfit(vector<int>& prices);对于prices,找到prices[j] - prices[i]的最大值,其中i<=j; 思路: 1.从数组第一个元素开始遍历数组,找到局部最高最低点push_back到一个新数组,比如76543212321,那么从7开始一路下降到的1就是一个最低点,再往后一路上升到的3是最高点; 2.呵呵,从第一个元素开始遍历暴力破解...,std::max_element查找之后的最大元素与当前元素做差值。 可能的败笔: 1.暴力破解... 效率对比: 前35%,比最优速度差25%,实际工作中忽略不计即可。 202. Happy Number 题意:“开心数”这种数要么能够循环回某个非开心数循环,要么就各位数平方和为1。 思路: 1.用set<int>存已经开心过的数,如果某一步循环回开心过的数,那么返回false。 败笔: 1.求各位数平方和说不定有啥非除10求余再相加的解法,比如构造一个array存储; 效率对比: 前98%,但是存在0ms的解,实际工作中如果此步骤重要且重复需要进一步查找深究。不过这个效率差距大概率在算法。 326. Power of Three 题意:检查一个数是否是3的某次方;bool isPowerOfThree(int n)。 思路: 1.暴力破解,3一直乘下去直到大于等于该数n; 2.注意数据溢出; 3.对于int可以取中位的数,如果大于该数,从该数开始乘; 败笔: 1.int想当然认为是32位并且闯对了,可以用static_cast<unsigned int>(-1) / 2或者climits下面的INT_MAX; 效率对比: 前百分之80,这个倒和stl无关。 231. Power of Two 题意:检查一个数是否是2的某次方;bool isPowerOfTwo(int n); 思路: 1.2每次乘方从位角度看是唯一的置位左移一位; 2.bitset<64>(n).count() == 1。 败笔: 暂无。 效率对比: 前百分之100。 83. Remove Duplicates from Sorted List 题意:从排序链表中删除重复结点;ListNode* deleteDuplicates(ListNode* head); 思路: 1.注意各种情况的边界条件; 2.delete结点; 败笔: 1.可以尝试定义class lnIter{ListNode * ln;},重载lnIter的前置++操作符和*、==,再用std::unique把重复的移动到链表最后,在手动删除或者remove_if。实现细节不少,但是std::unique保证无bug。 效率对比: 前百分之75,比最快的慢25%,实际工作可以忽略。 35. Search Insert Position 题意:int searchInsert(vector<int>& nums, int target);对于给定排好序的数组nums,找到最后一个不大于等于target的nums中数的下标; 思路: 1.return std::lower_bound(nums.cbegin(),nums.cend(),target) - nums.cbegin();用lower_bound找到下标; 败笔: 想不到。 效率对比: 前百分之百。 70. Climbing Stairs 题意:爬梯子;int climbStairs(int n);其实是对于n,找到一个数列1 2 1 1 2 1......只能有2或者1,并且数列之和为n,问这种组合的个数; 思路: 1.只能想到递归了,对于偶数的n = 2m,对第m阶台阶来说,有刚好走到m阶和走过m阶走到m+1阶两种情况:对于刚好走到m阶的情况,下一次不能走一步,因为走1步就会走到m+1阶,那么和情况2重复了,所以情况1有climbStairs(m) * climbStairs(m - 2)种走法;刚好走到m+1阶的情况2有climbStairs(m+1) * climbStairs(m-1)种走法;情况1和2之和就可以递归; 败笔: 1.非递归解法,需要查证。 效率对比: 前百分之100,不过此题和stl无关。 437. Path Sum III 题意:int pathSum(TreeNode* root, int sum);找到二叉树结点和等于sum的所有路径数量和; 思路: 1.递归,但是写得很麻烦,无法总结思路; 2.set<pair<TreeNode*,int>> tSet,用一个set来存储遍历过的结点和到该结点的当前数之和的二元组; 败笔: 1.此题 效率对比: 前百分之一,效率比最好的差了十倍。但是估计是算法差距。 53. Maximum Subarray 题意:int maxSubArray(vector<int>& nums);求最大连续子序列和; 思路: 1.先斩头去尾,把头尾的负数或0全部掉; 2.从vector最后一位开始向前加一直到前一位是负数,把和tempSum按情况赋值给max,再把tempSum往前加一直加到下一位是正数,如果tempSum此时是正数则加入下一回合计算,否则tempSum清0; 3.返回tempSum。 败笔: 1.可以从第一位开始加,并且可以用find和accumlate计算。 效率对比: 前百分之百。 191. Number of 1 Bits 题意:int hammingWeight(uint32_t n);返回n二进制1的数量; 思路: 1.bitset<64>(n).count(); 败笔: 1.可以用查表方式逐字节试试;也许还有某种位运算玩法; 效率对比: 前百分之5,效率是最快解法三分之一,如果实际工作这里不是关键重复处,可以接受,毕竟写法优雅呵呵。 263. Ugly Number 题意:bool isUgly(int num);如果一个数只能被2 3 5整除,那么num就是丑陋数; 思路: 1.除2一直到余数为0,再除3,除5,如果剩下1,那么就是丑陋的; 败笔: 1.暴力破解,可以尝试用素数去除; 效率对比: 前百分之35,是最好效率的一半,效率可接受,但解法太丑。 217. Contains Duplicate 题意:bool containsDuplicate(vector<int>& nums);数组中是否有重复数; 思路: 1.std::set<int> numSet,通过insert判断是否insert成功; 败笔: 1.unordered_multiset,哈希表应该更好;哈希表大小和哈系函数优化;这道题后面值得深入; 效率: 前百分之80,比最快慢了10%。 206. Reverse Linked List 题意:ListNode* reverseList(ListNode* head);反转链表; 思路: 1.直接反转; 败笔: 1.和unique要求的ForwardIterator不同,std::reverse需要双向迭代器,封装ListNode *调用stl恐怕不行; 效率: 前百分之85,比最快慢33%,但是和stl无关。 350. Intersection of Two Arrays II 题意:vector<int> intersect(vector<int>& nums1, vector<int>& nums2),求nums1和nums2的交集; 思路: 1.先sort重新排序两个数组,再std::set_intersection求交集; 败笔: 效率: 前百分之百,也证明了stl的高效。 551. Student Attendance Record I 题意:bool checkRecord(string s),如果字符串多余一个A,或者有两个以上连续的L,返回false; 思路: 1.用find_first_not_of分析出现A或者L的具体情况; 败笔: 效率: 前百分之100。 268. Missing Number 题意:int missingNumber(vector<int>& nums),nums含有0-n,n+1个数中的n个,找出少的那个数; 思路: 1.std::accumlate求和,再用公式求0-n的总和,返回差值; 败笔: 效率: 前百分之50,比最快慢五分之一。 541. Reverse String II 题意:string reverseStr(string s, int k),s按照k,先反转k个,再跳过k个。 思路: 1.用std::reverse; 败笔: 1.应该使用string自带的reverse,std不会比string更懂自己; 效率: 前百分之百。 543. Diameter of Binary Tree 题意:int diameterOfBinaryTree(TreeNode* root),求二叉树相差最远的两个结点距离; 思路: 1.递归,int findMax(TreeNode *root),返回树的最大深度,二叉树的周长等于左右子树深度之和,在求左右子树最大深度的同时更新周长; 效率: 前百分之40,比最快的慢三倍,此题和stl无关。 371. Sum of Two Integers 题意:求两个整数之和,不能用+-号;int getSum(int a, int b); 思路: 1.用个intWrapper,定义一个减法操作实际执行加法,int到intWrapper的构造函数,intWrapper到int的隐式转化; 败笔: 1.可以看下编译结果看是否最终代码没有调用任何函数; 效率: 前百分之40,尽管和stl无关,知识相通。 258. Add Digits 题意:返回数字各位数字之和,直到之和为各位数为止; 思路: 1.递归调用; 2.每一次递归,用to_string把int转换为string,再调用accumulate相加,再减去'0'*str.length(); 效率: 前百分之65,比最快慢一倍。 283. Move Zeroes 题意:void moveZeroes(vector<int>& nums),把nums中的0移到最后,其他的相对位置不变; 思路: 1.建议一个和nums等长初始值为0的数组; 2.copy_if拷贝; 效率: 前百分之90,额,其实此题要求不能拷贝数组,呵呵。 530. Minimum Absolute Difference in BST 题意:求二叉搜索树中两个相邻node的最大差值;相邻的node B是比当前node A大或者等于的第一个数; 思路: 1.遍历二叉树得到所有的数,sort,transform得到相邻数字差数组,再用min_element找到最小差值返回。 效率: 前百分之50,比最快慢25%。 506. Relative Ranks 题意:vector<string> findRelativeRanks(vector<int>& nums),求nums前三大的数; 思路: 1.std::sort,再遍历nums,用std::lower_bound找num对应的rank; 效率: 前百分之60。 时间紧张,下面的就写效率了。 167. Two Sum II - Input array is sorted 80%。 383. Ransom Note 先统计note中的字符频率,再find_if。 60%。 349. Intersection of Two Arrays 和350比起来多一个unique和resize。 100%。 453. Minimum Moves to Equal Array Elements 一个move等价于一个数减1。先min_element求到最小值min,再遍历求差值之和。 可以accumulate求和再求差值。 5%,比最快的慢60%。 455. Assign Cookies 80%。 387. First Unique Character in a String 用一个int cCount[26]计数。 90%。 100. Same Tree 遍历树,用字符串作为遍历的路线图,比如7l8m9r,前序遍历,7左子树结点(l),最后比较路线图字符串是否相同。路线图相同==数相同,需要证明。 65%。 237. Delete Node in a Linked List 85%。 169. Majority Element 先sort排序,返回nums[nums.size()/2]。 60%。慢30%。 409. Longest Palindrome 85%,慢一倍。 504. Base 7 10%,慢三倍。 242. Valid Anagram 先sort,再==比较。 其实统计字符数就可以了。 3%,慢4倍。 389. Find the Difference accumlate之后求差值,需要注意溢出。 70%。 508. Most Frequent Subtree Sum 50%,和stl无关。 448. Find All Numbers Disappeared in an Array std::iota填充满一个1-n 的数组,在sort源数组,再set_difference求得差集。 30%,慢50%。 520. Detect Capital 用any_of和find_if。 75%。 136. Single Number 75%。 442. Find All Duplicates in an Array 20%,和stl无关。 485. Max Consecutive Ones for_each,lamda函数,加局部变量。 30%,慢25%。 406. Queue Reconstruction by Height 8%,和stl无关。 463. Island Perimeter 70%,和stl无关。 344. Reverse String 直接调std::reverse。 应该调string::reverse。 30%,慢80%。 412. Fizz Buzz generate_n和lamda函数生成。 80%。 500. Keyboard Row 用到了find_if。 70%。 557. Reverse Words in a String III 用到了istringstream和reverse。 50%,慢40%。 461. Hamming Distance 40%,和c++新特性无关。 最后,从这些例子,我自己得到初步结论,使用stl不熟练时应该根据使用场景来测试代码性能选择是否使用;熟练使用后可以减少测试工作,通过分析来看是否用stl。一般来说,直接用,stl没bug,文档清晰,抛异常规范,接口通用,功能全面,这是优点;源码晦涩,不知道在干什么,这是不足(代码不易读,知乎和stackoverflow不少人都这么说,《stl源码剖析》头一章也显而易见);写stl的人是业界最牛的人,他们的代码可以放心使用,如果不好用,那说不定是自己用不好,这也是优点。
转载请注明原文地址: https://www.6miu.com/read-97173.html

最新回复(0)