在由一片0组成的海洋中,飘着由1组成的陆地(此处0和1可以替换为任何两个不同的符号)。针对岛屿有着一系列问题:
最基础的是这片海上有多少个岛屿?
DFS或者BFS都可以做,个人感觉DFS更好写一点。
代码:Number of Islands I
这次初始是一片海洋,题目会给一组坐标作为每次新增加的陆地。求每次增加陆地后的岛屿数。
暴力解法完全可以执行N轮问题200中的代码,但是这样的效率是很低的。因此需要用到一种常见的算法:并查集(Union Find)
给定一些点与点之间联通的情况,判断哪些点属于一个组中,不需要具体路径的时候,使用并查集就是比较好的。为了性能考虑我们采用带权值的树结构来存储点和群组。
代码:Number of Islands II
在不考虑旋转映射等情况下,这片海上有多少个不同的岛屿呢?
在 Number of Islands基础上,只需要储存节点相对位置即可:
代码:Number of Distinct Islands
接着694问,如果此时考虑旋转等情况,这片海上又有多少不同的岛屿呢?
这个主要的问题在于如何建模,使得旋转镜像的图形都属于同一类。这个我参考了网上代码,思路是:
对694中得到的节点位置进行normalize,目的是变换成同一种岛屿形状的唯一表示。
对于每一个坐标位置,其实总共有8种变换可能:x和y都可以分别变为相反数,且也可以同时变为相反数,再加上其本身,这就4种了。同时,上面的每一种情况都可以交换x和y的位置,这样总共就有8中变换方法。
使用8个大小相同的数组来保存各自所有的点。对于每一种形状,都对其所有的点进行排序,然后将每个点的绝对坐标变为相对坐标,即每个点都减去排序后第一个点的坐标,最后再对这8个形状排个序。
只要是属于同一个岛屿的形状,最后都会得到一模一样的8个形状数组,为了简单起见,只选取第一个形状形状当作这同一种岛屿的模型代表。
代码:Number of Distinct Islands II
回文数是一个比较麻烦的处理点,性质是从前往后读和从后往前读的结果相同。
这是一个将回文数和链表结合起来的问题。可以用快慢指针找到中点,然后翻转后半链表去比较,完事将链表还原。
这个的思路是顺位从开始到结尾判断是否是回文数,是则加入最后的表中
Palindrome Partitioning
与131不同,这次不需要给出全部的分割法,只需要给出需要分割的最小次数。
碰到类似的题,优先考虑DP。
思路是:
Palindrome Partitioning II
给出一个数组,最少删除几次就可以全部删完。如果有回味序列删一次就可以。
区间dp。主要是递推公式,见递推公式
Palindrome Removal
这个的思路是记录出现过的最长的回文数的位置和长度,同样是从0开始扩展。思路是寻找重复的单元找到重复单元两侧向外扩展寻找回文。
Longest Palindromic Substring
一个数当他自身是回文数,同时它的平方根(是整数)也是回文数的时候,我们称为超级回文数。求给定范围内的超级回文数。
回溯法应用,思路不是通过判断而是通过构造来找出满足的例子。
Super Palindromes
涉及到左右括号匹配等一系列问题
给出括号对数N,求出所有的有效表达式。
思路是记录有多少左括号,直到记录的括号数为0,然后类似回溯法生成即可。
Generate Parentheses
给出一个括号组成的string,找出最长的有效的括号对的长度。
利用一个栈来存储括号对应的pos,然后弹出匹配成功的段。最后利用一个循环确定最长的子串。
Longest Valid Parentheses
给出一段括号字符串,移除最少的括号使得该字符串左右括号匹配。
思路是DFS,对每一个字符做加入或者不加入的判断,最终将结果保存在set中即可。
Remove Invalid Parentheses
给定一个非负整数数组,最初定位在数组的第一个索引处。
数组中的每个元素表示该位置的最大跳转长度。以此衍生的一系列问题。
判断是否能够到达最后一个索引
用一个reach记录当前可以到达的最远距离,当i>reach意味着没办法从之前的路径到达之后的节点,所以认为是跳不过去。
jump game
求从开始到结束最少跳几次
与之前的问题类似,也是采用一个记录当前到达的点最多是多少
jump game II
一个排好序的数组,从某一个点切开旋转,得到的就是 Rotated Sorted Array。举例:[1,2,3,4,5]从3切开,得到[4,5,1,2,3]。以此衍生了一些问题。
给定要搜索的目标值。 如果在数组中找到则返回其索引,否则返回-1。
假设数组中不存在重复。算法的运行时复杂度必须为O(log n)。
这个思路很清晰,首先通过二分法找到旋转点,获得旋转点以后,我们可以以旋转点为offset取余,这样对我们来说,这个数组就和旋转前的数组没区别了。
Search in Rotated Sorted Array
您将获得要搜索的目标值。 如果在数组中找到则返回true,否则返回false。
与33题不同的地方在于,这次的问题中数组是包含重复的元素的,也就是说在二分法处理问题的时候我们需要额外判断一种情况:left=mid=right。其他没有变化,时间复杂度为O(logN)
Search in Rotated Sorted Array II
无重复元素的旋转排序数组找最小值。33中找到的rot就是最小值
和153同理,只不过额外判断当nums[mid] == nums[high]的情况
Find Minimum in Rotated Sorted Array
给出一个包含01的矩阵,判断里面最大的矩形的面积
DP问题,思路是分别用三个数组记录当前位置的高,左起始和右起始。最后算出来最大的区域面积。
Maximal Rectangle
问题类似于上面的矩阵问题,只不过换成了求最大正方形面积。只需要在原代码基础上在最后计算面积时选择pow(min(right[i] - left[i], height[i]), 2);
一个int数组中,每个数都出现了k(k>1)次,其中有一个元素出现p(p>=1 && p%k != 0)次。求找出这个元素。
这类型的题在评论区有位大佬有总结,我把它翻译过来如下:
这次是其中有两个元素出现了1次,其他都是出现两次。
代码逻辑如下:假设a,b是所求元素
首先通过^ 得到 r = a^b
a^ b的本质是保留了a,b中不相同的位保留下来。通过 r &(~(r-1))得到最右边1的位置,这个就可以把a,b分在不同的阵营。分别再做一次 ^ 得到最终结果。
single number III
关于BFS和DFS的最基本的应用。
Clone Graph
参加n门课程,课程之间有依赖关系(有向图),判断该有向图是否有环。
两种解决方案:
BFS:方法是重复寻找一个入度为0的顶点,将该顶点从图中删除(即放进一个队列里存着,这个队列的顺序就是最后的拓扑排序,具体见程序),并将该结点及其所有的出边从图中删除(即该结点指向的结点的入度减1),最终若图中全为入度为1的点,则这些点至少组成一个回路。
DFS:假设图以邻接矩阵表示,一条深度遍历路线中如果有结点被第二次访问到,那么有环。我们用一个变量来标记某结点的访问状态(未访问,访问过,其后结点都被访问过),然后判断每一个结点的深度遍历路线即可。
Course Schedule I
您必须参加总共n门课程,标记为0到n-1。
有些课程可能有先决条件,例如,要修课程0,你必须先修课程1,表示为一对:[0,1]
根据课程总数和先决条件对列表,返回完成所有课程所需的课程顺序。
只需要将入度为0的节点输出到数组中即可。
给定具有m×n个单元的矩阵,每个单元具有初始状态live(1)或dead(0)。每个单元格使用以下四个规则与其八个邻居(水平,垂直,对角线)进行交互:
编写一个函数来计算给定其当前状态的矩阵的下一个状态(在一次更新之后)。通过将上述规则同时应用于当前状态中的每个细胞来创建下一个状态,其中出生和死亡同时发生。
要求原址操作。
这类问题可以采用有限状态机方式,同时保存旧的状态和新的状态来实现原址操作。
game of life
判断一个字符串是不是合法的数字表达式,DFA。
valid number
二叉树是每个结点最多有两个子树的树结构。围绕二叉树会衍生出一系列问题
给定二叉树,返回其节点值的Z字形级别遍历。 (即,从左到右,然后从右到左进行下一级别并在之间交替)。
我给出的解法是,先把每一层输出出来,然后对奇数层的vector做一个reverse。讨论区给了一种更直接的方法,采用一个队列保存节点,弹出的时候则判断是应该正向输入还是逆向输入。
Binary Tree Zigzag Level Order Traversal
给出一个二叉树,找出所给两个节点的最小公共祖先(LCA)
有个关键的特性:如果待寻找的两个点出现在不同的子树中,那么当前节点就是我们要的最终结果。简化后的代码如下:
Lowest Common Ancestor of a Binary Tree
二叉树抢劫,不能抢劫相邻的节点(比如父节点和他的子节点),求能抢劫的最大钱数。
保存带上根节点的最大值和不带根节点的最大值即可。
House Robber III
给定整数n,生成存储值1 … n的所有结构上唯一的BST(二叉搜索树)。
可以通过深度优先搜索(递归)解决这道题。
因为二叉查找树满足父节点的值大于左子节点的值,小于右子节点的值,所以我们可以:
(1) 从 N=1 开始构建二叉查找树,则它的左子树节点数为 0,右子树节点数为 n-1;
(2) N=2 时,左子树节点数为 1,右子树节点数为 n-2;
……
(n) N=n 时,左子树节点数为 n-1,右子树节点数 0。
而在第(1)步中,右子树继续执行上述循环,子树的子树又执行这个循环,最终,我们可以将子树节点数减少到 1,而一个节点只有一种排列方式,所以此时可以毫不犹豫地将结果返回给上一级。然后包含有两个节点的二叉树排列方式又被返回给上一级。……
依此类推,我们最后可以得到所有不同结构的二叉查找树。
Unique Binary Search Trees II
二叉搜索树中两个元素对调了,在不改变结构的情况下,纠正回来。
学习一下Morris Traversal。
思路如下:
BST中序遍历是升序,当出现降序时,说明是被交换了。
Recover Binary Search Tree
序列化与反序列化树。联动问题:297 序列化与反序列化二叉树。
N叉树的一个序列化方法是用括号表示当前节点的子树。
反序列化则是利用栈来还原树。
Serialize and Deserialize N-ary Tree
很简单的思路:保证分出来的两部分一样多的情况下(分割数组1的i固定,意味着分割数组2的j固定),判断是否满足中位数要求(中位数本质:给出一个划分,使得左右两边都小于(或大于)这个划分)。
findMedianSortedArrays
排序链表, O(n log n) 时间复杂度,O(1)空间复杂度。
只有一点,虽然想到归并排序的方法,但是把归并过程想复杂了,在讨论区看到一个简洁的归并,代码如下:
sort list
合并K个已排序列表,用优先队列来辅助。
Merge k Sorted Lists
K个一组来翻转链表,找到长度为k的节点后,记录对应的节点,翻转当前节点,向下一个节点继续做同样的处理方法。
Reverse Nodes in k-Group
逆序存放就是按部就班加起来,最后别忘了进位要新建一个节点。
正序则是利用栈来辅助。
给定两个单词(beginWord和endWord)和一个字典的单词列表,找到从beginWord到endWord的最短变换序列的长度,满足:
注意:
这个题思路上来说是比较简单的,双向BFS。理论上也是可以用DFS,但是可能会超时。
Word Ladder
在I的基础上,输出所有长度最短的序列。
同样是BFS的应用。具体分析注释在代码中。
Word Ladder II
给一个2维的字母矩阵,判断所给的单词数组中哪些单词能够通过字母矩阵生成。
I 判断word是否能被字母矩阵生成,回溯法即可。
II 给一个单词列表,找出所有能被生成的单词。trie的应用是为了能够让拥有相同前缀的string在判断到达根节点以后同时加入到set。这个相比暴力的搜索法在拥有更多相同前缀的string时,效率要高得多。
Word Search II
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
I 只需要判断能否切割。这个时候使用动态规划即可解决。
II 在 I 的基础上需要输出所有字符串的拆分方式
思路是:首先用一个hash表储存给定的字典wordD,构造一个hashmap用来储存当前字符串长度和输出字符串memo。构造一个函数dp。核心思想是通过动态储存来减少重复计算的浪费。
word break II
买卖股票的问题在leetcode上一共包含六个。问题分别描述如下:
每个题目都会给一个数组,其中第i个元素是第i天给定股票的价格。
121:购买之前必须出售股票。每天只允许完成一笔交易,求最大利润
122:购买之前必须出售股票。尽可能的进行交易,求最大利润
123:购买之前必须出售股票。最多能进行两次交易,求最大利润
188:购买之前必须出售股票。最多能进行k次交易,求最大利润
309:购买之前必须出售股票。尽可能的进行交易,但是卖出后的一天内不能交易,求最大利润。
714:条件和122相同,但是每次完成交易都需要付一笔交易费fee,求最大利润
121,122,123,188其实是同一个问题的不同形式。其中以188是一般问题,121是k=1的情况,122是k= ∞ \infty ∞的情况,123则是k=2的情况。
先看一般问题的解法:
首先这个问题使用DP的核心表达式(子问题)为:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )
解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )
解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
base case:
dp[-1][k][0] = dp[i][0][0] = 0
//第i天没做交易,或者还没开始计时(第-1天),此时手上没有股票,利润都是0。
dp[-1][k][1] = dp[i][0][1] = -infinity
// 第i天没做交易,或者还没开始计时(第-1天),此时手上有股票,不可能,用负无穷表示。
309的问题是在122的基础上,增加了每次购买之后需要冷却一天的要求。这使得基本的子问题稍微发生变化,在hold的时候,当第i天想要购买是,减去的是i-2天的hold值。
714则是在122的基础上加了交易费,每次卖出时加一笔费用。
Best Time to Buy and Sell Stock
给出一系列不同高度的矩阵,求最大面积。
这个是一个单调栈思路,维护一个只保存一路递增的栈,在处理栈中元素时就能保证最大面积在其中。详细讲解和演示见这里
Largest Rectangle in Histogram
实现基本计算器来评估简单的表达式字符串。
表达式字符串仅包含非负整数,+, - 运算符和空格,左右括号。 整数除法应截断为零。
表达式字符串仅包含非负整数,+, - ,*,/运算符和空格。 整数除法应截断为零。
表达式字符串仅包含非负整数,+, - ,*,/运算符和空格,左右括号。 整数除法应向下截断。
我的解法是用到了stringstream,term 负责计算两个加减号之间的所有乘法的值,并在下一次读到加减号时,把结果加减在total上。括号则是按照栈来匹配每次调用子函数来计算括号中的内容,并返回结果修改原字符串。
227 是最基础的计算,不包含括号,可以直接用caculate_help解决。
224 是在227的基础上,只包含±,带括号,可以把主函数的乘除部分删去。
772 就是224 和 227 的合并。
代码:
Basic Calculator
另一种解法是依赖栈和运算符优先级解决,代码不放了。
滑动窗口的共性:
给出字符串S和T,找出S中包含T中全部字母的最小子字符串。
类似问题,需要一个数组记录目标的变化情况,并将每次达到要求的子串长度和其实位置记录下来,这个count就是精髓
Minimum Window Substring
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。
Longest Repeating Character Replacement
详细解释见代码
Sliding Window Maximum
查找给定字符串的最长子字符串T的长度(仅由小写字母组成),以便T中的每个字符出现次数不少于k次。
思路是这样的:逐位去寻找是否有合适的子串,并在找的过程中记录出现过的最大子串长度,如果找到了一个子串则没有必要检查它覆盖的子串。代码如下:
Longest Substring with At Least K Repeating Characters
给一个非空的数组,求按照频率排序下来的前k个元素
我所首先想到的思路是优先队列,C++中priority_queue的使用。
看了讨论区的解答后,还发现一种比较好用的方法,桶排序:
Top K Frequent Elements
通过戳爆所有的气球,来获得最大硬币和。
只需要掌握一点:选择一个气球不戳爆,以他为分割点,左右两个序列是相互独立的。也就是说一个首位为I和J的序列,以中间为K的点分割,则该序列的最大值为:
max[I][J] = nums[I-1]*NUMS[K]*nums[J+1] + max[I][K-1] + max[K+1][J]
以这个作为动态规划的公式:
Burst Balloons
给出一系列非负整数,求问他们能不能分成大小相同的两个组。比如[1,5,5,11]可以分成[1,5,5]和[11]
我的方法超空间了,记录讨论区一个大佬的解法:
用到了bitset作为数据结构,bitset里面有对这个结构的简单介绍。
通过移位操作记录之前每个数加了多少,在和自身或操作。bitset每一位代表是否能够取到对应位置的和。最后只需要看能不能找到所求值即可。这里说明的更加详细。
Partition Equal Subset Sum
给出一个数组,和整数k,求有多少个连续的子序列的和等于k
基本思路:如果累计总和,在索引 i 和 j 处相差 k,即 sum[i] - sum[j] = k,则位于索引 i 和 j 之间的元素之和是 k。
基于这些想法,可以使用了一个哈希表 unordered_map,它用于存储所有可能的索引的累积总和以及相同累加和发生的次数。我们遍历数组nums并继续寻找累积总和。每当我们遇到一个新的和时,我们在hashmap中创建一个与该总和相对应的新条目。如果再次出现相同的和,我们增加与map中的和相对应的计数。此外,对于遇到的每个总和,我们还确定已经发生 sum-k总和的次数,因为它将确定具有总和 kk的子阵列发生到当前索引的次数。我们将 count增加相同的量。
最终count就是结果
Subarray Sum Equals K
in-place旋转图像。先reverse数组在对位交换,很巧妙。注意reverse一个二维数组只会反正高维,不会反转低维。
Rotate Image
给出一个未排序的数组,找出最长增长子序列的长度
我们给出三个策略判断增加一个已存在的序列是否是安全的
当计算最大长度时,则只需要计算末尾的元素即可。
Longest Increasing Subsequence
该问题可以联动类似题目334. Increasing Triplet Subsequence
给定一个未排序的数组nums,重新排序,使得nums [0] < nums [1] > nums [2] < nums [3] O(N)时间和/或O(1)额外空间
这个问题如果不考虑附加条件比较简单,只需要先排好序然后大小混着填即可。考虑到O(n)的时间复杂度和常量级别的空间复杂度就比较困难了。我看到评论区里面有人给出了一种基于“虚拟index”思想的方法。
这个方法的思路是首先利用nth_element找到中位数,然后#define A(i) nums[(1+2*(i)) % (n|1)]
很巧妙,把给定数组按照先奇数后偶数的方式对应起来,
然后通过交换方法让左边的奇数位全部都是大于median的数,右边则全部是小于median的数,这样相应的原来的数组中顺序就是wiggle的。可以说是很巧妙了。
Wiggle Sort II
给定一个非负整数列表,将它们排列成最大数字。
我一开始的想法是利用基数排序,对于高位相同的数来说,位数少的优先,但这样实现起来很复杂,讨论区给出了一种简单的方法。其实思路很简单,不对数字排序,而是对转化后的字符串排序,利用sort函数 自己写一个比较器,这样会简洁的多。
Largest Number
给定n个整数的数组nums,是否有元素a,b,c在nums中,a + b + c = 0? 找到数组中所有唯一的三元组,它们的总和为零。
解决方案集不得包含重复的三元组。
这个问题,我一上来准备使用暴力方法,利用multiset解决重复问题,显然时间复杂度为O( N 3 N^3 N3)是行不通的。因此评论区给了一个方法,思路是:先对数组进行排序,排好序之后再固定一个元素的情况下,分别从前后两端开始查找。
3sum
给出两个用字符串表示的数字,返回用字符串表示的这两个数字的乘积。
关键的一点:当前位置可以由序号和相同的乘法加起来决定的。
Multiply Strings
这两个问题都是螺旋矩阵的问题,对于这类问题的处理有一个通用的解法。这里将59的代码放下面:
spiral matrix
格雷码是二进制数字系统,其中两个连续值仅在一位上不同。
给定代表代码中总位数的非负整数n,打印格雷码序列。 格雷码序列必须以0开头。
问题的突破点在于格雷码生成特性:输入为n的格雷码,后 2 n − 1 2^n-1 2n−1元素的值 = 前 2 n − 1 2^n-1 2n−1的元素的值的逆序 + 2 n − 1 2^{n-1} 2n−1。
Gray Code
给一个N个非负元素的数组,代表高度,求下雨时能积多少水。
其实该问题还有别的解决思路,也就是积累法:分别维护一个maxleft和maxright,将整个池子想象成一个大的水池,不断抬高边界高度即可。
Trapping Rain Water
给一个乱序数组,找到其中连续最长的序列的长度。比如[100,2,65,1,3],结果是3([1,2,3])。复杂度O(N)
这个问题我想的没问题,但是写起来的时候空间用多了,导致修改了很久。核心的思想是:维护一个哈希表unordered_map,用来记录数组中的数和对应的最大连接长度。只需要维护最长连接数组的两端即可。
Longest Consecutive Sequence
给出一系列长方形的起始坐标,终点坐标和高度,获得整个图形的轮廓坐标。
方法是描线法。将端点分为左右两个端点处理,每当最大高度不同时,即为转折点,保存下来。具体分析见代码。
The Skyline Problem
给定一组非重叠间隔,在间隔中插入新间隔(必要时合并)。
分析见代码注释。
Insert Interval
给一个未排序数组,返回它在排好序的时候,连续两个元素之间的最大差值。
这个问题用到了桶排序,核心要点是最大gap的下界是(maxV - minV )/ (sSize - 1)。依照这个思路简化我们需要计算的部分,从而时间复杂度是O(2N+K),K是桶的数目,空间复杂度是O(3K)。满足时空复杂度为线性的要求。
Maximum Gap
回溯+剪枝的应用。
Zuma Game
构建一个十叉树,很巧妙
K-th Smallest in Lexicographical Order
KMP算法的一个学习。
strstr