LeetCode101 对称二叉树
题目详情
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
树中节点数目在范围 [1, 1000] 内
-100 <= Node.val <= 100
进阶:你可以运用递归和迭代两种方法解决这个问题吗?
代码
public class LeetCode101 {
public static void main(String[] args) {
System.out.println(new Solution().isSymmetric(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, null, 3, null, 3})));
System.out.println(new Solution().isSymmetric(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, 3, 4, 4, 3})));
System.out.println(new Solution2().isSymmetric(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, null, 3, null, 3})));
System.out.println(new Solution2().isSymmetric(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, 3, 4, 4, 3})));
}
/*
递归
如果一个树的左子树与右子树镜像对称,那么这个树是对称的
因此,该问题可以转化为:两个树在什么情况下互为镜像
如果同时满足下面的条件,两个树互为镜像:
- 他们的两个根节点具有相同的值
- 每个树的右子树都与另一个树的左子树镜像对称
*/
static class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirror(root, root);
}
public boolean isMirror(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
return (t1.val == t2.val)
&& isMirror(t1.right, t2.left)
&& isMirror(t1.left, t2.right);
}
}
/*
迭代
方法一 中我们用递归的方法实现了对称性的判断,如何用迭代实现,首先我们引入一个队列,
这是把递归程序写成迭代程序的常用方法。初始化时我们把根节点入队两次。每次提取两个结点
并比较他们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点
的左右字节点按相反的顺序插入队列中。
当队列为空时,或者我们检测不到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
*/
static class Solution2 {
public boolean isSymmetric(TreeNode root) {
return check(root, root);
}
public boolean check(TreeNode u, TreeNode v) {
Queue q = new LinkedList();
q.offer(u);
q.offer(v);
while (!q.isEmpty()) {
u = q.poll();
v = q.poll();
if (u == null && v == null) {
continue;
}
if ((u == null || v == null) || (u.val != v.val)) {
return false;
}
q.offer(u.left);
q.offer(v.right);
q.offer(u.right);
q.offer(v.left);
}
return true;
}
}
}
LeetCode102 二叉树的层序遍历
题目详情
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
代码
class LeetCode102 {
public static void main(String[] args) {
System.out.println(new Solution().levelOrder(TreeNode.buildBinaryTree(new Integer[]{1, 3, 4, 5, 2, 6, null, 9})));
System.out.println(new Solution2().levelOrder(TreeNode.buildBinaryTree(new Integer[]{1, 3, 4, 5, 2, 6, null, 9})));
}
/*
广度优先搜索
我们可以想到最朴素的方法是用一个二元组(node,level)来表示状态,它表示某个节点和它所在的层数。
考虑如何优化空间开销:如何不用哈希映射,并且只用一个变量node表示状态,实现这个功能?
我们可以用一种巧妙的方法修改广度优先搜索:
- 首先根元素入队
- 当队列不为空的时候
- 求当前队列的长度si
- 依次从队列中取si个元素进行拓展,然后进入下一次迭代
*/
static class Solution {
public List> levelOrder(TreeNode root) {
List> ret = new ArrayList>();
if (root == null) {
return ret;
}
Queue queue = new LinkedList();
queue.offer(root);
while (!queue.isEmpty()) {
List level = new ArrayList();
int currentLevelSize = queue.size();
for (int i = 1; i <= currentLevelSize; ++i) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
ret.add(level);
}
return ret;
}
}
/*
BFS的使用场景总结:层序遍历、最短路径问题
本文将会讲解为什么这道题适合用广度优先搜索(BFS),以及BFS适用于什么样的场景。
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/bfs-de-shi-yong-chang-jing-zong-jie-ceng-xu-bian-l/
*/
static class Solution2 {
public List> levelOrder(TreeNode root) {
List> res = new ArrayList<>();
Queue queue = new ArrayDeque<>();
if (root != null) {
queue.add(root);
}
while (!queue.isEmpty()) {
int n = queue.size();
List level = new ArrayList<>();
for (int i = 0; i < n; i++) {
TreeNode node = queue.poll(); //删除头部
level.add(node.val);
if (node.left != null) {
queue.add(node.left); //添加到队尾
}
if (node.right != null) {
queue.add(node.right); //添加到队尾
}
}
res.add(level);
}
return res;
}
}
}
LeetCode104 二叉树的最大深度
题目详情
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
代码
public class LeetCode104 {
public static void main(String[] args) {
System.out.println(new Solution().maxDepth(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, null, 3, null, 3})));
System.out.println(new Solution2().maxDepth(TreeNode.buildBinaryTree(new Integer[]{1, 2, 2, null, 3, null, 3})));
}
/*
DFS 深度优先搜索策略
*/
static class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
} else {
int left_height = maxDepth(root.left);
int right_height = maxDepth(root.right);
return java.lang.Math.max(left_height, right_height) + 1;
}
}
}
/*
不用递归
*/
static class Solution2 {
public int maxDepth(TreeNode root) {
Queue> stack = new LinkedList<>();
if (root != null) {
stack.add(new Pair(root, 1));
}
int depth = 0;
while (!stack.isEmpty()) {
Pair current = stack.poll();
root = current.first;
int current_depth = current.second;
if (root != null) {
depth = Math.max(depth, current_depth);
stack.add(new Pair(root.left, current_depth + 1));
stack.add(new Pair(root.right, current_depth + 1));
}
}
return depth;
}
}
}
LeetCode105 从前序与中序遍历序列构造二叉树
题目详情
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
提示:
1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
preorder 和 inorder 均 无重复 元素
inorder 均出现在 preorder
preorder 保证 为二叉树的前序遍历序列
inorder 保证 为二叉树的中序遍历序列
代码
class LeetCode105 {
public static void main(String[] args) {
TreeNode node = new Solution().buildTree(
new int[]{3, 9, 20, 15, 7},
new int[]{9, 3, 15, 20, 7});
TreeNode.prettyPrintTree(node);
System.out.println(TreeNode.serialize(node));
TreeNode node2 = new Solution2().buildTree(
new int[]{3, 9, 20, 15, 7},
new int[]{9, 3, 15, 20, 7});
TreeNode.prettyPrintTree(node2);
System.out.println(TreeNode.serialize(node2));
}
/*
二叉树前序遍历的顺序为:
先遍历根节点;
随后递归地遍历左子树;
最后递归地遍历右子树。
二叉树中序遍历的顺序为:
先递归地遍历左子树;
随后遍历根节点;
最后递归地遍历右子树。
在「递归」地遍历某个子树的过程中,我们也是将这颗子树看成一颗全新的树,按照上述的顺序进行遍历。挖掘「前序遍历」和「中序遍历」的性质,我们就可以得出本题的做法。
对于任意一颗树而言,前序遍历的形式总是
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
即根节点总是前序遍历中的第一个节点。而中序遍历的形式总是
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位。
这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。
细节
在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希映射(HashMap)来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1)O(1) 的时间对根节点进行定位了。
*/
static class Solution {
private Map indexMap;
public TreeNode myBuildTree(int[] preorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点 就是pIndex值
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, 0, n - 1, 0, n - 1);
}
}
/*分而治之 -官方视频的 */
static class Solution2 {
public TreeNode buildTree(int[] preorder, int[] inorder) {
int preLen = preorder.length;
int inLen = inorder.length;
if (preLen != inLen) {
throw new RuntimeException("incorrect input data");
}
Map map = new HashMap<>(inLen);
for (int i = 0; i < inLen; i++) {
map.put(inorder[i], i);
}
return buildTree(preorder, 0, preLen - 1, map, 0, inLen - 1);
}
private TreeNode buildTree(int[] preorder, int preLeft, int preRight, Map map, int inLeft, int inRight) {
//递归终止条件
if (preLeft > preRight || inLeft > inRight) {
return null;
}
//构造根节点
int rootVal = preorder[preLeft];
TreeNode root = new TreeNode(rootVal);
int pIndex = map.get(rootVal);
//前序遍历 根(preLeft) (preLeft+1)左子树(pIndex-inLeft+preLeft) (pIndex-inLeft+preLeft+1)右子树(preRight)
//中序遍历 (inLeft)左子树(pIndex-1) 根(pIndex) (pIndex+1)右子树(inRight)
root.left = buildTree(preorder, preLeft + 1, pIndex - inLeft + preLeft, map, inLeft, pIndex - 1);
root.right = buildTree(preorder, pIndex - inLeft + preLeft + 1, preRight, map, pIndex + 1, inRight);
return root;
}
}
}
LeetCode114 二叉树展开为链表
题目详情
给你二叉树的根结点 root ,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
- 展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [0]
输出:[0]
提示:
树中结点数在范围 [0, 2000] 内
-100 <= Node.val <= 100
进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?
代码
class LeetCode114 {
public static void main(String[] args) {
TreeNode node1 = TreeNode.buildBinaryTree(new Integer[]{1, 2, 5, 3, 4, null, 6});
new Solution().flatten1(node1);
TreeNode.prettyPrintTree(node1);
TreeNode node2 = TreeNode.buildBinaryTree(new Integer[]{1, 2, 5, 3, 4, null, 6});
new Solution().flatten2(node2);
TreeNode.prettyPrintTree(node2);
TreeNode node3 = TreeNode.buildBinaryTree(new Integer[]{1, 2, 5, 3, 4, null, 6});
new Solution().flatten3(node3);
TreeNode.prettyPrintTree(node3);
}
/*
可以发现展开的顺序其实就是二叉树的先序遍历。算法和 94 题中序遍历的 Morris 算法有些神似,我们需要两步完成这道题。
将左子树插入到右子树的地方
将原来的右子树接到左子树的最右边节点
考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
*/
static class Solution {
void flatten1(TreeNode root) {
while (root != null) {
//左子树为 null,直接考虑下一个节点
if (root.left == null) {
root = root.right;
} else {
// 找左子树最右边的节点
TreeNode pre = root.left;
while (pre.right != null) {
pre = pre.right;
}
//将原来的右子树接到左子树的最右边节点
pre.right = root.right;
// 将左子树插入到右子树的地方
root.right = root.left;
root.left = null;
// 考虑下一个节点
root = root.right;
}
}
}
/*
题目中,要求说是 in-place,之前一直以为这个意思就是要求空间复杂度是 O(1)O(1)。
偶然看见评论区大神的解释, in-place 的意思可能更多说的是直接在原来的节点上改变指向,空间复杂度并没有要求。所以这道题也可以用递归解一下。
*/
void flatten2(TreeNode root) {
Stack toVisit = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while (cur != null || !toVisit.isEmpty()) {
while (cur != null) {
toVisit.push(cur); // 添加根节点
cur = cur.right; // 递归添加右节点
}
cur = toVisit.peek(); // 已经访问到最右的节点了
// 在不存在左节点或者右节点已经访问过的情况下,访问根节点
if (cur.left == null || cur.left == pre) {
toVisit.pop();
/**************修改的地方***************/
cur.right = pre;
cur.left = null;
/*************************************/
pre = cur;
cur = null;
} else {
cur = cur.left; // 左节点还没有访问过就先访问左节点
}
}
}
/*
解法二中提到如果用先序遍历的话,会丢失掉右孩子,除了用后序遍历,还有没有其他的方法避免这个问题。在 Discuss 又发现了一种解法。
为了更好的控制算法,所以我们用先序遍历迭代的形式,正常的先序遍历代码如下,
*/
void flatten3(TreeNode root) {
if (root == null) {
return;
}
Stack s = new Stack();
s.push(root);
TreeNode pre = null;
while (!s.isEmpty()) {
TreeNode temp = s.pop();
/***********修改的地方*************/
if (pre != null) {
pre.right = temp;
pre.left = null;
}
/********************************/
if (temp.right != null) {
s.push(temp.right);
}
if (temp.left != null) {
s.push(temp.left);
}
/***********修改的地方*************/
pre = temp;
/********************************/
}
}
}
}
LeetCode121 买卖股票的最佳时机
题目详情
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
代码
public class LeetCode121 {
public static void main(String[] args) {
System.out.println(new Solution().maxProfit(new int[]{7, 1, 5, 3, 6, 4}));
System.out.println(new Solution().maxProfit2(new int[]{7, 1, 5, 3, 6, 4}));
}
static class Solution {
/*
暴力法
*/
public int maxProfit(int[] prices) {
int maxprofit = 0;
for (int i = 0; i < prices.length - 1; i++) {
for (int j = i + 1; j < prices.length; j++) {
int profit = prices[j] - prices[i];
if (profit > maxprofit) {
maxprofit = profit;
}
}
}
return maxprofit;
}
/*
一次遍历
*/
public int maxProfit2(int[] prices) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
}
}
LeetCode124 二叉树中的最大路径和
题目详情
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
提示:
树中节点数目范围是 [1, 3 * 104]
-1000 <= Node.val <= 1000
代码
class LeetCode124 {
public static void main(String[] args) {
System.out.println(new Solution().maxPathSum(TreeNode.buildBinaryTree(new Integer[]{-10, 9, 20, null, null, 15, 7})));
}
/*
首先,考虑实现一个简化的函数 maxGain(node),
该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大。
具体而言,该函数的计算如下。
空节点的最大贡献值等于 00。
非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)。
例如,考虑如下二叉树。
-10
/ \
9 20
/ \
15 7
叶节点 99、1515、77 的最大贡献值分别为 99、1515、77。
得到叶节点的最大贡献值之后,再计算非叶节点的最大贡献值。节点 2020 的最大贡献值等于 20+\max(15,7)=3520+max(15,7)=35,节点 -10−10 的最大贡献值等于 -10+\max(9,35)=25−10+max(9,35)=25。
上述计算过程是递归的过程,因此,对根节点调用函数 maxGain,即可得到每个节点的最大贡献值。
根据函数 maxGain 得到每个节点的最大贡献值之后,如何得到二叉树的最大路径和?对于二叉树中的一个节点,
该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和。
维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和。
*/
static class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
public int maxGain(TreeNode node) {
if (node == null) {
return 0;
}
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
int leftGain = Math.max(maxGain(node.left), 0);
int rightGain = Math.max(maxGain(node.right), 0);
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
int priceNewpath = node.val + leftGain + rightGain;
// 更新答案
maxSum = Math.max(maxSum, priceNewpath);
// 返回节点的最大贡献值
return node.val + Math.max(leftGain, rightGain);
}
}
}
LeetCode128 最长连续序列
题目详情
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
代码
public class LeetCode128 {
public static void main(String[] args) {
System.out.println(new Solution().longestConsecutive(new int[]{0, 3, 7, 2, 5, 8, 4, 6, 0, 1}));
}
static class Solution {
/*
哈希表
*/
public int longestConsecutive(int[] nums) {
Set num_set = new HashSet();
for (int num : nums) {
num_set.add(num);
}
int longestStreak = 0;
for (int num : num_set) {
if (!num_set.contains(num - 1)) {
int currentNum = num;
int currentStreak = 1;
while (num_set.contains(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}
}
}
LeetCode136 只出现一次的数字
题目详情
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
代码
public class LeetCode136 {
public static void main(String[] args) {
System.out.println(new Solution().singleNumber(new int[]{4, 1, 2, 1, 2}));
System.out.println(new Solution().singleNumber2(new int[]{4, 1, 2, 1, 2}));
}
static class Solution {
public int singleNumber(int[] nums) {
Map map = new HashMap<>();
for (Integer i : nums) {
Integer count = map.get(i);
count = count == null ? 1 : ++count;
map.put(i, count);
}
for (Integer i : map.keySet()) {
Integer count = map.get(i);
if (count == 1) {
return i;
}
}
return -1; //找不到
}
/*
位运算
*/
public int singleNumber2(int[] nums) {
int ans = nums[0];
if (nums.length > 1) {
for (int i = 1; i < nums.length; i++) {
ans = ans ^ nums[i];
}
}
return ans;
}
}
}
LeetCode139 单词拆分
题目详情
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅有小写英文字母组成
wordDict 中的所有字符串 互不相同
代码
public class LeetCode139 {
public static void main(String[] args) {
System.out.println(new Solution().wordBreak("applepenapple", Arrays.asList("apple", "pen")));
System.out.println(new Solution().wordBreak2("applepenapple", Arrays.asList("apple", "pen")));
}
static class Solution {
/*
动态规划
*/
public boolean wordBreak(String s, List wordDict) {
Set wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
public boolean wordBreak2(String s, List wordDict) {
Set wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
}
LeetCode141 环形链表
题目详情
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
代码
public class LeetCode141 {
public static void main(String[] args) {
System.out.println(new Solution().hasCycle(ListNode.buildNode(new int[]{3, 2, 0, -4})));
}
static class Solution {
/*
哈希表
*/
public boolean hasCycle(ListNode head) {
Set nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
/*
我们设置快慢两个指针,快指针每次走2步,慢指针每次走1步
如视频所示,跑道是链表,兔子(快指针)每次走2步,乌龟(慢指针)每次走1步,在走完跑道(链表)的前提下,如果兔子和乌龟(快慢指针)相遇,说明跑道(链表)有环,否则说明跑道没有环。
*/
public boolean hasCycle2(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
}
LeetCode142 环形链表 II
题目详情
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
代码
public class LeetCode142 {
public static void main(String[] args) {
ListNode.printNode(new Solution().detectCycle(ListNode.buildNode(new int[]{3, 2, 0, -4})));
ListNode.printNode(new Solution2().detectCycle(ListNode.buildNode(new int[]{3, 2, 0, -4})));
ListNode.printNode(new Solution3().detectCycle(ListNode.buildNode(new int[]{3, 2, 0, -4})));
}
/*
哈希表
我们遍历链表中的每个节点,并将它记录下来;
一旦遇到了此前遍历过的节点,就可以判定链表中存在环。
借助哈希表可以很方便地实现。
*/
static class Solution {
public ListNode detectCycle(ListNode head) {
Set visited = new HashSet();
ListNode node = head;
while (node != null) {
if (visited.contains(node)) {
return node;
}
visited.add(node);
node = node.next;
}
return null;
}
}
/*
快慢指针
*/
static class Solution2 {
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head, fast = head;
while (fast != null) {
slow = slow.next;
if (fast.next != null) {
fast = fast.next.next;
} else {
return null;
}
if (fast == slow) {
ListNode ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
}
static class Solution3 {
private ListNode getIntersect(ListNode head) {
ListNode tortoise = head;
ListNode hare = head;
// 快指针要么循环循环并遇到慢指针,要么到达非循环列表末尾的 `null`。
while (hare != null && hare.next != null) {
tortoise = tortoise.next;
hare = hare.next.next;
if (tortoise == hare) {
return tortoise;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// 如果有一个循环,快/慢指针将在某个节点处相交。 否则,就没有循环,所以我们找不到循环的本质。
ListNode intersect = getIntersect(head);
if (intersect == null) {
return null;
}
// 为了找到循环的位置,我们有两个指针以相同的速度遍历——一个来自列表的前面,另一个来自交叉点。
ListNode ptr1 = head;
ListNode ptr2 = intersect;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1;
}
}
}