目录(序号为leetcode题号)
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
方法一:递归
解题思路:对左右子树递归,并进行判断是否相等。
public boolean isSymmetric(TreeNode root) {
// 对一棵树的左右子树递归并比较。
return recursion(root.left,root.right);
}
public boolean recursion(TreeNode leftRoot,TreeNode rightRoot){
if(leftRoot == null && rightRoot == null){
return true;
}
if(leftRoot == null && rightRoot != null || leftRoot!= null && rightRoot == null){
return false;
}
if(leftRoot.val != rightRoot.val){
return false;
}
if(!recursion(leftRoot.left,rightRoot.right)){
return false;
}
if(!recursion(leftRoot.right,rightRoot.left)){
return false;
}
return true;
}
方法二:层次
解题思路,使用层次遍历,将左右子树的值放入队列(镜像放入),比较队列中相邻的两个值是否相等,不相等则不是对称二叉树。
public boolean isSymmetric(TreeNode root) {
// 队列
Deque<TreeNode> deque = new LinkedList<>();
deque.add(root);
deque.add(root);
TreeNode leftRoot,rightRoot;
while(!deque.isEmpty()){
leftRoot = deque.poll();
rightRoot = deque.poll();
if(leftRoot == null && rightRoot == null) {
continue;
}
if(leftRoot == null || rightRoot == null){
return false;
}
if(leftRoot.val != rightRoot.val){
return false;
}
// 镜像插入队列
deque.add(leftRoot.left);
deque.add(rightRoot.right);
deque.add(leftRoot.right);
deque.add(rightRoot.left);
}
return true;
}
102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回其层序遍历结果:
[
[3],
[9,20],
[15,7]
]
解题思路:使用队列,在访问根节点时,将左右子树存储起来。进行层次遍历,将每层的节点放入同一个数组,访问完每层,加入最终的结果集合。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
Deque<TreeNode> deque = new LinkedList<>();
ArrayList<Integer> list;
deque.add(root);
int n;
while(!deque.isEmpty()){
n = deque.size();
list = new ArrayList<>();
for(int i = 0;i<n; i++){
root = deque.poll();
list.add(root.val);
if(root.left != null){
deque.add(root.left);
}
if(root.right != null){
deque.add(root.right);
}
}
result.add(list);
}
return result;
}
103. 二叉树的锯齿形层序遍历
给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回锯齿形层序遍历如下:
[
[3],
[20,9],
[15,7]
]
解题思路:使用队列辅助,做层次遍历,在层次遍历时,存储结果集的时候,根据层次的奇偶性存放的顺序不同,偶数层对元素从左到右存放,奇数层对元素从右到左存放。
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
ArrayDeque<TreeNode> deque = new ArrayDeque<>();
LinkedList<Integer> list;
// 偶数层从左到右 false,奇数层从右到左 true
boolean level = false;
deque.add(root);
while(!deque.isEmpty()){
int n = deque.size();
list = new LinkedList<>();
for(int i = 0;i < n;i++){
root = deque.poll();
if(level){
// 从右到左存放,每次将元素存储到首位,list为链表,存储在首位效率较高。
list.add(0,root.val);
}else{
// 从左到右存放,顺序存放即可
list.add(root.val);
}
if(root.left != null){
deque.add(root.left);
}
if(root.right != null) {
deque.add(root.right);
}
}
level = !level;
result.add(list);
}
return result;
}
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
方法一:DFS
解题思路:DFS递归,求出树的最大深度。
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return 1 + Math.max(maxDepth(root.left),maxDepth(root.right));
}
方法二:BFS
解题思路:BFS层次遍历,遍历的最大层次也就是最大深度。
public int maxDepth(TreeNode root) {
// BFS
if(root == null){
return 0;
}
Deque<TreeNode> queue = new LinkedList<>();
queue.add(root);
int depth = 0;
// 层次遍历,同时记录层数,层数就是深度。
while(!queue.isEmpty()){
depth++;
int n = queue.size();
for (int i = 0;i<n;i++){
root = queue.poll();
if(root.left!=null){
queue.add(root.left);
}
if(root.right!=null){
queue.add(root.right);
}
}
}
return depth;
}
105. 从前序与中序遍历序列构造二叉树
给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
二叉树前序遍历的顺序为:
先遍历根节点;
随后递归地遍历左子树;
最后递归地遍历右子树。
二叉树中序遍历的顺序为:
先递归地遍历左子树;
随后遍历根节点;
最后递归地遍历右子树。
示例:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
解题思路:前序遍历,先访问的是根节点,只需要根据前序遍历得到的根节点,将中序遍历中以根节点为界,分为左右两个子树,继续根据前序遍历得到根节点,继续将左右子树划分。重复以上过程直到左右子树为空或者为一个节点,即可返回。
public TreeNode buildTree(int[] preorder, int[] inorder) {
return recursion(preorder,inorder,0,0,inorder.length-1);
}
//pindex是指向树根节点的下标,inl是子树的左边界,inr是子树的右边界,子树的区间范围为 [inl,inr]
public TreeNode recursion(int[] preorder, int[] inorder,int pindex,int inl,int inr){
// 当区间中没有元素时,子树为空
if(inl > inr){
return null;
}
// 当区间中只有一个元素,即只有一个树根节点,直接返回子树的根节点。
if(inl == inr){
return new TreeNode(preorder[pindex]);
}
// 创建子树的根节点
TreeNode root = new TreeNode(preorder[pindex]);
// 根据子树的根节点,将子树进一步划分为左右两个子树。k为根节点在inorder中的下标
int k;
for(k=0;k<inorder.length;k++){
if(inorder[k] == preorder[pindex]){
break;
}
}
// 左子树根节点下标为当前根节点下标加一。
root.left = recursion(preorder,inorder,pindex+1,inl,k-1);
// 右子树根节点下标为:当前根节点下标加上 左子树元素的个数 + 1。
pindex += k - inl + 1;
root.right = recursion(preorder,inorder,pindex,k+1,inr);
return root;
}
108. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵高度平衡二叉搜索树。
高度平衡二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
解题思路:将整个数组看个一个树,根节点必须为树中的中间值,树才能高度平衡,只有选择树中的中间值作为根节点即可。选中中间值后,将整个树划分为左右子树,继续选择中间值作为子树根节点,继续划分子树,直到子树中节点为空。
public TreeNode sortedArrayToBST(int[] nums) {
return ArrayToBST(nums,0,nums.length-1);
}
// left 子树的左边界,right 子树的右边界。
public TreeNode ArrayToBST(int[] nums,int left,int right){
if(left > right){
return null;
}
if(left == right){
return new TreeNode(nums[left]);
}
int mid = left + (right-left + 1)/2;
// 选择中间值作为子节点。
TreeNode root = new TreeNode(nums[mid]);
// 划分子树
root.left = ArrayToBST(nums,left,mid-1);
root.right = ArrayToBST(nums,mid+1,right);
return root;
}
116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。
二叉树的定义
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
示例:
输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,
如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
方法一:使用队列层次遍历,修改next指针
public Node connect(Node root) {
if(root == null)
return null;
Deque<Node> deque = new LinkedList<>();
Node tmp = root;
deque.add(root);
while(!deque.isEmpty()){
int n = deque.size();
for(int i = 0 ;i < n;i++) {
root = deque.poll();
if (i < n - 1) {
// 修改next指针,指向下一个
root.next = deque.peek();
}
// 满二叉树,左边不空,右边一定不空
if (root.left != null) {
deque.add(root.left);
deque.add(root.right);
}
}
}
return tmp;
}
方法二:层次是从上到下遍历的,可以利用上层信息,对本层进行修改。在每一层遍历时,让左节点指向右节点,让右节点指向父亲节点的下一个节点的左节点(root.left.next = root.right,root.right.next = root.next.left)
递归版本
public Node connect(Node root) {
if(root == null){
return null;
}
// 如果有子节点
if(root.left!=null){
// 左节点指向右节点
root.left.next = root.right;
// 如果有下一个节点
if(root.next != null){
// 右节点指向下一个节点的左节点
root.right.next = root.next.left;
}
}
connect(root.left);
connect(root.right);
return root;
}
非递归版本
public Node connect(Node root) {
if(root == null){
return null;
}
Node tmp = root,left;
// 左节点不空,说明有下一层
while(root.left!=null){
// 保存左节点
left = root.left;
root.left.next = root.right;
// 遍历整层
while(root.next != null){
root.right.next = root.next.left;
root = root.next;
root.left.next = root.right;
}
// 进入下一层
root = left;
}
return tmp;
}
118. 杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
解题思路:按照杨辉三角定义求即可。
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list;
for(int i = 0; i < numRows;i++){
list = new ArrayList<>();
for (int j = 0; j <= i; ++j) {
// 第一个最后一个元素为 0
if (j == 0 || j == i) {
list.add(1);
} else {
// 中间元素为 上一行元素值之和
list.add(result.get(i - 1).get(j - 1) + result.get(i - 1).get(j));
}
}
result.add(list);
}
return result;
}
121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,
最大利润 = 6 - 1 = 5 。
注意利润不能是 7 - 1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
解题思路:采用动态规划,考虑求第n天的最大利润,第n天的价格减去之前的最低价格,与第n-1天能够得到的最大利润,取最大值。只需要存储第n天之前的最低价格,和第n-1天的最大利润。
public int maxProfit(int[] prices) {
// 第0天的最大利润为0.
int maxProfit = 0;
// 最小价格初始化为最大值
int minPrice = Integer.MAX_VALUE;
for(int i = 0;i < prices.length;i++){
// 迭代求最大利润。为当前最大利润和当天价格与之前最小价格的最大值。
maxProfit = Math.max(maxProfit,prices[i] - minPrice);
if(minPrice > prices[i]){
minPrice = prices[i];
}
}
return maxProfit;
}
122. 买卖股票的最佳时机 II
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例:
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出,
这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,
这笔交易所能获得利润 = 6-3 = 3 。
方法一:回溯,考虑所有的可能性。
回溯剪枝算法:超时了
public int maxProfit(int[] prices) {
// 回溯剪枝
return maxProfit(prices,0,0,true,0);
}
public int maxProfit(int[] prices,int n,int maxProfig,boolean flag,int p) {
if(n == prices.length){
return maxProfig;
}
if(flag){
// 未有股票
// 选择买 选择不买 取最大值返回
return Math.max(maxProfit(prices,n+1,maxProfig,false,n),
maxProfit(prices,n+1,maxProfig,true,n));
}else{
// 已有股票
// 选择卖
return Math.max(maxProfit(prices,n + 1,maxProfig,false,p),
prices[n]>prices[p]?maxProfit(prices,n+1,maxProfig + prices[n]
-prices[p],true,p):Integer.MIN_VALUE);
}
}
方法二:贪心
解题思路:只要第二天比前一天价格高的值,就计算入最大利润。
如图所示:只需要将所有的上升曲线的值加起来即可。
public int maxProfit(int[] prices) {
int profit = 0;
for(int i = 1;i < prices.length;i++){
profit += Math.max(prices[i] - prices[i-1],0);
}
return profit;
}
方法三:动态规划
考虑到「不能同时参与多笔交易」,因此每天交易结束后只可能存在手里有一支股票或者没有股票的状态。
定义状态 dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。
状态转移方程:
dp[i][0] 前一天可能没有股票,也可能有股票但是卖掉了,收获了prices[i] 的收益,状态转移方程为:
dp[i][0] = max{ dp[i−1][0], dp[i−1][1] + prices[i] }
dp[i][1] 前一天可能持有股票,也可能没有股票但是今天买入了股票,花掉了现金,状态转移方程为:
dp[i][1] = max{ dp[i−1][1],dp[i−1][0]−prices[i] }
考虑初始状态:
dp[0][0] = 0 第0天交易结束后,持有现金的收益为 0
dp[0][1] = -prices[0] 第0天交易结束后,持有股票的收益为 -prices[0],钱换成股票。
最后返回 dp[n-1][0]一定大于等于 dp[n-1][1]
优化:由于在状态转移方程中,只跟前一天状态有关,只需要使用两个变量即可。dp0表示前一天持有现金的最大利润。dp1表示前一天持有股票的最大利润。
public int maxProfit(int[] prices) {
// 初始化状态
int dp0 = 0,dp1 = -prices[0];
for(int i=1; i < prices.length; i++){
// 更新状态
dp0 = Math.max(dp0,dp1 + prices[i]);
dp1 = Math.max(dp1,dp0 - prices[i]);
}
return dp0;
}