做题,实际写出例子,然后分析可能遇到的情况,慢慢的,思路就会出来了。
线性表
33. Search in Rotated Sorted Array
这道题,不同于二分查找法在于,不能确定,中点一定是大于左端点的。所以,在进行二分查找时,需要多一步,即,先判断中点是否大于左端点。
如果中点大于左端,再去比较三个点,才可以按照二分边界赋值。
如果中点小于左端,再去比较三个点,再按照二分边界赋值。
15. 3Sum
参考2SUM/3SUM/4SUM问题
解决方法就是最外层遍历一遍,等于选出一个数,
之后的数组中转化为找和为target-nums[i]的2SUM问题。
16. 3Sum closest
参考 3Sum Closest 最近三数之和
所以我们需要定义一个变量diff用来记录差的绝对值,然后我们还是要先将数组排个序,然后开始遍历数组,思路跟那道三数之和很相似,都是先确定一个数,然后用两个指针left和right来滑动寻找另外两个数,每确定两个数,我们求出此三数之和,然后算和给定值的差的绝对值存在newDiff中,然后和diff比较并更新diff和结果closest即可,
31、next_permutation
参考[算法]——全排列(Permutation)以及next_permutation
其中,举出例子,然后一点点的去试,然后根据试出的步骤,写代码即可。
[6、5、4、8、7、5、1]
- 1,1和5替换,没有效果,那么1和7替换也没有效果,再往前,比1大的都没有效果。
- 2,从上面也能看出,5替换的话,一直到8,也没有效果。
- 3,4替换8有效果,但是为了找到最小的下一个全排列,所以,应该让4和后面的8-5中,最小的替换。
- 4、替换完事之后,发现变成了6558741,最后四位是降序的,不满足条件。6551478才满足条件,于是可以将后面的反转。
- 5、一开始就是降序的话,那么直接反转即可。
但是如果一个一个比较的话比较麻烦,可以看出,我们是从后面找起,找到不是降序的子数列为止。有一个方法,从后向前查找第一个相邻元素对(i,j),并且满足A[i] < A[j]。易知,此时从j到end必然是降序。可以用反证法证明。于是编程时,可以用这个,直接从第三步走起。
60、Permutation Sequence
参考 Permutation Sequence 序列排序
36、有效数独
参考Valid Sudoku
48. Rotate Image
在原地修改,是可以利用tmp等变量来进行修改的。
73. Set Matrix Zeroes
参考 Set Matrix Zeroes 矩阵赋零
- 1、最原始的,是用O(m*n)的时间复杂度,再建立一个新的矩阵,一个数一个数的读,一行一行的扫进去,遇到0 ,就把新的矩阵改行设置为0 。再一列一列的扫
- 2、将时间降到O(m+n),可以用两个数组,一个m维数组记录哪一行有0 ,一个n维数组记录哪一列有0。(in方法判断)最后更新
- 3、但是要求空间复杂度为O(1),不能用额外的空间。所以想到,用第一行,第一列存储。
- 4、但是,第一行第一列的数值也要保证不能变(除非改行有0)。按照下面的逻辑。
主要是更新顺序
- 先扫描第一行第一列,如果有0,则将各自的flag设置为true(最后再更新。)
- 然后扫描除去第一行第一列的整个数组,如果有0,则将对应的第一行和第一列的数字赋0
- 再次遍历除去第一行第一列的整个数组,如果对应的第一行和第一列的数字有一个为0,则将当前值赋0
- 最后根据第一行第一列的flag来更新第一行第一列
136. Single Number
参考Single Number
每次一个元素进行异或,0和5异或返回5.....
86. Partition List
单链表
61. Rotate List
- k是可以大于n的,所以需要对n取余数
- 倒着往回数,快慢指针,快的先走k步,然后慢的再走
字符串
125、有效回文
一般情况下做法:建立两个指针,左右相比较
对于不是字母的字符串,用list[l].isalnum()来判断真假,while循环跳过去就行
特殊情况,字符串是[]空。
几乎所有的题,都要考虑这个情况,对于字符串是空,对于链表一类的可能只有一个node。总之,考虑极端情况是对的。
5. Longest Palindromic Substring
参考 Longest palindromic substring 最长回文子串
栈
20、有效括号
问题的思路分析,即能够通过在字符间插入特定数目的竖线,使得整个字符串各个局部都对称。由这句话,局部对称来引入栈,
考虑到栈中可以通过一进一出,两进两出的类似对称。所以,可以用栈来解决此问题。
代码思路:
step1:初始化栈,并入栈输入串的第一个字符;
step2: 从输入串的第二个字符到最后一个字符,依次与栈顶元素对比,栈不为空且栈顶元素与字符匹配则出栈,否则入栈该字符;
Step3: 操作完最后一个字符后,如果栈为空(即有进必有出,各个局部均对称),则输入合法;
参考valid parentheses
另外,可以用list的append和pop方法来近似的构造出一个栈的数据结构。
若出现右边的括号类型,那么为了valid,上一个压入栈中的一定是这个括号的左边类型。
即,如果出现了“)”,那么栈最上面的一定是“(”才行。可以由此进行判断。
32、最大有效括号的长度longest parenthses
此算法, 用“(”的索引来进行压栈,入栈操作。是因为,需要计算合法括号字符的长度,用数字运算比较方便。
这个和有效括号有些不同,这个
- 1、原则1,就是,栈中只会存放左边括号的符号“(”位置索引。这是利用了括号有效的必要条件的特性,下面会把这些情况都解释一下。
- 2、这里的栈存放的不再是“(”,而是此符号的位置索引了。(当然也可以用两个栈,一个存放“(”,一个存放位置索引。没必要而已。)
接下来是代码思路:
参考longest parenthses
每次来了‘(’之后,无条件压栈。
用“)”进行消除,有这几种情况需要考虑。
首先就是“))()”这样的,即,碰到头两个右括号时,栈为空,那么一定不是合法的了,可以跳过。
然后就是“((())()”,此时碰到了第一个“)”时,消掉最上面的左括号,然后接下来是第二个“)”,可以再次消掉栈最上面的左括号,然后碰到“(” ,直接入栈,然后继续碰到“)”,再次消掉。
直到“)”用完为止或者栈为空为止。下面依次解释这两种情况。
1、“)”用完了。栈内还有剩余的‘(’。如上例:
此时栈里还剩一个“(”没有消掉,而“)”已经没有了。那么此时这个有效括号的长度就是当前“)”的索引减去栈顶“(”的索引值。对应到例子上就是,6-0=6。
然后再与前面已有的合法长度进行比较。maxLength = Math.max(i - (int)stack.peek() , maxLength)2、')'没用完,栈空了
此时需要引入一个新的变量start,用于表示新的合法括号字符串的起点。
例如:对于这种情况:())()(),可以正确的得出最大值为4。
start初始为-1,之后每次碰到‘)’且栈为空的时候更新为当前‘)’的index。
比如上例中, start就为第二个“)”的index,表示新的合法字符串起点为3.3、一直循环就是了。
此算法, 用“(”的索引来进行压栈,入栈操作。是因为,需要计算合法括号字符的长度,用数字运算比较方便。
树
144、二叉树前序遍历
注意,树中的node并不是一个int类型的数字。这个node具有属性的。分别是node.val,node.left 和 node.right。分别代表着这个node的值,左子节点和右子节点。
所以,压入栈中的,也是node,而不是node.val。
此题要求不能用递归的方法前序遍历二叉树。
想到前序遍历的路径,一直读入最左边的叶节点,然后退回父节点接着读父节点的右子节点(注意此时父节点已经被读取过了),和栈有些相似。于是想到用栈来解决问题。
(最好画个图,来分析循环的步骤。)
题中已经给出,节点具有的三个属性。
root作为根节点
令栈为空
- 1、读入节点1,如果1有左子节点,就继续遍历入栈,一直入栈到底(此时,栈里压入一个元素,list就append一个元素。)
- 2、弹栈,弹出一个元素,就检测该元素是否有右子节点。即root.right是否为空。若不为空,就遍历压入栈中。
-
- 弹出5,没有左右子节点,继续弹栈,1弹出后,有右子节点,于是按照上面步骤压入栈中。
- 4、一直循环到,节点为空,栈为空为止。
list_num= []
stack = []
while root or stack:
if root:
list_num.appent(root)
stack.append(root)
root = root.left
else:
stack.pop()
root = root.right
return list_num
逐层返回二叉树
参考# Binary Tree Level Order Traversal 二叉树层序遍历
层序遍历二叉树是典型的广度优先搜索BFS的应用,但是这里稍微复杂一点的是,我们要把各个层的数分开,存到一个二维向量里面,大体思路还是基本相同的,建立一个queue,然后先把根节点放进去,这时候找根节点的左右两个子节点,这时候去掉根节点,此时queue里的元素就是下一层的所有节点,用一个for循环遍历它们,然后存到一个一维向量里,遍历完之后再把这个一维向量存到二维向量里,以此类推,可以完成层序遍历。代码如下:
第二版本:
形,不同之处在于一行是从左到右遍历,下一行是从右往左遍历,交叉往返的之字形的层序遍历。根据其特点我们用到栈的后进先出的特点,这道题我们维护两个栈,相邻两行分别存到两个栈中,进栈的顺序也不相同,一个栈是先进左子结点然后右子节点,另一个栈是先进右子节点然后左子结点,这样出栈的顺序就是我们想要的之字形了,代码如下:
101 对称树
class Solution:
# @param root, a tree node
# @return a boolean
def isSymmetric(self, root):
# a new recursive function to check if two tree are symetric
def isMirror(root1,root2):
# they all could be Null to be symetric
if not root1 and not root2:
return True
# if they are not Null, they must carry the same value
elif (root1 and root2) and root1.val==root2.val:
# recursively call itself
return isMirror(root1.left,root2.right) and isMirror(root1.right, root2.left)
else:
return False
if not root:
return True
return isMirror(root.left, root.right)
114. Flatten Binary Tree to Linked List
参考 Flatten Binary Tree to Linked List 将二叉树展开成链表
这道题要求把二叉树展开成链表,根据展开后形成的链表的顺序分析出是使用先序遍历,那么只要是数的遍历就有递归和非递归的两种方法来求解,这里我们也用两种方法来求解。首先来看递归版本的,思路是先利用DFS的思路找到最左子节点,然后回到其父节点,把其父节点和右子节点断开,将原左子结点连上父节点的右子节点上,然后再把原右子节点连到新右子节点的右子节点上,然后再回到上一父节点做相同操作。代码如下:
112 path sum
递归:1 终止条件 2 每次都要变化的
- 首先,如果输入的是一个空节点,则直接返回false,如果如果输入的只有一个根节点,则比较当前根节点的值和参数sum值是否相同,若相同,返回true,否则false。 这个条件也是递归的终止条件。
- 下面我们就要开始递归了,由于函数的返回值是Ture/False,我们可以同时两个方向一起递归,中间用或||连接,只要有一个是True,整个结果就是True。
- 递归左右节点时,这时候的sum值应该是原sum值减去当前节点的值。代码如下:
129. Sum Root to Leaf Numbers
···
class Solution {
public:
int sumNumbers(TreeNode *root) {
return sumNumbersDFS(root, 0);
}
int sumNumbersDFS(TreeNode *root, int sum) {
if (!root) return 0;
sum = sum * 10 + root->val;
if (!root->left && !root->right) return sum;
return sumNumbersDFS(root->left, sum) + sumNumbersDFS(root->right, sum);
}
};
···
105、利用前序遍历和中序遍历构建二叉树
参考idiot-maker
实际上,就是利用这两个遍历的特性,确定遍历的边界。
要解决这道题目,就要分别利用preorder和inorder遍历的两个性质。preoder的第一个元素确定为根节点,然后再在inorder里,它左侧的就是左子树的全部节点,右侧的就是右子树的全部节点。然后再对左子树这些节点,递归调用上面的方法。
比如
- preorder,7是根节点
- inorder,7左边的是左子树全部节点,长度为4,7右边的是右子树全部节点,长度为3.
- preorder,7后边4个数,全是左子树的val。第5个数开始,全是右子树的val。可由此确定,左子树的根节点是10,右子树的根节点是2
- inorder,左子树的根节点10,左边的都是左子树的左子树长度为1,右边的都是左子树的右子树长度为2。
- preorder,10后边1个数,全是左子树的val。第2个数开始,全是右子树的val。可由此确定,左子树的根节点是4,右子树的根节点是3.
就这样递归下去。
对应成代码,就是
def buildTree(self, preorder, inorder):
if not preorder or not inorder:#递归的终止条件,当此树的遍历列表为空时,说明为叶节点。
return None
rootValue = preorder[0]#确定根节点的值
root = TreeNode(rootValue)#用TreeNode构造根节点
inorderIndex = inorder.index(rootValue)#确定根节点在中序遍历中的index
#root的左子树的长度,就是根节点在inorder中左边的部分的长度,
#那么可以知道,树的左子树的前序遍历就是preorder列表中[1:inorderIndex+1]的部分
#左子树的中序遍历就是inorder中的[:inorderIndex]部分。
root.left = buildTree(preorder[1:inorderIndex+1], inorder[:inorderIndex])
root.right = buildTree(preorder[inorderIndex+1:], inorder[inorderIndex+1:])
return root
98、Validate Binary Search Tree验证二分查找树是有效的
参考Validate Binary Search Tree
用递归的话,时间复杂度是O(n2)。
需要注意的是,左子树的所有节点都要比根节点小,而非只是其左孩子比其小,右子树同样。这是很容易出错的一点是,很多人往往只考虑了每个根节点比其左孩子大比其右孩子小。如下面非二分查找树,如果只比较节点和其左右孩子的关系大小,它是满足的。
通过这个思路进行判断。
二分查找树的中序遍历结果是一个递增序列。
所以,可以先对二分查找树进行中序遍历(递归),再判断此序列是不是一个递增序列。
108、 Convert Sorted Array to Binary Search Tree
参考 Convert Sorted Array to Binary Search Tree
1、二叉搜索树的特性:始终满足左<根<右
2、一个二叉搜索树的中序遍历就是一个递增序列。
于是,反过来,我们可以认为,根节点就是有序列表的中间点,从中间点分开为左右两个有序数组,在分别找出其中间点作为原中间点的左右两个子节点。递归进行下去就行。(这也是二分查找法的核心思想)
代码也与二分查找的代码类似。递归
111、Minimum Depth of Binary Tree
参考 Minimum Depth of Binary Tree
也是递归,关于minipath长度怎么叠加,
可以采用,在递归函数中,当当前节点是叶节点时,返回0.
如果当前节点有子节点,那么就返回min(left_path(),right_path()) +1。即当前子节点下面路径的最小值再加1。
那么得到的效果就是,从上往下,每次下沉,都会加1。一直加到 最终叶节点,得到 minipath为0。然后再一次返回到根节点,得到路径长度。
对参考的程序的解读。
1、如果左右子节点都为空,那么就返回1。(因为此时只有一层节点,路径也只能是1)
2、如果,左子节点为空,那么就返回右子节点的递归+1。
左子节点为空,说明右子节点不为空。要是画出图来,那么此路径就会经过父节点必须向右子节点过去。此时的路径长度就是返回右子节点的递归调用加1了。
注意!并不是说,左子节点为空,那么父节点就不往下走了,最短路径就已经求得了,而是说,左子节点为空,那么父节点,也得往下走,一直走到叶节点为止。3、如果,右子节点为空,那么就返回左子节点的递归+1。
4、如果是其余情况(都不为空),就返回左右子节点 二者递归调用的最小值。
排序
88. Merge Sorted Array
参考LeetCode_Merge Sorted Array
这里需要注意的是,num1和nums2都是数组,每次从nums2中挑出一个元素插入nums1中,都是需要改变数组的index的。
如果单纯得考虑从小到大地将两个数组进行合并的话,每次在num1中插入一个数的话,需要将后面的元素都向后移动一位,这样,整个处理过程的时间复杂度为O(m*n)。
题目已经给出,nums1可以认为长度是m+n。那么就可以先把nums2中的最大值和nums中的最大值进行比较,放到nums1中的最后一位(index=m+n-1)。
然后比较得到次最大值,将次最大值放在m+n-2位置,依次类推,这样在将元素放置到合适位置的时候,就不需要移动元素,这个方法的时间复杂度为O(m+n)。
每次循环中,如果nums2的元素被插入到了最大值中,那么n=n-1。下次可以用nums2[n-1]来对nums2中的次最大值进行索引。
21. Merge Two Sorted Lists
参考Merge Two Sorted Lists
链表有序合并也是数据结构里边的经典题目了。
- 1、分别扫描两个链表,比较两个节点的大小值,把较小的节点值尾插到结果链表屁股后边,
- 2、然后再次扫描两个链表,直到其中某一个链表或者两条链表同时结束。
- 3、如果是某条链表提前结束则应将另一条链表的其余节点都接到结果链表上。
75、sort colors
定义red指针指向开头位置,blue指针指向末尾位置
从头开始遍历原数组,如果遇到0,则交换该值和red指针指向的值,并将red指针后移一位。若遇到2,则交换该值和blue指针指向的值,并将blue指针前移一位。若遇到1,则继续遍历。