├── .gitattributes ├── README-En.md ├── Readme.md └── notes ├── LeetCode第101号问题:对称二叉树.md ├── LeetCode第102号问题:二叉树的层序遍历.md ├── LeetCode第103号问题:二叉树的锯齿形层次遍历.md ├── LeetCode第107号问题:二叉树的层次遍历II.md ├── LeetCode第110号问题:平衡二叉树.md ├── LeetCode第118号问题:杨辉三角.md ├── LeetCode第119号问题:杨辉三角II.md ├── LeetCode第121号问题:买卖股票的最佳时机.md ├── LeetCode第122号问题:买卖股票的最佳时机II.md ├── LeetCode第123号问题:买卖股票的最佳时机III.md ├── LeetCode第125号问题:验证回文串.md ├── LeetCode第131号问题:分割回文串.md ├── LeetCode第136号问题:只出现一次的数字.md ├── LeetCode第138号问题:复制带随机指针.md ├── LeetCode第139号问题:单词拆分.md ├── LeetCode第144号问题:二叉树的前序遍历.md ├── LeetCode第145号问题:二叉树的后序遍历.md ├── LeetCode第146号问题:LRU缓存机制.md ├── LeetCode第150号问题:逆波兰表达式求值.md ├── LeetCode第15号问题:三数之和.md ├── LeetCode第167号问题:两数之和II-输入有序数组.md ├── LeetCode第172号问题:阶乘后的零.md ├── LeetCode第187号问题:重复的DNA序列.md ├── LeetCode第191号问题:位1的个数.md ├── LeetCode第199号问题:二叉树的右视图.md ├── LeetCode第19号问题:删除链表的倒数第N个节点.md ├── LeetCode第1号问题:两数之和.md ├── LeetCode第201号问题:数字范围按位与.md ├── LeetCode第203号问题:移除链表元素.md ├── LeetCode第206号问题:反转链表.md ├── LeetCode第209号问题:长度最小的子数组.md ├── LeetCode第20号问题:有效的括号.md ├── LeetCode第219号问题:存在重复元素II.md ├── LeetCode第21号问题:合并两个有序链表.md ├── LeetCode第231号问题:2的幂.md ├── LeetCode第237号问题:删除链表中的节点.md ├── LeetCode第239号问题:滑动窗口最大值.md ├── LeetCode第23号问题:合并K个排序链表.md ├── LeetCode第24号问题:两两交换链表中的节点.md ├── LeetCode第268号问题:缺失数字.md ├── LeetCode第26号问题:删除排序数组中的重复项.md ├── LeetCode第279号问题:完全平方数.md ├── LeetCode第283号问题:移动零.md ├── LeetCode第295号问题:数据流的中位数.md ├── LeetCode第2号问题:两数相加.md ├── LeetCode第301号问题:删除无效的括号.md ├── LeetCode第326号问题:3的幂.md ├── LeetCode第328号问题:奇偶链表.md ├── LeetCode第342号问题:4的幂.md ├── LeetCode第344号问题:反转字符串.md ├── LeetCode第349号问题:两个数组的交集.md ├── LeetCode第350号问题:两个数组的交集II.md ├── LeetCode第3号问题:无重复字符的最长子串.md ├── LeetCode第445号问题:两数相加II.md ├── LeetCode第447号问题:回旋镖的数量.md ├── LeetCode第454号问题:四数相加II.md ├── LeetCode第642号问题:设计一个搜索自动完成系统.md ├── LeetCode第690号问题:员工的重要性.md ├── LeetCode第75号问题:颜色分类.md ├── LeetCode第86号问题:分割链表.md ├── LeetCode第877号问题:石子游戏.md ├── LeetCode第92号问题:反转链表II.md ├── LeetCode第94号问题:二叉树的中序遍历.md └── LeetCode第9号问题:回文数.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=java 2 | *.css linguist-language=java 3 | *.html linguist-language=java 4 | *.md linguist-language=java 5 | -------------------------------------------------------------------------------- /README-En.md: -------------------------------------------------------------------------------- 1 | ![LeetCode Animation All in One](https://upload-images.jianshu.io/upload_images/1940317-e837182a805cecce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | 3 | [![Travis](https://img.shields.io/badge/language-C++-red.svg)](https://developer.apple.com/) 4 | [![Travis](https://img.shields.io/badge/language-Java-yellow.svg)](https://developer.apple.com/) 5 | 6 | [这里有中文版本的README,点击它!](https://github.com/MisterBooo/LeetCodeAnimation/blob/master/Readme.md) 7 | 8 | I will do my best to demonstrate all the questions on LeetCode in the form of animation. 9 | 10 | I plan to take three to four years to complete it! 11 | 12 | I look forward to witnessing this day with you! 13 | 14 | The latest article published in WeChat **五分钟学算法** , you can pay attention to get the latest article. 15 | 16 | 17 | 18 | ## Problems 19 | 20 | 21 | | ID | Problem | Article | Animation | Date| 22 | | --- | --- | :--- |:--- | :--- | 23 | | 000 |Ten Classic Sorting Algorithms | [十大经典排序算法动画与解析,看我就够了!(配代码完全版)](https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg) | ![归并排序.gif](https://upload-images.jianshu.io/upload_images/1940317-92f62b62af03e233.gif?imageMogr2/auto-orient/strip)| 24 | | 001 |Two-Sum | [每天一算:Two Sum](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483740&idx=1&sn=1950545589ea9b86ee65fbb6be1f4290&chksm=fa0e6eddcd79e7cb542b7d4dc1304eead516994315fa4f52b575230f0f022c9e0a88ede3714e&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161033.gif) | 25 | | 002 |Add Two Numbers| [图解LeetCode第 2 号问题:两个数字相加](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484231&idx=2&sn=6a9eb4fd0619c822e4dede69b8d841c8&chksm=fa0e6cc6cd79e5d0c03fffcd65b665fed62db9dca9c97771898f388ea292ce806bfd6eb908b5&token=934487935&lang=zh_CN#rd) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210092831.gif)| 26 | | 003 |Longest Substring Without Repeating Characters| [图解LeetCode第 3 号问题:无重复字符的最长子串](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484265&idx=2&sn=7f72afb341865923315bd51e1f50beff&chksm=fa0e6ce8cd79e5fe4be925fd5f01f59f59010c6c965fb3daefac79992593a6e58990c212e0bb&token=1412967663&lang=zh_CN#rd)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210092855.gif)| 27 | | 019| Remove-Nth-Node-From-End-of-List |[每天一算:Remove Nth Node From End of List](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483821&idx=1&sn=11ecccab76cd53163e9dedb75effeb93&chksm=fa0e6e2ccd79e73ae9137c0d91b3533df4ea4ead4ad081834b8d91ff364c0d55c350ddcfa6c4&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161058.gif)| 28 | | 020|Valid-Parentheses | [每天一算:Valid Parentheses](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483824&idx=1&sn=ab9362e125dc5e2b3ef1611cad9448c2&chksm=fa0e6e31cd79e727c6e1e0e3c467e193edb6ae841a41e5dc8eef39d0bf3141cc53f63b019cba&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161106.gif)| 29 | | 024| Swap-Nodes-in-Pairs | [每天一算:Swap Nodes in Pairs](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483773&idx=1&sn=db6cf272df968cd6571eb0bb50ecc721&chksm=fa0e6efccd79e7ea26810d335e6ece9ac23b8e3ac31be00dbd534018737ccb3ef9a00f22aff3&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161115.gif)| 30 | | 026| Remove-Duplicates-from-Sorted-Array| [图解LeetCode第 26 号问题:删除排序数组中的重复项](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484284&idx=2&sn=c8af62a82a62a21217d0f0b2b5891e4f&chksm=fa0e6cfdcd79e5ebe8726a61f93b834467d29b7d9e60a44feb990388f9e98605ac1e3f7e723d&token=762342620&lang=zh_CN#rd) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161124.gif)| 31 | | 075| Sort-Colors| [每天一算:Sort Colors](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483706&idx=1&sn=905f43c882a91b55fd169d812620f277&chksm=fa0e6ebbcd79e7ad8857b0dad9ad14dbaf17fe557ef56ba600cec26b2bb668df2e171431d74c&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161136.gif)| 32 | | 086| Partition-List| [每天一算:Partition List](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483781&idx=1&sn=f31548ebbb2cf9ba56d979d3e51ddde2&chksm=fa0e6e04cd79e712d6cc7ff8e8b7631b7300ac0fa1a3e4c4e3b836de7a01fb5d0d6428a18bc4&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161147.gif)| 33 | |092 |Reverse-Linked-List-II | [每天一算:Reverse Linked List II](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483820&idx=1&sn=113e87b55c8ac8e22e9db00673798118&chksm=fa0e6e2dcd79e73b5835a262599b935783de3317a453bc0ed8df9fa5d1532785a085ea663e59&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161156.gif)| 34 | | 094|Binary-Tree-Inorder-Traversal | [每天一算:Binary Tree Inorder Traversal ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483853&idx=1&sn=94cd4b4ee8dc2268290a72334c6af57b&chksm=fa0e6e4ccd79e75a41a6b78397b80cdfccda332823874475b516f997f89e786488599fc5cc1e&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161208.gif)| 35 | |102 |Binary-Tree-Level-Order-Traversal| [每天一算:Binary Tree Level Order Traveral](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483868&idx=1&sn=d50041789fcd13a75a2296f620b69d71&chksm=fa0e6e5dcd79e74b0030ac5129f10ec4ba87c98da63c5904affe9f06e06ecf28695c410d3ec7&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161232.gif)| 36 | |103 |Binary Tree Zigzag Level Order Traversal| [图解LeetCode第 103 号问题:二叉树的锯齿形层次遍历](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484290&idx=2&sn=c29c4eefcbe8954cca6b3c8491ebccf1&chksm=fa0e6c03cd79e515581905322a3a22a3f3d10d24ca668a9d5aaef00932f0237eeaeaf3199668&token=1840661183&lang=zh_CN#rd)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210092922.gif)| 37 | |107 | Binary Tree Level Order Traversal II | 每天一算: Binary Tree Level Order Traversal II|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210092949.gif)| 38 | |136 | Single Number | [一道让你拍案叫绝的算法题 ](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484505&idx=1&sn=4c1c056dd4852c3b4b1ead51c90a9b2d&chksm=fa0e6bd8cd79e2ce8188dcdd8843a5d071248906bfb8971c62d513dbd69b816acc191a78e4b2&token=487128715&lang=zh_CN#rd)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190116110804.gif)| 2019-01-16 | 39 | |144 | Binary-Tree-Preorder-Traversal| [每天一算:Binary Tree Preorder Traversal](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483843&idx=1&sn=994bf0d42dd9941a879a3a3ed500a4d6&chksm=fa0e6e42cd79e75472404eb5da7ee98f20d303efe230eb4f41efec57164630f555e7111e62ff&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181207112441.gif)| 40 | | 145| Binary-Tree-Postorder-Traversal | [每天一算:Binary Tree Postorder Traversal](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483853&idx=1&sn=94cd4b4ee8dc2268290a72334c6af57b&chksm=fa0e6e4ccd79e75a41a6b78397b80cdfccda332823874475b516f997f89e786488599fc5cc1e&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181219084940.gif)| 41 | | 146| LRU Cache | LRU缓存机制 |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190125143756.gif)| 2019-01-25 Made by Jun chen| 42 | | 150| Evaluate-Reverse-Polish-Notation | [每天一算:Evaluate Reverse Polish Notation](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483834&idx=1&sn=27cbff99f10dfcdb56cb37c237d7f2bb&chksm=fa0e6e3bcd79e72dc430bf81aed9dde9bd01634239dcf7820d6befa881efd323d9d58d76d90d&scene=21#wechat_redirect) |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161304.gif)| 43 | | 167| Two-Sum-II-Input-array-is-sorted | [每天一算:Two Sum II ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483711&idx=1&sn=3afec74e9e9effa71dc0b22659e14b44&chksm=fa0e6ebecd79e7a84db7861c9b5dbccdc98aa9d9a6994dda49a37edeb729e8242ea6af8f20ad&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161314.gif)| 44 | |199 | Binary Tree Right Side View | 每天一算:Binary Tree Right Side View |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161328.gif)| 45 | | 203| Remove-Linked-List-Elements | [每天一算:Remove Linked List Elements](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483766&idx=1&sn=6721376a65680bf7cf9064cf7b1ae4ae&chksm=fa0e6ef7cd79e7e1665e60fe6ea3f2087bca518c1573bc4c4b9425573f98401bafc59542dca0&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161338.gif)| 46 | |206 | Reverse Linked List | [每天一算: Reverse Linked List ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483799&idx=1&sn=c2212c8963809e8d3392abeeb851dbfc&chksm=fa0e6e16cd79e7003c2d30b1a2bb4f23dc56df38e3efedd0ab2cfae291609280a832eabe67de&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210093009.gif)| 47 | |209 | Minimum Size Subarray Sum | 每天一算: Minimum Size Subarray Sum |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181210093031.gif)| 48 | | 219|Contains-Duplicate-II |[每天一算:Contains Duplicate II ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483755&idx=1&sn=2501b6ca09c43eaa9fba71a9bd1f5253&chksm=fa0e6eeacd79e7fc192c0a23cf90d98fe6f2c35f9e4f2d0f937ccba45a58cf23a0a9c49d35d5&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161352.gif)| 49 | |237| Delete-Node-in-a-Linked-List |每天一算:Delete Node in a Linked List|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161400.gif) | 50 | |279| Perfect Squares |[图解LeetCode第 279 号问题: 完全平方数](https://mp.weixin.qq.com/s/53AlHe29fJF1hEwj0cj7ZA)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190119213317.gif) | Made by 王琛 2019-01-19日| 51 | |283 |Move-Zeroes |[每天一算:Move Zeros](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483700&idx=1&sn=465f778d60e8560742feab5844d7cac5&chksm=fa0e6eb5cd79e7a357899d378edb532b498cd63e3ce9113f8ac74d397ce4b214ca5aa8198b7d&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161421.gif)| 52 | |328 |Odd-Even-Linked-List | [每天一算:Odd Even Linked List](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483786&idx=1&sn=f7810950b34675e1c4420361faf5e361&chksm=fa0e6e0bcd79e71d2c6fc6a4a68b6ef7a17abc3dc9897548f8e44b51e9494f52c4cebbc4176e&scene=21#wechat_redirect) |![](https://diycode.b0.upaiyun.com/photo/2018/94e5c38540029690c93314b3d697caaf.gif)| 53 | |344 | Reverse-String |每天一算:Reverse String |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181211110918.gif)| 54 | |349 | Intersection-of-Two-Arrays| [每天一算:Intersection of Two Arrays ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483726&idx=1&sn=a887f6b983058d97c183dd300832ecbb&chksm=fa0e6ecfcd79e7d985587b543622c85aadc83a4d7a074135e1356fb4a0ebfd07e7af13467906&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161441.gif)| 55 | | 350| Intersection-of-Two-Arrays-II| [每天一算:Intersection of Two Arrays II ](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483733&idx=1&sn=946bd6de3251437dd77b43ecab056c82&chksm=fa0e6ed4cd79e7c2a439b5f1853bf5154a3438ed282c7ba5e94948780c426a1f1492c0b201c4&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161729.gif)| 56 | |445| Add Two Numbers II |[图解LeetCode第 445 号问题: 两数相加 II](https://mp.weixin.qq.com/s/z8_1dK7mw9gxfhhSZUBVgg)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190119213714.gif) | Made by 王琛 2019-01-19日| 57 | |447 | Number-of-Boomerangs | [每日一算:Number of Boomerangs](http://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247483747&idx=1&sn=7774eee0b252b311257134f6a52c4e2d&chksm=fa0e6ee2cd79e7f44858c46c3d04859ced9073dbb9de95ce7ee0bcc131e613862ddfd9a6f158&scene=21#wechat_redirect)|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161738.gif)| 58 | |454 |4Sum-II | 每日一算:4Sum II|![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181206161751.gif)| 59 | |642 |Design Search Autocomplete System | [图解 LeetCode 第 642 号问题:搜索自动完成系统](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484491&idx=1&sn=b329d90370d183b5a58bbf03f6a436ae&chksm=fa0e6bcacd79e2dc05bb5eaabd888561b82c37700b511e4971aa76ec42a630c0a35ef3e4721b&token=397665543&lang=zh_CN#rd)|Made by Jun [Click here](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484491&idx=1&sn=b329d90370d183b5a58bbf03f6a436ae&chksm=fa0e6bcacd79e2dc05bb5eaabd888561b82c37700b511e4971aa76ec42a630c0a35ef3e4721b&token=397665543&lang=zh_CN#rd)| 60 | 61 | If the link of the article cannot be clicked, it means that the article has not been published. Please look forward to it :) 62 | 63 | ## Code source 64 | This warehouse code unless otherwise specified, all from this warehouse 65 | 66 | [Play-Leetcode](https://github.com/liuyubobobo/Play-Leetcode) 67 | 68 | 69 | ## Supplement 70 | 71 | The warehouse is kept up to date. 72 | 73 | 74 | 2018-12-29 explain: 75 | 76 | [《2019年LeetCodeAnimationd的更新计划》](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484375&idx=1&sn=5a5482d9863342650d8b43bb59171f7c&chksm=fa0e6c56cd79e540115e52500b80c8e72001c87ddceb7c0ae1de166fd283d632b960cde41aca&token=578760218&lang=zh_CN#rd) 77 | 78 | 2018-12-07 explain: 79 | 80 | In order to better perform LeetCode animation, i am working hard to learn more data structures and algorithms. 81 | 82 | I am currently writing an article 《动画图解数据结构》, and will continue to update this warehouse after finishing the series of articles on animation graphic data structure. 83 | 84 | E-mail:misterbigbooo@gmail.com 85 | 86 | like it! star❤️ it! 87 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ![LeetCode Animation All in One](https://upload-images.jianshu.io/upload_images/1940317-e837182a805cecce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | 3 | [![Travis](https://img.shields.io/badge/language-C++-red.svg)](https://developer.apple.com/.md) 4 | [![Travis](https://img.shields.io/badge/language-Java-yellow.svg)](https://developer.apple.com/.md) 5 | 6 | 7 | [There is an English version of README here. just click it!](https://github.com/MisterBooo/LeetCodeAnimation/blob/master/README-En.md) 8 | 9 | 我会尽力将 LeetCode 上所有的题目都用动画的形式演示出来,计划用 3 到 4 年时间去完成它,期待与你见证这一天! 10 | 11 | 文章最新首发于微信公众号 **五分钟学算法** ,您可以关注获取最新的文章。 12 | 13 | ## 汇总 14 | 15 | | 序号 | 题目&题解 | 16 | | ---- | ------------------------------------------------------------ | 17 | | 0 | [十大经典排序算法动画与解析,看我就够了!(配代码完全版)](https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg) | 18 | | 1 | [两数之和](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第1号问题:两数之和.md) | 19 | | 2 | [两数相加](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第2号问题:两数相加.md) | 20 | | 3 | [无重复字符的最长子串](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第3号问题:无重复字符的最长子串.md) | 21 | | 9 | [回文数](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第9号问题:回文数.md) | 22 | | 15 | [三数之和](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第15号问题:三数之和.md) | 23 | | 19 | [删除链表的倒数第 N 个节点](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第19号问题:删除链表的倒数第N个节点.md) | 24 | | 20 | [有效的括号](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第20号问题:有效的括号.md) | 25 | | 21 | [合并两个有序链表](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第21号问题:合并两个有序链表.md) | 26 | | 23 | [合并 K 个排序链表](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第23号问题:合并K个排序链表.md) | 27 | | 24 | [两两交换链表中的节点](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第24号问题:两两交换链表中的节点.md) | 28 | | 26 | [删除排序数组中的重复项](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第26号问题:删除排序数组中的重复项.md) | 29 | | 75 | [颜色分类](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第75号问题:颜色分类.md) | 30 | | 86 | [分割链表](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第86号问题:分割链表.md) | 31 | | 92 | [反转链表 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第92号问题:反转链表II.md) | 32 | | 94 | [二叉树的中序遍历](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第94号问题:二叉树的中序遍历.md) | 33 | | 101 | [对称二叉树](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第101号问题:对称二叉树.md) | 34 | | 102 | [二叉树的层序遍历](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第102号问题:二叉树的层序遍历.md) | 35 | | 103 | [二叉树的锯齿形层次遍历](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第103号问题:二叉树的锯齿形层次遍历.md) | 36 | | 107 | [二叉树的层次遍历 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第107号问题:二叉树的层次遍历II.md) | 37 | | 118 | [杨辉三角](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第118号问题:杨辉三角.md) | 38 | | 119 | [杨辉三角II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第119号问题:杨辉三角II.md) | 39 | | 110 | [平衡二叉树](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第110号问题:平衡二叉树.md) | 40 | | 121 | [买卖股票的最佳时机](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第121号问题:买卖股票的最佳时机.md) | 41 | | 122 | [买卖股票的最佳时机II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第122号问题:买卖股票的最佳时机II.md) | 42 | | 123 | [买卖股票的最佳时机III](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第123号问题:买卖股票的最佳时机III.md) | 43 | | 125 | [验证回文串](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第125号问题:验证回文串.md) | 44 | | 131 | [分割回文串](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第131号问题:分割回文串.md) | 45 | | 136 | [只出现一次的数字](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第136号问题:只出现一次的数字.md) | 46 | | 138 | [复制带随机指针](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第138号问题:复制带随机指针.md) | 47 | | 139 | [单词拆分](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第139号问题:单词拆分.md) | 48 | | 144 | [二叉树的前序遍历](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第144号问题:二叉树的前序遍历.md) | 49 | | 145 | [二叉树的后序遍历](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第145号问题:二叉树的后序遍历.md) | 50 | | 146 | [LRU缓存机制](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第146号问题:LRU缓存机制.md) | 51 | | 150 | [逆波兰表达式求值](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第150号问题:逆波兰表达式求值.md) | 52 | | 167 | [两数之和 II - 输入有序数组](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第167号问题:两数之和II-输入有序数组.md) | 53 | | 172 | [阶乘后的零](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第172号问题:阶乘后的零.md) | 54 | | 187 | [重复的 DNA 序列](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第187号问题:重复的DNA序列.md) | 55 | | 191 | [位1的个数](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第191号问题:位1的个数.md) | 56 | | 199 | [二叉树的右视图](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第199号问题:二叉树的右视图.md) | 57 | | 201 | [数字范围按位与](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第201号问题:数字范围按位与.md) | 58 | | 203 | [移除链表元素](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第203号问题:移除链表元素.md) | 59 | | 206 | [反转链表](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第206号问题:反转链表.md) | 60 | | 209 | [长度最小的子数组](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第209号问题:长度最小的子数组.md) | 61 | | 219 | [存在重复元素 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第219号问题:存在重复元素II.md) | 62 | | 231 | [2的幂](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第231号问题:2的幂.md) | 63 | | 237 | [删除链表中的节点](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第237号问题:删除链表中的节点.md) | 64 | | 239 | [滑动窗口最大值](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第239号问题:滑动窗口最大值.md) | 65 | | 268 | [缺失数字](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第268号问题:缺失数字.md) | 66 | | 279 | [完全平方数](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第279号问题:完全平方数.md) | 67 | | 283 | [移动零](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第283号问题:移动零.md) | 68 | | 295 | [数据流的中位数](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第295号问题:数据流的中位数.md) | 69 | | 301 | [删除无效的括号](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第301号问题:删除无效的括号.md) | 70 | | 326 | [3 的幂](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第326号问题:3的幂.md) | 71 | | 328 | [奇偶链表](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第328号问题:奇偶链表.md) | 72 | | 342 | [4的幂](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第342号问题:4的幂.md) | 73 | | 344 | [反转字符串](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第344号问题:反转字符串.md) | 74 | | 349 | [两个数组的交集](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第349号问题:两个数组的交集.md) | 75 | | 350 | [两个数组的交集 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第350号问题:两个数组的交集II.md) | 76 | | 445 | [两数相加 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第445号问题:两数相加II.md) | 77 | | 447 | [回旋镖的数量](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第447号问题:回旋镖的数量.md) | 78 | | 454 | [四数相加 II](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第454号问题:四数相加II.md) | 79 | | 642 | [设计一个搜索自动完成系统](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第642号问题:设计一个搜索自动完成系统.md) | 80 | | 690 | [员工的重要性](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第690号问题:员工的重要性.md) | 81 | | 877 | [石子游戏](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第877号问题:石子游戏.md) | 82 | 83 | 84 | 85 | ## 补充 86 | 该仓库保持随时更新。 87 | 88 | 2018-12-29 说明: 89 | 90 | [《2019年LeetCodeAnimationd的更新计划》](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484375&idx=1&sn=5a5482d9863342650d8b43bb59171f7c&chksm=fa0e6c56cd79e540115e52500b80c8e72001c87ddceb7c0ae1de166fd283d632b960cde41aca&token=578760218&lang=zh_CN#rd) 91 | 92 | 2018-12-07 说明: 93 | 94 | 为了更好的做好LeetCode动画,笔者正在努力的学习更多的数据结构与算法。 95 | 96 | 笔者目前正在写数据结构的文章与动画,将《动画图解数据结构》系列文章写完后将继续更新此仓库。 97 | 98 | 邮箱:misterbigbooo@gmail.com 99 | 100 | 喜欢就star❤️一下吧! 101 | 102 | ## 和我交流 103 | 104 | 105 | 106 | | 二维码 | 说明 | 107 | | --- | --- | 108 | |![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) | 欢迎前来和程序员小吴一起学算法 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /notes/LeetCode第101号问题:对称二叉树.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 101 号问题:对称二叉树 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 第 101 号问题:对称二叉树。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,检查它是否是镜像对称的。 12 | 13 | 例如,二叉树 `[1,2,2,3,4,4,3]` 是对称的。 14 | 15 | ``` 16 | 1 17 | / \ 18 | 2 2 19 | / \ / \ 20 | 3 4 4 3 21 | ``` 22 | 23 | ### 题目解析 24 | 25 | 用递归做比较简单:一棵树是对称的**等价**于它的左子树和右子树两棵树是对称的,问题就转变为判断两棵树是否对称。 26 | 27 | ### 代码实现 28 | 29 | ```java 30 | class Solution { 31 | public boolean isSymmetric(TreeNode root) { 32 | if(root == null) return true; 33 | //把问题变成判断两棵树是否是对称的 34 | return isSym(root.left, root.right); 35 | } 36 | //判断的是根节点为r1和r2的两棵树是否是对称的 37 | public boolean isSym(TreeNode r1, TreeNode r2){ 38 | if(r1 == null && r2 == null) return true; 39 | if(r1 == null || r2 == null) return false; 40 | //这两棵树是对称需要满足的条件: 41 | //1.俩根节点相等。 2.树1的左子树和树2的右子树,树2的左子树和树1的右子树都得是对称的 42 | return r1.val == r2.val && isSym(r1.left, r2.right) 43 | && isSym(r1.right, r2.left); 44 | } 45 | } 46 | ``` 47 | 48 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第102号问题:二叉树的层序遍历.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 102 号问题:二叉树的层序遍历 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 102 号问题:二叉树的层序遍历。题目难度为 Medium,目前通过率为 55.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。 12 | 13 | 例如: 14 | 给定二叉树: `[3,9,20,null,null,15,7]`, 15 | 16 | ``` 17 | 3 18 | / \ 19 | 9 20 20 | / \ 21 | 15 7 22 | ``` 23 | 24 | 返回其层次遍历结果: 25 | 26 | ``` 27 | [ 28 | [3], 29 | [9,20], 30 | [15,7] 31 | ] 32 | ``` 33 | 34 | ### 题目解析 35 | 36 | 该问题需要用到**队列** 37 | 38 | - 建立一个queue 39 | - 先把根节点放进去,这时候找根节点的左右两个子节点 40 | - 去掉根节点,此时queue里的元素就是下一层的所有节点 41 | - 用for循环遍历,将结果存到一个一维向量里 42 | - 遍历完之后再把这个一维向量存到二维向量里 43 | - 以此类推,可以完成层序遍历 44 | 45 | 46 | 47 | 48 | 49 | ### 动画描述 50 | 51 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181112084159.gif) 52 | 53 | 54 | 55 | ### 代码实现 56 | 57 | ``` 58 | /// BFS 59 | /// Time Complexity: O(n), where n is the number of nodes in the tree 60 | /// Space Complexity: O(n) 61 | class Solution { 62 | public: 63 | vector> levelOrder(TreeNode* root) { 64 | 65 | vector> res; 66 | if(root == NULL) 67 | return res; 68 | 69 | queue> q; 70 | q.push(make_pair(root, 0)); 71 | 72 | while(!q.empty()){ 73 | 74 | TreeNode* node = q.front().first; 75 | int level = q.front().second; 76 | q.pop(); 77 | 78 | if(level == res.size()) 79 | res.push_back(vector()); 80 | assert( level < res.size() ); 81 | 82 | res[level].push_back(node->val); 83 | if(node->left) 84 | q.push(make_pair(node->left, level + 1 )); 85 | if(node->right) 86 | q.push(make_pair(node->right, level + 1 )); 87 | } 88 | 89 | return res; 90 | } 91 | }; 92 | 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第103号问题:二叉树的锯齿形层次遍历.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 103 号问题:二叉树的锯齿形层次遍历 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 103 号问题:二叉树的锯齿形层次遍历。题目难度为 Medium,目前通过率为 43.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 12 | 13 | 例如: 14 | 给定二叉树 `[3,9,20,null,null,15,7]`, 15 | 16 | ``` 17 | 3 18 | / \ 19 | 9 20 20 | / \ 21 | 15 7 22 | ``` 23 | 24 | 返回锯齿形层次遍历如下: 25 | 26 | ``` 27 | [ 28 | [3], 29 | [20,9], 30 | [15,7] 31 | ] 32 | ``` 33 | 34 | ### 题目解析 35 | 36 | 该问题需要用到**队列**,与之前的[二叉树的层次遍历](https://xiaozhuanlan.com/topic/8579460312)类似,不同点在于在偶数层需要翻转一下。 37 | 38 | - 建立一个queue 39 | - 先把根节点放进去,这时候找根节点的左右两个子节点 40 | - 去掉根节点,此时queue里的元素就是下一层的所有节点 41 | - 循环遍历,将结果存到一个一维向量里 42 | - 遍历完之后再把这个一维向量存到二维向量里 43 | - 如果该层为偶数层,则reverse翻转一下 44 | - 以此类推,可以完成层序遍历 45 | 46 | ### 动画描述 47 | 48 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502103236.gif) 49 | 50 | ### 代码实现 51 | 52 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502103307.png) 53 | 54 | 55 | 56 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第107号问题:二叉树的层次遍历II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 107 号问题:二叉树的层次遍历 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 107 号问题:二叉树的层次遍历 II。题目难度为 Easy,目前通过率为 55.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 12 | 13 | 例如: 14 | 给定二叉树 `[3,9,20,null,null,15,7]`, 15 | 16 | ``` 17 | 3 18 | / \ 19 | 9 20 20 | / \ 21 | 15 7 22 | ``` 23 | 24 | 返回其自底向上的层次遍历为: 25 | 26 | ``` 27 | [ 28 | [15,7], 29 | [9,20], 30 | [3] 31 | ] 32 | ``` 33 | 34 | ### 题目解析 35 | 36 | 该问题需要用到**队列**,解法与上篇[每天一算:Binary Tree Level Order Traversal](https://xiaozhuanlan.com/topic/8579460312)类似,区别在于最后存储方式的不同。 37 | 38 | - 建立一个 queue 39 | - 先把根节点放进去,这时候找根节点的左右两个子节点 40 | - 去掉根节点,此时queue里的元素就是下一层的所有节点 41 | - 用 for 循环遍历,将结果存到一个一维向量里 42 | - 遍历完之后再把这个一维向量**插入**到二维向量里 43 | - 以此类推,可以完成层序遍历 44 | 45 | 46 | 47 | ### 动画描述 48 | 49 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181112203355.gif) 50 | 51 | 52 | 53 | ### 代码实现 54 | 55 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181112203645.png) 56 | 57 | 58 | 59 | 60 | 61 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第110号问题:平衡二叉树.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 110 号问题:平衡二叉树 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 110 号问题:平衡二叉树。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 12 | 13 | ### 题目解析 14 | 15 | 采取**后序遍历**的方式遍历二叉树的每一个结点。 16 | 17 | 在遍历到一个结点之前已经遍历了它的左右子树,那么只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶结点的路径的长度),就可以一边遍历一边判断每个结点是不是平衡的。 18 | 19 | ### 动画描述 20 | 21 | 待补充 22 | 23 | ### 代码实现 24 | 25 | ```java 26 | class Solution { 27 | private boolean isBalanced = true; 28 | public boolean isBalanced(TreeNode root) { 29 | getDepth(root); 30 | return isBalanced; 31 | } 32 | public int getDepth(TreeNode root) { 33 | if (root == null) 34 | return 0; 35 | int left = getDepth(root.left); 36 | int right = getDepth(root.right); 37 | if (Math.abs(left - right) > 1) { 38 | isBalanced = false; 39 | } 40 | return right > left ? right + 1 : left + 1; 41 | } 42 | } 43 | ``` 44 | 45 | 46 | 47 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第118号问题:杨辉三角.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 杨辉三角 4 | 5 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190507201419.png) 6 | 7 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode](https://github.com/MisterBooo/LeetCodeAnimation) 系列文章之一。 8 | > 9 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 10 | > 11 | 12 | 13 | 杨辉三角应该是大家很早就接触到的一个数学知识,它有很多有趣的性质: 14 | 15 | - 每个数字等于上一行的左右两个数字之和,即 *C(n+1,i) = C(n,i) + C(n,i-1)* 16 | - 每行数字左右对称,由 1 开始逐渐变大 17 | - 第 n 行的数字有 n 项 18 | - 第 n 行的第 m 个数和第 n - m + 1 个数相等 ,为[组合数](https://baike.baidu.com/item/%E7%BB%84%E5%90%88%E6%95%B0)性质之一 19 | - ( a + b )n的展开式中的各项[系数](https://baike.baidu.com/item/%E7%B3%BB%E6%95%B0)依次对应杨辉三角的第 ( n + 1 ) 行中的每一项 20 | - 。。。 21 | 22 | 23 | 24 | ## 杨辉三角 25 | 26 | 题目来源于 LeetCode 上第 118 号问题:杨辉三角。题目难度为 Easy,目前通过率为 61.8% 。 27 | 28 | ### 题目描述 29 | 30 | 给定一个非负整数 *numRows,*生成杨辉三角的前 *numRows* 行。 31 | 32 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 33 | 34 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 35 | 36 | **示例:** 37 | 38 | ``` 39 | 输入: 5 40 | 输出: 41 | [ 42 | [1], 43 | [1,1], 44 | [1,2,1], 45 | [1,3,3,1], 46 | [1,4,6,4,1] 47 | ] 48 | ``` 49 | 50 | ### 题目解析 51 | 52 | > 这道题目在各大高校的习题中经常出现。 53 | 54 | 对于本题而言,利用性质 1 :每一行的首个和结尾一个数字都是 1,从第三行开始,中间的每个数字都是上一行的左右两个数字之和。 55 | 56 | ### 代码实现 57 | 58 | ```java 59 | class Solution { 60 | public List> generate(int numRows) { 61 | 62 | List> result = new ArrayList<>(); 63 | if (numRows < 1) return result; 64 | 65 | for (int i = 0; i < numRows; ++i) { 66 | //扩容 67 | List list = Arrays.asList(new Integer[i+1]); 68 | list.set(0, 1); list.set(i, 1); 69 | for (int j = 1; j < i; ++j) { 70 | //等于上一行的左右两个数字之和 71 | list.set(j, result.get(i-1).get(j-1) + result.get(i-1).get(j)); 72 | } 73 | result.add(list); 74 | } 75 | return result; 76 | 77 | } 78 | } 79 | 80 | ``` 81 | 82 | 83 | 84 | ## 杨辉三角II 85 | 86 | 题目来源于 LeetCode 上第 119 号问题:杨辉三角II。题目难度为 Easy,目前通过率为 55.5% 。 87 | 88 | ### 题目描述 89 | 90 | 给定一个非负索引 *k*,其中 *k* ≤ 33,返回杨辉三角的第 *k* 行。 91 | 92 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 93 | 94 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 95 | 96 | **示例:** 97 | 98 | ``` 99 | 输入: 3 100 | 输出: [1,3,3,1] 101 | ``` 102 | 103 | **进阶:** 104 | 105 | 你可以优化你的算法到 *O*(*k*) 空间复杂度吗? 106 | 107 | ### 题目解析 108 | 109 | 这道题目的难点与思考点在于题目有额外限制条件,程序只能使用 O(k) 的额外空间,因此无法通过累加的方式将每一行都输出打印。 110 | 111 | 这里依旧使用杨辉三角的规律,很隐藏的规律:对于杨辉三角的同一行,第 ( i + 1) 项是第 i 项的` ( k - i ) /( i + 1 )` 倍。 112 | 113 | 比如: 114 | 115 | - 第 k 索引行的第 0 项:1 116 | - 第 k 索引行的第 1 项:1 * k 117 | - 第 k 索引行的第 2 项:1 * k * ( k - 1) / 2 118 | - 第 k 索引行的第 3 项:[1 * k * ( k - 1) / 2 ] * ( k - 2 ) / 3 119 | 120 | 121 | 122 | ### 代码实现 123 | 124 | ```java 125 | class Solution { 126 | public List getRow(int rowIndex) { 127 | List res = new ArrayList<>(rowIndex + 1); 128 | long index = 1; 129 | for (int i = 0; i <= rowIndex; i++) { 130 | res.add((int) index); 131 | index = index * ( rowIndex - i ) / ( i + 1 ); 132 | } 133 | return res; 134 | } 135 | } 136 | ``` 137 | 138 | 139 | 140 | ## 一个有趣的结论 141 | 142 | 感兴趣小伙伴的可以搜索一下李永乐讲得抽奖概率相关的视频,里面提及到了很多杨辉三角的神奇特点。 143 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190509165331.gif) 144 | 145 | 146 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第119号问题:杨辉三角II.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 杨辉三角 4 | 5 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190507201419.png) 6 | 7 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode](https://github.com/MisterBooo/LeetCodeAnimation) 系列文章之一。 8 | > 9 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 10 | > 11 | 12 | 13 | 杨辉三角应该是大家很早就接触到的一个数学知识,它有很多有趣的性质: 14 | 15 | - 每个数字等于上一行的左右两个数字之和,即 *C(n+1,i) = C(n,i) + C(n,i-1)* 16 | - 每行数字左右对称,由 1 开始逐渐变大 17 | - 第 n 行的数字有 n 项 18 | - 第 n 行的第 m 个数和第 n - m + 1 个数相等 ,为[组合数](https://baike.baidu.com/item/%E7%BB%84%E5%90%88%E6%95%B0)性质之一 19 | - ( a + b )n的展开式中的各项[系数](https://baike.baidu.com/item/%E7%B3%BB%E6%95%B0)依次对应杨辉三角的第 ( n + 1 ) 行中的每一项 20 | - 。。。 21 | 22 | 23 | 24 | ## 杨辉三角 25 | 26 | 题目来源于 LeetCode 上第 118 号问题:杨辉三角。题目难度为 Easy,目前通过率为 61.8% 。 27 | 28 | ### 题目描述 29 | 30 | 给定一个非负整数 *numRows,*生成杨辉三角的前 *numRows* 行。 31 | 32 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 33 | 34 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 35 | 36 | **示例:** 37 | 38 | ``` 39 | 输入: 5 40 | 输出: 41 | [ 42 | [1], 43 | [1,1], 44 | [1,2,1], 45 | [1,3,3,1], 46 | [1,4,6,4,1] 47 | ] 48 | ``` 49 | 50 | ### 题目解析 51 | 52 | > 这道题目在各大高校的习题中经常出现。 53 | 54 | 对于本题而言,利用性质 1 :每一行的首个和结尾一个数字都是 1,从第三行开始,中间的每个数字都是上一行的左右两个数字之和。 55 | 56 | ### 代码实现 57 | 58 | ```java 59 | class Solution { 60 | public List> generate(int numRows) { 61 | 62 | List> result = new ArrayList<>(); 63 | if (numRows < 1) return result; 64 | 65 | for (int i = 0; i < numRows; ++i) { 66 | //扩容 67 | List list = Arrays.asList(new Integer[i+1]); 68 | list.set(0, 1); list.set(i, 1); 69 | for (int j = 1; j < i; ++j) { 70 | //等于上一行的左右两个数字之和 71 | list.set(j, result.get(i-1).get(j-1) + result.get(i-1).get(j)); 72 | } 73 | result.add(list); 74 | } 75 | return result; 76 | 77 | } 78 | } 79 | 80 | ``` 81 | 82 | 83 | 84 | ## 杨辉三角II 85 | 86 | 题目来源于 LeetCode 上第 119 号问题:杨辉三角II。题目难度为 Easy,目前通过率为 55.5% 。 87 | 88 | ### 题目描述 89 | 90 | 给定一个非负索引 *k*,其中 *k* ≤ 33,返回杨辉三角的第 *k* 行。 91 | 92 | ![img](https://upload.wikimedia.org/wikipedia/commons/0/0d/PascalTriangleAnimated2.gif) 93 | 94 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 95 | 96 | **示例:** 97 | 98 | ``` 99 | 输入: 3 100 | 输出: [1,3,3,1] 101 | ``` 102 | 103 | **进阶:** 104 | 105 | 你可以优化你的算法到 *O*(*k*) 空间复杂度吗? 106 | 107 | ### 题目解析 108 | 109 | 这道题目的难点与思考点在于题目有额外限制条件,程序只能使用 O(k) 的额外空间,因此无法通过累加的方式将每一行都输出打印。 110 | 111 | 这里依旧使用杨辉三角的规律,很隐藏的规律:对于杨辉三角的同一行,第 ( i + 1) 项是第 i 项的` ( k - i ) /( i + 1 )` 倍。 112 | 113 | 比如: 114 | 115 | - 第 k 索引行的第 0 项:1 116 | - 第 k 索引行的第 1 项:1 * k 117 | - 第 k 索引行的第 2 项:1 * k * ( k - 1) / 2 118 | - 第 k 索引行的第 3 项:[1 * k * ( k - 1) / 2 ] * ( k - 2 ) / 3 119 | 120 | 121 | 122 | ### 代码实现 123 | 124 | ```java 125 | class Solution { 126 | public List getRow(int rowIndex) { 127 | List res = new ArrayList<>(rowIndex + 1); 128 | long index = 1; 129 | for (int i = 0; i <= rowIndex; i++) { 130 | res.add((int) index); 131 | index = index * ( rowIndex - i ) / ( i + 1 ); 132 | } 133 | return res; 134 | } 135 | } 136 | ``` 137 | 138 | 139 | 140 | ## 一个有趣的结论 141 | 142 | 感兴趣小伙伴的可以搜索一下李永乐讲得抽奖概率相关的视频,里面提及到了很多杨辉三角的神奇特点。 143 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190509165331.gif) 144 | 145 | 146 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第121号问题:买卖股票的最佳时机.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 浅谈什么是动态规划以及相关的「股票」算法题 4 | 5 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 6 | > 7 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 8 | 9 | ## 动态规划 10 | 11 | ### 1 概念 12 | 13 |   **动态规划**算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。在学习动态规划之前需要明确掌握几个重要概念。 14 | 15 |   **阶段**:对于一个完整的问题过程,适当的切分为若干个相互联系的子问题,每次在求解一个子问题,则对应一个阶段,整个问题的求解转化为按照阶段次序去求解。 16 | 17 |   **状态**:状态表示每个阶段开始时所处的客观条件,即在求解子问题时的已知条件。状态描述了研究的问题过程中的状况。 18 | 19 |   **决策**:决策表示当求解过程处于某一阶段的某一状态时,可以根据当前条件作出不同的选择,从而确定下一个阶段的状态,这种选择称为决策。 20 | 21 |   **策略**:由所有阶段的决策组成的决策序列称为全过程策略,简称策略。 22 | 23 |   **最优策略**:在所有的策略中,找到代价最小,性能最优的策略,此策略称为最优策略。 24 | 25 |   **状态转移方程**:状态转移方程是确定两个相邻阶段状态的演变过程,描述了状态之间是如何演变的。 26 | 27 | ### 2 使用场景 28 | 29 | 能采用动态规划求解的问题的一般要具有 3 个性质: 30 | 31 |   (1)**最优化**:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。子问题的局部最优将导致整个问题的全局最优。换句话说,就是问题的一个最优解中一定包含子问题的一个最优解。 32 | 33 |   (2)**无后效性**:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关,与其他阶段的状态无关,特别是与未发生的阶段的状态无关。 34 | 35 |    (3)**重叠子问题**:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势) 36 | 37 | ### 3 算法流程 38 | 39 |   (1)划分阶段:按照问题的时间或者空间特征将问题划分为若干个阶段。 40 |   (2)确定状态以及状态变量:将问题的不同阶段时期的不同状态描述出来。 41 |   (3)确定决策并写出状态转移方程:根据相邻两个阶段的各个状态之间的关系确定决策。 42 |   (4)寻找边界条件:一般而言,状态转移方程是递推式,必须有一个递推的边界条件。 43 |   (5)设计程序,解决问题 44 | 45 | ## 实战练习 46 | 47 | 下面的三道算法题都是来源于 LeetCode 上与股票买卖相关的问题 ,我们按照 **动态规划** 的算法流程来处理该类问题。 48 | 49 | **股票买卖**这一类的问题,都是给一个输入数组,里面的每个元素表示的是每天的股价,并且你只能持有一支股票(也就是你必须在再次购买前出售掉之前的股票),一般来说有下面几种问法: 50 | 51 | - 只能买卖一次 52 | - 可以买卖无数次 53 | - 可以买卖 k 次 54 | 55 | 需要你设计一个算法去获取最大的利润。 56 | 57 | ## 买卖股票的最佳时机 58 | 59 | 题目来源于 LeetCode 上第 121 号问题:买卖股票的最佳时机。题目难度为 Easy,目前通过率为 49.4% 。 60 | 61 | ### 题目描述 62 | 63 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 64 | 65 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 66 | 67 | 注意你不能在买入股票前卖出股票。 68 | 69 | **示例 1:** 70 | 71 | ``` 72 | 输入: [7,1,5,3,6,4] 73 | 输出: 5 74 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 75 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 76 | ``` 77 | 78 | **示例 2:** 79 | 80 | ``` 81 | 输入: [7,6,4,3,1] 82 | 输出: 0 83 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 84 | ``` 85 | 86 | ### 题目解析 87 | 88 | 我们按照动态规划的思想来思考这道问题。 89 | 90 | #### 状态 91 | 92 | 有 **买入(buy)** 和 **卖出(sell)** 这两种状态。 93 | 94 | #### 转移方程 95 | 96 | 对于买来说,买之后可以卖出(进入卖状态),也可以不再进行股票交易(保持买状态)。 97 | 98 | 对于卖来说,卖出股票后不在进行股票交易(还在卖状态)。 99 | 100 | 只有在手上的钱才算钱,手上的钱购买当天的股票后相当于亏损。也就是说当天买的话意味着损失`-prices[i]`,当天卖的话意味着增加`prices[i]`,当天卖出总的收益就是 `buy+prices[i]` 。 101 | 102 | 所以我们只要考虑当天买和之前买哪个收益更高,当天卖和之前卖哪个收益更高。 103 | 104 | * buy = max(buy, -price[i]) (注意:根据定义 buy 是负数) 105 | * sell = max(sell, prices[i] + buy) 106 | 107 | #### 边界 108 | 109 | 第一天 `buy = -prices[0]`, `sell = 0`,最后返回 sell 即可。 110 | 111 | ###代码实现 112 | 113 | ```java 114 | class Solution { 115 | public int maxProfit(int[] prices) { 116 | if(prices.length <= 1) 117 | return 0; 118 | int buy = -prices[0], sell = 0; 119 | for(int i = 1; i < prices.length; i++) { 120 | buy = Math.max(buy, -prices[i]); 121 | sell = Math.max(sell, prices[i] + buy); 122 | 123 | } 124 | return sell; 125 | } 126 | } 127 | ``` 128 | 129 | 130 | 131 | ## 买卖股票的最佳时机 II 132 | 133 | 题目来源于 LeetCode 上第 122 号问题:买卖股票的最佳时机 II。题目难度为 Easy,目前通过率为 53.0% 。 134 | 135 | ### 题目描述 136 | 137 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 138 | 139 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 140 | 141 | **注意**:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 142 | 143 | **示例 1:** 144 | 145 | ``` 146 | 输入: [7,1,5,3,6,4] 147 | 输出: 7 148 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 149 | 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 150 | ``` 151 | 152 | **示例 2:** 153 | 154 | ``` 155 | 输入: [1,2,3,4,5] 156 | 输出: 4 157 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 158 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 159 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 160 | ``` 161 | 162 | **示例 3:** 163 | 164 | ``` 165 | 输入: [7,6,4,3,1] 166 | 输出: 0 167 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 168 | ``` 169 | 170 | ### 题目解析 171 | 172 | #### 状态 173 | 174 | 有 **买入(buy)** 和 **卖出(sell)** 这两种状态。 175 | 176 | #### 转移方程 177 | 178 | 对比上题,这里可以有无限次的买入和卖出,也就是说 **买入** 状态之前可拥有 **卖出** 状态,所以买入的转移方程需要变化。 179 | 180 | - buy = max(buy, sell - price[i]) 181 | - sell = max(sell, buy + prices[i] ) 182 | 183 | #### 边界 184 | 185 | 第一天 `buy = -prices[0]`, `sell = 0`,最后返回 sell 即可。 186 | 187 | ### 代码实现 188 | 189 | ```java 190 | class Solution { 191 | public int maxProfit(int[] prices) { 192 | if(prices.length <= 1) 193 | return 0; 194 | int buy = -prices[0], sell = 0; 195 | for(int i = 1; i < prices.length; i++) { 196 | sell = Math.max(sell, prices[i] + buy); 197 | buy = Math.max( buy,sell - prices[i]); 198 | } 199 | return sell; 200 | } 201 | } 202 | ``` 203 | 204 | 205 | 206 | ## 买卖股票的最佳时机 III 207 | 208 | 题目来源于 LeetCode 上第 123 号问题:买卖股票的最佳时机 III。题目难度为 Hard,目前通过率为 36.1% 。 209 | 210 | ### 题目描述 211 | 212 | 给定一个数组,它的第 *i* 个元素是一支给定的股票在第 *i* 天的价格。 213 | 214 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 *两笔* 交易。 215 | 216 | **注意:** 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 217 | 218 | **示例 1:** 219 | 220 | ``` 221 | 输入: [3,3,5,0,0,3,1,4] 222 | 输出: 6 223 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 224 | 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 225 | ``` 226 | 227 | **示例 2:** 228 | 229 | ``` 230 | 输入: [1,2,3,4,5] 231 | 输出: 4 232 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 233 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 234 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 235 | ``` 236 | 237 | **示例 3:** 238 | 239 | ``` 240 | 输入: [7,6,4,3,1] 241 | 输出: 0 242 | 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 243 | ``` 244 | 245 | ### 题目解析 246 | 247 | 这里限制了最多两笔交易。 248 | 249 | #### 状态 250 | 251 | 有 **第一次买入(fstBuy)** 、 **第一次卖出(fstSell)**、**第二次买入(secBuy)** 和 **第二次卖出(secSell)** 这四种状态。 252 | 253 | #### 转移方程 254 | 255 | 这里最多两次买入和两次卖出,也就是说 **买入** 状态之前可拥有 **卖出** 状态,**卖出** 状态之前可拥有 **买入** 状态,所以买入和卖出的转移方程都需要变化。 256 | 257 | - fstBuy = max(fstBuy , -price[i]) 258 | - fstSell = max(fstSell,fstBuy + prices[i] ) 259 | - secBuy = max(secBuy ,fstSell -price[i]) (受第一次卖出状态的影响) 260 | - secSell = max(secSell ,secBuy + prices[i] ) 261 | 262 | #### 边界 263 | 264 | * 一开始 `fstBuy = -prices[0]` 265 | 266 | * 买入后直接卖出,`fstSell = 0` 267 | * 买入后再卖出再买入,`secBuy - prices[0]` 268 | * 买入后再卖出再买入再卖出,`secSell = 0` 269 | 270 | 最后返回 secSell 。 271 | 272 | ### 代码实现 273 | 274 | ```java 275 | class Solution { 276 | public int maxProfit(int[] prices) { 277 | int fstBuy = Integer.MIN_VALUE, fstSell = 0; 278 | int secBuy = Integer.MIN_VALUE, secSell = 0; 279 | for(int i = 0; i < prices.length; i++) { 280 | fstBuy = Math.max(fstBuy, -prices[i]); 281 | fstSell = Math.max(fstSell, fstBuy + prices[i]); 282 | secBuy = Math.max(secBuy, fstSell - prices[i]); 283 | secSell = Math.max(secSell, secBuy + prices[i]); 284 | } 285 | return secSell; 286 | 287 | } 288 | } 289 | ``` 290 | 291 | 292 | 293 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第122号问题:买卖股票的最佳时机II.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # LeetCode第122号问题:买卖股票的最佳时机II 4 | 5 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 6 | > 7 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 8 | 9 | 在之前有关 [**动态规划与股票问题一文**](https://github.com/MisterBooo/LeetCodeAnimation/tree/master/notes/LeetCode第122号问题:买卖股票的最佳时机II.md) 中,小吴使用了动态规划的思想进行了分析和写套路代码,但还是有一些小伙伴不是很明白,今天重新拿出一题从另外一个角度进行分析,希望能帮助大家更容易理解。 10 | 11 | ### 题目描述 12 | 13 | 题目来源于 LeetCode 上第 122 号问题:买卖股票的最佳时机 II。题目难度为 Easy,目前通过率为 53.0% 。 14 | 15 | ### 题目描述 16 | 17 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 18 | 19 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 20 | 21 | **注意**:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 22 | 23 | **示例 1:** 24 | 25 | ``` 26 | 输入: [7,1,5,3,6,4] 27 | 输出: 7 28 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 29 | 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 30 | ``` 31 | 32 | **示例 2:** 33 | 34 | ``` 35 | 输入: [1,2,3,4,5] 36 | 输出: 4 37 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 38 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 39 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 40 | ``` 41 | 42 | **示例 3:** 43 | 44 | ``` 45 | 输入: [7,6,4,3,1] 46 | 输出: 0 47 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 48 | ``` 49 | 50 | ### 题目解析 51 | 52 | 我们从实际场景去思考,假设你处于股票市场,你想获得最大收益的话理想操作是什么? 53 | 54 | 当然是 **低点买入,高点卖出** ! 55 | 56 | 举个简单的数组为例 [100,1,20,81],肉眼扫过去,第二天买第四天卖的话收益最高( 81 - 1) = 80 。 57 | 58 | 那为什么可以知道在第四天卖而不在第三天卖呢? 59 | 60 | 实际上,注意题目是没有限制买卖交易次数的,完全可以在第三天卖出去,只不过发现第四天有涨了,那么就在第三天再买回来。 61 | 62 | 即 ` (81 - 1) = [( 20 - 1 ) + ( 81 - 20 )]`。 63 | 64 | 也就是说,第二天买、第三天卖,第三天买、第四天卖这四个动作与第二天买、第四天卖结果是一致的。 65 | 66 | **所以只需要今天的价格比昨天更高,就卖出**!(反正可以再买入) 67 | 68 | 总结一下就是:从第二天开始观察,如果当前价格(今天)比之前价格(昨天)高,则把差值加入到利润中(因为我们可以昨天买入,今天卖出,如果明天价位更高的话,还可以今天买入,明天再抛出)。以此类推,遍历完整个数组后即可求得最大利润。 69 | 70 | ### 图片描述 71 | 72 | ![买卖股票的最佳时机 II](https://upload-images.jianshu.io/upload_images/1940317-07904ca85dc535a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 73 | 74 | ### 代码实现 75 | 76 | ```java 77 | //程序员小吴 78 | class Solution { 79 | public int maxProfit(int[] prices) { 80 | int profit = 0; 81 | for (int i = 1 ; i < prices.length; i++){ 82 | if (prices[i] > prices[i-1]){ 83 | profit = profit + prices[i] - prices[i - 1]; 84 | } 85 | } 86 | return profit; 87 | 88 | } 89 | } 90 | ``` 91 | 92 | 93 | 94 | 95 | 96 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /notes/LeetCode第123号问题:买卖股票的最佳时机III.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 浅谈什么是动态规划以及相关的「股票」算法题 4 | 5 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 6 | > 7 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 8 | 9 | ## 动态规划 10 | 11 | ### 1 概念 12 | 13 |   **动态规划**算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。在学习动态规划之前需要明确掌握几个重要概念。 14 | 15 |   **阶段**:对于一个完整的问题过程,适当的切分为若干个相互联系的子问题,每次在求解一个子问题,则对应一个阶段,整个问题的求解转化为按照阶段次序去求解。 16 | 17 |   **状态**:状态表示每个阶段开始时所处的客观条件,即在求解子问题时的已知条件。状态描述了研究的问题过程中的状况。 18 | 19 |   **决策**:决策表示当求解过程处于某一阶段的某一状态时,可以根据当前条件作出不同的选择,从而确定下一个阶段的状态,这种选择称为决策。 20 | 21 |   **策略**:由所有阶段的决策组成的决策序列称为全过程策略,简称策略。 22 | 23 |   **最优策略**:在所有的策略中,找到代价最小,性能最优的策略,此策略称为最优策略。 24 | 25 |   **状态转移方程**:状态转移方程是确定两个相邻阶段状态的演变过程,描述了状态之间是如何演变的。 26 | 27 | ### 2 使用场景 28 | 29 | 能采用动态规划求解的问题的一般要具有 3 个性质: 30 | 31 |   (1)**最优化**:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。子问题的局部最优将导致整个问题的全局最优。换句话说,就是问题的一个最优解中一定包含子问题的一个最优解。 32 | 33 |   (2)**无后效性**:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关,与其他阶段的状态无关,特别是与未发生的阶段的状态无关。 34 | 35 |    (3)**重叠子问题**:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势) 36 | 37 | ### 3 算法流程 38 | 39 |   (1)划分阶段:按照问题的时间或者空间特征将问题划分为若干个阶段。 40 |   (2)确定状态以及状态变量:将问题的不同阶段时期的不同状态描述出来。 41 |   (3)确定决策并写出状态转移方程:根据相邻两个阶段的各个状态之间的关系确定决策。 42 |   (4)寻找边界条件:一般而言,状态转移方程是递推式,必须有一个递推的边界条件。 43 |   (5)设计程序,解决问题 44 | 45 | ## 实战练习 46 | 47 | 下面的三道算法题都是来源于 LeetCode 上与股票买卖相关的问题 ,我们按照 **动态规划** 的算法流程来处理该类问题。 48 | 49 | **股票买卖**这一类的问题,都是给一个输入数组,里面的每个元素表示的是每天的股价,并且你只能持有一支股票(也就是你必须在再次购买前出售掉之前的股票),一般来说有下面几种问法: 50 | 51 | - 只能买卖一次 52 | - 可以买卖无数次 53 | - 可以买卖 k 次 54 | 55 | 需要你设计一个算法去获取最大的利润。 56 | 57 | ## 买卖股票的最佳时机 58 | 59 | 题目来源于 LeetCode 上第 121 号问题:买卖股票的最佳时机。题目难度为 Easy,目前通过率为 49.4% 。 60 | 61 | ### 题目描述 62 | 63 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 64 | 65 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 66 | 67 | 注意你不能在买入股票前卖出股票。 68 | 69 | **示例 1:** 70 | 71 | ``` 72 | 输入: [7,1,5,3,6,4] 73 | 输出: 5 74 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 75 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 76 | ``` 77 | 78 | **示例 2:** 79 | 80 | ``` 81 | 输入: [7,6,4,3,1] 82 | 输出: 0 83 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 84 | ``` 85 | 86 | ### 题目解析 87 | 88 | 我们按照动态规划的思想来思考这道问题。 89 | 90 | #### 状态 91 | 92 | 有 **买入(buy)** 和 **卖出(sell)** 这两种状态。 93 | 94 | #### 转移方程 95 | 96 | 对于买来说,买之后可以卖出(进入卖状态),也可以不再进行股票交易(保持买状态)。 97 | 98 | 对于卖来说,卖出股票后不在进行股票交易(还在卖状态)。 99 | 100 | 只有在手上的钱才算钱,手上的钱购买当天的股票后相当于亏损。也就是说当天买的话意味着损失`-prices[i]`,当天卖的话意味着增加`prices[i]`,当天卖出总的收益就是 `buy+prices[i]` 。 101 | 102 | 所以我们只要考虑当天买和之前买哪个收益更高,当天卖和之前卖哪个收益更高。 103 | 104 | - buy = max(buy, -price[i]) (注意:根据定义 buy 是负数) 105 | - sell = max(sell, prices[i] + buy) 106 | 107 | #### 边界 108 | 109 | 第一天 `buy = -prices[0]`, `sell = 0`,最后返回 sell 即可。 110 | 111 | ### 代码实现 112 | 113 | ```java 114 | class Solution { 115 | public int maxProfit(int[] prices) { 116 | if(prices.length <= 1) 117 | return 0; 118 | int buy = -prices[0], sell = 0; 119 | for(int i = 1; i < prices.length; i++) { 120 | buy = Math.max(buy, -prices[i]); 121 | sell = Math.max(sell, prices[i] + buy); 122 | 123 | } 124 | return sell; 125 | } 126 | } 127 | ``` 128 | 129 | 130 | 131 | ## 买卖股票的最佳时机 II 132 | 133 | 题目来源于 LeetCode 上第 122 号问题:买卖股票的最佳时机 II。题目难度为 Easy,目前通过率为 53.0% 。 134 | 135 | ### 题目描述 136 | 137 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 138 | 139 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 140 | 141 | **注意**:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 142 | 143 | **示例 1:** 144 | 145 | ``` 146 | 输入: [7,1,5,3,6,4] 147 | 输出: 7 148 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 149 | 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 150 | ``` 151 | 152 | **示例 2:** 153 | 154 | ``` 155 | 输入: [1,2,3,4,5] 156 | 输出: 4 157 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 158 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 159 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 160 | ``` 161 | 162 | **示例 3:** 163 | 164 | ``` 165 | 输入: [7,6,4,3,1] 166 | 输出: 0 167 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 168 | ``` 169 | 170 | ### 题目解析 171 | 172 | #### 状态 173 | 174 | 有 **买入(buy)** 和 **卖出(sell)** 这两种状态。 175 | 176 | #### 转移方程 177 | 178 | 对比上题,这里可以有无限次的买入和卖出,也就是说 **买入** 状态之前可拥有 **卖出** 状态,所以买入的转移方程需要变化。 179 | 180 | - buy = max(buy, sell - price[i]) 181 | - sell = max(sell, buy + prices[i] ) 182 | 183 | #### 边界 184 | 185 | 第一天 `buy = -prices[0]`, `sell = 0`,最后返回 sell 即可。 186 | 187 | ### 代码实现 188 | 189 | ```java 190 | class Solution { 191 | public int maxProfit(int[] prices) { 192 | if(prices.length <= 1) 193 | return 0; 194 | int buy = -prices[0], sell = 0; 195 | for(int i = 1; i < prices.length; i++) { 196 | sell = Math.max(sell, prices[i] + buy); 197 | buy = Math.max( buy,sell - prices[i]); 198 | } 199 | return sell; 200 | } 201 | } 202 | ``` 203 | 204 | 205 | 206 | ## 买卖股票的最佳时机 III 207 | 208 | 题目来源于 LeetCode 上第 123 号问题:买卖股票的最佳时机 III。题目难度为 Hard,目前通过率为 36.1% 。 209 | 210 | ### 题目描述 211 | 212 | 给定一个数组,它的第 *i* 个元素是一支给定的股票在第 *i* 天的价格。 213 | 214 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 *两笔* 交易。 215 | 216 | **注意:** 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 217 | 218 | **示例 1:** 219 | 220 | ``` 221 | 输入: [3,3,5,0,0,3,1,4] 222 | 输出: 6 223 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 224 | 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 225 | ``` 226 | 227 | **示例 2:** 228 | 229 | ``` 230 | 输入: [1,2,3,4,5] 231 | 输出: 4 232 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 233 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 234 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 235 | ``` 236 | 237 | **示例 3:** 238 | 239 | ``` 240 | 输入: [7,6,4,3,1] 241 | 输出: 0 242 | 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 243 | ``` 244 | 245 | ### 题目解析 246 | 247 | 这里限制了最多两笔交易。 248 | 249 | #### 状态 250 | 251 | 有 **第一次买入(fstBuy)** 、 **第一次卖出(fstSell)**、**第二次买入(secBuy)** 和 **第二次卖出(secSell)** 这四种状态。 252 | 253 | #### 转移方程 254 | 255 | 这里最多两次买入和两次卖出,也就是说 **买入** 状态之前可拥有 **卖出** 状态,**卖出** 状态之前可拥有 **买入** 状态,所以买入和卖出的转移方程都需要变化。 256 | 257 | - fstBuy = max(fstBuy , -price[i]) 258 | - fstSell = max(fstSell,fstBuy + prices[i] ) 259 | - secBuy = max(secBuy ,fstSell -price[i]) (受第一次卖出状态的影响) 260 | - secSell = max(secSell ,secBuy + prices[i] ) 261 | 262 | #### 边界 263 | 264 | - 一开始 `fstBuy = -prices[0]` 265 | - 买入后直接卖出,`fstSell = 0` 266 | - 买入后再卖出再买入,`secBuy - prices[0]` 267 | - 买入后再卖出再买入再卖出,`secSell = 0` 268 | 269 | 最后返回 secSell 。 270 | 271 | ### 代码实现 272 | 273 | ```java 274 | class Solution { 275 | public int maxProfit(int[] prices) { 276 | int fstBuy = Integer.MIN_VALUE, fstSell = 0; 277 | int secBuy = Integer.MIN_VALUE, secSell = 0; 278 | for(int i = 0; i < prices.length; i++) { 279 | fstBuy = Math.max(fstBuy, -prices[i]); 280 | fstSell = Math.max(fstSell, fstBuy + prices[i]); 281 | secBuy = Math.max(secBuy, fstSell - prices[i]); 282 | secSell = Math.max(secSell, secBuy + prices[i]); 283 | } 284 | return secSell; 285 | 286 | } 287 | } 288 | ``` 289 | 290 | 291 | 292 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /notes/LeetCode第125号问题:验证回文串.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 125 号问题:验证回文串 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 第 125 号问题:验证回文串。这道题目是 **初级程序员** 在面试的时候经常遇到的一道算法题,而且面试官喜欢面试者手写! 8 | 9 | 10 | 11 | ### 题目描述 12 | 13 | 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 14 | 15 | **说明:**本题中,我们将空字符串定义为有效的回文串。 16 | 17 | **示例 1:** 18 | 19 | ``` 20 | 输入: "A man, a plan, a canal: Panama" 21 | 输出: true 22 | ``` 23 | 24 | **示例 2:** 25 | 26 | ``` 27 | 输入: "race a car" 28 | 输出: false 29 | ``` 30 | 31 | ### 题目解析 32 | 33 | 先理解一个概念:所谓回文,就是一个正读和反读都一样的字符串。 34 | 35 | 先假设是验证单词 `level` 是否是回文字符串,通过概念涉及到 正 与 反 ,那么很容易想到使用双指针,从字符的开头和结尾处开始遍历整个字符串,相同则继续向前寻找,不同则直接返回 false。 36 | 37 | 而这里与单独验证一个单词是否是回文字符串有所区别的是加入了 空格 与 非字母数字的字符,但实际上的做法一样的: 38 | 39 | 一开始先建立两个指针,left 和 right , 让它们分别从字符的开头和结尾处开始遍历整个字符串。 40 | 41 | 如果遇到非字母数字的字符就跳过,继续往下找,直到找到下一个字母数字或者结束遍历,如果遇到大写字母,就将其转为小写。 42 | 43 | 当左右指针都找到字母数字时,可以进行比较的时候,比较这两个字符,如果相等,则两个指针向它们的前进方向挪动,然后继续比较下面两个分别找到的字母数字,若不相等,直接返回 false。 44 | 45 | ### 动画描述 46 | 47 | ![]() 48 | 49 | ### 代码实现 50 | 51 | 注:`isLetterOrDigit ` 方法确定指定的字符是否为字母或数字。 52 | 53 | ```java 54 | class Solution { 55 | public boolean isPalindrome(String s) { 56 | if(s.length() == 0) 57 | return true; 58 | int l = 0, r = s.length() - 1; 59 | while(l < r){ 60 | //确定指定的字符是否为字母或数字 61 | if(!Character.isLetterOrDigit(s.charAt(l))){ 62 | l++; 63 | }else if(!Character.isLetterOrDigit(s.charAt(r))){ 64 | r--; 65 | }else{ 66 | if(Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) 67 | return false; 68 | l++; 69 | r--; 70 | } 71 | } 72 | return true; 73 | } 74 | } 75 | 76 | ``` 77 | 78 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第131号问题:分割回文串.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 131 号问题:分割回文串 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 131 号问题:分割回文串。题目难度为 Medium,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个字符串 *s*,将 *s* 分割成一些子串,使每个子串都是回文串。 12 | 13 | 返回 *s* 所有可能的分割方案。 14 | 15 | **示例:** 16 | 17 | ```yaml 18 | 输入: "aab" 19 | 输出: 20 | [ 21 | ["aa","b"], 22 | ["a","a","b"] 23 | ] 24 | ``` 25 | 26 | ### 27 | 28 | ### 题目解析 29 | 30 | 首先,对于一个字符串的分割,肯定需要将所有分割情况都遍历完毕才能判断是不是回文数。不能因为 **abba** 是回文串,就认为它的所有子串都是回文的。 31 | 32 | 既然需要将所有的分割方法都找出来,那么肯定需要用到DFS(深度优先搜索)或者BFS(广度优先搜索)。 33 | 34 | 在分割的过程中对于每一个字符串而言都可以分为两部分:左边一个回文串加右边一个子串,比如 "abc" 可分为 "a" + "bc" 。 然后对"bc"分割仍然是同样的方法,分为"b"+"c"。 35 | 36 | 在处理的时候去优先寻找更短的回文串,然后回溯找稍微长一些的回文串分割方法,不断回溯,分割,直到找到所有的分割方法。 37 | 38 | 举个🌰:分割"aac"。 39 | 40 | 1. 分割为 a + ac 41 | 2. 分割为 a + a + c,分割后,得到一组结果,再回溯到 a + ac 42 | 3. a + ac 中 ac 不是回文串,继续回溯,回溯到 aac 43 | 4. 分割为稍长的回文串,分割为 aa + c 分割完成得到一组结果,再回溯到 aac 44 | 5. aac 不是回文串,搜索结束 45 | 46 | 47 | 48 | ### 动画描述 49 | 50 | ![]() 51 | 52 | ### 代码实现 53 | 54 | ```java 55 | class Solution { 56 | List> res = new ArrayList<>(); 57 | 58 | public List> partition(String s) { 59 | if(s==null||s.length()==0) 60 | return res; 61 | dfs(s,new ArrayList(),0); 62 | return res; 63 | } 64 | 65 | public void dfs(String s,List remain,int left){ 66 | if(left==s.length()){ //判断终止条件 67 | res.add(new ArrayList(remain)); //添加到结果中 68 | return; 69 | } 70 | for(int right=left;rightright是不是回文串 71 | if(isPalindroom(s,left,right)){ //判断是否是回文串 72 | remain.add(s.substring(left,right+1)); //添加到当前回文串到list中 73 | dfs(s,remain,right+1); //从right+1开始继续递归,寻找回文串 74 | remain.remove(remain.size()-1); //回溯,从而寻找更长的回文串 75 | } 76 | } 77 | } 78 | /** 79 | * 判断是否是回文串 80 | */ 81 | public boolean isPalindroom(String s,int left,int right){ 82 | while(left=right; 87 | } 88 | } 89 | ``` 90 | 91 | 92 | 93 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第136号问题:只出现一次的数字.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 136 号问题:只出现一次的数字 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 136 号问题:只出现一次的数字。题目难度为 Easy,目前通过率为 66.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 12 | 13 | **说明:** 14 | 15 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 16 | 17 | **示例 1:** 18 | 19 | ``` 20 | 输入: [2,2,1] 21 | 输出: 1 22 | ``` 23 | 24 | **示例 2:** 25 | 26 | ``` 27 | 输入: [4,1,2,1,2] 28 | 输出: 4 29 | ``` 30 | 31 | ### 题目解析 32 | 33 | 根据题目描述,由于加上了时间复杂度必须是 O(n) ,并且空间复杂度为 O(1) 的条件,因此不能用排序方法,也不能使用 map 数据结构。 34 | 35 | 程序员小吴想了一下午没想出来,答案是使用 **位操作Bit Operation** 来解此题。 36 | 37 | 将所有元素做异或运算,即a[1] ⊕ a[2] ⊕ a[3] ⊕ …⊕ a[n],所得的结果就是那个只出现一次的数字,时间复杂度为O(n)。 38 | 39 | ### 异或 40 | 41 | 异或运算A ⊕ B的真值表如下: 42 | 43 | | A | B | ⊕ | 44 | | :--- | :--: | ---: | 45 | | F | F | F | 46 | | F | T | T | 47 | | T | F | T | 48 | | T | T | F | 49 | 50 | ### 动画演示 51 | 52 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190115181920.gif) 53 | 54 | 55 | 56 | ### 进阶版 57 | 58 | 有一个 n 个元素的数组,除了两个数只出现一次外,其余元素都出现两次,让你找出这两个只出现一次的数分别是几,要求时间复杂度为 O(n) 且再开辟的内存空间固定(与 n 无关)。 59 | 60 | #### 示例 : 61 | 62 | 输入: [1,2,2,1,3,4] 63 | 输出: [3,4] 64 | 65 | ### 题目再解析 66 | 67 | 根据前面找一个不同数的思路算法,在这里把所有元素都异或,那么得到的结果就是那两个只出现一次的元素异或的结果。 68 | 69 | 然后,因为这两个只出现一次的元素一定是不相同的,所以这两个元素的二进制形式肯定至少有某一位是不同的,即一个为 0 ,另一个为 1 ,现在需要找到这一位。 70 | 71 | 根据异或的性质 `任何一个数字异或它自己都等于 0 `,得到这个数字二进制形式中任意一个为 1 的位都是我们要找的那一位。 72 | 73 | 再然后,以这一位是 1 还是 0 为标准,将数组的 n 个元素分成两部分。 74 | 75 | - 将这一位为 0 的所有元素做异或,得出的数就是只出现一次的数中的一个 76 | - 将这一位为 1 的所有元素做异或,得出的数就是只出现一次的数中的另一个。 77 | 78 | 这样就解出题目。忽略寻找不同位的过程,总共遍历数组两次,时间复杂度为O(n)。 79 | 80 | ### 动画再演示 81 | 82 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190115190719.gif) 83 | 84 | 85 | 86 | 87 | 88 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第138号问题:复制带随机指针.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 138 号问题:复制带随机指针的链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 8 | 9 | 题目来源于 LeetCode 上第 138 号问题:复制带随机指针的链表。题目难度为 Medium,目前通过率为 40.5% 。 10 | 11 | ### 题目描述 12 | 13 | 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 14 | 15 | 要求返回这个链表的**深拷贝**。 16 | 17 | **示例:** 18 | 19 | ``` 20 | 输入: 21 | {"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1} 22 | 23 | 解释: 24 | 节点 1 的值是 1,它的下一个指针和随机指针都指向节点 2 。 25 | 节点 2 的值是 2,它的下一个指针指向 null,随机指针指向它自己。 26 | ``` 27 | 28 | ### 题目解析 29 | 30 | 1. 在原链表的每个节点后面拷贝出一个新的节点 31 | 32 | 2. 依次给新的节点的随机指针赋值,而且这个赋值非常容易 cur->next->random = cur->random->next 33 | 34 | 3. 断开链表可得到深度拷贝后的新链表 35 | 36 | 之所以说这个方法比较巧妙是因为相较于一般的解法(如使用 Hash map )来处理,上面这个解法 **不需要占用额外的空间**。 37 | 38 | ### 动画描述 39 | 40 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502092750.gif) 41 | 42 | ### 代码实现 43 | 44 | 我发现带指针的题目使用 C++ 版本更容易描述,所以下面的代码实现是 C++ 版本。 45 | 46 | ```c++ 47 | class Solution { 48 | public: 49 | RandomListNode *copyRandomList(RandomListNode *head) { 50 | if (!head) return NULL; 51 | RandomListNode *cur = head; 52 | while (cur) { 53 | RandomListNode *node = new RandomListNode(cur->label); 54 | node->next = cur->next; 55 | cur->next = node; 56 | cur = node->next; 57 | } 58 | cur = head; 59 | while (cur) { 60 | if (cur->random) { 61 | cur->next->random = cur->random->next; 62 | } 63 | cur = cur->next->next; 64 | } 65 | cur = head; 66 | RandomListNode *res = head->next; 67 | while (cur) { 68 | RandomListNode *tmp = cur->next; 69 | cur->next = tmp->next; 70 | if(tmp->next) tmp->next = tmp->next->next; 71 | cur = cur->next; 72 | } 73 | return res; 74 | } 75 | }; 76 | ``` 77 | 78 | 79 | 80 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) 81 | 82 | -------------------------------------------------------------------------------- /notes/LeetCode第139号问题:单词拆分.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 139 号问题:单词拆分 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 139 号问题:单词拆分。 8 | 9 | ### 题目描述 10 | 11 | 给定一个**非空**字符串 *s* 和一个包含**非空**单词列表的字典 *wordDict*,判定 *s* 是否可以被空格拆分为一个或多个在字典中出现的单词。 12 | 13 | **说明:** 14 | 15 | - 拆分时可以重复使用字典中的单词。 16 | - 你可以假设字典中没有重复的单词。 17 | 18 | 19 | 20 | ### 题目解析 21 | 22 | 与 **分割回文串** 有些类似,都是拆分,但是如果此题采取 深度优先搜索 的方法来解决的话,答案是超时的,不信的同学可以试一下~ 23 | 24 | 为什么会超时呢? 25 | 26 | 因为使用 深度优先搜索 会重复的计算了有些位的可拆分情况,这种情况的优化肯定是需要 动态规划 来处理的。 27 | 28 | 如果不知道动态规划的,可以看一下小吴之前的万字长文,比较详细的介绍了动态规划的概念。 29 | 30 | 在这里,只需要去定义一个数组 boolean[] memo,其中第 i 位 memo[i] 表示待拆分字符串从第 0 位到第 i-1 位是否可以被成功地拆分。 31 | 32 | 然后分别计算每一位是否可以被成功地拆分。 33 | 34 | 35 | 36 | ### 动画描述 37 | 38 | 暂无~ 39 | 40 | ### 代码实现 41 | 42 | 43 | 44 | ```java 45 | class Solution { 46 | public boolean wordBreak(String s, List wordDict) { 47 | int n = s.length(); 48 | int max_length=0; 49 | for(String temp:wordDict){ 50 | max_length = temp.length() > max_length ? temp.length() : max_length; 51 | } 52 | // memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分 53 | boolean[] memo = new boolean[n + 1]; 54 | memo[0] = true; 55 | for (int i = 1; i <= n; i++) { 56 | for (int j = i-1; j >= 0 && max_length >= i - j; j--) { 57 | if (memo[j] && wordDict.contains(s.substring(j, i))) { 58 | memo[i] = true; 59 | break; 60 | } 61 | } 62 | } 63 | return memo[n]; 64 | } 65 | } 66 | ``` 67 | 68 | 69 | 70 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第144号问题:二叉树的前序遍历.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 144 号问题:二叉树的前序遍历 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 144 号问题:二叉树的前序遍历。题目难度为 Medium,目前通过率为 59.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回它的 *前序* 遍历。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: [1,null,2,3] 17 | 1 18 | \ 19 | 2 20 | / 21 | 3 22 | 23 | 输出: [1,2,3] 24 | ``` 25 | 26 | **进阶:** 递归算法很简单,你可以通过迭代算法完成吗? 27 | 28 | ### 题目解析 29 | 30 | 用**栈(Stack)**的思路来处理问题。 31 | 32 | 前序遍历的顺序为**根-左-右**,具体算法为: 33 | 34 | - 把根节点 push 到栈中 35 | - 循环检测栈是否为空,若不空,则取出栈顶元素,保存其值 36 | - 看其右子节点是否存在,若存在则 push 到栈中 37 | - 看其左子节点,若存在,则 push 到栈中。 38 | 39 | 40 | 41 | ### 动画描述 42 | 43 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502105303.gif) 44 | 45 | ### 代码实现 46 | 47 | ``` 48 | class Solution { 49 | public List preorderTraversal(TreeNode root) { 50 | //非递归前序遍历,需要借助栈 51 | Stack stack = new Stack<>(); 52 | List list = new LinkedList<>(); 53 | //当树为空树时,直接返回一个空list 54 | if(root == null){ 55 | return list; 56 | } 57 | //第一步是将根节点压入栈中 58 | stack.push(root); 59 | //当栈不为空时,出栈的元素插入list尾部。 60 | //当它的孩子不为空时,将孩子压入栈,一定是先压右孩子再压左孩子 61 | while(!stack.isEmpty()){ 62 | //此处的root只是一个变量的复用 63 | root = stack.pop(); 64 | list.add(root.val); 65 | if(root.right != null) stack.push(root.right); 66 | if(root.left != null) stack.push(root.left); 67 | } 68 | return list; 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第145号问题:二叉树的后序遍历.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 145 号问题:二叉树的后序遍历 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 145 号问题:二叉树的后序遍历。题目难度为 Hard,目前通过率为 25.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回它的 *后序* 遍历。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: [1,null,2,3] 17 | 1 18 | \ 19 | 2 20 | / 21 | 3 22 | 23 | 输出: [3,2,1] 24 | ``` 25 | 26 | **进阶:** 递归算法很简单,你可以通过迭代算法完成吗? 27 | 28 | ### 题目解析 29 | 30 | 用**栈(Stack)**的思路来处理问题。 31 | 32 | 后序遍历的顺序为**左-右-根**,具体算法为: 33 | 34 | - 先将根结点压入栈,然后定义一个辅助结点 head 35 | - while 循环的条件是栈不为空 36 | - 在循环中,首先将栈顶结点t取出来 37 | - 如果栈顶结点没有左右子结点,或者其左子结点是 head,或者其右子结点是 head 的情况下。我们将栈顶结点值加入结果 res 中,并将栈顶元素移出栈,然后将 head 指向栈顶元素 38 | - 否则的话就看如果右子结点不为空,将其加入栈 39 | - 再看左子结点不为空的话,就加入栈 40 | 41 | 42 | 43 | ### 动画描述 44 | 45 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181110154019.gif) 46 | 47 | ### 代码实现 48 | 49 | ``` 50 | public class Solution { 51 | public List postorderTraversal(TreeNode root) { 52 | List res = new ArrayList(); 53 | if(root == null) 54 | return res; 55 | Stack stack = new Stack(); 56 | stack.push(root); 57 | while(!stack.isEmpty()){ 58 | TreeNode node = stack.pop(); 59 | if(node.left != null) stack.push(node.left);//和传统先序遍历不一样,先将左结点入栈 60 | if(node.right != null) stack.push(node.right);//后将右结点入栈 61 | res.add(0,node.val); //逆序添加结点值 62 | } 63 | return res; 64 | } 65 | } 66 | ``` 67 | 68 | 69 | 70 | 71 | 72 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第146号问题:LRU缓存机制.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 146 号问题:LRU缓存机制 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 146 号问题:LRU缓存机制。题目难度为 Hard,目前通过率为 15.8% 。 8 | 9 | ### 题目描述 10 | 11 | 运用你所掌握的数据结构,设计和实现一个 [LRU (最近最少使用) 缓存机制](https://baike.baidu.com/item/LRU)。它应该支持以下操作: 获取数据 `get` 和 写入数据 `put` 。 12 | 13 | 获取数据 `get(key)` - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 14 | 写入数据 `put(key, value)` - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 15 | 16 | **进阶:** 17 | 18 | 你是否可以在 **O(1)** 时间复杂度内完成这两种操作? 19 | 20 | **示例:** 21 | 22 | ``` 23 | LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); 24 | 25 | cache.put(1, 1); 26 | cache.put(2, 2); 27 | cache.get(1); // 返回 1 28 | cache.put(3, 3); // 该操作会使得密钥 2 作废 29 | cache.get(2); // 返回 -1 (未找到) 30 | cache.put(4, 4); // 该操作会使得密钥 1 作废 31 | cache.get(1); // 返回 -1 (未找到) 32 | cache.get(3); // 返回 3 33 | cache.get(4); // 返回 4 34 | ``` 35 | 36 | ### 题目解析 37 | 38 | 这道题是让我们实现一个 LRU 缓存器,LRU是Least Recently Used的简写,就是最近最少使用的意思。 39 | 40 | 这个缓存器主要有两个成员函数,get和put。 41 | 42 | 其中 get 函数是通过输入 key 来获得 value,如果成功获得后,这对 (key, value) 升至缓存器中最常用的位置(顶部),如果 key 不存在,则返回 -1 。 43 | 44 | 而 put 函数是插入一对新的 (key, value),如果原缓存器中有该 key,则需要先删除掉原有的,将新的插入到缓存器的顶部。如果不存在,则直接插入到顶部。 45 | 46 | 若加入新的值后缓存器超过了容量,则需要删掉一个最不常用的值,也就是底部的值。 47 | 48 | 具体实现时我们需要三个私有变量,cap , l 和 m,其中 cap 是缓存器的容量大小,l 是保存缓存器内容的列表,m 是 HashMap,保存关键值 key 和缓存器各项的迭代器之间映射,方便我们以 O(1) 的时间内找到目标项。 49 | 50 | 然后我们再来看 get 和 put 如何实现。 51 | 52 | 其中,get 相对简单些,我们在 m 中查找给定的key,若不存在直接返回 -1;如果存在则将此项移到顶部。 53 | 54 | 对于 put ,我们也是现在 m 中查找给定的 key,如果存在就删掉原有项,并在顶部插入新来项,然后判断是否溢出,若溢出则删掉底部项(最不常用项)。 55 | 56 | ### 动画描述 57 | 58 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502105833.gif) 59 | 60 | ### 代码实现 61 | 62 | ```c++ 63 | class LRUCache{ 64 | public: 65 | LRUCache(int capacity) { 66 | cap = capacity; 67 | } 68 | 69 | int get(int key) { 70 | auto it = m.find(key); 71 | if (it == m.end()) return -1; 72 | l.splice(l.begin(), l, it->second); 73 | return it->second->second; 74 | } 75 | 76 | void put(int key, int value) { 77 | auto it = m.find(key); 78 | if (it != m.end()) l.erase(it->second); 79 | l.push_front(make_pair(key, value)); 80 | m[key] = l.begin(); 81 | if (m.size() > cap) { 82 | int k = l.rbegin()->first; 83 | l.pop_back(); 84 | m.erase(k); 85 | } 86 | } 87 | 88 | private: 89 | int cap; 90 | list> l; 91 | unordered_map>::iterator> m; 92 | }; 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第150号问题:逆波兰表达式求值.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 150 号问题:逆波兰表达式求值 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 150 号问题:逆波兰表达式求值。题目难度为 Medium,目前通过率为 43.7% 。 8 | 9 | ### 题目描述 10 | 11 | 根据[逆波兰表示法](https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437),求表达式的值。 12 | 13 | 有效的运算符包括 `+`, `-`, `*`, `/` 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 14 | 15 | **说明:** 16 | 17 | - 整数除法只保留整数部分。 18 | - 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 19 | 20 | **示例 1:** 21 | 22 | ``` 23 | 输入: ["2", "1", "+", "3", "*"] 24 | 输出: 9 25 | 解释: ((2 + 1) * 3) = 9 26 | ``` 27 | 28 | **示例 2:** 29 | 30 | ``` 31 | 输入: ["4", "13", "5", "/", "+"] 32 | 输出: 6 33 | 解释: (4 + (13 / 5)) = 6 34 | ``` 35 | 36 | **示例 3:** 37 | 38 | ``` 39 | 输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] 40 | 输出: 22 41 | 解释: 42 | ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 43 | = ((10 * (6 / (12 * -11))) + 17) + 5 44 | = ((10 * (6 / -132)) + 17) + 5 45 | = ((10 * 0) + 17) + 5 46 | = (0 + 17) + 5 47 | = 17 + 5 48 | = 22 49 | ``` 50 | 51 | ### 题目解析 52 | 53 | 用数据结构`栈`来解决这个问题。 54 | 55 | - 从前往后遍历数组 56 | - 遇到数字则压入栈中 57 | - 遇到符号,则把栈顶的两个数字拿出来运算,把结果再压入栈中 58 | - 遍历完整个数组,栈顶数字即为最终答案 59 | 60 | ### 动画描述 61 | 62 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181108162351.gif) 63 | 64 | ### 代码实现 65 | 66 | ``` 67 | class Solution { 68 | public: 69 | int evalRPN(vector& tokens) { 70 | 71 | stack nums; 72 | stack ops; 73 | for(const string& s: tokens){ 74 | if(s == "+" || s == "-" || s == "*" || s == "/"){ 75 | int a = nums.top(); 76 | nums.pop(); 77 | int b = nums.top(); 78 | nums.pop(); 79 | 80 | if(s == "+"){ 81 | nums.push(b + a); 82 | }else if(s == "-"){ 83 | nums.push(b - a); 84 | } else if(s == "*"){ 85 | nums.push(b * a); 86 | }else if(s == "/"){ 87 | nums.push(b / a); 88 | } 89 | } 90 | else{ 91 | nums.push(atoi(s.c_str())); 92 | } 93 | } 94 | return nums.top(); 95 | } 96 | }; 97 | ``` 98 | 99 | 100 | 101 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第15号问题:三数之和.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 15 号问题:三数之和 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 15 号问题:三数之和。 8 | 9 | ### 题目描述 10 | 11 | 给定一个包含 *n* 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 *a,b,c ,*使得 *a + b + c =* 0 ?找出所有满足条件且不重复的三元组。 12 | 13 | ### 题目解析 14 | 15 | 题目需要我们找出三个数且和为 0 ,那么除了三个数全是 0 的情况之外,肯定会有负数和正数,所以一开始可以先选择一个数,然后再去找另外两个数,这样只要找到两个数且和为第一个选择的数的相反数就行了。也就是说需要枚举 a 和 b ,将 c 的存入 map 即可。 16 | 17 | 需要注意的是返回的结果中,不能有有重复的结果。这样的代码时间复杂度是 O(n^2)。在这里可以先将原数组进行排序,然后再遍历排序后的数组,这样就可以使用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。 18 | 19 | ### 动画描述 20 | 21 | 待补充 22 | 23 | ### 代码实现 24 | 25 | ### 26 | 27 | ```c++ 28 | class Solution { 29 | public: 30 | vector> threeSum(vector& nums) { 31 | vector> res; 32 | sort(nums.begin(), nums.end()); 33 | if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {}; 34 | for (int k = 0; k < nums.size(); ++k) { 35 | if (nums[k] > 0) break; 36 | if (k > 0 && nums[k] == nums[k - 1]) continue; 37 | int target = 0 - nums[k]; 38 | int i = k + 1, j = nums.size() - 1; 39 | while (i < j) { 40 | if (nums[i] + nums[j] == target) { 41 | res.push_back({nums[k], nums[i], nums[j]}); 42 | while (i < j && nums[i] == nums[i + 1]) ++i; 43 | while (i < j && nums[j] == nums[j - 1]) --j; 44 | ++i; --j; 45 | } else if (nums[i] + nums[j] < target) ++i; 46 | else --j; 47 | } 48 | } 49 | return res; 50 | } 51 | }; 52 | ``` 53 | 54 | 55 | 56 | 57 | 58 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第167号问题:两数之和II-输入有序数组.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 167 号问题:两数之和 II - 输入有序数组 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 167 号问题:两数之和 II - 输入有序数组。题目难度为 Easy,目前通过率为 48.2% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个已按照**升序排列** 的有序数组,找到两个数使得它们相加之和等于目标数。 12 | 13 | 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2*。* 14 | 15 | **说明:** 16 | 17 | - 返回的下标值(index1 和 index2)不是从零开始的。 18 | - 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 19 | 20 | **示例:** 21 | 22 | ``` 23 | 输入: numbers = [2, 7, 11, 15], target = 9 24 | 输出: [1,2] 25 | 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 26 | ``` 27 | 28 | ### 题目解析 29 | 30 | 初始化左指针 left 指向数组起始,初始化右指针 right 指向数组结尾。 31 | 32 | 根据**已排序**这个特性, 33 | 34 | - (1)如果 numbers[left] 与 numbers[right] 的和 tmp 小于 target ,说明应该增加 tmp ,因此 left 右移指向一个较大的值。 35 | - (2)如果 tmp大于 target ,说明应该减小 tmp ,因此 right 左移指向一个较小的值。 36 | - (3)tmp 等于 target ,则找到,返回 left + 1 和 right + 1。(注意以 1 为起始下标) 37 | 38 | ### 动画描述 39 | 40 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502110607.gif) 41 | 42 | ### 代码实现 43 | 44 | ``` 45 | // 对撞指针 46 | // 时间复杂度: O(n) 47 | // 空间复杂度: O(1) 48 | class Solution { 49 | public: 50 | vector twoSum(vector& numbers, int target) { 51 | int l = 0, r = numbers.size() - 1; 52 | while(l < r){ 53 | if(numbers[l] + numbers[r] == target){ 54 | int res[2] = {l+1, r+1}; 55 | return vector(res, res+2); 56 | } 57 | else if(numbers[l] + numbers[r] < target) 58 | l ++; 59 | else // numbers[l] + numbers[r] > target 60 | r --; 61 | } 62 | } 63 | 64 | 65 | ``` 66 | 67 | 68 | 69 | 70 | 71 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第172号问题:阶乘后的零.md: -------------------------------------------------------------------------------- 1 | # LeetCode第 172 号问题:阶乘后的零 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 172 号问题:阶乘后的零。题目难度为 Easy,目前通过率为 38.0% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个整数 *n*,返回 *n*! 结果尾数中零的数量。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: 3 17 | 输出: 0 18 | 解释: 3! = 6, 尾数中没有零。 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ``` 24 | 输入: 5 25 | 输出: 1 26 | 解释: 5! = 120, 尾数中有 1 个零. 27 | ``` 28 | 29 | **说明:** 你算法的时间复杂度应为 *O*(log *n*) 。 30 | 31 | ### 题目解析 32 | 33 | 题目很好理解,数阶乘后的数字末尾有多少个零。 34 | 35 | 最简单粗暴的方法就是先乘完再说,然后一个一个数。 36 | 37 | 事实上,你在使用暴力破解法的过程中就能发现规律: **这 9 个数字中只有 2(它的倍数) 与 5 (它的倍数)相乘才有 0 出现**。 38 | 39 | 所以,现在问题就变成了这个阶乘数中能配 **多少对 2 与 5**。 40 | 41 | 举个复杂点的例子: 42 | 43 | ` 10! = 【 2 *( 2 * 2 )* 5 *( 2 * 3 )*( 2 * 2 * 2 )*( 2 * 5)】` 44 | 45 | 在 10!这个阶乘数中可以匹配两对 2 * 5 ,所以10!末尾有 2 个 0。 46 | 47 | 可以发现,一个数字进行拆分后 2 的个数肯定是大于 5 的个数的,所以能匹配多少对取决于 5 的个数。(好比现在男女比例悬殊,最多能有多少对异性情侣取决于女生的多少)。 48 | 49 | 那么问题又变成了 **统计阶乘数里有多少个 5 这个因子**。 50 | 51 | 需要注意的是,像 25,125 这样的不只含有一个 5 的数字的情况需要考虑进去。 52 | 53 | 比如 `n = 15`。那么在 `15!` 中 有 `3` 个 `5` (来自其中的`5`, `10`, `15`), 所以计算 `n/5` 就可以 。 54 | 55 | 但是比如 `n=25`,依旧计算 `n/5` ,可以得到 `5` 个`5`,分别来自其中的`5, 10, 15, 20, 25`,但是在 `25` 中其实是包含 `2 `个 `5` 的,这一点需要注意。 56 | 57 | 所以除了计算 `n/5` , 还要计算 `n/5/5 , n/5/5/5 , n/5/5/5/5 , ..., n/5/5/5,,,/5`直到商为0,然后求和即可。 58 | 59 | ### 代码实现 60 | 61 | ```java 62 | public class Solution { 63 | public int trailingZeroes(int n) { 64 | return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); 65 | } 66 | } 67 | ``` 68 | 69 | 70 | 71 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /notes/LeetCode第187号问题:重复的DNA序列.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 187 号问题:重复的 DNA 序列 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 187 号问题:重复的 DNA 序列。 8 | 9 | ### 题目描述 10 | 11 | 所有 DNA 由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。 12 | 13 | 编写一个函数来查找 DNA 分子中所有出现超过一次的 10 个字母长的序列(子串)。 14 | 15 | ### 题目解析 16 | 17 | 首先,先将 A , C , G , T 的 ASCII 码用二进制来表示: 18 | 19 | A: 0100 0**001**  C: 0100 0**011**  G: 0100 0**111**  T: 0101 0**100** 20 | 21 | 通过观察发现每个字符的后三位都不相同,因此可以用**末尾的三位**来区分这四个字符。 22 | 23 | 题目要求是查找 10 个字母长的序列,这里我们将每个字符用三位来区分的话,10 个字符就需要 30 位 ,在32位机上也 OK 。 24 | 25 | 为了提取出后 30 位,需要使用 **mask** ,取值为 0x7ffffff(二进制表示含有 27 个 1) ,先用此 mask 可取出**整个序列**的后 27 位,然后再向左平移三位可取出 10 个字母长的序列 ( 30 位)。 26 | 27 | 为了保存子串的频率,这里使用**哈希表**。 28 | 29 | 首先当取出第十个字符时,将其存在哈希表里,和该字符串出现频率映射,之后每向左移三位替换一个字符,查找新字符串在哈希表里出现次数,如果之前刚好出现过一次,则将当前字符串存入返回值的数组并将其出现次数加一,如果从未出现过,则将其映射到 1。 30 | 31 | ### 32 | 33 | ### 动画描述 34 | 35 | 待补充 36 | 37 | ### 代码实现 38 | 39 | ```c++ 40 | class Solution { 41 | public: 42 | vector findRepeatedDnaSequences(string s) { 43 | vector res; 44 | if (s.size() <= 10) return res; 45 | int mask = 0x7ffffff, cur = 0; 46 | unordered_map m; 47 | for (int i = 0; i < 9; ++i) { 48 | cur = (cur << 3) | (s[i] & 7); 49 | } 50 | for (int i = 9; i < s.size(); ++i) { 51 | cur = ((cur & mask) << 3) | (s[i] & 7); 52 | if (m.count(cur)) { 53 | if (m[cur] == 1) res.push_back(s.substr(i - 9, 10)); 54 | ++m[cur]; 55 | } else { 56 | m[cur] = 1; 57 | } 58 | } 59 | return res; 60 | } 61 | }; 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第191号问题:位1的个数.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 191 号问题:位 1 的个数 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 191 号问题: 位 1 的个数。题目难度为 Easy,目前通过率为 52.3% 。 8 | 9 | ### 题目描述 10 | 11 | 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 12 | 13 | 14 | 15 | **示例 1:** 16 | 17 | ``` 18 | 输入:00000000000000000000000000001011 19 | 输出:3 20 | 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 21 | ``` 22 | 23 | **示例 2:** 24 | 25 | ``` 26 | 输入:00000000000000000000000010000000 27 | 输出:1 28 | 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 29 | ``` 30 | 31 | **示例 3:** 32 | 33 | ``` 34 | 输入:11111111111111111111111111111101 35 | 输出:31 36 | 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 37 | ``` 38 | 39 | 40 | 41 | **提示:** 42 | 43 | - 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 44 | - 在 Java 中,编译器使用[二进制补码](https://baike.baidu.com/item/%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A5%E7%A0%81/5295284)记法来表示有符号整数。因此,在上面的 **示例 3** 中,输入表示有符号整数 `-3`。 45 | 46 | 47 | 48 | **进阶**: 49 | 如果多次调用这个函数,你将如何优化你的算法? 50 | 51 | ### 题目解析 52 | 53 | 该题比较简单,解法有挺多,有位移法、位操作法、查表法、二次查表法等方法。 54 | 55 | 观察一下 n 与 n-1 这两个数的二进制表示:对于 n-1 这个数的二进制来说,相对于 n 的二进制,它的最末位的一个 1 会变成 0,最末位一个 1 之后的 0 会全部变成 1,其它位相同不变。 56 | 57 | 比如 n = 8888,其二进制为 **10001010111000** 58 | 59 | 则 n - 1 = 8887 ,其二进制为 **10001010110111** 60 | 61 | 通过按位与操作后:n & (n-1) = **10001010110000** 62 | 63 | 也就是说:通过 **n&(n-1)这个操作**,可以起到**消除最后一个1**的作用。 64 | 65 | 所以可以通过执行 n&(n-1) 操作来消除 n 末尾的 1 ,消除了多少次,就说明有多少个 1 。 66 | 67 | 68 | 69 | ### 动画描述 70 | 71 | 暂无~ 72 | 73 | ### 代码实现 74 | 75 | ```c++ 76 | class Solution { 77 | public: 78 | int hammingWeight(uint32_t n) { 79 | int cnt = 0; 80 | while(n > 0){ 81 | cnt++; 82 | n = n & (n - 1); 83 | } 84 | return cnt; 85 | } 86 | }; 87 | ``` 88 | 89 | 90 | 91 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第199号问题:二叉树的右视图.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 199 号问题:二叉树的右视图 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 199 号问题:二叉树的右视图。题目难度为 Medium,目前通过率为 57.5% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: [1,2,3,null,5,null,4] 17 | 输出: [1, 3, 4] 18 | 解释: 19 | 20 | 1 <--- 21 | / \ 22 | 2 3 <--- 23 | \ \ 24 | 5 4 <--- 25 | ``` 26 | 27 | ### 题目解析 28 | 29 | 与之前[二叉树的层次遍历](https://xiaozhuanlan.com/topic/8579460312)类似的,该问题需要用到**队列**, 30 | 31 | - 建立一个 queue 32 | - 遍历每层的节点时,把下一层的节点都存入到 queue 中 33 | - 每当开始新一层节点的遍历之前,先把新一层最后一个节点值存到结果中 34 | 35 | 36 | 37 | ### 动画描述 38 | 39 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181115113908.gif) 40 | 41 | ### 代码实现 42 | 43 | ``` 44 | class Solution { 45 | public: 46 | vector rightSideView(TreeNode *root) { 47 | vector res; 48 | if (!root) return res; 49 | queue q; 50 | q.push(root); 51 | while (!q.empty()) { 52 | res.push_back(q.back()->val); 53 | int size = q.size(); 54 | for (int i = 0; i < size; ++i) { 55 | TreeNode *node = q.front(); 56 | q.pop(); 57 | if (node->left) q.push(node->left); 58 | if (node->right) q.push(node->right); 59 | } 60 | } 61 | return res; 62 | } 63 | }; 64 | ``` 65 | 66 | 67 | 68 | 69 | 70 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第19号问题:删除链表的倒数第N个节点.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 19 号问题:删除链表的倒数第 N 个节点 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 19 号问题:删除链表的倒数第 N 个节点。题目难度为 Medium,目前通过率为 34.4% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个链表,删除链表的倒数第 *n* 个节点,并且返回链表的头结点。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 给定一个链表: 1->2->3->4->5, 和 n = 2. 17 | 18 | 当删除了倒数第二个节点后,链表变为 1->2->3->5. 19 | ``` 20 | 21 | **说明:** 22 | 23 | 给定的 *n* 保证是有效的。 24 | 25 | **进阶:** 26 | 27 | 你能尝试使用一趟扫描实现吗? 28 | 29 | ### 题目解析 30 | 31 | 采取双重遍历肯定是可以解决问题的,但题目要求我们一次遍历解决问题,那我们的思路得发散一下。 32 | 33 | 我们可以设想假设设定了双指针`p`和`q`的话,当`q`指向末尾的`NULL`,`p`与`q`之间相隔的元素个数为`n`时,那么删除掉`p`的下一个指针就完成了要求。 34 | 35 | - 设置虚拟节点`dummyHead`指向`head` 36 | - 设定双指针`p`和`q`,初始都指向虚拟节点`dummyHead` 37 | - 移动`q`,直到`p`与`q`之间相隔的元素个数为`n` 38 | - 同时移动`p`与`q`,直到`q`指向的为`NULL` 39 | - 将`p`的下一个节点指向下下个节点 40 | 41 | ### 动画描述 42 | 43 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181106162853.gif) 44 | 45 | ### 代码实现 46 | 47 | ``` 48 | class Solution { 49 | public: 50 | ListNode* removeNthFromEnd(ListNode* head, int n) { 51 | ListNode* dummyHead = new ListNode(0); 52 | dummyHead->next = head; 53 | 54 | ListNode* p = dummyHead; 55 | ListNode* q = dummyHead; 56 | for( int i = 0 ; i < n + 1 ; i ++ ){ 57 | q = q->next; 58 | } 59 | 60 | while(q){ 61 | p = p->next; 62 | q = q->next; 63 | } 64 | 65 | ListNode* delNode = p->next; 66 | p->next = delNode->next; 67 | delete delNode; 68 | 69 | ListNode* retNode = dummyHead->next; 70 | delete dummyHead; 71 | 72 | return retNode; 73 | 74 | } 75 | }; 76 | ``` 77 | 78 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第1号问题:两数之和.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 1 号问题:两数之和 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | > 7 | > 视频讲解:[【跟着程序员小吴图解 LeetCode 】LeetCode 第 1 号问题:两数之和]() 8 | 9 | 题目来源于 LeetCode 上第 1 号问题:两数之和。题目难度为 Easy,目前通过率为 45.8% 。 10 | 11 | ### 题目描述 12 | 13 | 给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那 **两个** 整数,并返回他们的数组下标。 14 | 15 | 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 16 | 17 | **示例:** 18 | 19 | ``` 20 | 给定 nums = [2, 7, 11, 15], target = 9 21 | 22 | 因为 nums[0] + nums[1] = 2 + 7 = 9 23 | 所以返回 [0, 1] 24 | ``` 25 | 26 | ### 题目解析 27 | 28 | 使用查找表来解决该问题。 29 | 30 | 设置一个 map 容器 record 用来记录元素的值与索引,然后遍历数组 nums。 31 | 32 | * 每次遍历时使用临时变量 complement 用来保存目标值与当前值的差值 33 | * 在此次遍历中查找 record ,查看是否有与 complement 一致的值,如果查找成功则返回查找值的索引值与当前变量的值 i 34 | * 如果未找到,则在 record 保存该元素与索引值 i 35 | 36 | ### 动画描述 37 | 38 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181028221055.gif) 39 | 40 | ### 代码实现 41 | 42 | ``` 43 | // 1. Two Sum 44 | // https://leetcode.com/problems/two-sum/description/ 45 | // 时间复杂度:O(n) 46 | // 空间复杂度:O(n) 47 | class Solution { 48 | public: 49 | vector twoSum(vector& nums, int target) { 50 | unordered_map record; 51 | for(int i = 0 ; i < nums.size() ; i ++){ 52 | 53 | int complement = target - nums[i]; 54 | if(record.find(complement) != record.end()){ 55 | int res[] = {i, record[complement]}; 56 | return vector(res, res + 2); 57 | } 58 | 59 | record[nums[i]] = i; 60 | } 61 | } 62 | }; 63 | 64 | ``` 65 | 66 | 67 | 68 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第201号问题:数字范围按位与.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 201 号问题:数字范围按位与 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 号问题:。题目难度为 Medium,目前通过率为 39.1% 。 8 | 9 | ### 题目描述 10 | 11 | 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: [5,7] 17 | 输出: 4 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入: [0,1] 24 | 输出: 0 25 | ``` 26 | 27 | ### 题目解析 28 | 29 | 以 [ 26 ,30] 为例。 30 | 31 | 首先,将 [ 26 , 30 ] 的范围数字用二进制表示出来: 32 | 33 | **11**010  **11**011  **11**100  **11**101  **11**110 34 | 35 | 而输出 24 的二进制是 11000 。 36 | 37 | 可以发现,只要找到二进制的 **左边公共部分** 即可。 38 | 39 | 所以,可以先建立一个 32 位都是 1 的 mask,然后每次向左移一位,比较 m 和 n 是否相同,不同再继续左移一位,直至相同,然后把 m 和 mask 相与就是最终结果。 40 | 41 | ### 动画描述 42 | 43 | 暂无 44 | 45 | ### 代码实现 46 | 47 | ```c++ 48 | class Solution { 49 | public: 50 | int rangeBitwiseAnd(int m, int n) { 51 | unsigned int d = INT_MAX; 52 | while ((m & d) != (n & d)) { 53 | d <<= 1; 54 | } 55 | return m & d; 56 | } 57 | }; 58 | ``` 59 | 60 | 61 | 62 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第203号问题:移除链表元素.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 203 号问题:移除链表元素 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 203 号问题:移除链表元素。题目难度为 Easy,目前通过率为 55.8% 。 8 | 9 | ### 题目描述 10 | 11 | 删除链表中等于给定值 **val** 的所有节点。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: 1->2->6->3->4->5->6, val = 6 17 | 输出: 1->2->3->4->5 18 | ``` 19 | 20 | ### 题目解析 21 | 22 | 主要考察了基本的链表遍历和设置指针的知识点。 23 | 24 | 定义一个虚拟头节点`dummyHead `,遍历查看原链表,遇到与给定值相同的元素,将该元素的前后两个节点连接起来,然后删除该元素即可。 25 | 26 | ### 动画描述 27 | 28 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181102163006.gif) 29 | 30 | ### 代码实现 31 | 32 | #### 代码一 33 | 34 | ``` 35 | // 203. Remove Linked List Elements 36 | // https://leetcode.com/problems/remove-linked-list-elements/description/ 37 | // 使用虚拟头结点 38 | // 时间复杂度: O(n) 39 | // 空间复杂度: O(1) 40 | class Solution { 41 | public: 42 | ListNode* removeElements(ListNode* head, int val) { 43 | 44 | // 创建虚拟头结点 45 | ListNode* dummyHead = new ListNode(0); 46 | dummyHead->next = head; 47 | 48 | ListNode* cur = dummyHead; 49 | while(cur->next != NULL){ 50 | if(cur->next->val == val){ 51 | ListNode* delNode = cur->next; 52 | cur->next = delNode->next; 53 | delete delNode; 54 | } 55 | else 56 | cur = cur->next; 57 | } 58 | 59 | ListNode* retNode = dummyHead->next; 60 | delete dummyHead; 61 | 62 | return retNode; 63 | } 64 | }; 65 | 66 | ``` 67 | 68 | #### 代码二 69 | 70 | 用递归来解。 71 | 72 | 通过递归调用到链表末尾,然后回来,需要删的元素,将链表next指针指向下一个元素即可。 73 | 74 | ``` 75 | class Solution { 76 | public: 77 | ListNode* removeElements(ListNode* head, int val) { 78 | if (!head) return NULL; 79 | head->next = removeElements(head->next, val); 80 | return head->val == val ? head->next : head; 81 | } 82 | }; 83 | ``` 84 | 85 | ## 86 | 87 | 88 | 89 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第206号问题:反转链表.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 206 号问题:反转链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 206 号问题:反转链表。题目难度为 Easy,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 反转一个单链表。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: 1->2->3->4->5->NULL 17 | 输出: 5->4->3->2->1->NULL 18 | ``` 19 | 20 | **进阶:** 21 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 22 | 23 | ### 题目解析 24 | 25 | 设置三个节点`pre`、`cur`、`next` 26 | 27 | - (1)每次查看`cur`节点是否为`NULL`,如果是,则结束循环,获得结果 28 | - (2)如果`cur`节点不是为`NULL`,则先设置临时变量`next`为`cur`的下一个节点 29 | - (3)让`cur`的下一个节点变成指向`pre`,而后`pre`移动`cur`,`cur`移动到`next` 30 | - (4)重复(1)(2)(3) 31 | 32 | ### 动画描述 33 | 34 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181101175158.gif) 35 | 36 | ### 代码实现 37 | 38 | ``` 39 | class Solution { 40 | public: 41 | ListNode* reverseList(ListNode* head) { 42 | ListNode* pre = NULL; 43 | ListNode* cur = head; 44 | while(cur != NULL){ 45 | ListNode* next = cur->next; 46 | cur->next = pre; 47 | pre = cur; 48 | cur = next; 49 | } 50 | 51 | return pre; 52 | } 53 | }; 54 | ``` 55 | 56 | 57 | 58 | 59 | 60 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第209号问题:长度最小的子数组.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 209 号问题:长度最小的子数组 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 209 号问题:长度最小的子数组。题目难度为 Medium,目前通过率为 25.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个含有 **n** 个正整数的数组和一个正整数 **s ,**找出该数组中满足其和 **≥ s** 的长度最小的连续子数组**。**如果不存在符合条件的连续子数组,返回 0。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: s = 7, nums = [2,3,1,2,4,3] 17 | 输出: 2 18 | 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。 19 | ``` 20 | 21 | **进阶:** 22 | 23 | 如果你已经完成了*O*(*n*) 时间复杂度的解法, 请尝试 *O*(*n* log *n*) 时间复杂度的解法。 24 | 25 | ### 题目解析 26 | 27 | 定义两个指针 left 和 right ,分别记录子数组的左右的边界位置。 28 | 29 | 30 | * (1)让 right 向右移,直到子数组和大于等于给定值或者 right 达到数组末尾; 31 | 32 | * (2)更新最短距离,将 left 像右移一位,sum 减去移去的值; 33 | 34 | * (3)重复(1)(2)步骤,直到 right 到达末尾,且 left 到达临界位置 35 | 36 | 37 | 38 | ### 动画描述 39 | 40 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181027160331.gif) 41 | 42 | 设置滑动窗口的长度为 0 ,位于数轴的最左端。 43 | 44 | ##### 1 .滑动窗口右端 R 开始移动,直到区间满足给定的条件,也就是和大于 7 ,此时停止于第三个元素 2,当前的最优长度为 4 45 | 46 | ![图 1](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/8rr0w.jpg) 47 | 48 | 49 | 50 | ##### 2. 滑动窗口左端 L 开始移动,缩小滑动窗口的大小,停止于第一个元素 3,此时区间和为 6,使得区间和不满足给定的条件(此时不大于 7) 51 | 52 | ![图片 2](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/77oa4.jpg) 53 | 54 | 55 | 56 | #### 3. 滑动窗口右端 R 继续移动,停止于第四个元素 4,此时和位 10 ,但最优长度仍然为 4 57 | 58 | ![图片 3](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/8ksiz.jpg) 59 | 60 | 61 | 62 | ### 代码实现 63 | 64 | ``` 65 | // 滑动窗口的思路 66 | // 时间复杂度: O(n) 67 | // 空间复杂度: O(1) 68 | class Solution { 69 | public int minSubArrayLen(int s, int[] nums) { 70 | int l= 0,r = -1; // nums[l...r]为我们的滑动窗口 71 | int sum = 0; 72 | int result = nums.length + 1; 73 | while (l < nums.length){ // 窗口的左边界在数组范围内,则循环继续 74 | 75 | if( r+1 = s 79 | sum -= nums[l]; 80 | l++; 81 | } 82 | 83 | if(sum >= s){ 84 | result = (r-l+1) < result ? (r-l+1) : result ; 85 | } 86 | } 87 | if(result==nums.length+1){ 88 | return 0; 89 | } 90 | return result; 91 | } 92 | } 93 | 94 | ``` 95 | 96 | 97 | 98 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第20号问题:有效的括号.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 20 号问题:有效的括号 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 20 号问题:有效的括号。题目难度为 Easy,目前通过率为 37.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 12 | 13 | 有效字符串需满足: 14 | 15 | 1. 左括号必须用相同类型的右括号闭合。 16 | 2. 左括号必须以正确的顺序闭合。 17 | 18 | 注意空字符串可被认为是有效字符串。 19 | 20 | **示例 1:** 21 | 22 | ``` 23 | 输入: "()" 24 | 输出: true 25 | ``` 26 | 27 | **示例 2:** 28 | 29 | ``` 30 | 输入: "()[]{}" 31 | 输出: true 32 | ``` 33 | 34 | **示例 3:** 35 | 36 | ``` 37 | 输入: "(]" 38 | 输出: false 39 | ``` 40 | 41 | **示例 4:** 42 | 43 | ``` 44 | 输入: "([)]" 45 | 输出: false 46 | ``` 47 | 48 | **示例 5:** 49 | 50 | ``` 51 | 输入: "{[]}" 52 | 输出: true 53 | ``` 54 | 55 | ### 题目解析 56 | 57 | 这道题让我们验证输入的字符串是否为括号字符串,包括大括号,中括号和小括号。 58 | 59 | 这里我们使用**栈**。 60 | 61 | - 遍历输入字符串 62 | - 如果当前字符为左半边括号时,则将其压入栈中 63 | - 如果遇到右半边括号时,**分类讨论:** 64 | - 1)如栈不为空且为对应的左半边括号,则取出栈顶元素,继续循环 65 | - 2)若此时栈为空,则直接返回false 66 | - 3)若不为对应的左半边括号,反之返回false 67 | 68 | ### 动画描述 69 | 70 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181108111124.gif) 71 | 72 | ### 代码实现 73 | 74 | ``` 75 | class Solution { 76 | public: 77 | bool isValid(string s) { 78 | 79 | stack stack; 80 | for( int i = 0 ; i < s.size() ; i ++ ) 81 | if( s[i] == '(' || s[i] == '{' || s[i] == '[') 82 | stack.push(s[i]); 83 | else{ 84 | 85 | if( stack.size() == 0 ) 86 | return false; 87 | 88 | char c = stack.top(); 89 | stack.pop(); 90 | 91 | char match; 92 | if( s[i] == ')' ){ 93 | match = '('; 94 | } 95 | else if( s[i] == ']' ){ 96 | match = '['; 97 | } 98 | else{ 99 | match = '{'; 100 | } 101 | 102 | if(c != match) return false; 103 | } 104 | 105 | if( stack.size() != 0 ) 106 | return false; 107 | 108 | return true; 109 | } 110 | }; 111 | ``` 112 | 113 | 114 | 115 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第219号问题:存在重复元素II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 219 号问题:存在重复元素 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 219 号问题:存在重复元素 II。题目难度为 Easy,目前通过率为 34.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个整数数组和一个整数 *k*,判断数组中是否存在两个不同的索引 *i* 和 *j*,使得 **nums [i] = nums [j]**,并且 *i* 和 *j* 的差的绝对值最大为 *k*。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: nums = [1,2,3,1], k = 3 17 | 输出: true 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入: nums = [1,0,1,1], k = 1 24 | 输出: true 25 | ``` 26 | 27 | **示例 3:** 28 | 29 | ``` 30 | 输入: nums = [1,2,3,1,2,3], k = 2 31 | 输出: false 32 | ``` 33 | 34 | ### 题目解析 35 | 36 | 考虑用滑动窗口与查找表来解决。 37 | 38 | * 设置查找表`record`,用来保存每次遍历时插入的元素,`record `的最大长度为`k ` 39 | * 遍历数组`nums`,每次遍历的时候在`record `查找是否存在相同的元素,如果存在则返回`true`,遍历结束 40 | * 如果此次遍历在`record `未查找到,则将该元素插入到`record `中,而后查看`record `的长度是否为`k + 1` 41 | * 如果此时`record `的长度是否为`k + 1`,则删减`record`的元素,该元素的值为`nums[i - k]` 42 | * 如果遍历完整个数组`nums`未查找到则返回`false` 43 | 44 | ### 动画描述 45 | 46 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181031104805.gif) 47 | 48 | ### 代码实现 49 | 50 | ``` 51 | // 219. Contains Duplicate II 52 | // https://leetcode.com/problems/contains-duplicate-ii/description/ 53 | // 时间复杂度: O(n) 54 | // 空间复杂度: O(k) 55 | class Solution { 56 | public: 57 | bool containsNearbyDuplicate(vector& nums, int k) { 58 | 59 | if(nums.size() <= 1) return false; 60 | 61 | if(k <= 0) return false; 62 | 63 | 64 | unordered_set record; 65 | for(int i = 0 ; i < nums.size() ; i ++){ 66 | 67 | if(record.find(nums[i]) != record.end()){ 68 | return true; 69 | } 70 | 71 | record.insert(nums[i]); 72 | 73 | // 保持record中最多有k个元素 74 | // 因为在下一次循环中会添加一个新元素,使得总共考虑k+1个元素 75 | if(record.size() == k + 1){ 76 | record.erase(nums[i - k]); 77 | } 78 | } 79 | 80 | return false; 81 | } 82 | }; 83 | ``` 84 | 85 | 86 | 87 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第21号问题:合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 21 号问题:合并两个有序链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 21 号问题:合并两个有序链表。题目难度为 Easy,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入:1->2->4, 1->3->4 17 | 输出:1->1->2->3->4->4 18 | ``` 19 | 20 | ### 题目解析 21 | 22 | #### 一般方案 23 | 24 | ##### 1.1 解题思想 25 | 26 | > (1)对空链表存在的情况进行处理,假如 pHead1 为空则返回 pHead2 ,pHead2 为空则返回 pHead1。(两个都为空此情况在pHead1为空已经被拦截) 27 | > (2)在两个链表无空链表的情况下确定第一个结点,比较链表1和链表2的第一个结点的值,将值小的结点保存下来为合并后的第一个结点。并且把第一个结点为最小的链表向后移动一个元素。 28 | > (3)继续在剩下的元素中选择小的值,连接到第一个结点后面,并不断next将值小的结点连接到第一个结点后面,直到某一个链表为空。 29 | > (4)当两个链表长度不一致时,也就是比较完成后其中一个链表为空,此时需要把另外一个链表剩下的元素都连接到第一个结点的后面。 30 | 31 | ##### 1.2 代码实现 32 | 33 | ```c++ 34 | ListNode* mergeTwoOrderedLists(ListNode* pHead1, ListNode* pHead2){ 35 | ListNode* pTail = NULL;//指向新链表的最后一个结点 pTail->next去连接 36 | ListNode* newHead = NULL;//指向合并后链表第一个结点 37 | if (NULL == pHead1){ 38 | return pHead2; 39 | }else if(NULL == pHead2){ 40 | return pHead1; 41 | }else{ 42 | //确定头指针 43 | if ( pHead1->data < pHead2->data){ 44 | newHead = pHead1; 45 | pHead1 = pHead1->next;//指向链表的第二个结点 46 | }else{ 47 | newHead = pHead2; 48 | pHead2 = pHead2->next; 49 | } 50 | pTail = newHead;//指向第一个结点 51 | while ( pHead1 && pHead2) { 52 | if ( pHead1->data <= pHead2->data ){ 53 | pTail->next = pHead1; 54 | pHead1 = pHead1->next; 55 | }else { 56 | pTail->next = pHead2; 57 | pHead2 = pHead2->next; 58 | } 59 | pTail = pTail->next; 60 | 61 | } 62 | if(NULL == pHead1){ 63 | pTail->next = pHead2; 64 | }else if(NULL == pHead2){ 65 | pTail->next = pHead1; 66 | } 67 | return newHead; 68 | } 69 | ``` 70 | 71 | #### 2 递归方案 72 | 73 | ##### 2.1 解题思想 74 | 75 | > (1)对空链表存在的情况进行处理,假如 pHead1 为空则返回 pHead2 ,pHead2 为空则返回 pHead1。 76 | > (2)比较两个链表第一个结点的大小,确定头结点的位置 77 | > (3)头结点确定后,继续在剩下的结点中选出下一个结点去链接到第二步选出的结点后面,然后在继续重复(2 )(3) 步,直到有链表为空。 78 | 79 | ##### 2.2 代码实现 80 | 81 | ```c++ 82 | ListNode* mergeTwoOrderedLists(ListNode* pHead1, ListNode* pHead2){ 83 | ListNode* newHead = NULL; 84 | if (NULL == pHead1){ 85 | return pHead2; 86 | }else if(NULL ==pHead2){ 87 | return pHead1; 88 | }else{ 89 | if (pHead1->data < pHead2->data){ 90 | newHead = pHead1; 91 | newHead->next = mergeTwoOrderedLists(pHead1->next, pHead2); 92 | }else{ 93 | newHead = pHead2; 94 | newHead->next = mergeTwoOrderedLists(pHead1, pHead2->next); 95 | } 96 | return newHead; 97 | } 98 | } 99 | ``` 100 | 101 | ### 102 | 103 | 104 | 105 | 106 | 107 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第231号问题:2的幂.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 231 号问题:2 的幂 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 231 号问题:2 的幂。题目难度为 Easy,目前通过率为 45.6% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: 1 17 | 输出: true 18 | 解释: 2^0 = 1 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ``` 24 | 输入: 16 25 | 输出: true 26 | 解释: 2^4 = 16 27 | ``` 28 | 29 | **示例 3:** 30 | 31 | ``` 32 | 输入: 218 33 | 输出: false 34 | ``` 35 | 36 | ### 题目解析 37 | 38 | 首先,先来分析一下 2 的次方数的二进制写法: 39 | 40 | ![表格](https://user-gold-cdn.xitu.io/2019/2/21/1690d8f7ad5bc000?w=1630&h=190&f=jpeg&s=18479) 41 | 42 | 仔细观察,可以看出 2 的次方数都只有一个 1 ,剩下的都是 0 。根据这个特点,只需要每次判断最低位是否为 1 ,然后向右移位,最后统计 1 的个数即可判断是否是 2 的次方数。 43 | 44 | 代码很简单: 45 | 46 | ```c++ 47 | class Solution { 48 | public: 49 | bool isPowerOfTwo(int n) { 50 | int cnt = 0; 51 | while (n > 0) { 52 | cnt += (n & 1); 53 | n >>= 1; 54 | } 55 | return cnt == 1; 56 | } 57 | }; 58 | ``` 59 | 60 | 该题还有一种巧妙的解法。再观察上面的表格,如果一个数是 2 的次方数的话,那么它的二进数必然是最高位为1,其它都为 0 ,那么如果此时我们减 1 的话,则最高位会降一位,其余为 0 的位现在都为变为 1,那么我们把两数相与,就会得到 0。 61 | 62 | 比如 2 的 3 次方为 8,二进制位 1000 ,那么 ` 8 - 1 = 7`,其中 7 的二进制位 0111。 63 | 64 | ### 图片描述 65 | 66 | ![](https://user-gold-cdn.xitu.io/2019/2/21/1690d8f7ad92ad5e?w=356&h=466&f=jpeg&s=15576) 67 | 68 | ### 代码实现 69 | 70 | 利用这个性质,只需一行代码就可以搞定。 71 | 72 | ```c++ 73 | class Solution { 74 | public: 75 | bool isPowerOfTwo(int n) { 76 | return (n > 0) && (!(n & (n - 1))); 77 | } 78 | }; 79 | ``` 80 | 81 | 82 | 83 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) 84 | -------------------------------------------------------------------------------- /notes/LeetCode第237号问题:删除链表中的节点.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 237 号问题:删除链表中的节点 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 237 号问题:删除链表中的节点。题目难度为 Easy,目前通过率为 72.6% 。 8 | 9 | ### 题目描述 10 | 11 | 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。 12 | 13 | 现有一个链表 -- head = [4,5,1,9],它可以表示为: 14 | 15 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502113234.png) 16 | 17 | 18 | 19 | **示例 1:** 20 | 21 | ``` 22 | 输入: head = [4,5,1,9], node = 5 23 | 输出: [4,1,9] 24 | 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 25 | ``` 26 | 27 | **示例 2:** 28 | 29 | ``` 30 | 输入: head = [4,5,1,9], node = 1 31 | 输出: [4,5,9] 32 | 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 33 | ``` 34 | 35 | 36 | 37 | **说明:** 38 | 39 | - 链表至少包含两个节点。 40 | - 链表中所有节点的值都是唯一的。 41 | - 给定的节点为非末尾节点并且一定是链表中的一个有效节点。 42 | - 不要从你的函数中返回任何结果。 43 | 44 | ### 题目解析 45 | 46 | 此题注意的点是没有给我们链表的起点,只给我们了一个要删的节点,与以往处理的情况稍许不同。 47 | 48 | **这道题的处理方法是先把当前节点的值用下一个节点的值覆盖,然后我们删除下一个节点即可** 49 | 50 | ### 动画描述 51 | 52 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181105171450.gif) 53 | 54 | ### 代码实现 55 | 56 | ``` 57 | class Solution { 58 | public: 59 | void deleteNode(ListNode* node) { 60 | if (node == NULL) return; 61 | if (node->next == NULL) { 62 | delete node; 63 | node = NULL; 64 | return; 65 | } 66 | node->val = node->next->val; 67 | ListNode *delNode = node->next; 68 | node->next = delNode->next; 69 | 70 | delete delNode; 71 | } 72 | }; 73 | ``` 74 | 75 | 76 | 77 | 78 | 79 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第239号问题:滑动窗口最大值.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 239 号问题:滑动窗口最大值 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 239 号问题:滑动窗口最大值。题目难度为 Hard,目前通过率为 40.5% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个数组 *nums*,有一个大小为 *k* 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 *k* 内的数字。滑动窗口每次只向右移动一位。 12 | 13 | 返回滑动窗口最大值。 14 | 15 | **示例:** 16 | 17 | ``` 18 | 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 19 | 输出: [3,3,5,5,6,7] 20 | 解释: 21 | 22 | 滑动窗口的位置 最大值 23 | --------------- ----- 24 | [1 3 -1] -3 5 3 6 7 3 25 | 1 [3 -1 -3] 5 3 6 7 3 26 | 1 3 [-1 -3 5] 3 6 7 5 27 | 1 3 -1 [-3 5 3] 6 7 5 28 | 1 3 -1 -3 [5 3 6] 7 6 29 | 1 3 -1 -3 5 [3 6 7] 7 30 | ``` 31 | 32 | **注意:** 33 | 34 | 你可以假设 *k* 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。 35 | 36 | **进阶:** 37 | 38 | 你能在线性时间复杂度内解决此题吗? 39 | 40 | ### 题目解析 41 | 42 | 利用一个 **双端队列**,在队列中存储元素在数组中的位置, 并且维持队列的严格递减,,也就说维持队首元素是 **最大的 **,当遍历到一个新元素时, 如果队列里有比当前元素小的,就将其移除队列,以保证队列的递减。当队列元素位置之差大于 k,就将队首元素移除。 43 | 44 | ### 补充:什么是双端队列(Dqueue) 45 | 46 | Deque 的含义是 “double ended queue”,即双端队列,它具有队列和栈的性质的数据结构。顾名思义,它是一种前端与后端都支持插入和删除操作的队列。 47 | 48 | Deque 继承自 Queue(队列),它的直接实现有 ArrayDeque、LinkedList 等。 49 | 50 | ### 51 | 52 | 53 | 54 | ### 动画描述 55 | 56 | ![动画描述 Made by Jun Chen](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/20whr.gif) 57 | 58 | ### 代码实现 59 | 60 | ``` 61 | class Solution { 62 | public int[] maxSlidingWindow(int[] nums, int k) { 63 | //有点坑,题目里都说了数组不为空,且 k > 0。但是看了一下,测试用例里面还是有nums = [], k = 0,所以只好加上这个判断 64 | if (nums == null || nums.length < k || k == 0) return new int[0]; 65 | int[] res = new int[nums.length - k + 1]; 66 | //双端队列 67 | Deque deque = new LinkedList<>(); 68 | for (int i = 0; i < nums.length; i++) { 69 | //在尾部添加元素,并保证左边元素都比尾部大 70 | while (!deque.isEmpty() && nums[deque.getLast()] < nums[i]) { 71 | deque.removeLast(); 72 | } 73 | deque.addLast(i); 74 | //在头部移除元素 75 | if (deque.getFirst() == i - k) { 76 | deque.removeFirst(); 77 | } 78 | //输出结果 79 | if (i >= k - 1) { 80 | res[i - k + 1] = nums[deque.getFirst()]; 81 | } 82 | } 83 | return res; 84 | } 85 | } 86 | ``` 87 | 88 | 89 | 90 | 91 | 92 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第23号问题:合并K个排序链表.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 23 号问题:合并 K 个排序链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 23 号问题:合并 K 个排序链表。题目难度为 Hard,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 合并 *k* 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: 17 | [ 18 | 1->4->5, 19 | 1->3->4, 20 | 2->6 21 | ] 22 | 输出: 1->1->2->3->4->4->5->6 23 | ``` 24 | 25 | **输入** 26 | 27 | ![图一](https://user-gold-cdn.xitu.io/2019/4/9/169ff8cbaf694440?w=2360&h=614&f=jpeg&s=63179) 28 | 29 | **输出** 30 | 31 | ![图二](https://user-gold-cdn.xitu.io/2019/4/9/169ff8cbb372e71f?w=2518&h=572&f=jpeg&s=62543) 32 | 33 | ### 题目解析 34 | 35 | ### 题目分析一 36 | 37 | 这里需要将这 *k* 个排序链表整合成一个排序链表,也就是说有多个输入,一个输出,类似于漏斗一样的概念。 38 | 39 | 因此,可以利用最小堆的概念。如果你对堆的概念不熟悉,可以戳这先了解一下~ 40 | 41 | 取每个 Linked List 的最小节点放入一个 heap 中,排序成最小堆。然后取出堆顶最小的元素,放入输出的合并 List 中,然后将该节点在其对应的 List 中的下一个节点插入到 heap 中,循环上面步骤,以此类推直到全部节点都经过 heap。 42 | 43 | 由于 heap 的大小为始终为 k ,而每次插入的复杂度是 logk ,一共插入了 nk 个节点。时间复杂度为 O(nklogk),空间复杂度为O(k)。 44 | 45 | ### 动画演示 46 | 47 | ![动画演示](https://user-gold-cdn.xitu.io/2019/4/9/169ff8cbb36cb6f7?w=939&h=507&f=gif&s=6542126) 48 | 49 | ### 代码实现 50 | 51 | ```java 52 | class Solution { 53 | public ListNode mergeKLists(ListNode[] lists) { 54 | //用heap(堆)这种数据结构,也就是 java 里面的 PriorityQueue 55 | PriorityQueue pq = new PriorityQueue<>(new Comparator() { 56 | public int compare(ListNode a, ListNode b) { 57 | return a.val-b.val; 58 | } 59 | }); 60 | ListNode ret = null, cur = null; 61 | for(ListNode node: lists) { 62 | if(null != node) { 63 | pq.add(node); 64 | } 65 | } 66 | while(!pq.isEmpty()) { 67 | ListNode node = pq.poll(); 68 | if(null == ret) { 69 | ret = cur = node; 70 | } 71 | else { 72 | cur = cur.next = node; 73 | } 74 | if(null != node.next) { 75 | pq.add(node.next); 76 | } 77 | } 78 | return ret; 79 | } 80 | } 81 | ``` 82 | 83 | 84 | 85 | 86 | 87 | ### 题目分析二 88 | 89 | 这道题需要合并 k 个有序链表,并且最终合并出来的结果也必须是有序的。如果一开始没有头绪的话,可以先从简单的开始:**合并 两 个有序链表**。 90 | 91 | 合并两个有序链表:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 92 | 93 | **示例:** 94 | 95 | ``` 96 | 输入:1->2->4, 1->3->4 97 | 输出:1->1->2->3->4->4 98 | ``` 99 | 100 | 这道题目按照题目描述做下去就行:新建一个链表,比较原始两个链表中的元素值,把较小的那个链到新链表中即可。需要注意的一点时由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。 101 | 102 | 所以代码实现很容易写: 103 | 104 | ```java 105 | class Solution { 106 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 107 | //新建链表 108 | ListNode dummyHead = new ListNode(0); 109 | ListNode cur = dummyHead; 110 | while (l1 != null && l2 != null) { 111 | if (l1.val < l2.val) { 112 | cur.next = l1; 113 | cur = cur.next; 114 | l1 = l1.next; 115 | } else { 116 | cur.next = l2; 117 | cur = cur.next; 118 | l2 = l2.next; 119 | } 120 | } 121 | // 注意点:当有链表为空时,直接连接另一条链表 122 | if (l1 == null) { 123 | cur.next = l2; 124 | } else { 125 | cur.next = l1; 126 | } 127 | return dummyHead.next; 128 | } 129 | ``` 130 | 131 | 132 | 133 | 现在回到一开始的题目:合并 K 个排序链表。 134 | 135 | **合并 K 个排序链表** 与 **合并两个有序链表** 的区别点在于操作有序链表的数量上,因此完全可以按照上面的代码思路来实现合并 K 个排序链表。 136 | 137 | 这里可以参考 **归并排序 **的分治思想,将这 K 个链表先划分为两个 K/2 个链表,处理它们的合并,然后不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。 138 | 139 | ![归并-分治](https://user-gold-cdn.xitu.io/2019/4/9/169ff8cbb2c891fe?w=953&h=531&f=gif&s=164652) 140 | 141 | ### 代码实现 142 | 143 | 根据上面的动画,实现代码非常简单也容易理解,先划分,直到不能划分下去,然后开始合并。 144 | 145 | ```java 146 | class Solution { 147 | public ListNode mergeKLists(ListNode[] lists){ 148 | if(lists.length == 0) 149 | return null; 150 | if(lists.length == 1) 151 | return lists[0]; 152 | if(lists.length == 2){ 153 | return mergeTwoLists(lists[0],lists[1]); 154 | } 155 | 156 | int mid = lists.length/2; 157 | ListNode[] l1 = new ListNode[mid]; 158 | for(int i = 0; i < mid; i++){ 159 | l1[i] = lists[i]; 160 | } 161 | 162 | ListNode[] l2 = new ListNode[lists.length-mid]; 163 | for(int i = mid,j=0; i < lists.length; i++,j++){ 164 | l2[j] = lists[i]; 165 | } 166 | 167 | return mergeTwoLists(mergeKLists(l1),mergeKLists(l2)); 168 | 169 | } 170 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 171 | if (l1 == null) return l2; 172 | if (l2 == null) return l1; 173 | 174 | ListNode head = null; 175 | if (l1.val <= l2.val){ 176 | head = l1; 177 | head.next = mergeTwoLists(l1.next, l2); 178 | } else { 179 | head = l2; 180 | head.next = mergeTwoLists(l1, l2.next); 181 | } 182 | return head; 183 | } 184 | } 185 | ``` 186 | 187 | 188 | 189 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第24号问题:两两交换链表中的节点.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 24 号问题:两两交换链表中的节点 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 24 号问题:两两交换链表中的节点。题目难度为 Medium,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 12 | 13 | **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 14 | 15 | **示例:** 16 | 17 | ``` 18 | 给定 1->2->3->4, 你应该返回 2->1->4->3. 19 | ``` 20 | 21 | ### 题目解析 22 | 23 | 该题属于基本的链表操作题。 24 | 25 | - 设置一个虚拟头结点`dummyHead ` 26 | - 设置需要交换的两个节点分别为`node1 `、`node2`,同时设置`node2`的下一个节点`next` 27 | 28 | ##### 在这一轮操作中 29 | 30 | - 将`node2`节点的next设置为`node1`节点 31 | - 将`node1`节点的next设置为`next `节点 32 | - 将`dummyHead `节点的next设置为`node2 ` 33 | - 结束本轮操作 34 | 35 | 接下来的每轮操作都按照上述进行。 36 | 37 | ### 动画描述 38 | 39 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181103145019.gif) 40 | 41 | ### 代码实现 42 | 43 | ``` 44 | // 24. Swap Nodes in Pairs 45 | // https://leetcode.com/problems/swap-nodes-in-pairs/description/ 46 | // 时间复杂度: O(n) 47 | // 空间复杂度: O(1) 48 | class Solution { 49 | public: 50 | ListNode* swapPairs(ListNode* head) { 51 | 52 | ListNode* dummyHead = new ListNode(0); 53 | dummyHead->next = head; 54 | 55 | ListNode* p = dummyHead; 56 | while(p->next && p->next->next){ 57 | ListNode* node1 = p->next; 58 | ListNode* node2 = node1->next; 59 | ListNode* next = node2->next; 60 | node2->next = node1; 61 | node1->next = next; 62 | p->next = node2; 63 | p = node1; 64 | } 65 | 66 | ListNode* retHead = dummyHead->next; 67 | delete dummyHead; 68 | 69 | return retHead; 70 | } 71 | }; 72 | 73 | ``` 74 | 75 | 76 | 77 | 78 | 79 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第268号问题:缺失数字.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 268 号问题:缺失数字 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 今天分享一道很简单的算法题。 8 | 9 | 题目来源于 LeetCode 上第 268 号问题:缺失数字。题目难度为 Easy,目前通过率为 50.2% 。 10 | 11 | ## 题目描述 12 | 13 | 给定一个包含 `0, 1, 2, ..., n` 中 *n* 个数的序列,找出 0 .. *n* 中没有出现在序列中的那个数。 14 | 15 | **说明:** 16 | 17 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 18 | 19 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190516113448.png) 20 | 21 | ## 题目解析 22 | 23 | 这道题目有三种解法。 24 | 25 | ### 解法一:异或法 26 | 27 | 和之前那道 **只出现一次的数字** 很类似: 28 | 29 | > 只出现一次的数字: 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 30 | 31 | 如果我们补充一个完整的数组和原数组进行组合,那所求解的问题就变成了 **只出现一次的数字**。 32 | 33 | 将少了一个数的数组与 0 到 n 之间完整的那个数组进行异或处理,因为相同的数字异或会变为了 0 ,那么全部数字异或后,剩下的就是少了的那个数字。 34 | 35 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190516143539.png) 36 | 37 | #### 代码实现1 38 | 39 | ```java 40 | class Solution { 41 | public int missingNumber(int[] nums) { 42 | int res = 0; 43 | int i = 0; 44 | //注意数组越界情况 45 | for (; i < nums.length;i++){ 46 | // i 表示完整数组中的数字,与原数组中的数字 nums[i] 进行异或,再与保存的结果异或 47 | res = res^i^nums[i]; 48 | } 49 | //最后需要与循环中无法使用到的那个最大的数异或 50 | return res^i; 51 | } 52 | } 53 | ``` 54 | 55 | #### 代码实现2 56 | 57 | ```java 58 | class Solution { 59 | public int missingNumber(int[] nums) { 60 | int res = nums.length; 61 | for (int i = 0; i < nums.length; ++i){ 62 | res ^= nums[i]; 63 | res ^= i; 64 | } 65 | return res; 66 | } 67 | } 68 | ``` 69 | 70 | 71 | 72 | ### 解法二:求和法 73 | 74 | - 求出 0 到 n 之间所有的数字之和 75 | - 遍历数组计算出原始数组中数字的累积和 76 | - 两和相减,差值就是丢失的那个数字 77 | 78 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190516151203.gif) 79 | 80 | ```java 81 | //小吴之前担心会数据溢出,不过估计这题考察的不是这个,所以测试用例没写这种吧,还是能 AC 的 82 | class Solution { 83 | public int missingNumber(int[] nums) { 84 | int n = nums.length; 85 | int sum = (n+0)*(n+1)/2; 86 | for (int i=0; i 注:由于一开始进行了排序操作,因此使用二分法的性能是不如上面两种方法。 104 | 105 | ```java 106 | public class Solution { 107 | public int missingNumber(int[] nums) { 108 | Arrays.sort(nums); 109 | int left = 0; 110 | int right = nums.length; 111 | while (left < right){ 112 | int mid = (left + right) / 2; 113 | if (nums[mid] > mid){ 114 | right = mid; 115 | }else{ 116 | left = mid + 1; 117 | } 118 | } 119 | return left; 120 | } 121 | } 122 | ``` 123 | 124 | 125 | 126 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第26号问题:删除排序数组中的重复项.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 26 号问题:删除排序数组中的重复项 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 26 号问题:删除排序数组中的重复项。题目难度为 Easy,目前通过率为 48.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个排序数组,你需要在**原地**删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 12 | 13 | 不要使用额外的数组空间,你必须在**原地修改输入数组**并在使用 O(1) 额外空间的条件下完成。 14 | 15 | **示例 1:** 16 | 17 | ``` 18 | 给定数组 nums = [1,1,2], 19 | 20 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 21 | 22 | 你不需要考虑数组中超出新长度后面的元素。 23 | ``` 24 | 25 | **示例 2:** 26 | 27 | ``` 28 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 29 | 30 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 31 | 32 | 你不需要考虑数组中超出新长度后面的元素。 33 | ``` 34 | 35 | **说明:** 36 | 37 | 为什么返回数值是整数,但输出的答案是数组呢? 38 | 39 | 请注意,输入数组是以**“引用”**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 40 | 41 | 你可以想象内部操作如下: 42 | 43 | ``` 44 | // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 45 | int len = removeDuplicates(nums); 46 | 47 | // 在函数里修改输入数组对于调用者是可见的。 48 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 49 | for (int i = 0; i < len; i++) { 50 | print(nums[i]); 51 | } 52 | ``` 53 | 54 | ### 题目解析 55 | 56 | 使用快慢指针来记录遍历的坐标。 57 | 58 | - 开始时这两个指针都指向第一个数字 59 | - 如果两个指针指的数字相同,则快指针向前走一步 60 | - 如果不同,则两个指针都向前走一步 61 | - 当快指针走完整个数组后,慢指针当前的坐标加1就是数组中不同数字的个数 62 | 63 | ### 动画描述 64 | 65 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181116115601.gif) 66 | 67 | 68 | 69 | ### 代码实现 70 | 71 | ``` 72 | class Solution { 73 | public: 74 | int removeDuplicates(vector& nums) { 75 | if (nums.empty()) return 0; 76 | int pre = 0, cur = 0, n = nums.size(); 77 | while (cur < n) { 78 | if (nums[pre] == nums[cur]){ 79 | cur++; 80 | } else{ 81 | ++pre; 82 | nums[pre] = nums[cur]; 83 | cur++; 84 | } 85 | } 86 | return pre + 1; 87 | } 88 | }; 89 | ``` 90 | 91 | 92 | 93 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第279号问题:完全平方数.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 279 号问题:完全平方数 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 279 号问题:完全平方数。题目难度为 Medium,目前通过率为 49.1% 。 8 | 9 | ### 题目描述 10 | 11 | 给定正整数 *n*,找到若干个完全平方数(比如 `1, 4, 9, 16, ...`)使得它们的和等于 *n*。你需要让组成和的完全平方数的个数最少。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: n = 12 17 | 输出: 3 18 | 解释: 12 = 4 + 4 + 4. 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ``` 24 | 输入: n = 13 25 | 输出: 2 26 | 解释: 13 = 4 + 9. 27 | ``` 28 | 29 | ### 题目解析 30 | 31 | 这道题目很有意思。 32 | 33 | 大部分文章给出的答案都是依托于一个定理:**四平方定理**。 34 | 35 | 四平方定理讲的就是任何一个正整数都可以表示成不超过四个整数的平方之和。也就是说,这道题的答案只有 1,2 ,3,4 这四种可能。 36 | 37 | 同时,还有一个非常重要的推论满足四数平方和定理的数n(这里要满足由四个数构成,小于四个不行),必定满足 n = 4a * (8b + 7)。 38 | 39 | 根据这个重要的推论来解决此题,首先将输入的`n`迅速缩小。然后再判断,这个缩小后的数是否可以通过`两个平方数的和或一个平方数`组成,不能的话我们返回`3`,能的话我们返回`平方数的个数`。 40 | 41 | 所以代码很简洁,如下: 42 | 43 | ```java 44 | public int numSquares(int n) { 45 | while (n % 4 == 0){ 46 | n /= 4; 47 | } 48 | if ( n % 8 == 7){ 49 | return 4; 50 | } 51 | int a = 0; 52 | while ( (a * a) <= n){ 53 | int b = (int)Math.pow((n - a * a),0.5); 54 | if(a * a + b * b == n) { 55 | //如果可以 在这里返回 56 | if(a != 0 && b != 0) { 57 | return 2; 58 | } else{ 59 | return 1; 60 | } 61 | } 62 | a++; 63 | } 64 | return 3; 65 | } 66 | ``` 67 | 68 | 69 | 70 | 但因为本章是「广度优先遍历」的专栏,因此再补充一个图的广度优先遍历的答案: 71 | 72 | 使用广度优先搜索方法,将 n 依次减去比 n 小的所有平方数,直至 n = 0 ,此时的层数即为最后的结果。 73 | 74 | ### 动画描述 75 | 76 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502113958.gif) 77 | 78 | ### 代码实现 79 | 80 | ``` 81 | import java.util.LinkedList; 82 | import javafx.util.Pair; 83 | class Solution { 84 | public int numSquares(int n) { 85 | if(n == 0) 86 | return 0; 87 | 88 | LinkedList> queue = new LinkedList>(); 89 | queue.addLast(new Pair(n, 0)); 90 | 91 | boolean[] visited = new boolean[n+1]; 92 | visited[n] = true; 93 | 94 | while(!queue.isEmpty()){ 95 | Pair front = queue.removeFirst(); 96 | int num = front.getKey(); 97 | int step = front.getValue(); 98 | 99 | if(num == 0) 100 | return step; 101 | 102 | for(int i = 1 ; num - i*i >= 0 ; i ++){ 103 | int a = num - i*i; 104 | if(!visited[a]){ 105 | if(a == 0) return step + 1; 106 | queue.addLast(new Pair(num - i * i, step + 1)); 107 | visited[num - i * i] = true; 108 | } 109 | } 110 | } 111 | return 0; 112 | } 113 | } 114 | ``` 115 | 116 | 117 | 118 | 119 | 120 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第283号问题:移动零.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 283 号问题:移动零 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 283 号问题:移动零。题目难度为 Easy,目前通过率为 53.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个数组 `nums`,编写一个函数将所有 `0` 移动到数组的末尾,同时保持非零元素的相对顺序。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: [0,1,0,3,12] 17 | 输出: [1,3,12,0,0] 18 | ``` 19 | 20 | **说明**: 21 | 22 | 1. 必须在原数组上操作,不能拷贝额外的数组。 23 | 2. 尽量减少操作次数。 24 | 25 | ### 题目解析 26 | 27 | 设定一个临时变量 k = 0,遍历数组 nums ,将非零元素移动到 nums[k]位 置,同时 k++,而后将【k,….nums.size()】中的元素置零。 28 | 29 | ### 解法一 30 | 31 | 创建一个临时数组 nonZeroElements ,遍历 nums ,将 nums 中非 0 元素赋值到 nonZeroElements中,而后按顺序将 nonZeroElements 赋值到 nums 上,未遍历的元素置 0 ; 32 | 33 | 动画如下: 34 | 35 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181027160100.gif) 36 | 37 | 代码如下: 38 | 39 | ``` 40 | // 时间复杂度: O(n) 41 | // 空间复杂度: O(n) 42 | class Solution { 43 | public: 44 | void moveZeroes(vector& nums) { 45 | 46 | vector nonZeroElements; 47 | 48 | // 将vec中所有非0元素放入nonZeroElements中 49 | for(int i = 0 ; i < nums.size() ; i ++) 50 | if(nums[i]) 51 | nonZeroElements.push_back(nums[i]); 52 | 53 | // 将nonZeroElements中的所有元素依次放入到nums开始的位置 54 | for(int i = 0 ; i < nonZeroElements.size() ; i ++) 55 | nums[i] = nonZeroElements[i]; 56 | 57 | // 将nums剩余的位置放置为0 58 | for(int i = nonZeroElements.size() ; i < nums.size() ; i ++) 59 | nums[i] = 0; 60 | } 61 | }; 62 | 63 | ``` 64 | 65 | ### 解法二 66 | 67 | 设定一个临时变量 k = 0,遍历数组 nums ,将非零元素移动到 nums[k] 位置,同时 k++,而后将【k,….nums.size()】中的元素置零。 68 | 69 | 动画如下: 70 | 71 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181027160135.gif) 72 | 73 | 代码如下: 74 | 75 | ``` 76 | // 原地(in place)解决该问题 77 | // 时间复杂度: O(n) 78 | // 空间复杂度: O(1) 79 | class Solution { 80 | public: 81 | void moveZeroes(vector& nums) { 82 | 83 | int k = 0; // nums中, [0...k)的元素均为非0元素 84 | 85 | // 遍历到第i个元素后,保证[0...i]中所有非0元素 86 | // 都按照顺序排列在[0...k)中 87 | for(int i = 0 ; i < nums.size() ; i ++) 88 | if(nums[i]) 89 | nums[k++] = nums[i]; 90 | 91 | // 将nums剩余的位置放置为0 92 | for(int i = k ; i < nums.size() ; i ++) 93 | nums[i] = 0; 94 | } 95 | }; 96 | ``` 97 | 98 | ### 解法三 99 | 100 | 思路:设定一个临时变量 k = 0,遍历数组 nums,将非零元素与之前的零元素进行交换,维护变量k的值。 101 | 102 | 动画如下: 103 | 104 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181030085904.gif) 105 | 代码如下: 106 | 107 | ``` 108 | // 原地(in place)解决该问题 109 | // 时间复杂度: O(n) 110 | // 空间复杂度: O(1) 111 | class Solution { 112 | public: 113 | void moveZeroes(vector& nums) { 114 | 115 | int k = 0; // nums中, [0...k)的元素均为非0元素 116 | 117 | // 遍历到第i个元素后,保证[0...i]中所有非0元素 118 | // 都按照顺序排列在[0...k)中 119 | // 同时, [k...i] 为 0 120 | for(int i = 0 ; i < nums.size() ; i ++) 121 | if(nums[i]){ 122 | if(k != i){ 123 | swap(nums[k++] , nums[i]); 124 | }else{ 125 | k ++; 126 | } 127 | } 128 | } 129 | }; 130 | 131 | ``` 132 | 133 | 134 | 135 | 136 | 137 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第295号问题:数据流的中位数.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 295 号问题:数据流的中位数 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 295 号问题:数据流的中位数。难度级别为 Hard,目前通过率为 33.5% 。 8 | 9 | ### 题目描述 10 | 11 | 中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。 12 | 13 | 例如, 14 | 15 | [2,3,4] 的中位数是 3 16 | 17 | [2,3] 的中位数是 (2 + 3) / 2 = 2.5 18 | 19 | 设计一个支持以下两种操作的数据结构: 20 | 21 | - void addNum(int num) - 从数据流中添加一个整数到数据结构中。 22 | - double findMedian() - 返回目前所有元素的中位数。 23 | 24 | **示例:** 25 | 26 | ```java 27 | addNum(1) 28 | addNum(2) 29 | findMedian() -> 1.5 30 | addNum(3) 31 | findMedian() -> 2 32 | ``` 33 | 34 | 35 | 36 | ### 题目解析 37 | 38 | 这道题给我们一个数据流,让我们找出中位数。对于数据流这种动态(流动)的数据,如果使用数组存储,那么每次新进来一个数据都进行排序的话,效率很低。 39 | 40 | 处理动态数据来说一般使用的数据结构是栈、队列、二叉树、堆。 41 | 42 | 本题中,我们使用 **堆** 这种数据结构。 43 | 44 | 首先将数据分为两部分,位于 **上边最大堆** 的数据要比 **下边最小堆** 的数据都要小。 45 | 46 | 为了保证将数据平均分配到两个堆中,在动态的操作的过程中两个堆中数据的数目之差不能超过 1。 47 | 48 | 为了保证 **最大堆中的所有数据都小于最小堆中的数据**,在操作过程中,新添加进去的数据需要先和最大堆的最大值或者最小堆中的最小值进行比较。 49 | 50 | ### 动画描述 51 | 52 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502114925.gif) 53 | 54 | ### 代码实现 55 | 56 | 57 | 58 | ```java 59 | class MedianFinder { 60 | public PriorityQueue minheap, maxheap; 61 | public MedianFinder() { 62 | //维护较大的元素的最小堆 63 | maxheap = new PriorityQueue(Collections.reverseOrder()); 64 | //维护较小元素的最大堆 65 | minheap = new PriorityQueue(); 66 | } 67 | 68 | // Adds a number into the data structure. 69 | public void addNum(int num) { 70 | maxheap.add(num); 71 | minheap.add(maxheap.poll()); 72 | if (maxheap.size() < minheap.size()) { 73 | maxheap.add(minheap.poll()); 74 | } 75 | } 76 | 77 | // Returns the median of current data stream 78 | public double findMedian() { 79 | if (maxheap.size() == minheap.size()) { 80 | return (maxheap.peek() + minheap.peek()) * 0.5; 81 | } else { 82 | return maxheap.peek(); 83 | } 84 | } 85 | }; 86 | ``` 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第2号问题:两数相加.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 2 号问题:两数相加 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 2 号问题:两数相加。题目难度为 Medium,目前通过率为 33.9% 。 8 | 9 | ### 题目描述 10 | 11 | 给出两个 **非空** 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 **逆序** 的方式存储的,并且它们的每个节点只能存储 **一位** 数字。 12 | 13 | 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 14 | 15 | 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 16 | 17 | **示例:** 18 | 19 | ``` 20 | 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 21 | 输出:7 -> 0 -> 8 22 | 原因:342 + 465 = 807 23 | ``` 24 | 25 | ### 题目解析 26 | 27 | 设立一个表示进位的变量`carried`,建立一个新链表,把输入的两个链表从头往后同时处理,每两个相加,将结果加上`carried`后的值作为一个新节点到新链表后面。 28 | 29 | ### 动画描述 30 | 31 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181117122234.gif) 32 | 33 | ### 代码实现 34 | 35 | ``` 36 | /// 时间复杂度: O(n) 37 | /// 空间复杂度: O(n) 38 | /** 39 | * Definition for singly-linked list. 40 | * public class ListNode { 41 | * int val; 42 | * ListNode next; 43 | * ListNode(int x) { val = x; } 44 | * } 45 | */ 46 | class Solution { 47 | public: 48 | ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 49 | 50 | ListNode *p1 = l1, *p2 = l2; 51 | ListNode *dummyHead = new ListNode(-1); 52 | ListNode* cur = dummyHead; 53 | int carried = 0; 54 | while(p1 || p2 ){ 55 | int a = p1 ? p1->val : 0; 56 | int b = p2 ? p2->val : 0; 57 | cur->next = new ListNode((a + b + carried) % 10); 58 | carried = (a + b + carried) / 10; 59 | 60 | cur = cur->next; 61 | p1 = p1 ? p1->next : NULL; 62 | p2 = p2 ? p2->next : NULL; 63 | } 64 | 65 | cur->next = carried ? new ListNode(1) : NULL; 66 | ListNode* ret = dummyHead->next; 67 | delete dummyHead; 68 | return ret; 69 | } 70 | }; 71 | 72 | ``` 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第301号问题:删除无效的括号.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 301 号问题:删除无效的括号 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 第 301 号问题:删除无效的括号。有小伙伴后台留言说这题是中山大学计算机考研机试题。 8 | 9 | ### 题目描述 10 | 11 | 删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。 12 | 13 | **说明:** 输入可能包含了除 `(` 和 `)` 以外的字符。 14 | 15 | **示例 1:** 16 | 17 | ``` 18 | 输入: "()())()" 19 | 输出: ["()()()", "(())()"] 20 | ``` 21 | 22 | **示例 2:** 23 | 24 | ``` 25 | 输入: "(a)())()" 26 | 输出: ["(a)()()", "(a())()"] 27 | ``` 28 | 29 | **示例 3:** 30 | 31 | ``` 32 | 输入: ")(" 33 | 输出: [""] 34 | ``` 35 | 36 | 37 | 38 | ### 题目解析 39 | 40 | 所谓有效的括号,那么字符串中的左右括号数应该相同,而且每个右括号左边一定有其对应的左括号。这里很容易想到使用一个栈来模拟匹配过程,'(' 入栈,')' 出栈,若栈为空说明该串是符合题意的。 41 | 42 | 首先对括号进行删除,遍历从前往后遍历可以删除不符合的 ')' 括号,从后往前遍历可以删除不符合的'('括号,通过 BFS,不断的对队列的字符串进行 checkLeft 和 checkRight 操作,若遇到 ture,则说明当前的字符串已经是删除最少无效的括号的最优解了,接着就对队列中的其他字符串进行 check 即可。 43 | 这道题目的动画与 LeetCode 第 20 号问题--有效的括号很类似,这里就拿出来进行参考理解一下,区别点就在于多了遍历和哈希存储。 44 | 45 | ### 动画描述 46 | 47 | 待补充 48 | 49 | ### 代码实现 50 | 51 | ``` 52 | 53 | public class Solution { 54 | public List removeInvalidParentheses(String s) { 55 | queue.offer(s); 56 | vis.add(s); 57 | boolean flag=false; 58 | List ans=new ArrayList(); 59 | while (!queue.isEmpty()){ 60 | String cur=queue.poll(); 61 | if(flag){ 62 | check(cur,ans); 63 | }else{ 64 | flag=checkLeft(cur,ans)||checkRight(cur,ans); 65 | } 66 | } 67 | if(ans.size()==0){ 68 | ans.add(""); 69 | } 70 | return new ArrayList(ans); 71 | } 72 | 73 | Set vis=new HashSet(); 74 | Queue queue=new LinkedList(); 75 | 76 | public void check(String s,List ans){ //查看是否正确 77 | Stack st=new Stack(); 78 | for(char c:s.toCharArray()){ 79 | if(c=='('){ 80 | st.push(c); 81 | } 82 | if(c==')'){ 83 | if(st.isEmpty()){ 84 | return; 85 | } 86 | st.pop(); 87 | } 88 | } 89 | if(st.isEmpty()){ 90 | ans.add(s); 91 | } 92 | } 93 | 94 | public boolean checkLeft(String s,List ans){ //检查左边 95 | //delete right 96 | Stack st=new Stack(); 97 | for(int i=0;i=0;--j){ //删除不符合的')' 多种情况 103 | if(s.charAt(j)==')'){ 104 | String s1=s.substring(0,j)+s.substring(j+1); 105 | if(!vis.contains(s1)){ 106 | vis.add(s1); 107 | queue.offer(s1); 108 | } 109 | } 110 | } 111 | return false; 112 | }else{ 113 | st.pop(); 114 | } 115 | } 116 | } 117 | if(st.isEmpty()){ 118 | ans.add(s); 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | public boolean checkRight(String s,List ans){ //检查右边 125 | //delete right 126 | Stack st=new Stack(); 127 | st.clear(); 128 | for(int i=s.length()-1;i>=0;--i){ 129 | if(s.charAt(i)==')'){ 130 | st.push(s.charAt(i)); 131 | }else if(s.charAt(i)=='('){ 132 | if(st.isEmpty()){ 133 | for(int j=i;j 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 326 号问题:3 的幂。题目难度为 Easy,目前通过率为 43.5% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个整数,写一个函数来判断它是否是 3 的幂次方。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: 27 17 | 输出: true 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入: 0 24 | 输出: false 25 | ``` 26 | 27 | **进阶:** 28 | 你能不使用循环或者递归来完成本题吗? 29 | 30 | ### 题目解析 31 | 32 | 正常的思路是不停地去除以 3,看最后的迭代商是否为 1。这种思路的代码使用到了循环,逼格不够高。 33 | 34 | 这里取巧的方法 **用到了数论的知识:3 的幂次的质因子只有 3**。 35 | 36 | 题目要求输入的是 int 类型,正数范围是 0 - 231,在此范围中允许的最大的 3 的次方数为 319 = 1162261467 ,那么只要看这个数能否被 n 整除即可。 37 | 38 | ### 动画描述 39 | 40 | 待补充 41 | 42 | ### 代码实现 43 | 44 | 45 | 46 | ```java 47 | class Solution { 48 | public boolean isPowerOfThree(int n) { 49 | return n > 0 && 1162261467 % n == 0; 50 | } 51 | } 52 | ``` 53 | 54 | 55 | 56 | 57 | 58 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第328号问题:奇偶链表.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 328 号问题:奇偶链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 328 号问题:奇偶链表。题目难度为 Medium,目前通过率为 52.0% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 12 | 13 | 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 14 | 15 | **示例 1:** 16 | 17 | ``` 18 | 输入: 1->2->3->4->5->NULL 19 | 输出: 1->3->5->2->4->NULL 20 | ``` 21 | 22 | **示例 2:** 23 | 24 | ``` 25 | 输入: 2->1->3->5->6->4->7->NULL 26 | 输出: 2->3->6->7->1->5->4->NULL 27 | ``` 28 | 29 | **说明:** 30 | 31 | - 应当保持奇数节点和偶数节点的相对顺序。 32 | - 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。 33 | 34 | ### 题目解析 35 | 36 | 这道题给了我们一个链表,让我们分开奇偶节点,所有奇节点在前,偶节点在后。 37 | 38 | * 设定两个虚拟节点,`dummyHead1 `用来保存奇节点,`dummyHead2 `来保存偶节点; 39 | * 遍历整个原始链表,将奇节点放于`dummyHead1 `中,其余的放置在`dummyHead2 `中 40 | * 遍历结束后,将`dummyHead2 `插入到`dummyHead1 `后面 41 | 42 | ### 动画描述 43 | 44 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181104142817.gif) 45 | 46 | ### 代码实现 47 | 48 | ``` 49 | class Solution { 50 | public: 51 | ListNode* oddEvenList(ListNode* head) { 52 | 53 | if(head == NULL || head->next == NULL || head->next->next == NULL) 54 | return head; 55 | 56 | ListNode* dummyHead1 = new ListNode(-1); 57 | ListNode* dummyHead2 = new ListNode(-1); 58 | ListNode* p1 = dummyHead1; 59 | ListNode* p2 = dummyHead2; 60 | ListNode* p = head; 61 | for(int i = 0; p; i ++) 62 | if(i % 2 == 0){ 63 | p1->next = p; 64 | p = p->next; 65 | p1 = p1->next; 66 | p1->next = NULL; 67 | } 68 | else{ 69 | p2->next = p; 70 | p = p->next; 71 | p2 = p2->next; 72 | p2->next = NULL; 73 | } 74 | 75 | p1->next = dummyHead2->next; 76 | ListNode* ret = dummyHead1->next; 77 | 78 | delete dummyHead1; 79 | delete dummyHead2; 80 | return ret; 81 | } 82 | }; 83 | ``` 84 | 85 | 86 | 87 | 88 | 89 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第342号问题:4的幂.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 342 号问题:4 的幂 2 | 3 | 题目来源于 LeetCode 上第 342 号问题:4 的幂。题目难度为 Easy,目前通过率为 45.3% 。 4 | 5 | ### 题目描述 6 | 7 | 给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入: 16 13 | 输出: true 14 | ``` 15 | 16 | **示例 2:** 17 | 18 | ``` 19 | 输入: 5 20 | 输出: false 21 | ``` 22 | 23 | **进阶:** 24 | 你能不使用循环或者递归来完成本题吗? 25 | 26 | ### 题目解析 27 | 28 | 这道题最直接的方法就是不停的去除以 4 ,看最终结果是否为 1 ,参见代码如下: 29 | 30 | ```java 31 | class Solution { 32 | public boolean isPowerOfFour(int num) { 33 | while ( (num != 0) && (num % 4 == 0)) { 34 | num /= 4; 35 | } 36 | return num == 1; 37 | } 38 | } 39 | ``` 40 | 41 | 不过这段代码使用了 **循环** ,逼格不够高。 42 | 43 | 对于一个整数而言,如果这个数是 4 的幂次方,那它必定也是 2 的幂次方。 44 | 45 | 我们先将 2 的幂次方列出来找一下其中哪些数是 4 的幂次方。 46 | 47 | | 十进制 | 二进制 | 48 | | ------ | ------------------------------- | 49 | | 2 | 10 | 50 | | 4 | **100** (1 在第 3 位) | 51 | | 8 | 1000 | 52 | | 16 | **10000**(1 在第 5 位) | 53 | | 32 | 100000 | 54 | | 64 | **1000000**(1 在第 7 位) | 55 | | 128 | 10000000 | 56 | | 256 | **100000000**(1 在第 9 位) | 57 | | 512 | 1000000000 | 58 | | 1024 | **10000000000**(1 在第 11 位) | 59 | 60 | 找一下规律: 4 的幂次方的数的二进制表示 1 的位置都是在**奇数位**。 61 | 62 | 之前在小吴的文章中判断一个是是否是 2 的幂次方数使用的是位运算 `n & ( n - 1 )`。同样的,这里依旧可以使用位运算:将这个数与特殊的数做位运算。 63 | 64 | 这个特殊的数有如下特点: 65 | 66 | * 足够大,但不能超过 32 位,即最大为 1111111111111111111111111111111( 31 个 1) 67 | 68 | * 它的二进制表示中奇数位为 1 ,偶数位为 0 69 | 70 | 符合这两个条件的二进制数是: 71 | 72 | ```java 73 | 1010101010101010101010101010101 74 | ``` 75 | 76 | **如果用一个 4 的幂次方数和它做与运算,得到的还是 4 的幂次方数**。 77 | 78 | 将这个二进制数转换成 16 进制表示:0x55555555 。有没有感觉逼格更高点。。。 79 | 80 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190509194742.png) 81 | 82 | 83 | 84 | ### 图片描述 85 | 86 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190510090919.jpeg) 87 | 88 | 89 | 90 | ### 代码实现 91 | 92 | ```java 93 | class Solution { 94 | public boolean isPowerOfFour(int num) { 95 | if (num <= 0) 96 | return false; 97 | //先判断是否是 2 的幂 98 | if ((num & num - 1) != 0) 99 | return false; 100 | //如果与运算之后是本身则是 4 的幂 101 | if ((num & 0x55555555) == num) 102 | return true; 103 | return false; 104 | } 105 | } 106 | ``` 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /notes/LeetCode第344号问题:反转字符串.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 344 号问题:反转字符串 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 第 344 号问题:反转字符串。面试官最喜欢让你手写的一道算法题! 8 | 9 | ### 题目描述 10 | 11 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。 12 | 13 | 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 14 | 15 | 你可以假设数组中的所有字符都是 [ASCII](https://baike.baidu.com/item/ASCII) 码表中的可打印字符。 16 | 17 | **示例 1:** 18 | 19 | ``` 20 | 输入:["h","e","l","l","o"] 21 | 输出:["o","l","l","e","h"] 22 | ``` 23 | 24 | **示例 2:** 25 | 26 | ``` 27 | 输入:["H","a","n","n","a","h"] 28 | 输出:["h","a","n","n","a","H"] 29 | ``` 30 | 31 | ### 32 | 33 | ### 题目解析 34 | 35 | 这道题没什么难度,直接从两头往中间走,同时交换两边的字符。注意需要白板编程写出来即可,也注意千万别回答一句使用 reverse() 这种高级函数来解决。。。 36 | 37 | ### 动画描述 38 | 39 | ![]() 40 | 41 | ### 代码实现 42 | 43 | ``` 44 | class Solution { 45 | public: 46 | string reverseString(string s) { 47 | int i = 0, j = s.size() - 1; 48 | while (i < j){ 49 | swap(s[i],s[j]); 50 | i++; 51 | j--; 52 | } 53 | return s; 54 | } 55 | }; 56 | ``` 57 | 58 | 59 | 60 | 61 | 62 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第349号问题:两个数组的交集.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 349 号问题:两个数组的交集 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 349 号问题:两个数组的交集。题目难度为 Easy,目前通过率为 62.3% 。 8 | 9 | ### 题目描述 10 | 11 | 给定两个数组,编写一个函数来计算它们的交集。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 17 | 输出: [2] 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 24 | 输出: [9,4] 25 | ``` 26 | 27 | **说明:** 28 | 29 | - 输出结果中的每个元素一定是唯一的。 30 | - 我们可以不考虑输出结果的顺序。 31 | 32 | ### 题目解析 33 | 34 | 容器类 [set](https://zh.cppreference.com/w/cpp/container/set) 的使用。 35 | 36 | - 遍历 num1,通过 set 容器 record 存储 num1 的元素 37 | - 遍历 num2 ,在 record 中查找是否有相同的元素,如果有,用 set 容器 resultSet 进行存储 38 | - 将 resultSet 转换为 vector 类型 39 | 40 | ### 动画描述 41 | 42 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502123122.gif) 43 | 44 | ### 代码实现 45 | 46 | ``` 47 | class Solution { 48 | public: 49 | vector intersection(vector& nums1, vector& nums2) { 50 | set record; 51 | for(int i = 0; i < nums1.size(); i ++){ 52 | record.insert(nums1[i]); 53 | } 54 | 55 | set resultSet; 56 | for(int i = 0; i < nums2.size();i++){ 57 | if(record.find(nums2[i]) != record.end()){ 58 | resultSet.insert(nums2[i]); 59 | } 60 | } 61 | 62 | vector resultVector; 63 | for(set::iterator iter=resultSet.begin(); iter != resultSet.end();iter++){ 64 | resultVector.push_back(*iter); 65 | } 66 | return resultVector; 67 | } 68 | }; 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第350号问题:两个数组的交集II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 350 号问题:两个数组的交集 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 350 号问题:两个数组的交集 II。题目难度为 Easy,目前通过率为 41.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定两个数组,编写一个函数来计算它们的交集。 12 | 13 | **示例 1:** 14 | 15 | ``` 16 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 17 | 输出: [2,2] 18 | ``` 19 | 20 | **示例 2:** 21 | 22 | ``` 23 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 24 | 输出: [4,9] 25 | ``` 26 | 27 | **说明:** 28 | 29 | - 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。 30 | - 我们可以不考虑输出结果的顺序。 31 | 32 | **进阶:** 33 | 34 | - 如果给定的数组已经排好序呢?你将如何优化你的算法? 35 | - 如果 *nums1* 的大小比 *nums2* 小很多,哪种方法更优? 36 | - 如果 *nums2* 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办? 37 | 38 | ### 题目解析 39 | 40 | 容器类 [map](https://zh.cppreference.com/w/cpp/container/map) 的使用。 41 | 42 | - 遍历 num1,通过map容器 record 存储 num1 的元素与频率 43 | - 遍历 num2 ,在 record 中查找是否有相同的元素(该元素的存储频率大于0),如果有,用map容器resultVector 进行存储,同时该元素的频率减一 44 | 45 | ### 动画描述 46 | 47 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181027160512.gif) 48 | 49 | ### 代码实现 50 | 51 | ``` 52 | // 350. Intersection of Two Arrays II 53 | // https://leetcode.com/problems/intersection-of-two-arrays-ii/description/ 54 | // 时间复杂度: O(nlogn) 55 | // 空间复杂度: O(n) 56 | class Solution { 57 | public: 58 | vector intersect(vector& nums1, vector& nums2) { 59 | 60 | map record; 61 | for(int i = 0 ; i < nums1.size() ; i ++){ 62 | record[nums1[i]] += 1; 63 | } 64 | 65 | vector resultVector; 66 | for(int i = 0 ; i < nums2.size() ; i ++){ 67 | if(record[nums2[i]] > 0){ 68 | resultVector.push_back(nums2[i]); 69 | record[nums2[i]] --; 70 | } 71 | } 72 | 73 | return resultVector; 74 | } 75 | }; 76 | ``` 77 | 78 | #### 执行结果 79 | 80 | ![img](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181029083150.png) 81 | 82 | 83 | 84 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第3号问题:无重复字符的最长子串.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 3 号问题:无重复字符的最长子串 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 3 号问题:无重复字符的最长子串。题目难度为 Medium,目前通过率为 29.0% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个字符串,请你找出其中不含有重复字符的 **最长子串** 的长度。 12 | 13 | **示例 1:** 14 | 15 | ```java 16 | 输入: "abcabcbb" 17 | 输出: 3 18 | 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 19 | ``` 20 | 21 | ### 题目解析 22 | 23 | 建立一个256位大小的整型数组 freg ,用来建立字符和其出现位置之间的映射。 24 | 25 | 维护一个滑动窗口,窗口内的都是没有重复的字符,去尽可能的扩大窗口的大小,窗口不停的向右滑动。 26 | 27 | - (1)如果当前遍历到的字符从未出现过,那么直接扩大右边界; 28 | - (2)如果当前遍历到的字符出现过,则缩小窗口(左边索引向右移动),然后继续观察当前遍历到的字符; 29 | - (3)重复(1)(2),直到左边索引无法再移动; 30 | - (4)维护一个结果res,每次用出现过的窗口大小来更新结果 res,最后返回 res 获取结果。 31 | 32 | ### 动画描述 33 | 34 | ![动画描述](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/o2acw.gif) 35 | 36 | ### 代码实现 37 | 38 | ```c++ 39 | // 滑动窗口 40 | // 时间复杂度: O(len(s)) 41 | // 空间复杂度: O(len(charset)) 42 | class Solution { 43 | public: 44 | int lengthOfLongestSubstring(string s) { 45 | int freq[256] = {0}; 46 | int l = 0, r = -1; //滑动窗口为s[l...r] 47 | int res = 0; 48 | // 整个循环从 l == 0; r == -1 这个空窗口开始 49 | // 到l == s.size(); r == s.size()-1 这个空窗口截止 50 | // 在每次循环里逐渐改变窗口, 维护freq, 并记录当前窗口中是否找到了一个新的最优值 51 | while(l < s.size()){ 52 | if(r + 1 < s.size() && freq[s[r+1]] == 0){ 53 | r++; 54 | freq[s[r]]++; 55 | }else { //r已经到头 || freq[s[r+1]] == 1 56 | freq[s[l]]--; 57 | l++; 58 | } 59 | res = max(res, r-l+1); 60 | } 61 | return res; 62 | } 63 | }; 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /notes/LeetCode第445号问题:两数相加II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 445 号问题:两数相加 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 445 号问题:两数相加 II。题目难度为 Medium,目前通过率为 48.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定两个**非空**链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。 12 | 13 | 14 | 15 | 你可以假设除了数字 0 之外,这两个数字都不会以零开头。 16 | 17 | **进阶:** 18 | 19 | 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。 20 | 21 | **示例:** 22 | 23 | ``` 24 | 输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 25 | 输出: 7 -> 8 -> 0 -> 7 26 | ``` 27 | 28 | ### 题目解析 29 | 30 | 由于计算时要保证最右边的数对齐,那么很自然的想到先用**栈**存放链表中的每个值,然后依次计算。由于相加时可能产生进位,所以使用一个flag表示是否有进位。 31 | 32 | 提示:若栈中元素相加结束之后仍有进位,则需要新加入一个头结点。 33 | 34 | ### 动画描述 35 | 36 | ![](https://diycode.b0.upaiyun.com/photo/2019/3b0e95a2e5c00ab1071a7232ca329e62.gif) 37 | 38 | ### 代码实现 39 | 40 | ```python 41 | class Solution: 42 | def addTwoNumbers(self, l1, l2): 43 | # 分别入栈 44 | stack1 = [] 45 | stack2 = [] 46 | while l1: 47 | stack1.append(l1.val) 48 | l1 = l1.next 49 | while l2: 50 | stack2.append(l2.val) 51 | l2 = l2.next 52 | 53 | flag = 0 54 | head = None 55 | while stack1 or stack2 or flag != 0: 56 | if stack1: 57 | flag += stack1.pop() 58 | if stack2: 59 | flag += stack2.pop() 60 | node = ListNode(flag % 10) 61 | node.next = head 62 | head = node 63 | flag = flag // 10 64 | return head 65 | ``` 66 | 67 | 68 | 69 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第447号问题:回旋镖的数量.md: -------------------------------------------------------------------------------- 1 | # LeetCode第447号问题:回旋镖的数量 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 447 号问题:回旋镖的数量。题目难度为 Easy,目前通过率为 45.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定平面上 *n* 对不同的点,“回旋镖” 是由点表示的元组 `(i, j, k)` ,其中 `i` 和 `j` 之间的距离和 `i` 和 `k` 之间的距离相等(**需要考虑元组的顺序**)。 12 | 13 | 找到所有回旋镖的数量。你可以假设 *n* 最大为 **500**,所有点的坐标在闭区间 **[-10000, 10000]** 中。 14 | 15 | **示例:** 16 | 17 | ``` 18 | 输入: 19 | [[0,0],[1,0],[2,0]] 20 | 21 | 输出: 22 | 2 23 | 24 | 解释: 25 | 两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]] 26 | ``` 27 | 28 | ### 题目解析 29 | 30 | n 最大为 500,可以使用时间复杂度为 O(n^2)的算法。 31 | 32 | - 遍历所有的点,让每个点作为一个锚点 33 | - 然后再遍历其他的点,统计和锚点距离相等的点有多少个 34 | - 然后分别带入 n(n-1) 计算结果并累加到res中 35 | 36 | ##### Tips: 37 | 38 | ###### Tip1 39 | 40 | - 如果有一个点 a,还有两个点 b 和 c ,如果 ab 和 ac 之间的距离相等,那么就有两种排列方法 abc 和 acb; 41 | - 如果有三个点 b,c,d都分别和a之间的距离相等,那么有六种排列方法,abc, acb, acd, adc, abd, adb; 42 | - 如果有 n 个点和点 a 距离相等,那么排列方式为 n(n-1)。 43 | 44 | ###### Tip2 45 | 46 | - 计算距离时不进行开根运算, 以保证精度; 47 | - 只有当n大于等于2时,res值才会真正增加,因为当n=1时,增加量为`1*(1-1)=0`。 48 | 49 | 50 | 51 | ### 动画描述 52 | 53 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181030112917.gif) 54 | 55 | ### 代码实现 56 | 57 | ``` 58 | // 447. Number of Boomerangs 59 | // https://leetcode.com/problems/number-of-boomerangs/description/ 60 | // 时间复杂度: O(n^2) 61 | // 空间复杂度: O(n) 62 | class Solution { 63 | public: 64 | int numberOfBoomerangs(vector>& points) { 65 | 66 | int res = 0; 67 | for( int i = 0 ; i < points.size() ; i ++ ){ 68 | 69 | // record中存储 点i 到所有其他点的距离出现的频次 70 | unordered_map record; 71 | for(int j = 0 ; j < points.size() ; j ++){ 72 | if(j != i){ 73 | // 计算距离时不进行开根运算, 以保证精度 74 | record[dis(points[i], points[j])] += 1; 75 | } 76 | } 77 | 78 | for(unordered_map::iterator iter = record.begin() ; iter != record.end() ; iter ++){ 79 | res += (iter->second) * (iter->second - 1); 80 | } 81 | } 82 | return res; 83 | } 84 | 85 | private: 86 | int dis(const pair &pa, const pair &pb){ 87 | return (pa.first - pb.first) * (pa.first - pb.first) + 88 | (pa.second - pb.second) * (pa.second - pb.second); 89 | } 90 | }; 91 | 92 | 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第454号问题:四数相加II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 454 号问题:四数相加 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 454 号问题:四数相加 II。题目难度为 Medium,目前通过率为 50.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 `(i, j, k, l)` ,使得 `A[i] + B[j] + C[k] + D[l] = 0`。 12 | 13 | 为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。 14 | 15 | **例如:** 16 | 17 | ``` 18 | 输入: 19 | A = [ 1, 2] 20 | B = [-2,-1] 21 | C = [-1, 2] 22 | D = [ 0, 2] 23 | 24 | 输出: 25 | 2 26 | 27 | 解释: 28 | 两个元组如下: 29 | 1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0 30 | 2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 31 | ``` 32 | 33 | ### 题目解析 34 | 35 | 与[Two Sum](https://xiaozhuanlan.com/topic/7923618450)类似,需要用哈希表来解决问题。 36 | 37 | - 把 A 和 B 的两两之和都求出来,在哈希表中建立两数之和与其出现次数之间的映射 38 | - 遍历 C 和 D 中任意两个数之和,只要看哈希表存不存在这两数之和的相反数就行了 39 | 40 | 41 | 42 | ### 动画描述 43 | 44 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181029154232.gif) 45 | 46 | ### 代码实现 47 | 48 | ``` 49 | // 454. 4Sum II 50 | // https://leetcode.com/problems/4sum-ii/description/ 51 | // 时间复杂度: O(n^2) 52 | // 空间复杂度: O(n^2) 53 | class Solution { 54 | public: 55 | int fourSumCount(vector& A, vector& B, vector& C, vector& D) { 56 | 57 | unordered_map hashtable; 58 | for(int i = 0 ; i < A.size() ; i ++){ 59 | for(int j = 0 ; j < B.size() ; j ++){ 60 | hashtable[A[i]+B[j]] += 1; 61 | } 62 | } 63 | 64 | int res = 0; 65 | for(int i = 0 ; i < C.size() ; i ++){ 66 | for(int j = 0 ; j < D.size() ; j ++){ 67 | if(hashtable.find(-C[i]-D[j]) != hashtable.end()){ 68 | res += hashtable[-C[i]-D[j]]; 69 | } 70 | } 71 | } 72 | 73 | return res; 74 | } 75 | }; 76 | 77 | ``` 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第642号问题:设计一个搜索自动完成系统.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 642 号问题:设计一个搜索自动完成系统 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 642 号问题:设计一个搜索自动完成系统。题目难度为 Hard,目前通过率为 37.8% 。 8 | 9 | ### 题目描述 10 | 11 | 为搜索引擎设计一个搜索自动完成系统。用户可以输入一个句子(至少一个单词,并以一个特殊的字符'#'结尾)。对于除'#'之外的每个字符,您需要返回与已输入的句子部分前缀相同的前3个历史热门句子。具体规则如下: 12 | 13 | 一个句子的热度定义为用户输入完全相同句子的次数。 14 | 返回的前3个热门句子应该按照热门程度排序(第一个是最热的)。如果几个句子的热度相同,则需要使用ascii代码顺序(先显示较小的一个)。 15 | 如果少于3个热门句子,那么就尽可能多地返回。 16 | 当输入是一个特殊字符时,它意味着句子结束,在这种情况下,您需要返回一个空列表。 17 | 您的工作是实现以下功能: 18 | 19 | 构造函数: 20 | 21 | AutocompleteSystem(String[] sentence, int[] times):这是构造函数。输入是历史数据。句子是由之前输入的句子组成的字符串数组。Times是输入一个句子的相应次数。您的系统应该记录这些历史数据。 22 | 23 | 现在,用户想要输入一个新句子。下面的函数将提供用户类型的下一个字符: 24 | 25 | List input(char c):输入c是用户输入的下一个字符。字符只能是小写字母(“a”到“z”)、空格(“”)或特殊字符(“#”)。另外,前面输入的句子应该记录在系统中。输出将是前3个历史热门句子,它们的前缀与已经输入的句子部分相同。 26 | 27 | 例子: 28 | 操作:AutocompleteSystem(["i love you", "island","ironman", "i love leetcode"], [5,3,2,2]) 29 | 系统已经追踪到以下句子及其对应的时间: 30 | 31 | "i love you" : 5 times 32 | "island" : 3 times 33 | "ironman" : 2 times 34 | "i love leetcode" : 2 times 35 | 36 | 现在,用户开始另一个搜索: 37 | 38 | 操作:输入(“i”) 39 | 输出:["i love you", "island","i love leetcode"] 40 | 解释: 41 | 有四个句子有前缀“i”。其中,《ironman》和《i love leetcode》有着相同的热度。既然“ ” ASCII码为32,“r”ASCII码为114,那么“i love leetcode”应该在“ironman”前面。此外,我们只需要输出前3个热门句子,所以“ironman”将被忽略。 42 | 43 | 操作:输入(' ') 44 | 输出:[“i love you”,“i love leetcode”] 45 | 解释: 46 | 只有两个句子有前缀“i”。 47 | 48 | 操作:输入(' a ') 49 | 输出:[] 50 | 解释: 51 | 没有以“i a”为前缀的句子。 52 | 53 | 操作:输入(“#”) 54 | 输出:[] 55 | 解释: 56 | 用户完成输入后,在系统中将句子“i a”保存为历史句。下面的输入将被计算为新的搜索。 57 | 58 | 注意: 59 | 60 | 输入的句子总是以字母开头,以“#”结尾,两个单词之间只有一个空格。 61 | 要搜索的完整句子不会超过100个。包括历史数据在内的每句话的长度不会超过100句。 62 | 在编写测试用例时,即使是字符输入,也请使用双引号而不是单引号。 63 | 请记住重置在AutocompleteSystem类中声明的类变量,因为静态/类变量是跨多个测试用例持久化的。详情请点击这里。 64 | 65 | ### 题目解析 66 | 67 | 设计一个搜索自动补全系统,它需要包含如下两个方法: 68 | 69 | #### 构造方法: 70 | 71 | AutocompleteSystem(String[] sentences, int[] times): 输入句子sentences,及其出现次数times 72 | 73 | #### 输入方法: 74 | 75 | List input(char c): 输入字符c可以是26个小写英文字母,也可以是空格,以'#'结尾。返回输入字符前缀对应频率最高的至多3个句子,频率相等时按字典序排列。 76 | 77 | ### 思路解析: 78 | 79 | 核心点:Trie(字典树) 80 | 81 | 利用字典树记录所有出现过的句子集合,利用字典保存每个句子出现的次数。 82 | 83 | #### 解题思路 84 | 85 | 题目的要求是补全的句子是按之前出现的频率排列的,高频率的出现在最上面,如果频率相同,就按字母顺序来显示。 86 | 87 | 频率 这种要求很容易想到 堆、优先队列、树、Map等知识点,这里涉及到 字典 与 树,那肯定使用 字典树 能解决。 88 | 89 | 所以首先构造 Trie 的 trieNode 结构以及 insert 方法,构造完 trieNode 类后,再构造一个树的根节点。 90 | 91 | 由于每次都要输入一个字符,我们可以用一个私有的 Node:curNode 来追踪当前的节点。 92 | 93 | curNode 初始化为 root ,在每次输入完一个句子时,即输入的字符为‘#’时,我们需要将其置为root。 94 | 95 | 同时还需要一个 string 类型 stn 来表示当前的搜索的句子。 96 | 97 | 每输入一个字符,首先检查是不是结尾标识“#”,如果是的话,将当前句子加入trie树,重置相关变量,返回空数组。 98 | 99 | * 如不是,检查当前 TrieNode 对应的 child 是否含有 c 的对应节点。如果没有,将 curNode 置为 NULL 并且返回空数组。 100 | 101 | * 若存在,将curNode 更新为c对应的节点,并且对curNode进行dfs。 102 | 103 | dfs 时,我们首先检查当前是不是一个完整的句子,如果是,将句子与其次数同时加入 priority_queue 中,然后对其 child 中可能存在的子节点进行 dfs 。 104 | 105 | 进行完 dfs 后,只需要取出前三个,需要注意的是,可能可选择的结果不满3个,所以要在 while 中多加入检测 q 为空的条件语句。 106 | 107 | 最后要将 q 中的所有元素都弹出。 108 | 109 | ### 动画描述 110 | 111 | 动画是使用 AE 制作,体积比较大,有 32 M,无法使用GIF播放,因此采取视频播放形式,手机党慎点:) 112 | 113 | 感谢 **Jun Chen** 大佬提供动画技术支持,笔芯。 114 | 115 | https://v.qq.com/x/page/m08267nr4fv.html 116 | 117 | ### 代码实现 118 | 119 | #### C++ 120 | ``` 121 | class TrieNode{ 122 | public: 123 | string str; 124 | int cnt; 125 | unordered_map child; 126 | TrieNode(): str(""), cnt(0){}; 127 | }; 128 | 129 | struct cmp{ 130 | bool operator() (const pair &p1, const pair &p2){ 131 | return p1.second < p2.second || (p1.second == p2.second && p1.first > p2.first); 132 | } 133 | }; 134 | 135 | class AutocompleteSystem { 136 | public: 137 | AutocompleteSystem(vector sentences, vector times) { 138 | root = new TrieNode(); 139 | for(int i = 0; i < sentences.size(); i++){ 140 | insert(sentences[i], times[i]); 141 | } 142 | curNode = root; 143 | stn = ""; 144 | } 145 | 146 | vector input(char c) { 147 | if(c == '#'){ 148 | insert(stn, 1); 149 | stn.clear(); 150 | curNode = root; 151 | return {}; 152 | } 153 | stn.push_back(c); 154 | if(curNode && curNode->child.count(c)){ 155 | curNode = curNode->child[c]; 156 | }else{ 157 | curNode = NULL; 158 | return {}; 159 | } 160 | 161 | dfs(curNode); 162 | 163 | vector ret; 164 | int n = 3; 165 | while(n > 0 && !q.empty()){ 166 | ret.push_back(q.top().first); 167 | q.pop(); 168 | n--; 169 | } 170 | while(!q.empty()) q.pop(); 171 | 172 | return ret; 173 | } 174 | 175 | void dfs(TrieNode* n){ 176 | if(n->str != ""){ 177 | q.push({n->str, n->cnt}); 178 | } 179 | for(auto p : n->child){ 180 | dfs(p.second); 181 | } 182 | } 183 | 184 | void insert(string s, int cnt){ 185 | TrieNode* cur = root; 186 | for(auto c : s){ 187 | if(cur->child.count(c) == 0){ 188 | cur->child[c] = new TrieNode(); 189 | } 190 | cur = cur->child[c]; 191 | } 192 | cur->str = s; 193 | cur->cnt += cnt; 194 | } 195 | 196 | private: 197 | TrieNode *root, *curNode; 198 | string stn; 199 | priority_queue, vector>, cmp > q; 200 | 201 | }; 202 | 203 | ``` 204 | 205 | 206 | 207 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第690号问题:员工的重要性.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 690 号问题:员工的重要性 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 第 690 号问题:员工的重要性。 8 | 9 | ### 题目描述 10 | 11 | 给定一个保存员工信息的数据结构,它包含了员工**唯一的id**,**重要度** 和 **直系下属的id**。 12 | 13 | 比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15, 10, 5 。那么员工 1 的数据结构是[1, 15, [2]],员工 2 的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。注意虽然员工 3 也是员工 1 的一个下属,但是由于**并不是直系**下属,因此没有体现在员工1的数据结构中。 14 | 15 | 现在输入一个公司的所有员工信息,以及单个员工 id,返回这个员工和他所有下属的重要度之和。 16 | 17 | **示例 1:** 18 | 19 | ``` 20 | 输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1 21 | 输出: 11 22 | 解释: 23 | 员工 1 自身的重要度是 5,他有两个直系下属 2 和 3 ,而且 2 和 3 的重要度均为 3 。因此员工 1 的总重要度是 5 + 3 + 3 = 11。 24 | ``` 25 | 26 | **注意:** 27 | 28 | 1. 一个员工最多有一个**直系**领导,但是可以有多个**直系**下属 29 | 2. 员工数量不超过 2000。 30 | 31 | ### 32 | 33 | ### 题目解析 34 | 35 | 利用哈希表来存储员工的信息,找到指定 id 的员工后,采用广度优先遍历算法来遍历编号为 id 的员工及其下属员工。 36 | 37 | ### 动画描述 38 | 39 | 待补充 40 | 41 | ### 代码实现 42 | 43 | ``` 44 | public int getImportance(List employees, int id) { 45 | Employee emp = null; 46 | //重要度 47 | int sum = 0; 48 | //存储员工信息 49 | HashMap map=new HashMap(); / 50 | for(Employee e:employees) { 51 | map.put(e.id, e); 52 | } 53 | //使用广度优先遍历员工 54 | ArrayDeque queue=new ArrayDeque(); 55 | queue.addLast(map.get(id)); 56 | while(!queue.isEmpty()) { 57 | emp=queue.removeFirst(); 58 | sum+=emp.importance; 59 | for(int i:emp.subordinates) { 60 | queue.addLast(map.get(i)); 61 | } 62 | } 63 | return sum; 64 | 65 | } 66 | ``` 67 | 68 | 69 | 70 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第75号问题:颜色分类.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 75 号问题:颜色分类 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 75 号问题:颜色分类。题目难度为 Medium,目前通过率为 51.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个包含红色、白色和蓝色,一共 *n* 个元素的数组,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 12 | 13 | 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 14 | 15 | **注意:** 16 | 不能使用代码库中的排序函数来解决这道题。 17 | 18 | **示例:** 19 | 20 | ``` 21 | 输入: [2,0,2,1,1,0] 22 | 输出: [0,0,1,1,2,2] 23 | ``` 24 | 25 | **进阶:** 26 | 27 | - 一个直观的解决方案是使用计数排序的两趟扫描算法。 28 | 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 29 | - 你能想出一个仅使用常数空间的一趟扫描算法吗? 30 | 31 | ### 题目解析 32 | 33 | 结合三路快排 partition 思路的应用。 34 | 35 | 设定两个索引,一个从左往右滑动`zero`,一个从右往左滑动`two`。 36 | 37 | * 遍历`nums`,当`nums[i]`的值为1时,`i++`; 38 | * 当`nums[i]`的值为2时,`two`的值先减1,而后交换`nums[i]`与`nums[two]`,此时在观察`nums[i]`的值; 39 | * 当`nums[i]`的值为0时,`zero++`,而后交换`nums[i]`与`nums[zero]`,`i++`;当 `i = two`时,结束循环。 40 | 41 | ### 动画描述 42 | 43 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502094455.gif) 44 | 45 | ### 代码实现 46 | 47 | ``` 48 | // 三路快速排序的思想 49 | // 对整个数组只遍历了一遍 50 | // 时间复杂度: O(n) 51 | // 空间复杂度: O(1) 52 | class Solution { 53 | public: 54 | void sortColors(vector &nums) { 55 | 56 | int zero = -1; // [0...zero] == 0 57 | int two = nums.size(); // [two...n-1] == 2 58 | for(int i = 0 ; i < two ; ){ 59 | if(nums[i] == 1){ 60 | i ++; 61 | }else if (nums[i] == 2){ 62 | two--; 63 | swap( nums[i] , nums[two]); 64 | }else{ // nums[i] == 0 65 | zero++; 66 | swap(nums[zero] , nums[i]); 67 | i++; 68 | } 69 | } 70 | } 71 | }; 72 | 73 | ``` 74 | 75 | 76 | 77 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第86号问题:分割链表.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 86 号问题:分割链表 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 86 号问题:分割链表。题目难度为 Easy,目前通过率为 47.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个链表和一个特定值 *x*,对链表进行分隔,使得所有小于 *x* 的节点都在大于或等于 *x* 的节点之前。 12 | 13 | 你应当保留两个分区中每个节点的初始相对位置。 14 | 15 | **示例:** 16 | 17 | ``` 18 | 输入: head = 1->4->3->2->5->2, x = 3 19 | 输出: 1->2->2->4->3->5 20 | ``` 21 | 22 | ### 题目解析 23 | 24 | 这道题要求我们划分链表,把所有小于给定值的节点都移到前面,大于该值的节点顺序不变,相当于一个局部排序的问题。 25 | 26 | - 设定两个虚拟节点,`dummyHead1 `用来保存小于于该值的链表,`dummyHead2 `来保存大于等于该值的链表 27 | - 遍历整个原始链表,将小于该值的放于`dummyHead1 `中,其余的放置在`dummyHead2 `中 28 | - 遍历结束后,将`dummyHead2 `插入到`dummyHead1 `后面 29 | 30 | ### 动画描述 31 | 32 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181104095701.gif) 33 | 34 | ### 代码实现 35 | 36 | ``` 37 | class Solution { 38 | public: 39 | ListNode* partition(ListNode* head, int x) { 40 | 41 | ListNode* dummyHead1 = new ListNode(-1); 42 | ListNode* dummyHead2 = new ListNode(-1); 43 | ListNode* prev1 = dummyHead1; 44 | ListNode* prev2 = dummyHead2; 45 | 46 | for(ListNode* cur = head ; cur != NULL ;){ 47 | if(cur->val < x){ 48 | prev1->next = cur; 49 | cur = cur->next; 50 | prev1 = prev1->next; 51 | prev1->next = NULL; 52 | } 53 | else{ 54 | prev2->next = cur; 55 | cur = cur->next; 56 | prev2 = prev2->next; 57 | prev2->next = NULL; 58 | } 59 | } 60 | 61 | prev1->next = dummyHead2->next; 62 | ListNode* ret = dummyHead1->next; 63 | 64 | delete dummyHead1; 65 | delete dummyHead2; 66 | return ret; 67 | } 68 | }; 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第877号问题:石子游戏.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 877 号问题:石子游戏 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | ### 题目描述 8 | 9 | 喜羊羊和灰太狼用几堆石子在做游戏。偶数堆石子**排成一行**,每堆都有正整数颗石子 `piles[i]` 。 10 | 11 | 游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。 12 | 13 | 喜羊羊和灰太狼轮流进行,喜羊羊先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。 14 | 15 | 假设喜羊羊和灰太狼都发挥出最佳水平,当喜羊羊赢得比赛时返回 `true` ,当灰太狼赢得比赛时返回 `false` 。 16 | 17 | ### 题目分析 18 | 19 | 举两个例子来帮助理解题意。 20 | 21 | #### 例子一: 22 | 23 | 输入:[ 5,3,4,5 ] 24 | 25 | 输出:true 26 | 27 | **解释**: 28 | 29 | 喜羊羊先开始,只能拿前 5 颗或后 5 颗石子 。 30 | 31 | 假设他取了前 5 颗,这一行就变成了 [ 3 ,4,5 ] 。 32 | 33 | 如果灰太狼拿走前 3 颗,那么剩下的是 [ 4,5 ],喜羊羊拿走后 5 颗赢得 10 分。 34 | 35 | 如果灰太狼拿走后 5 颗,那么剩下的是 [ 3,4 ],喜羊羊拿走后 4 颗赢得 9 分。 36 | 37 | 这表明,取前 5 颗石子对喜羊羊来说是一个胜利的举动,所以我们返回 true 。 38 | 39 | 40 | 41 | 42 | 43 | #### 例子二: 44 | 45 | 输入:[ 5,10000,2,3 ] 46 | 47 | 输出:true 48 | 49 | **解释**: 50 | 51 | 喜羊羊先开始,只能拿前 5 颗或后 3 颗石子 。 52 | 53 | 假设他取了后 3 颗,这一行就变成了 [ 5,10000,2 ]。 54 | 55 | 灰太狼肯定会在剩下的这一行中取走前 5 颗,这一行就变成了 [ 10000,2 ]。 56 | 57 | 然后喜羊羊取走前 10000 颗,总共赢得 10003 分,灰太狼赢得 7 分。 58 | 59 | 这表明,取后 3 颗石子对喜羊羊来说是一个胜利的举动,所以我们返回 true 。 60 | 61 | **这个例子表明,并不是需要每次都挑选最大的那堆石头**。 62 | 63 | 64 | 65 | ### 题目回答 66 | 67 | 涉及到最优解的问题,那么肯定要去尝试一下使用 **动态规划 **来解决了。 68 | 69 | 先看一下力扣的正规题解: 70 | 71 | 让我们改变游戏规则,使得每当灰太狼得分时,都会从喜羊羊的分数中扣除。 72 | 73 | 令 `dp(i, j)` 为喜羊羊可以获得的最大分数,其中剩下的堆中的石子数是 `piles[i], piles[i+1], ..., piles[j]`。这在比分游戏中很自然:我们想知道游戏中每个位置的值。 74 | 75 | 我们可以根据 `dp(i + 1,j)` 和 `dp(i,j-1)` 来制定 `dp(i,j)` 的递归,我们可以使用动态编程以不重复这个递归中的工作。(该方法可以输出正确的答案,因为状态形成一个DAG(有向无环图)。) 76 | 77 | 当剩下的堆的石子数是 `piles[i], piles[i+1], ..., piles[j]` 时,轮到的玩家最多有 2 种行为。 78 | 79 | 可以通过比较 `j-i`和 `N modulo 2` 来找出轮到的人。 80 | 81 | 如果玩家是喜羊羊,那么它将取走 `piles[i]` 或 `piles[j]` 颗石子,增加它的分数。之后,总分为 `piles[i] + dp(i+1, j)` 或 `piles[j] + dp(i, j-1)`;我们想要其中的最大可能得分。 82 | 83 | 如果玩家是灰太狼,那么它将取走 `piles[i]` 或 `piles[j]` 颗石子,减少喜羊羊这一数量的分数。之后,总分为 `-piles[i] + dp(i+1, j)` 或 `-piles[j] + dp(i, j-1)`;我们想要其中的最小可能得分。 84 | 85 | 代码如下: 86 | 87 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502124645.jpg) 88 | 89 | 90 | 91 | 上面的代码并不算复杂,当然,如果你看不懂也没关系,不影响解决问题,请看下面的数学分析。 92 | 93 | 94 | 95 | ### 数学分析 96 | 97 | 因为石头的数量是奇数,因此只有两种结果,输或者赢。 98 | 99 | 喜羊羊先开始拿石头,随便拿!然后比较石头数量: 100 | 101 | 1. 如果石头数量多于对手,赢了; 102 | 2. 如果石头数量少于对手,自己拿石头的顺序和对手拿石头的顺序对调(**因为是偶数堆石头,所以可以全部对调**),还是赢。 103 | 104 | 所以代码如下: 105 | 106 | ```java 107 | class Solution { 108 | public boolean stoneGame(int[] piles) { 109 | return true; 110 | } 111 | } 112 | ``` 113 | 114 | 看完之后,你的心情是怎么样的? 115 | 116 | 此题的LeetCode 的评论区里一片吐槽:**这是什么沙雕题目!** 117 | 118 | 可能搞过 ACM 等竞赛的人都会微微一笑:不会几万个套路怎么好意思说自己是 acmer 。我们这些普通人为之惊奇的题目,到他们这里就是彻底被玩坏了,各种稀奇古怪的秒解。 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第92号问题:反转链表II.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 92 号问题:反转链表 II 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 92 号问题:反转链表 II。题目难度为 Medium,目前通过率为 43.8% 。 8 | 9 | ### 题目描述 10 | 11 | 反转从位置 *m* 到 *n* 的链表。请使用一趟扫描完成反转。 12 | 13 | **说明:** 14 | 1 ≤ *m* ≤ *n* ≤ 链表长度。 15 | 16 | **示例:** 17 | 18 | ``` 19 | 输入: 1->2->3->4->5->NULL, m = 2, n = 4 20 | 输出: 1->4->3->2->5->NULL 21 | ``` 22 | 23 | ### 题目解析 24 | 25 | **[Reverse Linked List](https://xiaozhuanlan.com/topic/7513064892)**的延伸题。 26 | 27 | 可以考虑取出需要反转的这一小段链表,反转完后再插入到原先的链表中。 28 | 29 | **以本题为例:** 30 | 31 | 变换的是 2,3,4这三个点,那么我们可以先取出 2 ,用 front 指针指向 2 ,然后当取出 3 的时候,我们把 3 加到 2 的前面,把 front 指针前移到 3 ,依次类推,到 4 后停止,这样我们得到一个新链表 4 -> 3 -> 2 , front 指针指向4。 32 | 33 | 对于原链表来说,**有两个点的位置很重要**,需要用指针记录下来,分别是 1 和 5 ,把新链表插入的时候需要这两个点的位置。 34 | 35 | - 用 pre 指针记录 1 的位置 36 | - 当 4 结点被取走后,5 的位置需要记下来 37 | - 这样我们就可以把倒置后的那一小段链表加入到原链表中 38 | 39 | 40 | 41 | ### 动画描述 42 | 43 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20181103160226.gif) 44 | 45 | ### 代码实现 46 | 47 | ``` 48 | class Solution { 49 | public: 50 | ListNode *reverseBetween(ListNode *head, int m, int n) { 51 | ListNode *dummy = new ListNode(-1); 52 | dummy->next = head; 53 | ListNode *cur = dummy; 54 | ListNode *pre, *front, *last; 55 | for (int i = 1; i <= m - 1; ++i) cur = cur->next; 56 | pre = cur; 57 | last = cur->next; 58 | for (int i = m; i <= n; ++i) { 59 | cur = pre->next; 60 | pre->next = cur->next; 61 | cur->next = front; 62 | front = cur; 63 | } 64 | cur = pre->next; 65 | pre->next = front; 66 | last->next = cur; 67 | return dummy->next; 68 | } 69 | }; 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第94号问题:二叉树的中序遍历.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 94 号问题:二叉树的中序遍历 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 题目来源于 LeetCode 上第 94 号问题:二叉树的中序遍历。题目难度为 Medium,目前通过率为 35.8% 。 8 | 9 | ### 题目描述 10 | 11 | 给定一个二叉树,返回它的*中序* 遍历。 12 | 13 | **示例:** 14 | 15 | ``` 16 | 输入: [1,null,2,3] 17 | 1 18 | \ 19 | 2 20 | / 21 | 3 22 | 23 | 输出: [1,3,2] 24 | ``` 25 | 26 | **进阶:** 递归算法很简单,你可以通过迭代算法完成吗? 27 | 28 | ### 题目解析 29 | 30 | 用**栈(Stack)**的思路来处理问题。 31 | 32 | 中序遍历的顺序为**左-根-右**,具体算法为: 33 | 34 | - 从根节点开始,先将根节点压入栈 35 | - 然后再将其所有左子结点压入栈,取出栈顶节点,保存节点值 36 | - 再将当前指针移到其右子节点上,若存在右子节点,则在下次循环时又可将其所有左子结点压入栈中 37 | 38 | 39 | 40 | ### 动画描述 41 | 42 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/20190502102629.gif) 43 | 44 | ### 代码实现 45 | 46 | ``` 47 | class Solution { 48 | public List inorderTraversal(TreeNode root) { 49 | List list = new ArrayList<>(); 50 | Stack stack = new Stack<>(); 51 | TreeNode cur = root; 52 | while (cur != null || !stack.isEmpty()) { 53 | if (cur != null) { 54 | stack.push(cur); 55 | cur = cur.left; 56 | } else { 57 | cur = stack.pop(); 58 | list.add(cur.val); 59 | cur = cur.right; 60 | } 61 | } 62 | return list; 63 | } 64 | } 65 | ``` 66 | 67 | 68 | 69 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) -------------------------------------------------------------------------------- /notes/LeetCode第9号问题:回文数.md: -------------------------------------------------------------------------------- 1 | # LeetCode 第 9 号问题:回文数 2 | 3 | > 本文首发于公众号「五分钟学算法」,是[图解 LeetCode ]()系列文章之一。 4 | > 5 | > 个人网站:[https://www.cxyxiaowu.com](https://www.cxyxiaowu.com) 6 | 7 | 8 | 9 | 题目来源于 LeetCode 第 9 号问题:回文数。题目难度为 Easy,目前通过率为 56.0%。 10 | 11 | ## 题目描述 12 | 13 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 14 | 15 | **示例 1:** 16 | 17 | ``` 18 | 输入: 121 19 | 输出: true 20 | ``` 21 | 22 | **示例 2:** 23 | 24 | ``` 25 | 输入: -121 26 | 输出: false 27 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 28 | ``` 29 | 30 | **示例 3:** 31 | 32 | ``` 33 | 输入: 10 34 | 输出: false 35 | 解释: 从右向左读, 为 01 。因此它不是一个回文数。 36 | ``` 37 | 38 | **进阶:** 39 | 40 | 你能不将整数转为字符串来解决这个问题吗? 41 | 42 | 43 | 44 | ## 题目解析 45 | 46 | ### 解法一:普通解法 47 | 48 | 最好理解的一种解法就是先将 **整数转为字符串** ,然后将字符串分割为数组,只需要循环数组的一半长度进行判断对应元素是否相等即可。 49 | 50 | #### 动画描述 51 | 52 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190525181152.gif) 53 | 54 | #### 代码实现 55 | 56 | ```java 57 | ///简单粗暴,看看就行 58 | class Solution { 59 | public boolean isPalindrome(int x) { 60 | String reversedStr = (new StringBuilder(x + "")).reverse().toString(); 61 | return (x + "").equals(reversedStr); 62 | } 63 | } 64 | ``` 65 | 66 | 67 | 68 | ### 解法二:进阶解法---数学解法 69 | 70 | 通过取整和取余操作获取整数中对应的数字进行比较。 71 | 72 | 举个例子:1221 这个数字。 73 | 74 | - 通过计算 1221 / 1000, 得首位1 75 | - 通过计算 1221 % 10, 可得末位 1 76 | - 进行比较 77 | - 再将 22 取出来继续比较 78 | 79 | #### 动画描述 80 | 81 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190525181202.gif) 82 | 83 | #### 代码实现 84 | 85 | ```java 86 | class Solution { 87 | public boolean isPalindrome(int x) { 88 | //边界判断 89 | if (x < 0) return false; 90 | int div = 1; 91 | // 92 | while (x / div >= 10) div *= 10; 93 | while (x > 0) { 94 | int left = x / div; 95 | int right = x % 10; 96 | if (left != right) return false; 97 | x = (x % div) / 10; 98 | div /= 100; 99 | } 100 | return true; 101 | } 102 | } 103 | ``` 104 | 105 | 106 | 107 | ### 解法三:进阶解法---巧妙解法 108 | 109 | 直观上来看待回文数的话,就感觉像是将数字进行对折后看能否一一对应。 110 | 111 | 所以这个解法的操作就是 **取出后半段数字进行翻转**。 112 | 113 | 这里需要注意的一个点就是由于回文数的位数可奇可偶,所以当它的长度是偶数时,它对折过来应该是相等的;当它的长度是奇数时,那么它对折过来后,有一个的长度需要去掉一位数(除以 10 并取整)。 114 | 115 | 具体做法如下: 116 | 117 | - 每次进行取余操作 ( %10),取出最低的数字:`y = x % 10` 118 | - 将最低的数字加到取出数的末尾:`revertNum = revertNum * 10 + y` 119 | - 每取一个最低位数字,x 都要自除以 10 120 | - 判断 `x` 是不是小于 `revertNum` ,当它小于的时候,说明数字已经对半或者过半了 121 | - 最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。 122 | 123 | #### 动画描述 124 | 125 | ![](https://raw.githubusercontent.com/MisterBooo/myBlogPic/master/20190525181211.png) 126 | 127 | #### 代码实现 128 | 129 | ```java 130 | class Solution { 131 | public boolean isPalindrome(int x) { 132 | //思考:这里大家可以思考一下,为什么末尾为 0 就可以直接返回 false 133 | if (x < 0 || (x % 10 == 0 && x != 0)) return false; 134 | int revertedNumber = 0; 135 | while (x > revertedNumber) { 136 | revertedNumber = revertedNumber * 10 + x % 10; 137 | x /= 10; 138 | } 139 | return x == revertedNumber || x == revertedNumber / 10; 140 | } 141 | } 142 | ``` 143 | 144 | 145 | 146 | ![](https://bucket-1257126549.cos.ap-guangzhou.myqcloud.com/blog/fz0rq.png) --------------------------------------------------------------------------------