java 刷题_剑指offer刷题报告(Java)

这篇博客介绍了多种算法问题的解决方案,包括二维数组中查找整数的三种方法(暴力、二分查找、右上角起点比较),字符串空格替换、链表逆序打印、二叉树重建以及用两个栈实现队列。同时,还探讨了旋转数组最小值的寻找、斐波那契数列的计算以及不同步态跳台阶问题的解答。文章重点在于算法的时间复杂度和空间效率优化。
摘要由CSDN通过智能技术生成

1. 二维数组中的查找

题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

// 解法一:220ms 暴力n^2

public class Solution {

public boolean Find(int target, int [][] array) {

int x = array.length, y = array[0].length;

boolean flag = false;

for(int i = x - 1;i >= 0;i--) {

for(int j = y - 1;j >= 0;j--) {

if(array[i][j] == target) {

flag = true;

break;

}

}

}

return flag;

}

}

// 解法二:182ms 把每一行看成有序递增的数组,利用二分查找,通过遍历每一行得到答案,时间复杂度是nlognpublic class Solution {

public boolean Find(int target, int [][] array) {

if(array == null || array.length == 0 || array[0].length == 0) {

return false;

}

int row = array.length, col = array[0].length;

for(int i = 0;i < row;i++) {

// 每一行进行二分 int left = 0, right = col - 1;

while(left <= right) {

int mid = (left + right) / 2;

if(array[i][mid] > target) {

right = mid - 1;

} else if(array[i][mid] < target) {

left = mid + 1;

} else {

return true;

}

}

}

return false;

}

}

/*解法三:195ms 利用二维数组由上到下,由左到右递增的规律,那么选取右上角或者左下角的元素a[row][col]与target进行比较,当target小于元素a[row][col]时,那么target必定在元素a所在行的左边,即col--;当target大于元素a[row][col]时,那么target必定在元素a所在列的下边,即row++;*/

public class Solution {

public boolean Find(int target, int [][] array) {

if(array == null || array.length == 0 || array[0].length == 0) {

return false;

}

int row = array.length, col = array[0].length;

int r = 0, c = col - 1;//从右上角开始 while(r < row && c >= 0) {

if(array[r][c] > target) {

c--;

} else if(array[r][c] < target) {

r++;

} else {

return true;

}

}

return false;

}

}

2. 替换空格

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

// 解法一:20ms 常规解法public class Solution {

public String replaceSpace(StringBuffer str) {

StringBuilder tempStr = new StringBuilder();

for(int i = 0;i < str.length();i++) {

if(str.charAt(i) == ' ') {

tempStr.append("%20");

} else {

tempStr.append(str.charAt(i));

}

}

return tempStr.toString();

}

}

3. 从尾到头打印链表

题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

// 解法一:运行时间:20ms 占用内存:9252k// 利用递归求解/*** public class ListNode {* int val;* ListNode next = null;** ListNode(int val) {* this.val = val;* }* }**/

import java.util.ArrayList;

public class Solution {

public ArrayList printListFromTailToHead(ListNode listNode) {

ArrayList resList = new ArrayList();

if(listNode == null) {

return resList;

} else {

return returnListFromTailToHead(listNode);

}

}

public ArrayList returnListFromTailToHead(ListNode listNode) {

ArrayList tempList;

if(listNode.next == null) {

tempList = new ArrayList();

tempList.add(listNode.val);

} else {

tempList = returnListFromTailToHead(listNode.next);

tempList.add(listNode.val);

}

return tempList;

}

}

// 解法二: 超简洁版,运行时间:30ms 占用内存:9340k// 相当于也是递归,然后输出/*** public class ListNode {* int val;* ListNode next = null;** ListNode(int val) {* this.val = val;* }* }**/

import java.util.ArrayList;

public class Solution {

ArrayList resList = new ArrayList();

public ArrayList printListFromTailToHead(ListNode listNode) {

if(listNode != null) {

printListFromTailToHead(listNode.next);

resList.add(listNode.val);

}

return resList;

}

}

//解法三:运行时间:27ms 占用内存:9248k//先获得正序的ArrayList,在借助Collections.reverse()反转链表/*** public class ListNode {* int val;* ListNode next = null;** ListNode(int val) {* this.val = val;* }* }**/

import java.util.ArrayList;

import java.util.Collections;

public class Solution {

ArrayList resList = new ArrayList();

public ArrayList printListFromTailToHead(ListNode listNode) {

while(listNode != null) {

resList.add(listNode.val);

listNode = listNode.next;

}

Collections.reverse(resList);

return resList;

}

}

4. 重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

// 解法一: 运行时间:190ms 占用内存:22148k/* 代码里有注释基本思想是递归分治,利用了先序遍历和中序遍历的特性。先序遍历的特性是:第一个访问的结点一定是根结点。(即数组第一个值是根结点的值)中序遍历的特性是:数组中的一个值(现结点),其左边的全部是现结点的左子树中的值,右边的全部是现结点的右子树中的值。*/

/*** Definition for binary tree* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/

public class Solution {

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {

return buildTree(pre, 0, pre.length - 1, in, 0, in.length - 1);

}

// 根据pre[pre_l, pre_r], in[in_l, in_r] 构建二叉树 public TreeNode buildTree(int [] pre, int pre_l, int pre_r, int [] in, int in_l, int in_r) {

// 边界处理 if(pre_l > pre_r) {

return null;

}

if(pre_l == pre_r) return new TreeNode(pre[pre_l]);

//找到前序遍历的根节点,在中序遍历中的位置 int index = find(in, in_l, in_r, pre[pre_l]);

//左子树长度 int len_l = index - in_l;

// 构建根节点 TreeNode root = new TreeNode(pre[pre_l]);

// 根据pre[pre_l + 1, pre_l + len_l], in[in_l, index - 1]构建左子树 TreeNode leftNode = buildTree(pre, pre_l+1, pre_l+len_l, in, in_l, index - 1);

// 根据pre[pre_l + len_l + 1, pre_r], in[index+1, in_r]构建右子树 TreeNode rightNode = buildTree(pre, pre_l+len_l+1, pre_r, in, index+1, in_r);

root.left = leftNode;

root.right = rightNode;

return root;

}

// 找到根节点在中序遍历中的位置 public int find(int [] in, int in_l, int in_r, int target) {

int len = in.length;

for(int i = 0;i < len;i++) {

if(target == in[i]) {

return i;

}

}

return -1;

}

}

//解法二: 运行时间:179ms 占用内存:22980k 超简洁版本/*** Definition for binary tree* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/

import java.util.*;

public class Solution {

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {

if(pre.length == 0) {

return null;

}

TreeNode root = new TreeNode(pre[0]);

if(pre.length == 1) {

return root;

}

for(int i = 0;i < in.length;i++) {

if(pre[0] == in[i]) { //根节点在中序遍历中的位置 //Arrays.copyOfRange() 是左闭右开区间 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));

}

}

return root;

}

}

5. 用两个栈实现队列

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

// 解法一: 运行时间:15ms 占用内存:9404k

import java.util.Stack;

public class Solution {

Stack stack1 = new Stack();

Stack stack2 = new Stack();

public void push(int node) {

stack1.push(node);

}

public int pop() {

if(stack2.empty()) {

while(!stack1.empty()) {

int x = stack1.peek();

stack1.pop();

stack2.push(x);

}

}

int node = stack2.peek();

stack2.pop();

return node;

}

}

6. 旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

//解法一:运行时间:383ms 占用内存:30980k//排序import java.util.ArrayList;

import java.util.Collections;

import java.util.*;

public class Solution {

public int minNumberInRotateArray(int [] array) {

if(array.length == 0) {

return 0;

}

Arrays.sort(array);

return array[0];

}

}

//解法二:运行时间:324ms 占用内存:28728k//变向二分法,分析在注释里,也可以看这个https://www.nowcoder.com/questionTerminal/9f3231a991af4f55b95579b44b7a01ba//~~~这种二分法,时间小了点,内存小了点import java.util.ArrayList;

import java.util.Collections;

import java.util.*;

public class Solution {

public int minNumberInRotateArray(int [] array) {

if(array.length == 0) {

return 0;

}

int left = 0, right = array.length - 1;

int mid = 0;

// 确保left在左边递增序列,right在右边递增序列 while(array[left] >= array[right]) {

//边界条件 if(right - left == 1) {

mid = right;

break;

}

mid = (left + right) / 2;

//三者相等的情况下,无法判断,只能顺序查找 if(array[mid] == array[left] && array[mid] == array[right]){

return minNumOfArray(array, left, right + 1);

}

// 中间元素位于前面的递增子数组 // 此时最小元素位于中间元素的后面 if(array[mid] >= array[left]) {

left = mid;

}

// 中间元素位于后面的递增子数组 // 此时最小元素位于中间元素的前面 else {

right = mid;

}

}

return array[mid];

}

/*** 获得一段数组中的最小值*/

public int minNumOfArray(int [] array, int left, int right) {

int minNum = array[left];

for(int i = left;i < right;i++) {

if(array[i] < minNum) {

minNum = array[i];

}

}

return minNum;

}

}

// 解法三:运行时间:354ms 占用内存:28200k// 也是二分,上面那种方法的简洁版本,不需要定义一个辅助函数import java.util.ArrayList;

import java.util.Collections;

import java.util.*;

public class Solution {

public int minNumberInRotateArray(int [] array) {

int low = 0 ; int high = array.length - 1;

while(low < high){

int mid = (low + high) / 2;

if(array[mid] > array[low]){

low = mid;

} else if(array[mid] == array[low]){

low = low + 1;

}else{

high = mid;

}

}

return array[low];

}

}

7.斐波那契数列

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39

// 解法一:运行时间:1348ms 占用内存:9408k// 暴力递归,最简单的public class Solution {

public int Fibonacci(int n) {

if(n == 0 || n == 1) {

return n;

}

return Fibonacci(n-1) + Fibonacci(n-2);

}

}

// 解法二: 运行时间:1408ms 占用内存:9300k// 存下中间值(按道理应该比上面那种方法快的)import java.util.*;

public class Solution {

int [] fibo = new int[40];

public int fibona(int n) {

if(n == 0 || n == 1) {

return n;

}

if(fibo[n] != 0) {

return fibo[n];

} else {

return fibona(n - 1) + fibona(n - 2);

}

}

public int Fibonacci(int n) {

Arrays.fill(fibo, 0);

fibo[1] = 1;

return fibona(n);

}

}

// 解法三: O(n) 运行时间:16ms 占用内存:9324k// 循环求,复杂度O(n),递归的好处是简单,但是做了很多无用的操作import java.util.*;

public class Solution {

int [] fibo = new int[40];

public int Fibonacci(int n) {

fibo[1] = 1;

fibo[0] = 0;

for(int i = 2;i <= n;i++) {

fibo[i] = fibo[i - 1] + fibo[i - 2];

}

return fibo[n];

}

}

// 解法四:O(logn) 运行时间:14ms 占用内存:9412k// 矩阵快速幂:https://blog.csdn.net/aaakkk_1996/article/details/87927108/** O(logN)解法:由f(n) = f(n-1) + f(n-2),可以知道* [f(n),f(n-1)] = [f(n-1),f(n-2)] * {[1,1],[1,0]}* 所以最后化简为:[f(n),f(n-1)] = [1,1] * {[1,1],[1,0]}^(n-2)* 所以这里的核心是:* 1.矩阵的乘法* 2.矩阵快速幂(因为如果不用快速幂的算法,时间复杂度也只能达到O(N))*/

public class Solution {

public int Fibonacci(int n) {

if(n == 0) {

return 0;

} else if(n == 2 || n == 1) {

return 1;

}

int [][]base = {{1, 1}, {1, 0}};

int [][]res = matrixPower(base, n - 2);

return res[0][0] + res[0][1];

}

//矩阵乘法 public int [][] matrixMultiply(int [][] m1, int [][]m2) {

int [][] m = new int[m1.length][m2[0].length];

for(int i = 0;i < m1.length;i++) {

for(int j = 0;j < m2[0].length;j++) {

for(int k = 0;k < m2.length;k++) {

m[i][j] += m1[i][k] * m2[k][j];

}

}

}

return m;

}

/** 矩阵的快速幂:* 1.假如不是矩阵,叫你求m^n,如何做到O(logn)?答案就是整数的快速幂:* 假如不会溢出,如10^75,把75用用二进制表示:1001011,那么对应的就是:* 10^75 = 10^64*10^8*10^2*10* 2.把整数换成矩阵,是一样的*/

public int [][] matrixPower(int [][] m, int p) {

int [][]res = new int [m.length][m[0].length];

//初始化res微单位矩阵 for(int i = 0;i < res.length;i++) {

res[i][i] = 1;

}

// tmp矩阵保存m^n, n是2的指数倍,初始化为m int [][] tmp = m;

//快速幂 for(;p != 0;p >>= 1) {

if((p&1) != 0) {

res = matrixMultiply(res, tmp);

}

tmp = matrixMultiply(tmp, tmp);

}

return res;

}

}

8. 跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

//解法:多列几个,找找规律,依旧是斐波那契,直接按题目7来就行了

9. 变态跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

//解法一:运行时间:13ms 占用内存:9412k// record[i] = 2 * record[i - 1]public class Solution {

public static final int N = 100001;

public int JumpFloorII(int target) {

if(target == 1) {

return 1;

}

int a = 1;

for(int i = 2;i <= target;i++) {

a *= 2;

}

return a;

}

}

// 解法二: 运行时间:21ms 占用内存:9228k// 相当于求2^(n - 1)这就可以直接库函数public class Solution {

public static final int N = 100001;

public int JumpFloorII(int target) {

if(target == 1) {

return 1;

}

return (int)Math.pow(2, target - 1);

}

}

// 解法三:运行时间:22ms 占用内存:11620k// 快速幂public class Solution {

public static final int N = 100001;

public int JumpFloorII(int target) {

if(target == 1) {

return 1;

}

return ksm(2, target - 1);

}

//快速幂 public static int ksm(int a, int p) {

int res = 1;

int tmp = a;

for(; p != 0;p >>= 1) {

if((p&1) != 0) {

res *= tmp;

}

tmp *= tmp;

}

return res;

}

}

10.矩形覆盖

题目:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

// 解法: 斐波那契数列,参考7、8题

// 1.递归做 运行时间:311ms 占用内存:9276k// 2.存中间值,防止重复求值:运行时间:50ms 占用内存:25736k// 3.矩阵快速幂 运行时间:22ms 占用内存:9364k

11.二进制中1的个数

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

//解法一:运行时间:15ms 占用内存:9344k//转二进制字符串,遍历字符串public class Solution {

public int NumberOf1(int n) {

int num = 0;

String str = Integer.toBinaryString(n);

for(int i = 0;i < str.length();i++) {

if(str.charAt(i) == '1') {

num++;

}

}

return num;

}

}

// 解法二:运行时间:20ms 占用内存:9340k// 最优解法/*如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。*/

public class Solution {

public int NumberOf1(int n) {

int num = 0;

while(n != 0) {

num ++;

n = n & (n - 1);

}

return num;

}

}

其他题目,因为篇幅太长就不贴了,大家可以看我的CSDN刷题报告:剑指offer66题​blog.csdn.net

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值