剑指offer Java代码解析
- 剑指 offer 03:数组中重复的数字
- 剑指 offer 04:二维数组中的查找
- 剑指 offer 05:替换空格
- 剑指 offer 06:从尾到头打印链表
- 剑指 offer 07:重建二叉树
- 剑指 offer 08:二叉树的下一个节点
- 剑指 offer 09:用两个栈实现队列
- 剑指 offer 10-I:斐波那契数列
- 剑指 offer 10-II:青蛙跳台阶问题
- 剑指 offer 11:旋转数组的最小数字
- 剑指 offer 12:矩阵中的路径
- 剑指 offer 13:机器人的运动范围
- 剑指 offer 14-I:剪绳子
- 剑指 offer 14-II:剪绳子
- 剑指 offer 15:二进制中1的个数
- 剑指 offer 16:数值的整数次方
- 剑指 offer 17:打印从1到最大的n位数
- 剑指 offer 18:删除链表的节点
- 剑指 offer 21:调整数组顺序使奇数位于偶数前面
- 剑指 offer 22:链表中倒数第k个节点
- 剑指 offer 24:反转链表
- 剑指 offer 25:合并两个有序链表
- 剑指 offer 27:二叉树的镜像
- 剑指 offer 28:对称二叉树
- 剑指 Offer 31. 栈的压入、弹出序列
- 剑指 Offer 50. 第一个只出现一次的字符
- 剑指 Offer 52. 两个链表的第一个公共节点
剑指 offer 03:数组中重复的数字
题目描述
找出数组中重复的数字。(传送门)
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
解题思路+代码实现
方法一: 排序+for循环
public int findRepeatNumber(int[] nums) {
int len = nums.length;
Arrays.sort(nums);
for (int i = 0; i < len - 1; i++) {
if (nums[i + 1] - nums[i] == 0) {
return nums[i];
}
}
return -1;
}
方法二:HashSet
这里使用set集合的性质:
- Set集合中的对象不按特定的方式排序,只是简单的将对象加入到集合中,但是Set集合不能包括重复对象
- Set接口继承了Collection接口,因此也包含Collection接口的所有方法
public int findRepeatNumber2(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) {
if (!set.add(num)) {//当重复元时会返回false
return num;
}
}
return -1;
}
方法三:原地交换
首先注意这句话在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。,那么每个数字都可以放在它对应的下标,比如说0放在nums[0] 位置,1放在nums[1]位置。
- 从头到尾依次扫描这个数组的每一个元素
- 看下标为 i 的数字是不是等于 i ,如果它的值为m != i 就和下标为m 的值进行比较,如果相等就返回这个重复的数字,如果不相同那就交换。
- 重复这样的操作,知道遍历结束
public int findRepeatNumber(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
while (i != nums[i]) { // 注意在这里时while 循环可能 交换回来的数也不等于 i
if (nums[i] != nums[nums[i]]) {
// 不等于则进行交换
int temp = nums[i];
nums[i] = nums[temp];//注意这里如果写nums[i] = nums[nums[i]];会超出时间复杂度
nums[temp] = temp;
}else{
return nums[i];
}
}
}
return -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]
]
解题思路+代码实现
方法一: 暴力查找
public boolean findNumberIn2DArray11(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int ROW = matrix.length;
int COL = matrix[0].length;
for (int i = 0; i < ROW ; i++) {
for (int j = 0; j < COL; j++) {
if (matrix[i][j] == target) {
return true;
}
}
}
return false;
}
方法二:优化方法
我们可以从右上角开始查找,根据矩阵元素的规律,当左上角元素小于目标值则改行一定是都小于目标值的,直接就不需要判断该行的其他元素行++即可;当左上角元素大于该目标值,则改行的下方也就找不到,一定就在该列的前边,进行列–即可。这样就优化了暴力遍历每一个值了。
查找过程:
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int i = 0;
int j = matrix[0].length-1;
while (i < matrix.length && j >= 0) {
int tmp = matrix[i][j];
if (tmp > target) {
j--;
} else if (tmp < target) {
i++;
} else {
return true;
}
}
return false;
}
剑指 offer 05:替换空格
题目描述
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
(传送门)
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
解题思路+代码实现
方法一: replaceAll
public String replaceSpace(String s) {
//输入:s = "We are happy."
//输出:"We%20are%20happy."
return s.replaceAll(" ","%20");
}
方法二:字符数组
public String replaceSpace(String s) {
//输入:s = "We are happy."
//输出:"We%20are%20happy."
//return s.replaceAll(" ","%20");
int len = s.length();
char[] array = new char[3 * len]; // 按照最坏打算扩容
int size = 0; // 记录下标位置
for (int i = 0; i < len; i++) {
if (s.charAt(i) != ' ') {
array[size++] = s.charAt(i);
} else {
array[size++] = '%';
array[size++] = '2';
array[size++] = '0';
}
}
String ans = new String(array, 0, size);// 将有效部分转为字符串
return ans;
}
剑指 offer 06:从尾到头打印链表
题目描述
输入一个链表的头节点,从尾到头反过来返回每个节点的(用数组返回)。(传送门)
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
解题思路+代码实现
方法一:递归
public int[] reversePrint(ListNode head) {
int count = 0;
ListNode tmp = head;
while (tmp != null) {
tmp = tmp.next;
count++;
}
int[] array = new int[count];
reversePrintHelper(head,array,count-1);
return array;
}
private void reversePrintHelper(ListNode head, int[] array,int i) {
if (head == null) {
return;
}
array[i] = head.val;
reversePrintHelper(head.next,array,--i);
}
方法二:辅助栈
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
int size = stack.size();
int[] ans = new int[size];
for (int i = 0; i < size; i++) {
ans[i] = stack.pop().val;
}
return ans;
}
剑指 offer 07:重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。(传送门)
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
解题思路+代码实现
方法一:递归
public class Interview07 {
//输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
//前序遍历 preorder = [3,9,20,15,7]
//中序遍历 inorder = [9,3,15,20,7]
private int index = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
index = 0;
return buildTreeHelper(preorder, inorder, 0, inorder.length);//递归
}
//[inorderLeft,inorderRight) 表示当前这个子树的中序遍历区间
private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int inorderLeft, int inorderRight) {
if (inorderLeft >= inorderRight) {
return null;
}
if (index >= preorder.length) {
return null;
}
// 创建一个新的结点
TreeNode newNode = new TreeNode(preorder[index]);
// 在中序结果中查找newNode的位置
int pos = find(inorder, inorderLeft, inorderRight, newNode.val);
index++;
newNode.left = buildTreeHelper(preorder, inorder, inorderLeft, pos);
newNode.right = buildTreeHelper(preorder, inorder, pos + 1, inorderRight);
return newNode;
}
// 在中序中查找 newNode.val位置
private int find(int[] inorder, int inorderLeft, int inorderRight, int val) {
for (int i = inorderLeft; i < inorderRight; i++) {
if (inorder[i] == val) {
return i;
}
}
return -1;
}
}
剑指 offer 08:二叉树的下一个节点
题目描述
给定一棵二叉树和其中的一个节点,如何找到中序遍历的序列的下一个节点?树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
例如:如下的二叉树:
a
/ \
b c
/ \ / \
d e f g
/ \
h i
其中序遍历结果为=={ d, b, h, e, i, a, f, c, g}==
解题思路+代码实现
现在分为两大种情况:
- 1.该节点有右子树,则下一节点为他右子树的中序遍历的第一个值(例如节点b的下一节点为h,节点a的下一个节点是f)
- 2.该节点没有右孩子,则需要去找其父亲节点(反正结果一定是一个父节点或则null)
- 2.1 如果该节点是父亲节点的左孩子,那么下一节点就是该父节点(例如d的下一节点是b)
- 2.2 如果该节点是父亲节点的右孩子,那么需要沿着父节点向上遍历,直到找到那个是父节点的左孩子的节点,那么下一节点是该父节点。(例如b的父节点a就是节点i的下一节点)
- 2.3 如果遍历到root根节点仍然没有找到是父节点左孩子的节点,那么则返回None (例如节点g没有下一节点)
Java代码
public TreeLinkNode getNext(TreeLinkNode pNode){
// 1,首先判断为空的情况
if(pNode == null){
return null;
}
// 2.当前节点有右子节点
TreeLinkNode curr = pNode;
if(null != pNode.right){
//则去找最左边的子节点即可
curr = curr.right;
while (curr.left!=null){
curr = curr.left;
}
return curr;
// 3.当前节点没有由右子节点
}else {
//就去父节点中找,反正得到的结果一定是一个父节点
//下面这个可以考虑i节点的下一节点是a的情况
while(curr.parent !=null && curr == curr.parent.right){
curr = curr.parent;
}
return curr.parent;
}
}
剑指 offer 09:用两个栈实现队列
题目描述
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )(传送门)
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
解题思路+代码实现
Java代码
public class Interview09 {
class CQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public CQueue() {
stack1 = new Stack<Integer>();// 负责入栈
stack2 = new Stack<Integer>();// 负责出栈
}
// 尾插
public void appendTail(int value) {
stack1.add(value);// 直接入栈
}
// 头删
public int deleteHead() {
// stack2 不为空直接出栈,并返回
if (!stack2.isEmpty()) {
return stack2.pop();
}
// stack2为空,从stack1 往2 中压栈(全部)
if(stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
// 如果 2 还是空直接返回 -1 ,否则 stack2 出栈即可
return stack2.isEmpty() ? -1 : stack2.pop();
}
}
}
剑指 offer 10-I:斐波那契数列
题目描述
(传送门)写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 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
示例 2:
输入:n = 5
输出:5
解题思路+代码实现
Java代码
public class Interview10_1 {
public int fib(int n) {
// F(0) = 0, F(1) = 1
// F(N) = F(N - 1) + F(N - 2)
if (n < 2) {
return n;
}
int f0 = 0;
int f1 = 1;
int f2 = 1;
for (int i = 2; i < n; i++) {
f2 = (f0 + f1)%1000000007;
f0 = f1;
f1 = f2;
}
return f2;
}
}
剑指 offer 10-II:青蛙跳台阶问题
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。(传送门)
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
解题思路+代码实现
Java代码
public static int numWays(int n) {
//输入:n = 2
//输出:2
if (n < 2) {
return 1;
}
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = (dp[i - 1] + dp[i - 2])%1000000007;
}
return dp[n];
}
剑指 offer 11:旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 (传送门)
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
解题思路+代码实现
方法一:暴力
暴力从左往右找,因为他是有序的经过旋转一次,当出现从大到小的转变,那么那个小的数就是找的最小值。
public int minArray111(int[] numbers) {
int len = numbers.length;
for (int i = 0; i < len - 1; i++) {
if (numbers[i] > numbers[i + 1]) {
return numbers[i + 1];
}
}
return numbers[0];
}
方法二:二分
注意为什莫是left = mid+1;(mid大于right,那么mid肯定不是最小,可以直接排除掉)
注意为什莫是right = mid;(mid小于right,mid有可能是最小的就不能排除)
第三种需要考虑到相同时,如下两种情况不能判断最小是在左边区间还是右边区间,所以只能暴力排除一个:
public int minArray(int[] numbers) {
// 1 3 5
int len = numbers.length;
int left = 0;
int right = len - 1;
int mid = 0;
while (left < right) {
mid = left + (right - left) / 2;
if (numbers[mid] > numbers[right]) {
left = mid+1;
} else if (numbers[mid] < numbers[right]) {
right = mid;
} else {
right--;// 因为当出现相同的元素是就不能判断最小值在他的左边还是右边所以需要,--(暴力遍历结合)
}
}
return numbers[right];
}
剑指 offer 12:矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。(传送门)
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
解题思路+代码实现
方法一:DFS+剪枝
public class Interview12 {
public boolean exist(char[][] board, String word) {
int len = word.length();
int ROW = board.length;
int COL = board[0].length;
if (ROW * COL < len) {
return false;
}
int index = 0;
// 监视每个位置是否被用过了
boolean[][] visit_board = new boolean[ROW][COL];
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (existHelper(board, visit_board, ROW, COL, i, j, word, len, index)) {
return true;
}
}
}
return false;
}
// [["a","b"],["c","d"]]
// "abcd"
private boolean existHelper(char[][] board, boolean[][] visit_board, int ROW, int COL, int i, int j, String word, int len, int index) {
if (i < 0 || j < 0 || i >= ROW || j >= COL || visit_board[i][j] || board[i][j] != word.charAt(index)) {
return false;// 有一个为真,就返回flase说明不满足条件,是找不到的
}
if (visit_board[i][j] == false && (board[i][j] == word.charAt(index))) {
visit_board[i][j] = true;
// 找到最后一个,说明找到了
if (index + 1 >= len) {
return true;
}
}
// 去找他的四个方向
if ( existHelper(board, visit_board, ROW, COL, i+1, j, word, len, index+1)||
existHelper(board, visit_board, ROW, COL, i-1, j, word, len, index+1)||
existHelper(board, visit_board, ROW, COL, i, j+1, word, len, index+1)||
existHelper(board, visit_board, ROW, COL, i, j-1, word, len, index+1)) {
return true;
}
// 四个方向都没有找到,那么visit_board[i][j]这个位置去找是找不到的,置为false在往回退
visit_board[i][j] = false;
return false;
}
}
剑指 offer 13:机器人的运动范围
题目描述
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?(传送门)
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
解题思路+代码实现
方法一:DFS
/**
* @ClassName Interview13
* @Description :TODO
* @Author Josvin
* @Date 2021/01/16/13:33
*/
public class Interview13 {
public int movingCount(int m, int n, int k) {
// 需要一个监视数组,记录位置是否被访问
boolean[][] visit = new boolean[m][n];
return movingCountHelper(m, n, 0, 0, k, visit);
}
private int movingCountHelper(int m, int n, int i, int j, int k, boolean[][] visit) {
// 递归返回条件:越界,该位置已经被访问,各个位数之和大于k
if (i < 0 || j < 0 || i >= m || j >= n || visit[i][j] == true || digit_num(i, j) > k) {
return 0;
}
// 程序走到这里说明,该位置没有被访问,并且没有越界,符合访问条件,则将其置为true
visit[i][j] = true;
// 递归搜索四个方向
return 1 + movingCountHelper(m, n, i, j - 1, k,visit) + movingCountHelper(m, n, i, j + 1, k,visit) + movingCountHelper(m, n, i + 1, j,k, visit) + movingCountHelper(m, n, i - 1, j, k,visit);
}
// 计算(i,j),i和j各个位数之和
private int digit_num(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
}
剑指 offer 14-I:剪绳子
题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。(传送门)
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
解题思路+代码实现
方法一:动态规划
public int cuttingRope(int n) {
int[] dp = new int[n + 1];// 长度为 n 时最大的乘积
dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 2; j < i; j++) {
// j来遍历 i 之前的基础上
dp[i] = Math.max( Math.max(j*(i-j),dp[i-j]*j), dp[i]);
}
}
return dp[n];
}
方法二:数学规律
由数学推导得到:尽可能将绳子以长度 为3 等分为多段时,乘积最大。
public int cuttingRope(int n) {
if (n <= 3) {
return n - 1;
}
if (n == 4) {
return 4;
}
int ans = 1;
while (n > 4) {
ans *= 3;
n = n - 3;
}
// 最后n的值只有可能是:2、3、4。而2、3、4能得到的最大乘积恰恰就是自身值
// 因为2、3不需要再剪了(剪了反而变小);4剪成2x2是最大的,2x2恰巧等于4
return ans*n;
}
剑指 offer 14-II:剪绳子
题目描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。(传送门)
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
解题思路+代码实现
方法一:数学规律
由数学推导得到:尽可能将绳子以长度 为3 等分为多段时,乘积最大。
这道题和上道题的区别就是数据范围变大了,在I 的第二种解法上进行取模即可,注意的是要使用long类型。
public int cuttingRope(int n) {
if (n <= 3) {
return n - 1;
}
if (n == 4) {
return 4;
}
long ans = 1;
while (n > 4) {
ans *= 3;
ans %= 1000000007;
n = n - 3;
}
return (int)(ans*n%1000000007);
// 最后n的值只有可能是:2、3、4。而2、3、4能得到的最大乘积恰恰就是自身值
// 因为2、3不需要再剪了(剪了反而变小);4剪成2x2是最大的,2x2恰巧等于4
}
剑指 offer 15:二进制中1的个数
题目描述
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。(传送门)
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
参考链接:位运算-计算十进制数字转为二进制1的个数
解题思路+代码实现
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
if((n&1) == 1) {
count++;
}
n = n>>>1;
}
return count;
}
剑指 offer 16:数值的整数次方
题目描述
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。(传送门)
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例 2:
输入: 2.10000, 3
输出: 9.26100
示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
解题思路+代码实现
采用快速幂方法:
Java 代码中 int32变量n∈[−2147483648,2147483647] ,因此当 n = -2147483648时执行 n = -n会因越界而赋值出错。解决方法是先将 n存入 long 变量tmp,后面用tmp 操作即可。
public double myPow(double x, int n) {
// n = 0 的情况
if (n == 0) {
return 1;
}
double ans = 1.0 ;
//Java 代码中 int32变量n∈[−2147483648,2147483647] ,因此当 n = -2147483648时执行 n = -n会因越界而赋值出错。解决方法是先将 n存入 long 变量tmp,后面用tmp 操作即可。
long tmp = n;
// 负数变为整数,并转换倒数
if (n < 0) {
x = 1 / x;
tmp = -tmp;
}
while (tmp > 0) {
if ((tmp & 1) == 1) {
ans *= x;
}
tmp = tmp >>> 1;
x *= x;
}
return ans;
}
剑指 offer 17:打印从1到最大的n位数
题目描述
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。(传送门)
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
解题思路&代码解析(暂不考虑大数)
大数一般转换为字符串来做
public int[] printNumbers(int n) {
int num = (int) Math.pow(10, n);
int[] array = new int[num-1];
for (int i = 0; i < num-1; i++) {
array[i] = i+1;
}
return array;
}
剑指 offer 18:删除链表的节点
题目描述
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。(传送门)
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
解题思路&代码解析
/**
* @ClassName Interview18
* @Description :TODO
* @Author Josvin
* @Date 2021/01/26/21:21
*/
public class Interview18 {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) {
return head;
}
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode tmp = newHead;
ListNode prev = newHead.next;
while (prev != null) {
if (prev.val == val) {
ListNode aa = prev.next;
tmp.next = aa;
prev = aa;
} else {
prev = prev.next;
tmp = tmp.next;
}
}
return newHead.next;
}
}
剑指 offer 21:调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。(传送门)
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
解题思路+代码实现
方法一:头尾指针
- 定义头指针 left ,尾指针 right .
- left 一直往右移,直到它指向的值为偶数
- right 一直往左移, 直到它指向的值为奇数
- 交换 nums[left] 和 nums[right]
- 重复上述操作,直到 left == right
import java.util.Arrays;
/**
* @ClassName Interview21
* @Description :TODO
* @Author Josvin
* @Date 2021/01/31/20:45
*/
public class Interview21 {
public static int[] exchange(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
while (left <right && nums[left] % 2 != 0) {
left++;
}
while ( left < right&& nums[right] % 2 == 0) {
right--;
}
if (left < right) {
int tmp = nums[right];
nums[right] = nums[left];
nums[left] = tmp;
}
}
return nums;
}
public static void main(String[] args) {
int[] nums = {4, 2, 6, 5, 1252, 62, 72};
System.out.println(Arrays.toString(exchange(nums)));
}
}
剑指 offer 22:链表中倒数第k个节点
题目描述
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。(传送门)
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
解题思路+代码实现
方法一:暴力
两趟循环,第一趟计算出链表长度,第二趟找出倒数第k个。
public ListNode getKthFromEnd(ListNode head, int k) {
int len = 0;
ListNode tmp = head;
while (head != null) {
len++;
}
int index = len - k;
while (index-- > 0) {
tmp = tmp.next;
}
return tmp;
}
方法二:快慢指针
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && k-- > 0) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
剑指 offer 24:反转链表
题目描述
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。(传送门)
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路+代码实现
public ListNode reverseList(ListNode head) {
ListNode prev = head;
ListNode next = head;
if (head != null) {
next = next.next;
head.next = null;
}
while (next != null) {
ListNode tmp = next.next;
next.next = prev;
prev = next;
next = tmp;
}
return prev;
}
剑指 offer 25:合并两个有序链表
题目描述
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。(传送门)
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
解题思路+代码实现
剑指 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]
解题思路+代码实现
import java.util.Stack;
/**
* @ClassName Interview27
* @Description :TODO
* @Author Josvin
* @Date 2021/05/13/21:24
*/
public class Interview27 {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode temp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
public TreeNode mirrorTree2(TreeNode root) {
//当 root 为空时,直接返回 null
if (root == null) {
return null;
}
Stack<TreeNode> stack = new Stack<>();
//栈(或队列),本文用栈,并加入根节点 root
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node.left != null) {
stack.add(node.left);
}
if (node.right != null) {
stack.add(node.right);
}
// 循环交换
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
return root;
}
}
剑指 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
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
解题思路+代码实现
根据以下规律,考虑从顶至底递归,判断每对节点是否对称,从而判断树是否为对称二叉树。
- L.val=R.val :即此两对称节点值相等。
- L.left.val = R.right.val L.left.val=R.right.val :即 L的 左子节点 和 R的右子节点 对称;
- L.right.val = R.left.val L.right.val=R.left.val :即 L的 右子节点 和 R的左子节点 对称。
/**
* @ClassName Interview28
* @Description :TODO
* @Author Josvin
* @Date 2021/05/14/19:41
*/
public class Interview28 {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : isSymmetricHelper(root.left, root.right);
}
private boolean isSymmetricHelper(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null || right.val != left.val) {
return false;
}
return isSymmetricHelper(left.left, right.right) && isSymmetricHelper(left.right, right.left);
}
}
剑指 Offer 31. 栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。(传送门)
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
解题思路+代码实现
借助一个辅助栈来实现,将pushed 数组元素依次入栈,每一次入栈与popped 数组元素进行对比,如果相同进行出栈,这里一定要是while 循环对比出栈,i , j 分别记录两个数组的下标。当循环结束栈为空说明序列正确,否则是错误的。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack1 = new Stack<>();
for (int i = 0,j=0; i < popped.length; i++) {
stack1.push(pushed[i]);
while (j<popped.length && !stack1.isEmpty() && popped[j] == stack1.peek()) {
stack1.pop();
j++;
}
}
return stack1.isEmpty();
}
}
剑指 Offer 50. 第一个只出现一次的字符
题目描述
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。(传送门)
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
解题思路+代码实现
public static char firstUniqChar(String s) {
HashMap<Character, Boolean> hashMap = new HashMap<>();
for (char c:s.toCharArray()) {
if (!hashMap.containsKey(c)) {
hashMap.put(c, false);
} else {
hashMap.put(c, true);
}
}
for (char c : s.toCharArray()) {
if (!hashMap.get(c)) {
return c;
}
}
return ' ';
}
剑指 Offer 52. 两个链表的第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共节点。(传送门)
解题思路+代码实现
同160:【小象算法Java版】第一节:链表(上)