算法---- Leetcode剑指offer-Java版题解
Posted whc__
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法---- Leetcode剑指offer-Java版题解相关的知识,希望对你有一定的参考价值。
剑指offer题解
- 1. 数组中重复的数字
- 2. 二维数组中的查找
- 3. 替换空格
- 4. 从尾到头打印链表
- 5. 重建二叉树
- 6. 用两个栈实现队列
- 7. I_斐波那契数列
- 8. II_青蛙跳台阶问题
- 9. 旋转数组的最小数字
- 10. 矩阵中的路径
- 11. 机器人的运动范围
- 12. I_剪绳子
- 13. II_剪绳子
- 14. 二进制中1的个数
- 15. 数值的整数次方
- 16. 打印从1到最大的n位数
- 17. 删除链表的节点
- 18. 正则表达式匹配
- 19. 表示数值的字符串
- 20. 调整数组顺序使奇数位于偶数前面
- 21. 链表中倒数第k个节点
- 22. 反转链表
- 23. 合并两个排序的链表
- 24. 树的子结构
- 25. 二叉树的镜像
- 26. 对称的二叉树
- 27. 顺时针打印矩阵
- 28. 栈的压入_弹出序列
- 29. I_从上到下打印二叉树
- 30. II_从上到下打印二叉树
- 31. III_从上到下打印二叉树
- 32. 二叉搜索树的后序遍历序列
- 33. 二叉树中和为某一值的路径
- 34. 复杂链表的复制
- 35. 二叉搜索树与双向链表
- 36. 序列化二叉树
- 37. 字符串的排列
- 38. 数组中出现次数超过一半的数字
- 39. 最小的k个数
- 40. 数据流中的中位数
- 41. 连续子数组的最大和
- 42. 1到n整数中1出现的次数
- 43. 把数组排成最小的数
- 44. 把数字翻译成字符串
- 45. 礼物的最大价值
- 46. 最长不含重复字符的子字符串
- 47. 丑数
- 48. 第一个只出现一次的字符
- 49. 数组中的逆序对
- 50. 两个链表的第一个公共节点
- 51. I_在排序数组中查找数字
- 52. II_缺失的数字
- 53. 二叉搜索树的第k大节点
- 54. I_二叉树的深度
- 55. II_平衡二叉树
- 56. I_数组中数字出现的次数
- 57. II_数组中数字出现的次数
- 58. II_和为s的连续正数序列
- 59. 和为s的两个数字
- 60. I_翻转单词顺序
- 61. II_左旋转字符串
- 62. I_滑动窗口的最大值
- 63. II_队列的最大值
- 64. n个骰子的点数
- 65. 扑克牌中的顺子
- 66. 圆圈中最后剩下的数字
- 67. 股票的最大利润
- 68. 求1到n的和
- 69. 不用加减乘除做加法
- 70. 构建乘积数组
- 71. 把字符串转换成整数
- 72. I_二叉搜索树的最近公共祖先
- 73. 二叉树的最近公共祖先
- 补充类似题
1. 数组中重复的数字
/**
* 思路: 原地哈希,将nums[i]不断与nums[nums[i]]交换,直到nums[i] = i为止
* 如果在交换过程中出现nums[nums[i]]位置已经有元素了,那么就返回结果nums[i]
* 时间复杂度: O(n)
* 空间复杂度: O(1)
*/
public int findRepeatNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
while(nums[i] != i) {
if(nums[nums[i]] == nums[i]) {
return nums[i];
}
int temp = nums[i];
nums[i] = nums[nums[i]];
nums[temp] = temp;
}
}
return -1;
}
2. 二维数组中的查找
// 暴力解法,时间复杂度O(n*m) 空间复杂度O(1)
/*public boolean findNumberIn2DArray(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if(matrix[i][j] == target) {
return true;
}
}
}
return false;
}*/
/**
* 思路: 二分查找
* 枚举每行的最后一个元素x,进行二分查找
* 如果 x == target, 则直接返回
* 如果 x > target, 则排除当前整一列(纵坐标减1)
* 如果 x < target, 则排除当前整一行(横坐标加1)
*
* 时间复杂度: O(n+m)
* 空间复杂度: O(1)
*/
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0) {
return false;
}
int i = 0, j = matrix[0].length-1;
while(i <= matrix.length && j >= 0) {
if(matrix[i][j] == target) {
return true;
} else if(matrix[i][j] > target) {
j--;
} else {
i++;
}
}
return false;
}
3. 替换空格
/**
* 思路: 遍历字符串,遇到空格,则添加%20
* 时间: O(n)
* 额外空间: O(n)
*/
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
for(int i = 0; i < s.length(); i++) {
if(s.charAt(i) == ' ') {
res.append("%20");
} else {
res.append(s.charAt(i));
}
}
return res.toString();
}
4. 从尾到头打印链表
/*
* 思路: 用栈存储遍历得到的数据,然后再依次出栈添加到结果数组中
* 时间: O(n)
* 空间:O(n)
*/
/*public int[] reversePrint(ListNode head) {
Stack<Integer> stack = new Stack<>();
while(head != null) {
stack.push(head.val);
head = head.next;
}
int[] res = new int[stack.size()];
int idx = 0;
while(!stack.isEmpty()) {
res[idx++] = stack.pop();
}
return res;
}*/
/**
* 思路: 两次遍历链表
* 时间: O(n)
* 空间: O(1)
*/
public int[] reversePrint(ListNode head) {
int count = 0;
ListNode node = head;
while(node != null) {
count++;
node = node.next;
}
int[] res = new int[count];
node = head;
for (int i = count-1; i >= 0; i--) {
res[i] = node.val;
node = node.next;
}
return res;
}
5. 重建二叉树
/**
* 思路: 递归
* 1. 取前序遍历的第一个元素作为根节点
* 2. 先切割中序数组,根据根节点查找中序数组中的位置,切割成中序左数组[i_start,i_root_index)和中序右数组[i_root_index+1,i_end) 【左闭右开】
* 3. 再切割前序数组,根据中序数组中左数组的长度进行切割,切割成前序左数组[p_start+1, p_start+1+(i_root_index-i_start))和前序右数组[p_start+1+(i_root_index-i_start),p_end)
*/
// 存储前序遍历中的根节点在中序遍历的下标位置
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 初始化赋值
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
// 左闭右开区间
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
public TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
// 递归终止条件,前序数组遍历完,直接返回null
if(p_start == p_end) {
return null;
}
int root_val = preorder[p_start];
// 中序数组中根节点的下标位置, 根据此下标划分中序数组中的 中序左数组 和 中序右数组
int i_root_index = map.get(root_val);
// 构建根节点
TreeNode root = new TreeNode(root_val);
// 递归构造左子树
root.left = buildTreeHelper(preorder, p_start+1, p_start + 1 + (i_root_index-i_start), inorder, i_start, i_root_index);
// 递归构造右子树
root.right = buildTreeHelper(preorder, p_start+1+(i_root_index-i_start), p_end, inorder, i_root_index+1, i_end);
return root;
}
6. 用两个栈实现队列
Stack<Integer> pushStack;
Stack<Integer> popStack;
public _剑指_Offer_09_用两个栈实现队列() {
this.pushStack = new Stack<>();
this.popStack = new Stack<>();
}
public void appendTail(int value) {
pushStack.add(value);
}
public int deleteHead() {
// 弹出元素的栈中仍有元素,则返回出栈元素
if(popStack.size() > 0) {
return popStack.pop();
}
// 弹出元素的栈为空,入栈元素的栈也为空,返回-1
if(popStack.size() == 0) return -1;
// 入栈元素的栈为空,则不断从出栈的栈弹出元素
while(pushStack.size() > 0) {
popStack.add(pushStack.pop());
}
return popStack.pop();
}
7. I_斐波那契数列
public int fib(int n) {
if(n < 2) {
return n;
}
int a = 0;
int b = 1;
for(int i = 2; i <= n; i++) {
int sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return b;
}
8. II_青蛙跳台阶问题
/**
* 思路: 动态规划
* 1. dp[i] = dp[i-1] + dp[i-2]
* 2. 由于dp[i]只与前面两项相关,所以用三个变量sum,a,b记录,优化空间,降为O(1)
*
* 时间:O(n)
* 空间:O(1)
*/
public int numWays(int n) {
if(n < 2) {
return 1;
}
int a = 1;
int b = 1;
int sum = 0;
for(int i = 2; i <= n; i++) {
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return b;
}
9. 旋转数组的最小数字
/**
* 思路: 二分查找
* 将中间元素mid和右边元素right相比较,缩小搜索的范围
* numbers[mid] < numbers[right], 则 right = mid
* numbers[mid] > numbers[right], 则 left = mid + 1
* numbers[mid] = numbers[right], 则 right--
*/
public int minArray(int[] numbers) {
int left = 0;
int right = numbers.length-1;
while(left < right) {
int mid = (left + right) >>> 1;
if(numbers[mid] < numbers[right]) {
right = mid;
} else if(numbers[mid] > numbers[right]) {
left = mid + 1;
} else {
right--;
}
}
return numbers[left];
}
10. 矩阵中的路径
/**
*
* 思路: 深度优先搜索
* 1. 枚举矩阵中每个位置
* 2. 然后从当前位置开始深度优先搜索,从上下左右四个方向开始搜索
* 3. 过程中把遍历过的位置设置为一个特殊的标志符
* 时间复杂度: O(MN * k^3)
* 需要枚举MN个起点,时间复杂度为(MN)
* 方案数计算: 设字符串长度为 K ,搜索中每个字符有上、下、左、右四个方向可以选择,舍弃回头(上个字符)的方向,剩下 3 种选择,因此方案数的复杂度为 O(3^K)
* 空间复杂度: O(K)
* 最差K=MN
*/
int[] dx = {-1, 0, 1, 0}; // 上右下左
int[] dy = {0, 1, 0, -1};
public boolean hasPath (char[][] matrix, String word) {
if(matrix == null || matrix.length == 0) {
return false;
}
// 遍历矩阵中的每个元素,判断路径是否可达
for(int i = 0; i< matrix.length; i++) {
for(int j = 0; j < matrix[0].length; j++) {
if(dfs(matrix, word, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] matrix, String word, int x, int y, int k) {
// 不满足
if(matrix[x][y] != word.charAt(k)) {
return false;
}
if(k == word.length() - 1) {
return true;
}
char temp = matrix[x][y];
matrix[x][y] = '/';
// 上下左右四个方向
for(int i = 0; i < 4; i++) {
int a = x + dx[i];
int b = y + dy[i];
if(a >= 0 && a < matrix.length && b >= 0 && b < matrix[0].length) {
if(dfs(matrix, word, a, b, k+1)) {
return true;
}
}
}
// 若不满足,则进行回溯
matrix[x][y] = temp;
return false;
}
11. 机器人的运动范围
/**
* 思路: 深度优先搜索
*
* 1. 设置一个visited布尔数组记录已经被访问过的节点
* 2. 从(0,0)位置开始出发,因此只会往右和往下两个方向走
* 3. 设置一个全局变量res,记录dfs过程中满足条件的位置
*
* 时间: O(n*m)
* 空间: O(n*m)
*/
/*int res = 0;
int[] dx = {0,1}; // 向右 和 向下
int[] dy = {1,0};
public int movingCount(int m, int n, int k) {
if(m <= 0 || n <= 0 || k < 0) {
return 0;
}
boolean[][] visited = new boolean[m][n];
dfs(visited, 0, 0, m, n, k);
return res;
}
private void dfs(boolean[][] visited, int x, int y, int m, int n, int k) {
// 已被遍历过 返回
if(visited[x][y]) {
return;
}
visited[x][y] = true;
res++;
for (int i = 0; i < 2; i++) {
int a = x + dx[i];
int b = y + dy[i];
// 满足条件向右和向下搜索
if(a >= 0 && a < m && b >= 0 && b < n && digitSum(a) + digitSum(b) <= k) {
dfs(visited, a, b, m, n, k);
}
}
}
private int digitSum(int x) {
int sum = 0;
while(x > 0) {
sum += x % 10;
x /= 10;
}
return sum;
}*/
/**
* 思路: 宽度优先搜索(bfs)
* 1. 创建visited[][]数组记录已被访问过的节点
* 2. 队列中存放可被访问的位置,每次弹出一个位置坐标(x,y),判断是否被访问过,
* 如果被访问过,则continue(易错点,应该在这里),并将其下方和右下方可以被访问的位置入队
* 3. 全局变量res记录队列中可以到达的格子数量
* 4. 队列为空结束循环,返回res
*
* 时间: O(n*m)
* 空间: O(n*m)
*/
public int movingCount(int m, int n, int k) {
if(m <= 0 || n <= 0 || k < 0) {
return 0;
}
int res = 0;
boolean[][] visited = new boolean[m][n];
// 存储每个节点的横纵坐标
Queue<int[]> queue = new LinkedList<>();
queue.add(new int[]{0,0});
int[] dx = {0,1};
int[] dy = {1,0};
while(!queue.isEmpty()) {
int[] x = queue.poll();
if(visited[x[0]][x[1]]) {
continue;
}
res++;
visited[x[0]][x[1]] = true;
for(int i = 0; i < 2; i++) {
int a = x[0] + dx[i];
int b = x[1] + dy[i];
if(a >= 0 && a < m && b >= 0 && b < n && digitSum(a) + digitSum(b) <= k) {
queue.add(new int[]{a,b});
}
}
}
return res;
}
private int digitSum(int x) {
int sum = 0;
while(x > 0) {
sum += x % 10;
x /= 10;
}
return sum;
}
// 如果给的数组是一维数组,需要将二维数组转化为一维数组
// 深度优先搜索
/*int res = 0;
int[] dx = {0, 1}; // 右下
int[] dy = {1, 0};
public int movingCount(int threshold, int rows, int cols) {
if(rows <= 0 || cols <= 0 || threshold < 0) return 0;
// 一维代替二维
boolean[] visited = new boolean[rows * cols];
dfs(visited, rows, cols, 0, 0, threshold);
return res;
}
public void dfs(boolean[] visited, int rows, int cols, int row, int col, int threshold) {
// 不满足条件以上是关于算法---- Leetcode剑指offer-Java版题解的主要内容,如果未能解决你的问题,请参考以下文章
算法leetcode剑指 Offer II 041. 滑动窗口的平均值(rust重拳出击)
算法leetcode剑指 Offer II 041. 滑动窗口的平均值(rust重拳出击)
算法---- Leetcode剑指offer-Java版题解
算法---- Leetcode剑指offer-Java版题解