剑指Offer - java版
文章目录
- 剑指Offer - java版
-
- JZ01
- JZ02 替换空格
- JZ03 从尾到头打印链表
- JZ04 重建二叉树
- JZ05 用两个栈实现队列
- JZ06 旋转数组的最小数字
- JZ07 斐波那契数列
- JZ08 跳台阶问题
- JZ09 青蛙跳台阶
- JZ10 矩形覆盖
- JZ11 二进制中1的个数
- JZ12 数值的整数次方
- JZ13 调整数组顺序,使奇数位于偶数前面
- JZ14 链表中倒数第k个结点
- JZ15 反转链表
- JZ16 合并两个排序的链表
- JZ17 树的子结构
- JZ18 二叉树的镜像
- JZ19 顺时针打印矩阵
- JZ20 包含min函数的栈
- JZ21 栈的压入、弹出序列
- JZ22 从上往下打印二叉树
- JZ23 二叉树的后序遍历
- JZ24 二叉树中和为某值的路径
- JZ25 复杂链表的复制
- JZ26 二叉搜索树与双向链表
- JZ27 字符串的排列
- JZ28 数组中出现次数超过一半的数字
- JZ29 最小的k个数
- JZ30 连续子数组的最大和
- JZ31 整数中1出现的次数
- JZ32 把数组排成最小的数
- JZ33 丑数
- JZ34 第一个只出现一次的字符
- JZ35 数组中的逆序对
- JZ36 两个链表的第一个公共结点
- JZ37 数字在升序数组中出现的次数
- JZ38 二叉树的深度
- JZ39 平衡二叉树
- JZ40 数组中只出现一次的数字
- JZ41 和为S的连续正数序列
- JZ42 和为s的两个数字
- JZ43 左旋转字符串
- JZ44 反转单词顺序
- JZ45 扑克牌顺子
- JZ46 孩子们的游戏
- JZ47 求1+2+3+...+n
- JZ48 不用加减乘除做加法
- JZ49 把字符串转换成整数
- JZ50 数组中重复的数字
- JZ51 构建乘积数组
- JZ52 正则表达式匹配
- JZ53 表示数值的字符串
- JZ54 字符流中第一个不重复的字符
- JZ55 链表中换的入口结点
- JZ56 删除链表中的重复结点
- JZ57 二叉树的下一个结点
- JZ58 对称的二叉树
- JZ59 按之字型顺序打印二叉树
- JZ60 把二叉树打印成多行
- JZ61 序列化二叉树
- JZ62 二叉搜索树的第k个结点
- JZ63 数据流中的中位数
- JZ64 滑动窗口的最大值
- JZ65 矩阵中的路径
- JZ66 机器人的运动范围
- JZ67 剪绳子
JZ01
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
暴力解法,直接双循环。时间复杂度为: O(n^2)
。
public boolean Find(int target, int [][] array) {
for(int i = 0 ; i < array.length; i++){
for(int j = 0; j < array[i].length; j++){
if(target == array[i][j]){
return true;
}
}
}
return false;
}
根据题意,是从小到大递增的数组,所以按照这个规律,先比较每行最末尾的数,若target小于该数,遍历该行即可;若存在,直接返回;不存在,到下一行。
JZ02 替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
直接遍历,替换空格。
public String replaceSpace(StringBuffer str) {
String sb = "";
for(int i = 0; i < str.length(); i++){
if( str.charAt(i) != ' '){
sb = sb + str.charAt(i);
} else
sb = sb + "%20";
}
return sb;
}
另外也可以直接调用java的函数.replace()
public String replaceSpace(StringBuffer str) {
return str.toString().replace(" ", "%20");
}
JZ03 从尾到头打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
按部就班,将链表中的值输入到另一个数组中,再将数组中的值,反序输入到ArrayList中。
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> arry = new ArrayList<Integer>();
int[] arr = new int[10000];
int i = 0;
if(listNode == null)
return arry;
while(listNode.next != null ){
arr[i] = listNode.val;
listNode = listNode.next;
i++;
}
arr[i] = listNode.val;
for( int j = i ; j >= 0 ; j--){
arry.add(arr[j]);
}
return arry;
}
JZ04 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
在这里首先要明确二叉树的前序、中序和后序遍历。
- 前序:根左右
- 中序:左根右
- 后序:左右根
那么前序遍历的第一个值,即为根节点;中序遍历根节点前的均为左子树的中序遍历,即可得到左子树的节点数,那么就可以在前序遍历中得到左子树的前序遍历。二者中剩余的变为右子树的,即可带入遍历中。每次遍历返回一个根节点,即为上一次的子节点。另外因为要切割数组,所以用到了java.util.Arrays
工具类中的copyOfRange()
。牢记左开右闭。
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if( pre.length == 0 || in.length == 0 )
return null;
TreeNode root = new TreeNode(pre[0]);
for(int i = 0 ; i < in.length ; i++){
if( pre[0] == in[i]) {
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
break;
}
}
return root;
}
JZ05 用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
首先应该明确:栈,先进后出;队列,先进先出。所以考虑直接输出最先出栈的值,其余的值用第二个栈先保存,输出完毕后再输入回第一个栈。有点类似于汉诺塔。
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
int lenth = stack1.size();
for (int i = 0; i < lenth; i++) {
stack2.push(stack1.pop());
}
int res = stack2.pop();
lenth = stack2.size();
for (int i = 0; i < lenth ; i++) {
stack1.push(stack2.pop());
}
return res;
}
}
JZ06 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
- 直接使用遍历,暴力求解。
public int minNumberInRotateArray(int [] array) {
int min = array[0];
for(int i = 1; i< array.length; i++){
min = (min < array[i] ? min : array[i]);
}
return min;
}
- 利用二分法的思想,由于题目中是非递减排序后的数组的旋转,所以可以利用这个特点,但同时也要考虑该特点的特异性(011111或类似相同的数组的一种旋转)。一组非递减排序后的数组,经过旋转之后,可由最小值处分为两段。联想到二分法,mid与last进行比较。有以下情况:
mid > last
, 则最小值在mid 和 last之中。mid < last
,则最小值在first 和 mid之中。mid == last
,分不清,只能一步步缩小last的值。
public int minNumberInRotateArray(int [] array) {
if( array.length == 0 )
return 0;
int first = 0;
int last = array.length - 1;
while (first < last ){
if( array[first] < array[last] )
return array[first];
int mid = ( first + last) >> 1;
if( array[mid] < array[last]){
last = mid;
} else if ( array[mid] > array[last])
first = mid + 1;
else
--last;
}
return array[first];
}
JZ07 斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n<=39
经典问题:直接递归。
public int Fibonacci(int n) {
if(n == 0)
return 0;
if(n==1)
return 1;
return Fibonacci(n-1) + Fibonacci(n-2);
}
JZ08 跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
看到这个题,直接斐波那契数列求解。需要注意的是,在不同的数列中,f0的取值不同。
public int JumpFloor(int target) {
if( target <= 1)
return 1;
return JumpFloor(target - 1) + JumpFloor(target - 2);
}
当然也可以使用,从下到上的动态规划来解题。
public int JumpFloor(int target) {
int[] dp = new int[target + 1];
if(target == 0)
dp[0] = 1;
if(target >= 1)
{
dp[0] = 1;
dp[1] = 1;
}
for(int i = 2; i < target + 1; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[target];
}
JZ09 青蛙跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
很明显的递归问题:第一次跳有n种跳法,假设第一次跳了x步,则还剩(n-x)步,将(n-x)又回到原题中了。当剩下的步数(n-x)为0时,就跳到了终点,此时加1即可。
public class Solution {
public static int COUNT = 0;
public int JumpFloorII(int target) {
COUNT = 0;
jump(target);
return COUNT;
}
public void jump(int n){
for( int i = 1; i <= n; i++){
if( i== n)
COUNT ++;
else
jump(n-i);
}
}
}
通过测试分析可以发现,n = 1
时,有1种;n = 2
时,有2种;n = 3
时,有4种…
发现规律,最终的总数为2的n-1次方。
public class Solution {
public int JumpFloorII(int target) {
return Math.pow(2, target - 1);
}
}
JZ10 矩形覆盖
我们可以用2*1
的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1
的小矩形无重叠地覆盖一个2*n
的大矩形,总共有多少种方法?
比如n=3时,2*3的矩形块有3种覆盖方法。
看似复杂,实则还是斐波那契数列,只是需要注意,此时f(n)序列中的f(0)值为1,但实际上f(0)的值为0。这一点要考虑上。
public int RectCover(int target) {
int[] dp = new int[target + 1];
if(target == 0)
dp[0] = 1;
if( target >= 1 ) {
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < target + 1; i++)
dp[i] = dp[i - 1] + dp[i - 2];
}
if( target == 0 ) return 0;
return dp[target];
}
JZ11 二进制中1的个数
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
考察位运算,直接&1
即可求得最后一位是否是1,然后依次右移即可。但是这种简单的方法,在java中超出了运算时间2ms。
public int NumberOf1(int n) {
int count = 0;
while ( n != 0 ){
if((n & 1) == 1)
count++;
n >>= 1;
}
return count;
}
所以考虑对其进行优化。在上面的方法中每次都要对0进行判断,所以如果能越过0就能节约时间。11001000 - 1
后为11000111
再与原数求与,得到11000000
即,每次都将最右的一位1去掉了。
public int NumberOf1(int n) {
int count = 0;
while ( n != 0 ){
++ count;
n = n & (n-1);
}
return count;
}
JZ12 数值的整数次方
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
直接用Math包(import java.lang.Math)
下的pow()
方法即可。
public double Power(double base, int exponent) {
return Math.pow(base, exponent);
}
当然,也可以直接一个while循环,exponent--
.
JZ13 调整数组顺序,使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
暴力求解:直接三次循环。1. 将数组中的奇数放入暂存数组中。2. 将数组中的偶数放入暂存数组中。3. 将暂存数组放回到原来的数组。
public void reOrderArray(int [] array) {
int[] b = new int[array.length];
int j = 0;
for(int i = 0; i < array.length ; i++) {
if( array[i] % 2 == 1 ){
b[j] = array[i];
j ++;
}
}
for(int i = 0; i < array.length ; i++) {
if( array[i] % 2 == 0 ){
b[j] = array[i];
j ++;
}
}
for(int i = 0; i < array.length ; i++) {
array[i] = b[i];
}
}
JZ14 链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点。
两种思路,一是直接遍历该链表,得到链表的长度length,倒数第k个结点,即为正数第(n+1-k)个。
public ListNode FindKthToTail(ListNode head,int k) {
if(head == null || k <= 0 )
return null;
int length = 0;
ListNode out = head;
while( out != null){
out = out.next;
length ++;
}
if( k > length)
return null;
for(int i = 0; i < length - k ; i++) {
head = head.next;
}
return head;
}
二是快慢指针,同时两个指针对链表进行遍历,fast指针先走k步,那么当fast到null时,slow到倒数第k个位置。
public ListNode FindKthToTail(ListNode head,int k) {
if( head == null || k <= 0 ) return null;
ListNode slow = head;
ListNode fast = head;
while( k > 0 ){
if( fast == null)
return null;
fast = fast.next;
k--;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
JZ15 反转链表
输入一个链表,反转链表后,输出新链表的表头。
用三个指针来调整指针的位置。
- pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
- cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
- nex指针指向待反转链表的第二个节点。
首先将指向下一个位置的指针保留到nex中;然后才进行反转操作。
把当前元素的下一个位置的指针指向上一个元素。
然后把当前元素接到pre中。
当前元素指向原始序列的下一个元素中。
public ListNode ReverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
ListNode nex = null;
while( cur != null ) {
nex = cur.next;
cur.next = pre;
pre = cur;
cur = nex;
}
return pre;
}
JZ16 合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
有两个思路,一个是直接依次比较,然后输出整个链表。其二是迭代。
直接比较时,需要两个个指向新链表头部的指针。一个为了保存新链表的头部,作为输出;另一个则指向当前元素,做添加操作。所以在每次的循环结束后,都会将next指向它。
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode newList = null;
ListNode head = null;
if( list1 == null ) return list2;
if( list2 == null ) return list1;
if(list1.val < list2.val){
newList = list1;
list1 = list1.next;
}
else {
newList = list2;
list2 = list2.next;
}
head = newList;
while( !( list1 == null && list2 == null ) ){
if(list1 == null ) {
newList.next = list2;
list2 = list2.next;
} else if (list2 == null ) {
newList.next = list1;
list1 = list1.next;
}else if(list1.val < list2.val) {
newList.next = list1;
list1 = list1.next;
}
else {
newList.next = list2;
list2 = list2.next;
}
newList = newList.next;
}
return head;
}
其二是通过迭代的方式去得到最终结果。需要考虑的是迭代的终止条件到底是什么?是输入的两个指针都为空,还是至少一个为空。至少一个为空时,将剩余的接上即可。
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode head = null;
if(list1.val <= list2.val){
head = list1;
head.next = Merge(list1.next, list2);
} else {
head = list2;
head.next = Merge(list1, list2.next);
}
return head;
}
JZ17 树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
错误想法:
同样也是两个思路
1. 直接遍历A的所有节点,与B进行比较。
2. 迭代比较,直接比较A B, 若A != B
则比较A的左右子树和B是否相同。
子结构:树A和树B的根结点相等,并且树A的左子树和树B的左子树相等,树A的右子树和树B的右子树相等。
- 递归函数的功能:判断2个数是否有相同的结构,如果相同,返回true,否则返回false。
- 递归终止条件:如果树B为空,返回true,此时,不管树A是否为空,都为true。
否则,如果树B不为空,但是树A为空,返回false,此时B还没空但A空了,显然false。 - 下一步递归参数:如果A的根节点和B的根节点不相等,直接返回false。否则,相等,就继续判断A的左子树和B的左子树,A的右子树和B的右子树
然后就是正确遍历树A中的各个根节点,并对这些根节点进行判断。
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if( root1 == null || root2 == null ) return false;
return equals(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
public boolean equals (TreeNode root1,TreeNode root2) {
if( root2 == null ) return true;
if( root1 == null ) return false;
return root1.val == root2.val && equals(root1.left, root2.left) && equals(root1.right, root2.right);
}
错误的迭代方法:
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if( root1 == null || root2 == null )
return false;
if( equals(root1, root2) )
return true;
else
return HasSubtree(root1.left, root2) || HasSubtree(root1.left, root2);
}
public boolean equals(TreeNode root1,TreeNode root2) {
if( root2 != null ) {
if( root1.val != root2.val )
return false;
else {
boolean left;
boolean right;
if( root2.left != null)
left = equals(root1.left, root2.left);
else
left = false;
if( root2.right != null)
right = equals(root1.right, root2.right);
else
right = false;
if( left && right)
return true;
else
return false;
}
} else return false;
}
JZ18 二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
依旧是遍历问题,但是要注意合适的终止情况,当节点为null时,或节点的两个子节点为空时,退出。而其余情况,交换两个节点的位置,然后对该节点继续遍历。
public void Mirror(TreeNode root) {
TreeNode temp;
if( root == null );
else if( root.left == null && root.right == null ) {
} else {
temp = root.left;
root.left = root.right;
root.right = temp;
Mirror(root.left);
Mirror(root.right);
}
}
JZ19 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
明显的循环问题,注意循环体结束的点。定义上下左右四个边界并不断地收缩矩阵的边界,当出界时,终止循环。
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> list = new ArrayList<>();
if(matrix == null || matrix.length == 0 || matrix[0].length == 0)
return list;
int up = 0;
int down = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;
while(true) {
for(int col = left; col <= right ; col++)
list.add(matrix[up][col]);
up ++;
if(up > down)
break;
for(int row = up; row <= down; row++)
list.add(matrix[row][right]);
right --;
if(right < left)
break;
for(int col = right; col >= left; col--)
list.add(matrix[down][col]);
down --;
if( down < up)
break;
for(int row = down; row >= up; row-- )
list.add(matrix[row][left]);
left ++;
if( left > right)
break;
}
return list;
}
JZ20 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
使用双栈法,注意