文章目录
- 1、[剑指 Offer 04. 二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/)
- 2、[剑指 Offer 11. 旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)
- 3、[剑指 Offer 50. 第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/)
- 4、[面试题32 - I. 从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)
- 5、[剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)
- 6、[剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
- 7、[剑指 Offer 26. 树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)
- 8、[剑指 Offer 27. 二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)
- 9、[剑指 Offer 28. 对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)
- 10、[剑指 Offer 10- I. 斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)
1、剑指 Offer 04. 二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
思路(参考题解)
从左下角开始遍历,i表示行,j表示列。i从最大行开始遍历(matrix.length - 1),如果目标值<当前值,说明该行大于目标值,因为是行递增的;j从第0列开始遍历,如果目标值>当前值,说明该行后面可能有满足条件的值,列方向往后移。找到满足条件的值返回true
public class findNumberIn2DArray {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
//(从左下角开始)i:行 j:列
int i = matrix.length - 1, j = 0;
while (i >= 0 && j < matrix[0].length) {
//如果目标值<当前值,说明该行大于目标值,因为是行递增的
if (target < matrix[i][j]) {
i --;
//如果目标值>当前值,说明该行后面可能有满足条件的值,列方向往后移
} else if (target > matrix[i][j]) {
j ++;
} else {
//找到目标值,返回true
return true;
}
}
return false;
}
}
2、剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
思路:
暴力解法:遍历查找最小值
public int minArray(int[] numbers) {
//暴力解法
int res = numbers[0];
for (int i = 1; i < numbers.length; i ++) {
if (numbers[i] < res) {
res = numbers[i];
}
}
return res;
}
二分法
最小值元素一定在右排序数组中,因此通过二分法找到右排序数组,其中首位就是最小值。本题使用j与mid的值进行比较,从而缩小区间,不能用i,因为当mid>i时,无法判断mid在哪个排序数组中 ,如果与j比较,j肯定在右排序数组中。
public int minArray2(int[] numbers) {
//二分法
//旋转数组 最小值肯定在右排序数组
int i = 0, j = numbers.length - 1;
while (i < j) {
int mid = (i + j) / 2;
//mid在左排序数组中
if (numbers[mid] > numbers[j]) {
i = mid + 1;
//mid在右排序数组中
} else if(numbers[mid] < numbers[j]) {
j = mid;
} else {
j --;
}
}
return numbers[i];
}
}
3、剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = “abaccdeff”
输出:‘b’
思路:哈希表
先将字符串转为字符数组,第一次遍历,如果该字符是第一次出现,添加键值对dic(c,true),如果不是第一次出现,则添加键值对dic(c,false);第二次遍历,查找第一个值为true的字符,找到返回字符。如果没有,返回空格。
public class firstUniqChar {
public char firstUniqChar2(String s) {
//参考题解 哈希表
//dic(c,true)
HashMap<Character, Boolean> dic = new HashMap<Character, Boolean>();
char[] sc = s.toCharArray();//字符串转为字符数组
//第一次遍历,将所有字符添加到map
for (char c : sc) {
//如果map中不包含字符c,第一次出现,添加键值对dic(c,true)
if (! dic.containsKey(c)) {
dic.put(c, true);
} else {
//如果包含了字符c,说明重复出现,添加键值对dic(c,false)
dic.put(c, false);
}
}
//第二次遍历,返回map中的第一个值
for (char c : sc) {
//dic.get(c)为true,说明只出现了1次,返回
if (dic.get(c)) {
return c;
}
}
return ' ';
}
}
4、面试题32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如: 给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回:[3,9,20,15,7]
思路:二叉树层次遍历,利用队列
先遍历根节点,如果根节点为空,返回空数组;如果根节点不为空,将根节点入队,根节点的值放入数组列表list中(动态数组可以自动扩充,最终放入数组中用于返回),依次判断当前节点的左右节点,不为空则放入队列中,进行下一次的遍历。
//二叉树节点
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
//层次遍历
public class levelOrder {
public int[] levelOrder(TreeNode root) {
//如果是空树,返回空数组
if (root == null) {
return new int[0];
}
ArrayList<Integer> list = new ArrayList<>();//动态数组列表:用于存放返回值
Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
queue.add(root);//根节点不为空,先入队
//队列不为空
while (! queue.isEmpty()) {
TreeNode node = queue.poll();//移除并返回队头节点
list.add(node.val);//将队头节点的值放入list
//判断当前节点的左右节点是否为空,如果不为空,放入节点队列
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
int[] res = new int[list.size()];//返回数组
//遍历动态数组列表,将其值存入返回数组
for (int i = 0; i < list.size(); i ++) {
res[i] = list.get(i);
}
return res;
}
}
5、剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如: 给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
思路:与上一题基本相似,不同点就是每层遍历需要单独打印
每一轮遍历的队列中存放的就是该层的节点,需要增加一个for循环遍历,判断该层遍历是否完成,一层一层的处理
使用的是双层List的形式返回:List<List<Integer>>,内层List<Integer>表示每层的数,外层List表示多少层
public class levelOrder2 {
public List<List<Integer>> levelOrder(TreeNode root) {
//List<List<Integer>>:内层List<Integer>表示每层的数,外层List表示多少层
List<List<Integer>> res = new ArrayList<>();//存放返回数组列表
Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
if (root != null) {
queue.add(root);//根节点不为空,先入队
}
//队列不为空
while (! queue.isEmpty()) {
List<Integer> temp = new ArrayList<>();//存放当层的数
//当前queue中就是该当前层的节点数,一层一层处理
for (int i = queue.size(); i > 0; i --) {
TreeNode node = queue.poll();//移除并返回队头节点
temp.add(node.val);//将当层节点的值放入temp
//判断当前节点的左右节点是否为空,如果不为空,放入节点队列,此处为下一层
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
//当层节点值全部放入temp,处理完一层后,将整个temp放入res
res.add(temp);
}
return res;
}
}
6、剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:给定二叉树: [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
思路:跟上题类似,不同点是:对每层输出是进行判断,奇数层直接打印,偶数层逆序之后再打印
public class levelOrder3 {
public List<List<Integer>> levelOrder(TreeNode root) {
//List<List<Integer>>:内层List<Integer>表示每层的数,外层List表示多少层
List<List<Integer>> res = new ArrayList<>();//存放返回数组列表
Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
if (root != null) {
queue.add(root);//根节点不为空,先入队
}
//队列不为空
int j = 0;//记录当前层是奇数还是偶数,奇数层从左往右打印,偶数层从右往左打印,即逆序
while (! queue.isEmpty()) {
j ++;
List<Integer> temp = new ArrayList<>();//存放当层的数
//当前queue中就是该当前层的节点数,一层一层处理
for (int i = queue.size(); i > 0; i --) {
TreeNode node = queue.poll();//移除并返回队头节点
temp.add(node.val);//将当层节点的值放入temp
//判断当前节点的左右节点是否为空,如果不为空,放入节点队列,此处为下一层
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
//当层节点值全部放入temp,处理完一层后,将整个temp放入res
//如果是奇数层,直接放入res,如果是偶数层,逆序之后再放入res
if (j % 2 == 0) {
Collections.reverse(temp);
}
res.add(temp);
}
return res;
}
}
7、剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/
4 5
/
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
public class isSubStructure2 {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//1.当A B任一个为空,返回false
//2.recur(A, B):以A为根节点的子树包含B
//3.isSubStructure(A.left, B):B是A的左子树结构;isSubStructure(A.right, B):B是A的右子树结构
return (A != null && B != null && (recur(A, B) ||
isSubStructure(A.left, B) || isSubStructure(A.right, B)));
}
public boolean recur(TreeNode A, TreeNode B) {
//3个终止条件
//1.B为空,说明树B已匹配完成,返回true
if (B == null) {
return true;
}
//2.A为空,说明已经越过A,匹配失败,返回false
//3.A B的值不同,匹配失败,返回false
if (A == null || A.val != B.val) {
return false;
}
//继续匹配子节点
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
//第二种写法
public class isSubStructure {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//A B有一个为空,则不能是子结构
if (A == null || B == null) {
return false;
}
//如果A B根节点的值相等,则继续比较后续节点(A的左右节点、B的左右节点),使用方法helper比较后续节点
if (A.val == B.val && (helper(A.left, B.left) && helper(A.right, B.right))) {
return true;
}
//如果A B的根节点不同,则将A的左右子节点分别再与B进行比较
return isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
//判断后续节点
public boolean helper(TreeNode root1, TreeNode root2) {
//B的子节点为空,如果之前节点是其子结构,则返回true
if (root2 == null) {
return true;
}
//B的子节点不为空,A的子节点为空,表明B不是A的子结构,返回false
if (root1 == null) {
return false;
}
//A B子节点都不为空,且其值相等,继续比较后续节点(左右子节点)
if (root1.val == root2.val) {
return helper(root1.left, root2.left) && helper(root1.right, root2.right);
} else {
//A B子节点的值不相等,表明B不是A的子结构,返回false
return false;
}
}
}
class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int max = 0, min = 14;
for(int num : nums) {
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(repeat.contains(num))
return false; // 若有重复,提前返回 false
repeat.add(num); // 添加此牌至 Set
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
8、剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/
2 7
/ \ /
1 3 6 9
镜像输出:
4
/
7 2
/ \ /
9 6 3 1
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
思路:
遍历二叉树,交换每个节点的左右节点
public class mirrorTree {
public TreeNode mirrorTree(TreeNode root) {
//当前根节点为空,表明已经越过叶子结点,返回null
if (root == null) {
return null;
}
TreeNode tmp = root.left;//保存根节点的左节点
root.left = mirrorTree(root.right);//对右子树镜像,赋值给左节点
root.right = mirrorTree(tmp);//对左子树镜像,赋值给右节点
return root;
}
}
9、剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/
2 2
\
3 3
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
思路:
从上至下遍历,判断根节点的左右节点是否是对称的
public class isSymmetric {
public boolean isSymmetric(TreeNode root) {
//空树对称
if (root == null) {
return true;
}else {
return recur(root.left, root.right);//判断根节点的左右子树是否对称
}
}
public boolean recur(TreeNode leftNode, TreeNode rightNode) {
//左右子树都为空,对称
if (leftNode == null && rightNode == null) {
return true;
}
//左右子树只有一个为空,或者都不为空但其值不同,不对称
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
//继续判断左右子树的左右子树 注意:左右节点的对应关系
return recur(leftNode.left, rightNode.right) && recur(rightNode.left, leftNode.right);
}
}
10、剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
class Solution {
public int fib(int n) {
if(n == 0) return 0;
int fn0 = 0;
int fn1 = 1;
int temp;
for(int i = 2; i <= n; i++){
temp = fn0 + fn1;
fn0 = fn1;
fn1 = temp% 1000000007; // 每次运算都取模,避免越界
}
return fn1;
}
}