剑指offer程序题设计相关的知识点(Java语言)
1.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
注意:
1.对于一个二维数组array,row = array.length;col = array[0].length
2.根据此二维数组的规律,即一行数组从小到大,一列数字也是从小到大,所以遍历从每一行的最后一个数字开始遍历,移动方向为向左向下。
2.替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
注意:
1.注意StringBuffer和String的区别,需要将StringBuffer类别转换为String类别,StringBuffer str;String s = new String(str);
2.可以直接用Sting类型数据的replace()方法直接替换目标字符(串)s.replace(' ", "%20")
。
3.可以用一种更快更少内存的方法,Java中有一个indexOf()方法,用于锁定某字符的位置,str.indexOf(" ")
,可以先不用进行字符串转换,然后用StringBuffer的replace()方法替换字符,str.replace(begin,begin+1, "%20")
,不包括end,然后接着begin的位置再往下寻找新的indexOf,str.indexOf(" ", begin)
。
4.在字符串不经常发生变化的业务场景优先使用String(代码更清晰简洁)。如常量的声明,少量的字符串操作(拼接,删除等)。
5.在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。不能使用String"+"来拼接而是使用,避免产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。如JSON的封装等。
6.在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。如HTTP参数解析和封装等。
3.从头到尾打印列表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
注意:
1.首先要创建一个链表,包括值和指针。
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
2.创建两个arraylist,用于存储正序和倒叙的链表。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList<Integer> result = new ArrayList<Integer>();
ListNode temp = listNode;
while(temp!=null){
list.add(temp.val);
temp = temp.next;
}
for(int i=list.size()-1;i>=0;i--){
result.add(list.get(i));
}
return result;
}
}
3.一个链表存储的内容是头节点的值以及next指针,根据这两个值客可以遍历整个链表。
4.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
注意:
1.首先要定义一个二叉树。
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
2.定义一个整数,用于代表在前序中根节点的位置。
private int pre_index = 0; //walk in pre,for find the value next root
3.定义一个函数,用于实现在某个区间搜索特定值的索引。
// find the root, the subtree brlong to [x,y]
public int Find(int target, int x, int y, int shuzu[]){
int i;
for(i=x;i<=y;i++){
if(shuzu[i]==target){
break;
}
}
return i;
}
4.定义一个函数,用于利用前序和中序遍历结果,递归在左右子树中寻找父节点,依次输出(根节点 -左子树-右子树,构建二叉树的顺序)。
public TreeNode LeftNodeAndRightNode(int x, int y, int shuzu[]){
int index = Find(pre[this.pre_index++], x, y, shuzu);
TreeNode temp = new TreeNode(shuzu[index]);
if(index!=x){
temp.left = LeftNodeAndRightNode(x, index - 1, shuzu);
}
if(index != y){
temp.right = LeftNodeAndRightNode(index+1, y, shuzu);
}
return temp;
}
5.先通过前序遍历确认根节点,之后调用以上两个函数,在左右子树中递归遍历。
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
TreeNode tn = new TreeNode(pre[0]);// the tree only have root
this.pre = pre;
int in_len = in.length;
int rootNode_index = Find(pre[pre_index++], 0, in_len, in);
if(rootNode_index!=0){
tn.left = LeftNodeAndRightNode(0, rootNode_index-1,in);
}
if(rootNode_index!=in_len-1){
tn.right = LeftNodeAndRightNode(rootNode_index+1, in_len-1, in);
}
return tn;
}
5.用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
注意:
1. 思路:由于队列是先进先出的,而栈是先进后出的,所以要用2个栈来实现队列的入队出队功能,队列的入队功能与栈的一样,出队时,先将第一个栈中的元素全部弹出,并倒入到第二个栈中,将第二个栈中栈顶元素弹出,并将stack2中剩下的元素倒回到stack1中,即实现一次出队。
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); // the push() of stack
//出栈函数
public int pop(){
Integer re = null;
if(!stack2.empty()){ //如果栈2不是空的,就把最上面的取出来(第一次操作肯定是空的)
re = stack2.pop(); //除了第一次不执行,之后都执行这一句,取出栈顶元素
}else{
// 如果栈2是空的,就把栈1里的数一个个取出来,放到栈2里
while(!stack1.empty()){
re = stack1.pop();
stack2.push(re);
}
if(!stack2.empty()){
// 栈2里面有数之后,再次把里面的数取出来
re = stack2.pop();
}
}
return re;
}
}
6.旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转*。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。*
注意:
1. 第一种是自己的方法,因为数字是非递减的,所以遍历旋转数组,依次将当前数字与后一个数字比较,如果后一个数字比当前数字小,则一定是前面的元素,如果遍历完还没有这种情况,一直是相等,那说明数组中的数字大小一致。这种方法的速度更快,更简单。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
/* 逐个比较法(快)
int len = array.length;
if(len==0){
return 0;
}
for(int i=0;i<len-1;i++){
if(array[i]<=array[i+1]){
if(i==len-2){ //!!!!!注意索引不要出界
return array[i];
}
continue;
}else{
return array[i+1];
}
}
return 0;
}
2. 第二种是二分法比较。这种方法的速度慢,复杂。
思路:二分查找(慢)
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
low = low - 1
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]
array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误
import java.util.ArrayList;
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[high]){ // 如果中间值更大,说明最小值在中间的右边 移动low指针
low = mid + 1;
}
else if(array[mid]==array[high]){ // 如果两个值相等,难以判断 数组有重复数字 要一个一个试试
low = low - 1;
high = high - 1;
}else{
// 如果中间值小,说明最小值在左边 移动high指针 但是如果只剩两个数字 mid返回的是low位置的 此时high=mid-1会报错 所以high=mid
high = mid;
}
}
return array[low]; // 最后只会剩两个 最小值肯定在low位置
}
}