Median of Two Sorted Arrays
这个问题显然可以用归并排序来处理,但是复杂度是线性的。问题要求给出一个O(log(m + n))的算法,应该是从两序列分别有序这一特性来入手,利用类似二分的方式来完成中位数的查找。
Longest Substring Without Repeating Characters
从每个位置向后遍历256个字符即可,因为ASCII就这么多字符。复杂度可以算作是线性的,但是应该存在更漂亮的做法。
Update: 确实存在更优雅的滑动窗口解法,大意是,先试图移动头指针,没法向前的话就while循环缩短尾部。然后写起来的话是正好相反,在循环体内就直接先收缩尾部然后移动头部。
Add Two Numbers
链表水题,但是可以不用头节点吗?
Longest Palindromic Substring
存在O(n)的算法,但是思路复杂,利用了类似后缀树的性质。朴素想法是从每个位置同时向两边检索,判断是否回文,复杂度O(n^2)。注意需要考虑回文长度分别为奇数和偶数的情况。
ZigZag Conversion
模拟水题。
Reverse Integer
利用sprintf或者sstream可以很轻松地实现。
String to Integer (atoi)
考察思考的仔细程度。判断是否溢出可以直接用long long类型,但是要注意类型转换时是否会出现问题,必要时还是要加上括号。另外,可能存在+-号同时出现的情况。
Palindrome Number
判断数字是否回文,还是用sprintf或者自己实现一个即可。
Regular Expression Matching
实现基本的正则表达式,本质上是一个DP。这个问题还是非常有意思的,为此专门写了一篇博客分析。
Integer to Roman && Roman to Integer
整数转换为罗马数字,理解罗马数字是怎么定义的即可。
Longest Common Prefix
全部遍历一下即可,应该不存在更优的算法了。
3Sum && 3Sum Closest && 4Sum
先前已经总结过,排序后使用头尾指针遍历的方法可以优化掉一层循环的复杂度,对于这类问题已经是最优的算法了。如果要求精确相等,我们可以不考虑当前位置前面的元素,否则就必须要考虑到,这是3Sum与3Sum Closest这两种问题的区别。
Letter Combinations of a Phone Number
直接上Python,可以写出很漂亮的代码。
Remove Nth Node From End of List
依然是链表操作问题,使用快慢指针的思想来处理。具体的做法就是慢指针等待一定时间后再出发。不过需要注意的一点是,程序要判断需要删除的是否是头节点。
Valid Parentheses
用判断括号表达式是否合法。注意虽然是三种括号但是仍然是只用一个栈。
Remove Duplicates from Sorted Array
水题,但是注意输入的数组可能为空!
Merge k Sorted Lists
这题真是卡得爽死我了,应该早点想到的,显然不存在线性算法,比如如果全是只有一个节点的链表,那么就相当于要对这些值排个序,复杂度最低也是O(nlogn)呢。既然可以引入log的复杂度,那么就好想了。一种思路是使用multiset,创建一个结构体或者对象包装一下ListNode *,重载比较运算符,扔进set里面来自动维护有序。每次取的时候选取最小的元素即可,复杂度是O(n*k*logk)。另外一种方式则是用堆(优先队列)来维护有序,本质上是一样的么,但是应该可以缩减掉一个常数。
当然其实更好想的做法是分治,类似归并排序的方式不断合并两个小的链表成为一个大链表,不过看起来占用空间反而会更严重的样子,所以还是不予考虑了(其实是没想到)。
重新考虑一下,其实还是基于堆的算法复杂度最小,可以到O(nlogk)。
Swap Nodes in Pairs && Reverse Nodes in k-Group
两道翻转链表的题目,题意是要求将链表k个一组来翻转。是一个实现起来复杂细节比较多的问题。链表翻转本身是一个经典的操作,但是其实这个题目给出的限定条件有点坑。如果不达指定长度的链表节点同样要求翻转的话,其实可以实现一个只需遍历一遍链表即可完成的算法,而且代码也会非常精简。总而言之,这个题目实现起来需要抠很多操作细节。
Remove Element && Implement strStr()
两道直接在网页上敲,盲交然后1A的水题……
Divide Two Integers
要求模拟二进制操作来实现除法。需要注意的是,可能会给出非常极端的数据,直接就溢出了。对待这种情况,最好的办法就是糙快猛地引入long long,用更大值域的类型来处理问题,省心省力!
Substring with Concatenation of All Words
本质上还是在用滑动窗口的思路来处理,设一个窗口的宽度为L.size() * L[0].size(),即为所有words的长度;从S[0]开始滑动,如果当前窗口中有一个word匹配不上,就将窗口移动一个字符,从S[1]继续,以此类推……
需要注意的细节是,输入数据存在重复的word,所以必须检查每个word出现了多少次,在每个窗口中用map记录一下即可搞定。
Search for a Range
二分查找练习。
Search Insert Position
还是STL里的lower_bound。
Valid Sudoku
判断数独是否合法,几个循环套上去即可。
Count and Say
理解题意的话就好办了,意思是在模拟一个人数数字的过程。直接模拟就可以了,似乎题目所给的数据规模不大。
Rotate Image
就是旋转矩阵,新开辟空间的话做法是显然的。如果一定要求In Place即常数空间占用的话,则矩阵必须是方阵,需要用类似一层一层旋转的技巧来完成。参考链接:http://en.wikipedia.org/wiki/In-place_matrix_transposition、http://blog.gssxgss.me/rotate-90-degress-nxn-matrix/
Anagrams
折腾半天没有明白题意,其实这东西叫做“字母易位造词法”,意思是说,给你一系列String,让你给出它们中包含字母相同的所有字符串。思路就很简单了,首先把字符串排序后,用map做哈希,加入到所对应的vector中就可以了。STL大法好!
Pow(x, n)
就是实现快速幂,但是要注意,输入中可能存在负数。而且它的测试case相当严格,会给出一个最小的int32,这个负数是无法转换为32位长度的正数int的。为了省事我直接就改了数据类型为long long(逃)。
N-Queens && N-Queens II
经典的NP八皇后问题,剪枝思路就是尽量利用对角线性质,不用再多说了。第一问需要能够输出所有可行的方案,我的做法是利用全局的字符数组来记录,不知道有没有更为优雅的解决方案。
Maximum Subarray
非常经典的题目,正确的状态表示其实是这样的:记max[i]为前i个元素中,从任意位置起始,到最右端元素结束的连续序列的最大和。这样的话,我们就有状态转移方程:
它的意思是说,在第i个位置,我们要不然将它与前面的连续子序列连接形成更长的子序列,要不然就从它开始一个新的子序列。注意到状态之间只有前后依赖关系,所以并不需要显式开辟数组。但我们需要注意的是,实际要求的是max[i]中的最大值,因此在递推max[i]的过程中,我们需要再维护一下其最大值。
Spiral Matrix && Spiral Matrix II
就是一个模拟,要旋转着来输出矩阵中的每一个元素。这类题目可以通过计算下角标规律的方式来完成,不过更为简洁的做法是,把它看作是模拟行走,每次不能继续向前的话就右转,直到走完该走的步数即可。超出界限的位置、以及先前走过的位置,被认为是不能走的位置。注意处理需要返回空向量的情况,即输入矩阵为空。
Sort List
链表归并排序,核心思路和数组归并排序是一致的。首先使用快慢指针将链表分拆(Partitioning),然后分别对两个链表进行排序(Sort),最后递归合并(Merge)即可。可惜这个合并的递归并非尾递归,所以效率上不是很高,某种意义上而言还是开辟了非常数空间的内存。我们也可以使用不那么漂亮的方式,用具体的链表操作一步一步地合并链表,虽然看起来冗长多了,但是效率会有明显的提升。
Merge Intervals
典型的区间合并问题,使用贪心排序策略来处理。首先按照左端点优先排序,如果左端相同的话就按照右端来排。排序之后,简单考虑一下合并策略即可。对于其后那道Insert Interval,我们可以直接复用此题的代码来完成。当然这个题目也可以把代码写成线性复杂度的,不过也就意味着还要做一些特殊情况的处理和判断,个人觉得意义不大。
Rotate List
模拟链表移位。如果输入数据k保证不超过链表长度的话,那么这个问题可以用快慢指针一次遍历链表完成。否则的话,我们必须事先知道链表的长度,求模之后才能够进行处理。
Unique Paths && Unique Paths II
非常显然的DP类型问题,子问题的划分也是很简单的。状态转移方程:dp[i][j] = dp[i][j - 1] + dp[i - 1][j]。对于存在障碍物的情况,直接在DP的时候进行判断即可。
Minimum Path Sum
状态转移方程:dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]。
Add Binary && Plus One
N进制字符串相加,显然的模拟水题。先直接相加,然后按位调整即可。反正最多只会增加一位。
Path Sum && Path Sum II
裸的DFS搜索,注意边界条件的情况判定。
Text Justification
巨麻烦的字符串处理问题,题意也很难理解,本质上应该源自word之类文字处理软件的行排版算法。总而言之,一点一点抠边界细节就行了。
Sqrt(x)
实现整数的开方运算,其实就是二分查找法。注意,对于开方后出现整型截断的情况,得到的答案平方值会小于被开方数。这种情形下,需要记录先前一个合法的小于值,其实就是类似lower_bound的那种简便处理方法。
Climbing Stairs
DP水题,状态转移方程:dp[i] = dp[i - 1] + dp[i - 2]。正好是斐波那契数列。
Simplify Path
对UNIX风格的路径进行化简,模拟水题。需要注意一个小地方,就是如果试图访问根路径/的上一层目录,这是一个合法的访问不会出错,只是会一直停留在根路径而已,因为root的父亲就是它自己。
Edit Distance
经典的DP问题,状态转移方程分为两种状态:如果word1[i] == word2[j],则直接有dp[i][j] = dp[i - 1][j - 1];否则,那么当前状态可以由三种情况转移过来:min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1,分别对应着替换字符、插入字符、删除字符的情况。注意初始化条件:dp[0][i] = dp[i][0] = i,其对应的含义是显然的。
Set Matrix Zeroes
水题一枚,用set记录并去重即可。或者也可以用标记的方式来写。
Search a 2D Matrix I && II
这个题纯粹就是二分查找的二维版本,核心在于行与行之间的关系是单调递增的。我们首先在某一行里面做lower_bound查找,如果找到了,直接返回true,否则根据查找结束时指针的位置,按照二分的思想再去查找对应的行即可。
第二问更有意思一些,首先容易想到类似二分查找的递归,复杂度为\(T(n)=3T(\frac{n}{4})+O(1)\),所以利用主方法解出复杂度为\(O(n^{log_43})\),其中n为矩阵大小。但这个算法虽然优美但并非最优。实际上它有一个\(O(m+n)\)的算法,只需要从右上角开始移动指针,如果当前值大于目标,指针左移;小于目标,指针往下移动即可。正确性容易证明。
Sort Colors
就是计数排序的简化版本。
Combinations && Subsets && Subsets II
裸的DFS问题,注意终止条件的区别,以及如何去重。
对于包含重复元素的Subsets问题,为了有效地去除重复,我们在遍历元素的时候必须保证当前元素已经与前一个不相等,此时才能够继续DFS下去。否则,DFS得到的子集就必然会包含重复的集合。简而言之,就是允许在不同层的递归中DFS一段连续的元素,但是不能在同一层的递归中去连续DFS元素,因为这样必将导致出现多次与先前状态完全等价的情形。
Word Search
典型的DFS搜索,注意使用标记数组,防止重复走到同一个位置上面。
Word Search II
DFS搜索加字典树剪枝,还是要注意边界条件。
Remove Duplicates from Sorted Array II
把状态转移想明白就可以,分支条件不复杂,O(n)的算法。
Remove Duplicates from Sorted List
基础的链表操作问题,细节需要考虑清楚,画图非常有助于理解。
Remove Duplicates from Sorted List II
这个链表操作就相当繁琐了,实现思路的核心是在大循环内部,使用两重while循环去除掉所有重复的元素。总而言之,编码思路必须清晰。
Maximal Rectangle
最大子矩阵和的变体,也算是最大连续子序列的一种应用吧,压缩掉了一个维度的复杂度。这里的失败判定条件不是出现和为负,而是子矩阵的某一行没有被1所填满,其实问题是变得更简单了。
看了一下程序耗时发现明显偏慢,于是搜了一下网上的题解,发现这题被LeetCode降低数据规模了。实际上可以把算法优化到O(n^2),方法是首先从每一行起,将矩阵转化为直方图,然后套用上一题的算法,直接O(n)算出最大矩形,即为答案。“必须填满矩阵”这个特性被用来巧妙地优化了算法,这俩题目放在一起着实用心良苦……
Construct Binary Tree from Preorder and Inorder Traversal
经典问题,从先序遍历和中序遍历的结果构造出原本的树。还是要把握二叉树的递归性质,既然是递归遍历出的结果,自然能够递归求解。核心在于,先序遍历结果的第一个元素一定是root,于是在中序遍历中找到这个root元素,将序列拆分后递归左右子树即可。需要注意的是,不要算错下角标,最好采用左闭右开的区间表示法。
Construct Binary Tree from Inorder and Postorder Traversal
完全同上,只不过是由中序遍历和后序遍历来重新构造出树。还是利用一样的性质,后序遍历的序列中,最后一个元素是根元素。
一个需要注意的小地方是递归终点的写法,由于输入的树可能是空树,因此代码中不应该假设其中至少有一个元素。
Partition List
考察使用O(1)的空间复杂度就地划分链表。其实由于链表的特殊性质,很多在它上面的操作并不需要额外的空间,比如经典的归并排序。因此我们设置两个指针,分别指向大于等于给定x的链表,以及小于x的链表,然后遍历一遍输入链表,将每个节点分别连接到这两个链表之一中即可。最后,注意合并链表的时候,一定要对情况分类讨论!不然容易忽略边界细节。
Merge Sorted Array
考察数组的就地归并排序,核心思想是首先在数组尾部预留出空间,然后倒着遍历一遍来进行操作即可。可以写出非常简短漂亮的代码。
Gray Code
相对无聊的找规律问题,核心在于正确推导格雷码的生成规律。看起来LeetCode这个OJ没有Special Judge,因为题目说只能对一组固定的特解进行Judge,略渣。
Decode Ways
斐波那契数列的扩展问题,由于输入数据中存在0,因此需要考虑到一些边界细节,比如存在前导零的情况。总而言之,还是利用传统的斐波那契递推,只不过递推条件发生了些许改变,需要剪掉那些不合法不能继续推的状态。
Reverse Linked List II
反转链表中指定的一段,还是一个考察细节的链表操作题目,在纸上模拟好流程,敲成代码就行了。大体就是先找到起始位置,然后一边反转一边走到终点位置,最后分情况讨论,把它们重新连接好。这题终于能裸敲1A了……
Restore IP Addresses
依然是DFS问题,尝试搜索将IP地址拆分为四段,然后判断是否可行即可。问题在于一些边界细节的处理,比如前导零。
Interleaving String
遇到这种类型的字符串问题,很容易想到其解法应该就是DP。使用dp[i][j]表示s1的前i个字符和s2的前j个字符是否能够组成s3中的前i+j个字符,状态转移方程就是:dp[i][j]=(dp[i-1][j]&&s1[i-1]==s3[i+j-1])||(dp[i][j-1]&&s2[j-1]==s3[i+j-1]),初始化条件也就显然可得了,但是要注意边界细节。
Validate Binary Search Tree
判断一个二叉搜索树是否合法。按照定义,中序遍历BST一定可以得到一个非降序列。但是这题的数据非常严格,所以需要注意实现方法。此时就不能在求取子树中最值的时候使用一个有限的INF值了,而是应该设置好完善的判断条件。
Symmetric Tree
判断一棵二叉树在结构上是否呈对称状态,还是递归处理。这个题目仅存的难点应该就在于各种边界细节了,难得的能1A的题目。
Binary Tree Level Order Traversal I && II
分层遍历二叉树,也是那种没什么难度的题目,一遍BFS就可以了。注意用pair的话不要手残写出编译错误!
第二道题目要求反序输出遍历结果,所以就是输出前反转一下即可。感觉这样做并不是很优雅,但是理论上应该也不会存在一遍就能完成的做法了吧?如果返回值要求是List的话,是可以做到不反转的,因为这种数据结构在头部增加元素的时候是O(1)的复杂度,DFS或者BFS的时候反着往里面添加就可以了;但是C++的代码要求返回vector<vector<int>>,这种情况下就不行了。
Binary Tree Zigzag Level Order Traversal
承接上题思路,注意到只有深度为奇数的层需要被反转,于是在输出前简单处理一下即可。STL里面的reverse函数可以轻易完成这个操作。
Convert Sorted Array to Binary Search Tree
题意没说清楚,但其实是超水的数组转成平衡二叉搜索树。我就按照正常的左闭右开的区间表示方法来写了,虽然不知道判题系统是不是有SPJ功能,但总之是1A了。
Convert Sorted List to Binary Search Tree
唯一技巧性的地方在于快慢指针的使用,可以把代码实现变得非常简洁优美。其实链表跟数组一样可以使用左闭右开的区间表示法,但是取中间位置的时候就不得不遍历一遍了。使用快慢指针,终止条件是while(p!=r&&p->next!=r)。而初始状态下,则应该有:TreeNode *l=head, *r=NULL;。
Balanced Binary Tree
判断一颗二叉树是否是平衡的,定义是:左右子树的最大高度差不超过1,且左右子树均是平衡二叉树。还是毫无疑问的利用递归子结构性质,没有难度就不多说了。
Minimum Depth of Binary Tree
求二叉树的最小深度,这里的“深度”定义为从根节点到叶子节点的节点数。非常烦人的题目,主要是在考察对题意的理解,以及边界细节的处理。总而言之,此时判定递归终点就不能再简单地看指针是否为NULL了,而是要确认找到叶子节点。
Valid Palindrome
判断是否是有效回文,水题一枚,直接用Python写函数式了。
Pascal's Triangle I && II
第一问是个水题,就是输出杨辉三角,找规律即可。
第二问要求直接输出对应行,利用二项式定理来算就行,不占用额外的空间复杂度。但是注意,计算组合数的代码段,只适用于中间值C(n,k)*k不超过整型范围的情况。在这个题目中不能保证这一点,因此不得不开long long。
Triangle
这个DP实在是太经典了,不可能有人不会吧……
Sum Root to Leaf Numbers
依然是树上的DFS问题,但是要稍微小心一下递归逻辑,有点容易写错。
Surrounded Regions
这题就比较坑了,首先它在栈空间上面做了限制,没法用最爱的DFS来完成;其次,即便是BFS来写,也会吃一发MLE。事实上这个问题属于经典的思路反转:我们要求标记掉所有不贴边的连通区域,那么必然会导致很多时候搜索完之后发现这块区域贴边了,于是该次搜索作废。当然我们可以采用某些记忆化的手段来进行优化,保证这样的失败只会出现一次,只是这样会稍微繁琐些。换个思路来看,如果我们从四个边缘上开始搜索,包含O的连通区域都会被标记掉;对于其他位置,直接置为X即可。这样的话,我们就能够以非常简洁的算法完成任务。
Clone Graph
其实就是BFS算法克隆一个图。注意这个图是无向有环且有自环的,因此需要我们在新建节点的时候加以注意:如果这个节点已经新建过了,那么直接取它的指针拿来用;如果确实还没有建立过,才会new一个新的出来。另外就是注意BFS的搜索顺序以及标记顺序的问题。
Gas Station
最大连续子段和的又一个变体问题。考虑两个基本性质:
1. 如果所有的gas加在一起还不能满足消耗,那么一定无法完成一圈;
2. 如果上述条件不成立,那么必然只存在两种情况:从位置0开始即可完成一圈;或者存在一个位置i,在区间[0, i)内,消耗大于油量,在区间[i, n)内,消耗小于油量。
因此,问题转换为我们需要找到一个最长子段[0, i),使得它的和为负值。在这个最长的位置之后,剩下的一段[i, n)就可以保证一定可以走完,并且走完之后的剩余油量还足够再走完这段油耗大于油量的区间[0, i)从而回到起始点i。
Reverse Linked List && Reorder List
Reverse list仅仅是简单的就地反转链表,这种题目显然没什么可说的,几行就够了。对于第二题,要求按照一种特定的顺序来重排序链表,这里只需要意识到,这种结构稍微复杂的就地重排,不可能仅仅遍历一遍就完成(递归算法好像可以,但本质上并不是常数空间复杂度的)。所以实际上只需要把链表截断,将后半段反转,然后就地归并即可,最终时间复杂度O(n),空间复杂度O(1)。
Insertion Sort List
链表插入排序,基本思路跟在数组上的插入排序类似。不同点在于,如果当前节点并不满足有序性,需要移动位置时,就需要先把它从链表中移除,找到合适的插入位置以后,再O(1)进行插入。注意指针操作时,正确地维护链表头部即可。
Evaluate Reverse Polish Notation
就是用栈对逆波兰式求值而已,需要仔细考虑一些细节的边界条件,题目本身没有难度。
Intersection of Two Linked Lists
又是快慢指针的应用:先分别从两头出发,测量整条链表的长度;然后链表长的一端的指针先出发,短的一端后出发,一定会在分叉点处相遇。
Maximum Product Subarray
源自于Maximum Subarray的经典线性递推思路,只不过维护的状态多了一个而已。因为最大值的来源可能有两种:最大值与当前正值的乘积,或者最小值与当前负值的乘积,因此状态转移方程有:
同样需要注意,这里在递推的时候,可以避免显式开辟数组,但是由于是两个方程,所以存在一点点的依赖关系,即计算min的时候要依赖max,所以在更新max前需要先将其备份。此外,状态max[i]、min[i]的含义还是类似,即“从任意位置开始到结尾的连续序列的最大(小)积”,因此,我们需要在计算过程中维护一个最大值,才是我们所需要的答案。
Find Peak Element
题目要求复杂度是O(logn),因此必然是利用二分思想来处理。这里需要把二分时的判断条件想清楚,为此,我们需要分析这个数列的性质。考虑到其左右两端均为负无穷,因此数列中必然存在至少一个波峰,以及零个或者多个波谷,此时在脑海中大致能够描述其形状了。我们直接对数组进行二分,则二分到的中间位置mid必然只有四种情况:增加、减少、波峰、波谷。如果是波峰,则直接返回答案即可;如果是波谷,说明左侧或者右侧区间必然存在一个波峰,选择其一进行递归即可;如果是单增,说明右侧必然存在波峰;如果是单减,说明左侧必然存在波峰。至此,有了二分的判断条件,再加上合适的边界条件,就可以得到答案了。
Compare Version Numbers
这个就是水题了,只不过需要列出俩边界case而已:比如1.0.0和1.0应该是相等的,但是1.1.1要大于1.1。
Fraction to Recurring Decimal
需要注意繁琐细节的除法模拟题,注意到循环小数的出现是有规律的:当同样的除数和余数pari出现的时候,说明已经完成了一次循环节。因此,以此pair进行哈希即可。这题主要需要注意细节。
Excel Sheet Column Title
稍微复杂一点的进制转换问题,核心思路是找规律。我们注意到A在其中并不是代表着十进制0的含义,因此,在转换过程中,为了避免余数为零导致进位,我们需要在能够整除的情况下,将数值减1。
Excel Sheet Column Number
上一道题目的反转版本,这时候就非常简单了。
Majority Element I && II
经典的摩尔投票算法及其扩展,用于在O(n)的复杂度内确定数组中占1/n多数的元素,其中n可取2、3、4,……这个算法的核心思想是这样:占1/n多数的元素最多存在n-1种,对于这最多n-1种元素,即便剩余的所有元素与之抵消,依然能够保证它们会有剩余。因此,我们只需预留n-1个位置,跑一遍正常的投票流程,那么占1/n多数的n-1种元素一定会留在这n-1个位置上面。但是这个算法本质上只保证必要性,所以我们还需要再遍历一遍来确保充分性。
Factorial Trailing Zeroes
数学题。这里利用到这样一个性质,阶乘末尾零的个数,取决于n个数总共提供了多少个(2, 5)配对,其乘积就会作为最后的末尾零。但是,我们考虑到在阶乘的过程中,2出现的次数要远远多于5出现的次数,因此,实际上我们只需要求出1~n中总共有多少个5的约数即可,这个统计过程可以在log(n)复杂度内完成:
Binary Search Tree Iterator
实际上就是求二叉搜索树的后继,只不过这颗树没有带parent指针,而是需要自己用栈维护而已。还是需要牢记二叉搜索树的定义啊。
Largest Number
贪心算法,只要排序处理就可以了。考虑最优的排列下,我们如果任意调换一对数字的排序,必然导致组合出的结果变小。因此,这个最优排列必然是有序的。这样的话,我们只需要定义一个排序函数,两个数字比较时,比较的结果是两者两种组合方式的较大者,这样就能够保证最终结果的有序性质了。
Repeated DNA Sequences
注意到DNA序列的长度为10,因此显然存在取巧的办法。事实上,这个长度的特定序列完全可以塞进一个int里面进行哈希判重了,所以很容易可以解决。另外注意,保存答案的时候也需要注意去重。
Rotate Array
考察数组的就地移位,这里有两种实现思路。第一种是,直接将前后两段反转,然后再把整段一起反转回来。第二种是复杂的就地移位,边界条件比较难以考虑。总而言之,尽量使用第一种思路,非常好写。
Valid Perfect Square
标准的二分,应该不会有低于O(logn)的算法了。注意,这题同样有一个数学规律,即1 + 3 + 5 + ... + 2n - 1 = n ^ 2。当然,用这个规律的算法,复杂度反而要高一些,只是一个结论罢了。
House Robber
一维DP问题,记状态f[i]为一定包含第i个元素的最大值,g[i]为一定不包含第i个元素的最大值,则有状态转移方程:
由于状态之间依赖关系简单,因此不需要显式存储,直接迭代更新并记录最大值即可。
Binary Tree Right Side View
题意有点难以理解,其实是要求给出二叉树最右侧的那些节点的值。这样的话我们只需要BFS一遍二叉树,把每个节点的深度信息传递一下,利用其分层遍历的性质,把每一层最右侧相应的值存储到相应的位置即可。
Number of Islands
无脑BFS打标记即可,这种题目简直没有难度可言。
Bitwise AND of Numbers Range
一道找规律的位运算题目,这道题要求我们将一个区间内的所有数字按位做and操作。我们只需要注意到一个规律,运算结果其实就是这个范围内所有数字的公共前缀,此时问题就转化成求这样一个公共前缀。这里有一个非常精妙的算法:我们只需要枚举一个初始状态下所有bit均为1的一个mask,直至mask与m和n的and结果相等,如果不相等就将mask左移,这样我们就找出了这个公共前缀,也就是我们所要的答案了。总而言之,这是一个非常精妙的位运算技巧。
Happy Number
哈希打表。
Count Primes
筛法求素数。应该不会有低复杂度的做法。
Isomorphic Strings
同样是哈希,注意一定要对称做映射,然后比较的时候是要先映射之后再比较。
Course Schedule I && II
拓扑排序判断图中是否有环,套路题。
Implement Trie (Prefix Tree)
字典树,直接敲就行,注意理解前缀的含义。
Minimum Size Subarray Sum
又是经典的滑动窗口,一定要想清楚头尾指针怎么移动,不然会挂corner case。
Add and Search Word - Data structure design
字典树上的广搜,注意一些边角处理。广搜容易写错,貌似深搜比较好写的样子。
Kth Largest Element in an Array
经典的区间第k大,注意选择哨兵是随机选取的,但需要对序列做特殊检查,如果整个区间元素都一样,就应该直接返回,否则会陷入无限递归爆栈。
Kth Smallest Element in a BST
BST树上第k大,跟区间是一样的原理,但是利用树的递归性质非常好写。另外存在一个常数优化,不过代码不是很直观。
Product of Array Except Self
双端遍历,利用输出数组存储状态,就可以不占用额外的空间。
Lowest Common Ancestor of a Binary Tree
这题既然没有保证树的有序性,那么肯定是要DFS一遍了。还是以递归方式来处理,两个目标节点可能具有几种情况:同时在左子树or右子树,或者是一个在根节点一个在左子树,以及一个在根节点一个在右子树。以正确的方式来记录答案即可。