2022年2月16日,大三开始刷剑指offer。不知道为毛,几个网站上都是从第三题开始,不懂。
目录
第三题--数组中重复的数字
描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
数据范围:0 <= n <= 10000
进阶:时间复杂度O(n),空间复杂度O(n)
示例1
输入:
[2, 3, 1, 0, 2, 5, 3]
返回值:
2
说明:
2或者3都对
个人思路:
暴力法:数组排序,然后循环比较每一位数与后一位数是否相等,相等则直接返回该值。
进阶法:使用hashset,判断数字是否重复。(我没想到这方法比第一个方法还慢)
高级法:使用一个同等长度Boolean数组,循环遍历数组,判断同数组值相同的索引位置Boolean值是否为true(默认为false),若为true代表重复,则直接返回。否则表示首次出现,将该Boolean值改为true。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param numbers int整型一维数组
* @return int整型
*/
public int duplicate (int[] numbers) {
//同等长度boolean数组,默认值为false
boolean[] t=new boolean[numbers.length];
for(int i=0;i<numbers.length;i++){
//同数组值相同的Boolean数组索引位置的值
if(t[numbers[i]]){
//已经存在该值,则重复
return numbers[i];
}else{
//首次出现,记录该值
t[numbers[i]]=true;
}
}
//无重复值,返回-1
return -1;
}
}
之前只能想到hashset,没想到手写代码比原生方法还快,可能hashset的优势不在这里。
第四题--二维数组中的查找
描述
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
数据范围:矩阵的长宽满足 0 <= n,m <= 5000矩阵中的值满 0<=val<=10^9
进阶:空间复杂度 O(1) ,时间复杂度 O(n+m)
示例1
输入: 7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值: true
说明: 存在7,返回true
示例2
输入: 1,[[2]]
返回值: false
示例3
输入: 3,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值: false
说明: 不存在3,返回false
个人思路
暴力法:预估超时
个人优化思路:先判断每一行第一列值,找到一个小于目标值的最大值。然后从那一行开始遍历,若发现更大值则向上一行搜索。(最后发现这个方法有些麻烦,而且只能通过大概80%的案例,其他案例会超时)
线性搜索法:直接从左下角开始搜索,若当前值更小则向右,若更大则向上(因为左边的值小于目标值,而左边的上边的值只会更小,所以当一个值因为小于目标值被排除时。那它左,上方全部值都被排除。这样一来遍历只会向右上方进行),相同则返回。
public class Solution {
public boolean Find(int target, int [][] array) {
//获取数组长宽
int n = array.length, m = array[0].length;
//排除空数组
if(n == 0 || m == 0){
return false;
}
//获取左下角数组坐标
int index=n-1, i=0;
while(index>=0&&i<m){
//获取当前位置数组值
int t = array[index][i];
if(t==target){
//相同则返回true
return true;
}else if(t<target){
//小于目标值则向右方遍历
i++;
}else{
//大于目标值则向上方遍历
index--;
}
}
//找不到目标值
return false;
}
}
这道题一开始理解错了,以为只有第一列是从小到大的,后面才发现每一列都是。如果看懂了题应该可以自己写出线性搜索,所以要注意审题。
第五题--替换空格
描述
请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
数据范围:0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。
进阶:时间复杂度 O(n), 空间复杂度 O(n)
示例1
输入: "We Are Happy"
返回值: "We%20Are%20Happy"
示例2
输入: " "
返回值: "%20"
个人思路
1.调用string的replace(str,str)方法直接替换
2.使用stringbuilder构造
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return string字符串
*/
/*public String replaceSpace (String s) {
return s.replace(" ", "%20");
}*/
//这道题没啥好说的
public String replaceSpace(String s) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ')
stringBuilder.append("%20");
else
stringBuilder.append(s.charAt(i));
}
return stringBuilder.toString();
}
}
第六题--从尾到头打印链表
描述
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
如输入{1,2,3}的链表如下图:
返回一个数组为[3,2,1]
0 <= 链表长度 <= 10000
示例1
输入: {1,2,3}
返回值: [3,2,1]
示例2
输入: {67,0,24,58}
返回值: [58,24,0,67]
个人思路
1. Collections.reverse(new ArrayList<>());
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
//1.递归写法
import java.util.*;
public class Solution {
ArrayList<Integer> list = new ArrayList<>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode != null){
printListFromTailToHead(listNode.next);
list.add(listNode.val);
}
return list;
}
}
//2.栈
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList list = new ArrayList();
Stack s = new Stack();
//全部进栈
while(listNode != null){
s.push(listNode.val);
listNode = listNode.next;
}
//全部出栈,顺序翻转
while(!s.empty()){
list.add(s.pop());
}
return list;
}
//3.list集合每一次插入都插入在第一位
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
ArrayList list = new ArrayList();
while (listNode != null) {
//每一次都插入到一位,自然会翻转
list.add(0, listNode.val);
listNode = listNode.next;
}
return list;
}
}
这道题目这么多解法我居然都没想到。
第七题--重建二叉树
描述
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
提示:
1.vin.length == pre.length
2.pre 和 vin 均无重复元素
3.vin出现的元素均出现在 pre里
4.只需要返回根结点,系统会自动输出整颗树做答案对比
数据范围:n≤2000,节点的值 −10000≤val≤10000
要求:空间复杂度O(n),时间复杂度 O(n)
示例1
输入:[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
返回值:{1,2,3,4,#,5,6,#,7,#,#,8}
说明:返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示
示例2
输入:[1],[1]
返回值:{1}
示例3
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:{1,2,5,3,4,6,7}
import java.util.*;
/**
* 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 [] vin) {
if(pre.length == 0 || vin.length == 0){
return null;
}
//找到根节点
TreeNode tree = new TreeNode(pre[0]);
//在中序中找根节点
for(int i=0;i<vin.length;i++){
//找到根节点(数字不重复)
if(vin[i] == pre[0]){
//递归
tree.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1), Arrays.copyOfRange(vin, 0, i));
tree.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i+1, pre.length), Arrays.copyOfRange(vin, i+1, vin.length));
break;
}
}
return tree;
}
}
这道题目一开始就想到了递归,可是不知道具体的写法,看了大佬的,真简单,但是怕是记不住。
第九题--用两个栈实现队列
描述
用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
数据范围:n≤1000
要求:存储n个元素的空间复杂度为 O(n)O(n) ,插入与删除的时间复杂度都是 O(1)O(1)
示例1
输入:["PSH1","PSH2","POP","POP"]
返回值:1,2
说明:"PSH1":代表将1插入队列尾部 "PSH2":代表将2插入队列尾部 "POP“:代表删除一个元素,先进先出=>返回1 "POP“:代表删除一个元素,先进先出=>返回2
示例2
输入:["PSH2","POP","PSH1","POP"]
返回值:2,1
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() {
//这个判空很重要,非空意味着还有前面还有未出队数据,所以初始是空的时候再一次性全进
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
这道题一开始就想到了写法,但是没有在pop中对satck2判空,导致部分出错。
第十二题--矩阵中的路径
描述
请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
数据范围:0≤n,m≤20 ,1≤len≤25
进阶:时间复杂度O(n^2) ,空间复杂度O(n^2 )
示例1
输入:[[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcced"
返回值:true
示例2
输入:[[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcb"
返回值:false
备注:
0 <= matrix.length <= 200
0 <= matrix[i].length <= 200
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param matrix char字符型二维数组
* @param word string字符串
* @return bool布尔型
*/
public boolean hasPath (char[][] matrix, String word) {
// write code here
char[] chars = word.toCharArray();
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
//有一个成立则全部成立
if(dfs(matrix, chars, i, j, 0)){
return true;
}
}
}
//找不到成立的则不成立
return false;
}
private boolean dfs (char[][] matrix, char[] word, int i, int j, int len){
//边界 或者 字符不对则停止递归
if(i<0||j<0||i>=matrix.length||j>=matrix[0].length||matrix[i][j]!=word[len]){
return false;
}
//长度达到要求字符串
if(len == word.length-1){
return true;
}
//记录坐标值
char tmp = matrix[i][j];
//标记为已经走过
matrix[i][j] = '.';
//判断四周
boolean res = dfs(matrix, word, i+1,j,len+1) ||
dfs(matrix, word, i,j+1,len+1) ||
dfs(matrix, word, i-1,j,len+1) ||
dfs(matrix, word, i,j-1,len+1);
//回复坐标
matrix[i][j] = tmp;
//返回结果
return res;
}
}
这道题目的方法一开始就想到了,但是搜索的细节没想到,看的解析。
第二十一题--调整数组顺序使奇数位于偶数前面(一)
描述
输入一个长度为 n 整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
数据范围:0≤n≤5000,数组中每个数的值 0≤val≤10000
要求:时间复杂度 O(n),空间复杂度 O(n)
进阶:时间复杂度 O(n^2),空间复杂度 O(1)
示例1
输入:[1,2,3,4]
返回值:[1,3,2,4]
示例2
输入:[2,4,6,5,7]
返回值:[5,7,2,4,6]
示例3
输入:[1,3,5,6,7]
返回值:[1,3,5,7,6]
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
//先准备两数组记录奇数和偶数
int[] answer = new int[array.length], tmp = new int[array.length];
//s和t分别记录奇数和偶数数量
int len = array.length, s = 0, t=0;
//分别将奇数和偶数按顺序存入对应数组
for(int i=0;i<len;i++){
if(array[i]%2==1){
answer[s++] = array[i];
}else{
tmp[t++] = array[i];
}
}
//将偶数数组接到奇数数组后
for(int i=0;i<t;i++){
answer[s++] = tmp[i];
}
return answer;
}
public int[] reOrderArray3 (int[] array) {
//所给数组的长度
int len = array.length;
//辅助数组
int [] nums = new int[len];
//双指针:left right并初始化
int left=0;
int right = len-1;
int tp_left=left;
int tp_right = right;
//循环条件:left<len && right>=0
while(left<len && right>=0){
//处理奇数情况
if(array[left] % 2==1){
nums[tp_left] = array[left];
tp_left++;
}
left++;
//处理偶数情况
if(array[right] % 2==0){
nums[tp_right] = array[right];
tp_right--;
}
//向左移动指针right
right--;
}
return nums;
}
}
两种方法都是自己想出来的,但是那个双指针脑子抽了一下,没有想到还可以用数组辅助指针,因为数组长度是固定的,所以可以从最后面开始插入,又学到了。