A 参考资料
- 书本《剑指offer(第二版)》
JZ01 二位数组中的查找
1.题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
示例1
输入
7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值
true
说明
存在7,返回true
2.暴力求解
当然最容易想到的是暴力求解,就是一个个查找,如果找到就返回true,没找到就返回false,代码很简单,没什么可说的。
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int rows = matrix.length, columns = matrix[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (matrix[i][j] == target) {
return true;
}
}
}
return false;
}
3.线性查找
题中说了每行都是递增的,每列也是递增的。所以我们查找的时候可以利用这个特性,如果我们从左上角开始找,当目标值target大于当前值的时候,我们需要往更大的找,但这个时候无论往右找还是往下找都是比当前值大,所以我们无法确定该往哪个方向找。同理右下角也一样,所以我们只能从右上角或者左下角开始找。我们就用上面的数据当target等于23的时候从右上角开始找,来画个图看一下
从右上角开始找有个方便的地方就是他左边的都是比他小的,他下边的都是比他大的,如果target大于当前值我们就往下边找,如果target小于当前值我们就往左边找,来看下代码。
public boolean Find(int target, int[][] array) {
if (array == null || array.length == 0 || array[0].length == 0) {
return false;
}
int rows = array.length, col = array[0].length;
//从第0行col - 1列开始查找,也就是第1行最后一列的那个数字开始
int row = 0;
int column = col - 1;
while (row < rows && column >= 0) {
//num表示当前值
int num = array[row][column];
if (num == target) {
//如果找到直接返回
return true;
} else if (num > target) {
//到前面查找
column--;
} else {
//到下面查找
row++;
}
}
return false;
}
当然从左下角查找也是可以的,因为左下角右边的值是比他大的,上边的值是比他小的,也能区分,代码和上面差不多,来看下
public boolean Find(int target, int[][] array) {
if (array == null || array.length == 0 || array[0].length == 0) {
return false;
}
int rows = array.length, col = array[0].length;
int row = rows - 1;
int column = 0;
while (row >= 0 && column < col) {
int num = array[row][column];
if (num == target) {
//如果找到直接返回
return true;
} else if (num > target) {
//往上面找
row--;
} else {
//往右边找
column++;
}
}
return false;
}
JZ02 替换空格
1.题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
示例1
输入
"We Are Happy"
返回值
"We%20Are%20Happy"
2.String.replaceAll()
直接调用String的封装方法
public String replaceSpace (String s) {
if (s == null || "".equals(s))
return s;
return s.replaceAll(" ", "%20");
}
3.Spring.split()
常规方法,拆分加替换
public String replaceSpace (String s) {
StringBuilder sb = new StringBuilder();
if (s == null || "".equals(s))
return s;
String[] strs = s.split("");
for (String str : strs) {
if (" ".equals(str))
sb.append("%20");
else
sb.append(str);
}
return sb.toString();
}
4.传统解法
虽然Java版的参数传入是String类,不像C语言直接就是字符数组,也不能直接在数组后面扩容。但是其实可以利用StringBuffer类来实现这样的操作。具体的思路的话书上已经讲解了,这里主要是贴出程序来介绍Java的实现方式。
pubilc class Solution{
pubilc String replaceSpace(String s){
if(s == null){ return null; }
StringBuffer str = new StringBuffer(s);
int length = str.length();
int spaceNum = 0;
for(int i = 0;i < length;i++){ if(str.charAt(i) == ' '){ spaceNum++; } }
int oldStr = length - 1;
length += 2 * spaceNum;
int newStr = length - 1;
str.setLength(length);
while(spaceNum > 0 && newStr >= 0){
char ch = str.charAt(oldStr--);
if(ch == ' '){
str.setCharAt(newStr--,'0');
str.setCharAt(newStr--,'2');
str.setCharAt(newStr--,'%');
spaceNum--;
}
else{ str.setCharAt(newStr--,ch); }
}
return str.toString();
}
}
JZ03 从尾到头打印链表
1.题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
示例1
输入
{67,0,24,58}
返回值
[58,24,0,67]
2.解题
一、非递归
1. 分析
listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 listNode 即可得到逆序链表
2. 代码
import java.util.*;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode tmp = listNode;
while(tmp!=null){
list.add(0,tmp.val);
tmp = tmp.next;
}
return list;
}
}
3. 复杂度
时间复杂度:O(n^2)
空间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndugQ35t-1620795871448)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921537-2de5fdce-dc30-44be-ad76-d8119b027bde.svg)]
二、递归
1. 分析
既然非递归都实现了,那么我们也可以利用递归,借助系统的"栈"帮忙打印
2. 代码
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;
}
}
3. 复杂度
时间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imH3FR4w-1620795871450)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921566-b2ef8ff7-8760-41ef-ad2c-76725b493c08.svg)]
空间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h21ye6vT-1620795871452)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921579-3b6e130a-420f-4a18-9d2e-e57d90f82d74.svg)]
4.牛客编程题高级数据结构格式说明(待整理)
JZ04 重建二叉树
1.题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
示例1
输入
{67,0,24,58}
返回值
[58,24,0,67]
2.解题一
关键是:利用前序序列根节点在前找到根节点,用根节点去中序序列划分成两部分,左部分是左子树,右部分是右子树。再利用子树长度去前序序列把前序序列中的左右子树找出来,同时可以找出根节点。递归进行此步骤,如果子树长度为0,则不需要生成子问题。
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
/**
* 关键是:利用前序序列根节点在前找到根节点,用根节点去中序序列划分成两部分,左部分是左子树,右部分是右子树。再利用子树长度去前序序列把前序序列中的左右子树找出来,同时可以找出根节点。递归进行此步骤,如果子树长度为0,则不需要生成子问题。
*/
public class Solution {
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
TreeNode root = new TreeNode(pre[0]);
build(root, pre, 0, pre.length, in, 0, in.length);
return root;
}
/**
* 递归和二分思想,将问题不断划分,直到问题容易解决。
* 做法是:对于一个根节点,先去中序序列中找到根节点的值所在位置,利用这个位置分成2部分,左部分的中序序列长度即为前序序列中左部分的中序序列长度,右部分亦然。
* 然后开始生成子问题,如果序列长度为0则不需要生成子问题。否则:利用前序序列第一个元素为根节点的性质生成根节点,然后构造子问题。
* @param root 根节点
* @param pre 前序序列 范围是[pleft,pright)
* @param in 中序序列 范围是[ileft,iright)
*/
public void build(TreeNode root, int[] pre, int pleft, int pright, int[] in, int ileft, int iright) {
int i;
for (i = ileft; i < iright; i++) {
if (in[i] == root.val) {//从中序序列寻找根节点的位置
break;
}
}
int t = i - ileft;
if (t > 0) {//子树长度为0时不必生成子问题
root.left = new TreeNode(pre[pleft + 1]);
build(root.left, pre, pleft + 1, pleft + 1 + t, in, ileft, i);
}
if (pright - pleft - 1 - t > 0) {
root.right = new TreeNode(pre[pleft + 1 + t]);
build(root.right, pre, pleft + 1 + t, pright, in, i + 1, iright);
}
}
}
目前报错数组越界,应该是索引没有完全找对,这个方法确实最好把图画出来
3.解题二
思路:
前序遍历:根→左→右中序遍历:左→根→右
-
由前序遍历序列pre={1,2,4,7,3,5,6,8}可知根结点是1;
-
则在中序遍历序列in={4,7,2,1,5,3,8,6}中找到1,便可知1所在位置的左侧是左子树,1所在位置的右侧是右子树;
-
递归调用:将左子树和右子树分别看成一颗树,将其前序遍历序列、中序遍历序列分别传入到该方法中,便可得到左子树的根结点、右子树的根结点。此时需要用第一步得到的根结点连接它们;
-
递归调用的终止条件:直到传入数组为空,说明已经没有节点,直接返回null。
import java.util.Arrays;
public class Solution {
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(root.val == 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;
}
}
1.关于Java中的Arrays.copyOfRange()方法
JZ04 重建二叉树
1.题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
示例1
输入
{67,0,24,58}
返回值
[58,24,0,67]
2.解题一
3.解题二
JZ04 重建二叉树
1.题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
示例1
输入
{67,0,24,58}
返回值
[58,24,0,67]
2.解题一
3.解题二
JZ04 重建二叉树
1.题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
示例1
输入
{67,0,24,58}
返回值
[58,24,0,67]