剑指offer编程题66道题 1-25
1.二维数组中的查找
题目描述
利用二维数组由上到下,由左到右递增的规律,
那么选取右上角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) {
int row=0;
int col=array[0].length-1;
while(row<=array.length-1&&col>=0){
if(target==array[row][col])
return true;
else if(target>array[row][col])
row++;
else
col--;
}
return false;
}
}
注意:
对于一个二维数组:
int[][] arr = {
{2,3,4},
{4,5,6},
{7,8,9}
};
int rows = i.length;//行数
int columns = i[0].length;//列数
[[]]这样一个数组代表行数是1,列数时0.
2.替换空格
题目描述
public class Solution {
public String replaceSpace(StringBuffer str) {
StringBuffer strCopy= new StringBuffer();
for(int i=0 ; i<str.length();i++){
String c = String.valueOf(str.charAt(i));
if(c.equals(" ")){
strCopy.append("%20");
}else {
strCopy.append(c);
}
}
return strCopy.toString();
}
}
注意:
Java中字符数组、String类、StringBuffer三者的相互转换
3.从尾到头打印链表
题目描述
思路:遇到先进后出的情况,可以采取两种思路:
1.利用堆栈的思想
2.利用递归的思想
下面是采用堆栈的思想:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<ListNode> stack = new Stack<ListNode>();
while(listNode!=null){
stack.push(listNode);
listNode = listNode.next;
}
ArrayList<Integer> list = new ArrayList<Integer>();
while(!stack.isEmpty()){
list.add(stack.pop().val);
}
return list;
}
}
下面是采用递归的思想 :
import java.util.ArrayList;
public class Solution {
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode!=null){
this.printListFromTailToHead(listNode.next);
list.add(listNode.val);
}
return list;
}
}
4.重建二叉树
题目描述
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 [] in) {
if(pre.length == 0||in.length == 0){
return null;
}
TreeNode node = new TreeNode(pre[0]);
for(int i = 0; i < in.length; i++){
if(pre[0] == in[i]){
node.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1), Arrays.copyOfRange(in, 0, i));
node.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i+1, pre.length), Arrays.copyOfRange(in, i+1,in.length));
break;
}
}
return node;
}
}
这是形式最为简洁的方法,首先前序的第一个数字肯定是根节点,然后根据这个元素在中序中找到根节点位置i,
那么中序左边0,i是左子树,右边i+1,in.length是右子树。
并且也能在前序中确定左子树是从1,i+1的元素,右子树是i+1,pre.length。(注意这里是左边界包含,右边界不包含)
然后从原数组中分别复制前序和中序的左子树和右子树,递归调用即可。直到pre.length或者in.length(当然这里他俩本来就一样)为0表示递归到叶子节点。
注:Arrays.copyOfRange()方法
startPre>endPre||startIn>endIn为表示已经递归完成
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
TreeNode root=reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
return root;
}
//前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {
if(startPre>endPre||startIn>endIn)
return null;
TreeNode root=new TreeNode(pre[startPre]);
for(int i=startIn;i<=endIn;i++)
if(in[i]==pre[startPre]){
root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
root.right=reConstructBinaryTree(pre,i-startIn+startPre+1,endPre,in,i+1,endIn);
break;
}
return root;
}
}
5.用两个栈实现队列
题目描述
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();
}
}
6.旋转数组的最小数字
题目描述
public int minNumberInRotateArray(int[] array) {
if (array.length == 0)
return 0;
for (int i = 0; i < array.length - 1; i++) {
if (array[i] > array[i + 1])
return array[i + 1];
}
return array[0];
}
3.采用二分法解答这个问题,
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]){
high=mid;
}else if(array[mid]==array[high]){
high = high-1;
}else{
low = mid +1;
}
}
return array[low];
}
}
7——10 都是斐波拉契数列的变形题
7.斐波拉契数列
题目描述
n<=39
public class Solution {
public int Fibonacci(int n) {
if(n<2)
return n;
int fb1 = 0;
int fb2 = 1;
int fbn = 0;
for(int i=2;i<=n;i++){
fbn = fb1+fb2;
fb1 = fb2;
fb2 = fbn;
}
return fbn;
}
}
8.跳台阶
题目描述
| 1, (n=1)
f(n) = | 2, (n=2)
对于本题,前提只有 一次 1阶或者2阶的跳法。
a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
e.可以发现最终得出的是一个斐波那契数列:
思路:
1.用迭代的方法
public int JumpFloor(int target) {
if(target == 1 || target == 2)
return target;
int floorOne = 1;
int floorTwo = 2;
int floorSum = 0;
for(int i=3;i<=target;i++){
floorSum = floorOne +floorTwo;
floorOne = floorTwo;
floorTwo = floorSum;
}
return floorSum;
}
2.用递归的方法
public int JumpFloor(int target) {
if(target == 1 || target == 2){
return target;
}else{
return JumpFloor(target-1)+JumpFloor(target-2);
}
}
9.变态跳台阶
题目描述
思路:
每个台阶都有跳与不跳两种情况(除了最后一个台阶),最后一个台阶必须跳。所以共用2^(n-1)中情况
解法有两种:一种用移位操作,一个用Math工具
import java.lang.Math;
public class Solution {
public int JumpFloorII(int target) {
//return (int)Math.pow(2,target-1);
return 1<< --target;
}
}
10.矩形的覆盖
题目描述
public class Solution {
public int RectCover(int target) {
if(target == 1 || target == 2)
return target;
int floorOne = 1;
int floorTwo = 2;
int floorSum = 0;
for(int i=3;i<=target;i++){
floorSum = floorOne +floorTwo;
floorOne = floorTwo;
floorTwo = floorSum;
}
return floorSum;
}
}
11.二进制中1的个数
题目描述
方法:
1.最简单方法
return Integer.bitCount(n);
2.用1(1自身左移运算,其实后来就不是1了)和n的每位进行位与,来判断1的个数
public int NumberOf1(int n) {
int count = 0;
int flag1 = 1;
while (flag1 != 0){
if((n & flag1 ) != 0)
count++;
flag1 = flag1<<1;
}
return count;
}
}
3.如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
public class Solution {
public int NumberOf1(int n) {
int count = 0;
while(n!= 0){
count++;
n = n & (n - 1);
}
return count;
}
}
12.数值的整数次方
题目描述
public class Solution {
public double Power(double base, int exponent) {
if(base == 0 && exponent < 0){
throw new RuntimeException("分母不能为0");
}else if(exponent==0){
return 1;
}
double absResult = 0;
if(exponent<0){
absResult = absExponent(base,-exponent);
absResult = 1/absResult;
}else{
absResult = absExponent(base,exponent);
}
return absResult;
}
public double absExponent(double base,double exponent){
double result = base;
for(int i=1;i<exponent;i++){
result = base*result;
}
return result;
}
}
13.调整数组使奇数位于偶数前面
题目描述
思路:
时间复杂度为O(n),空间复杂度为O(n)的算法,用空间换时间
public class Solution {
public void reOrderArray(int [] array) {
int[] tempArray = new int[array.length];
int j =0;
for (int i = 0; i<array.length;i++){
if((array[i]&1) == 1){
tempArray[j] = array[i];
j++;
}
}
for (int i = 0; i<array.length;i++){
if((array[i]&1) != 1){
tempArray[j] = array[i];
j++;
}
}
for(int i=0;i<array.length;i++){
array[i] = tempArray[i];
}
}
}
14.链表中第k个节点
题目描述
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
//头结点为空节点或者输入的k为0
if (head == null || k == 0){
return null;
}
ListNode listA = head;
ListNode listB = head;
for (int i=0; i<k-1;i++){
if(listA.next != null)
listA = listA.next;
//链表的长度小于k
else
return null;
}
//遍历链表一次找到第k个节点
while(listA.next != null){
listA = listA.next;
listB = listB.next;
}
return listB;
}
}
2.遇到这种倒置行为一定要想到堆栈,使用Stack,将结点压入栈中,再取出第k个就好
if(head == null || k ==0 ){
return null;
}
//可以先把链表反转,然后找出第k个
Stack<ListNode> stack = new Stack<ListNode>();
int count = 0;
while(head != null){
stack.push(head);
head = head.next;
count++;
}
if(count < k){
return null;
}
ListNode knode = null;
for(int i = 0; i < k; i++){
knode = stack.pop();
}
return knode;
15.反转链表
题目描述
public class Solution { public ListNode ReverseList(ListNode head) { if(head==null) return null; //head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null; ListNode pre = null; ListNode next = null; //当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点 //需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head next1->next2 //即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了 //所以需要用到pre和next两个节点 //1->2->3->4->5 //1<-2<-3 4->5、 while(head!=null){ //做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre //如此就可以做到反转链表的效果 //先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂 next = head.next; //保存完next,就可以让head从指向next变成指向pre了,代码如下 head.next = pre; //head指向pre后,就继续依次反转下一个节点 //让pre,head,next依次向后移动一个节点,继续下一次的指针反转 pre = head; head = next; } //如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点 //直接输出pre就是我们想要得到的反转后的链表 return pre; } }
16.合并两个排序的链表
题目描述
public class Solution { public ListNode Merge(ListNode list1,ListNode list2) { if(list1 == null) return list2; else if(list2 == null) return list1; ListNode mergedHead = null; if(list1.val< list2.val){ mergedHead = list1; mergedHead.next = Merge(list1.next,list2); }else{ mergedHead = list2; mergedHead.next = Merge(list1,list2.next); } return mergedHead; } }
迭代:
public ListNode Merge(ListNode list1, ListNode list2) { ListNode head = new ListNode(-1); ListNode cur = head; while (list1 != null && list2 != null) { if (list1.val <= list2.val) { cur.next = list1; list1 = list1.next; } else { cur.next = list2; list2 = list2.next; } cur = cur.next; } if (list1 != null) cur.next = list1; if (list2 != null) cur.next = list2; return head.next; }
17.树的子结构
题目描述
1.递归思想,如果根节点相同则递归调用DoesTree1HaveTree2(),
如果根节点不相同,则判断tree1的左子树和tree2是否相同,
再判断右子树和tree2是否相同
Tree2为空,则说明第二棵树遍历完了,即匹配成功;
如果tree1为空&&tree2不为空说明不匹配;如果两个根节点相同则继续遍历判断
public class Solution { public boolean HasSubtree(TreeNode root1,TreeNode root2) { if(root1 ==null || root2 == null) return false; return DoTreeHavaTree2(root1,root2) || HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2); } public boolean DoTreeHavaTree2(TreeNode root1,TreeNode root2){ if(root2 == null) return true; if(root1 == null) return false; if(root1.val == root2.val) return DoTreeHavaTree2(root1.left,root2.left) && DoTreeHavaTree2(root1.right,root2.right); else return false; } }
18.二叉树的镜像
题目描述
思路:先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子节点,
当交换完所有的非叶子结点的左右子结点之后,就得到了树的镜像.
import java.util.Stack; public class Solution { public void Mirror(TreeNode root) { if(root ==null || root.left == null && root.right ==null) return; Stack<TreeNode> tStack = new Stack<TreeNode>(); tStack.add(root); while(tStack.size() != 0){ TreeNode p = tStack.pop(); if(p.left!=null || p.right!=null){ TreeNode temp = p.left; p.left = p.right; p.right = temp; if(p.left != null) tStack.push(p.left); if(p.right != null) tStack.push(p.right); } } } }
19.顺时针打印矩阵
题目描述
import java.util.ArrayList; public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix) { int row = matrix.length; int col = matrix[0].length; ArrayList<Integer> list = new ArrayList<Integer>(); if(row == 0 || col ==0) return list; // 定义四个关键变量,表示左上和右下的打印范围 int left = 0; int top = 0; int right = col - 1; int bottom = row - 1; while (left <= right && top <= bottom) { // left to right for(int i = left; i <= right; ++i) list.add(matrix[top][i]); // top to bottom for (int i = top + 1; i <= bottom; ++i) list.add(matrix[i][right]); // right to left 特别注意这里和后面两个for循环的判断条件,防止单行或者单列出现重复扫描技术的情况 //如果top != bottom说明从右到左的扫描不会和之前从左到右重复 if (top != bottom) for (int i = right - 1; i >= left; --i) list.add(matrix[bottom][i]); // bottom to top if (left != right) for (int i = bottom - 1; i >= top+1; --i) list.add(matrix[i][left]); left++;top++;right--;bottom--; } return list; } }
20.包含min函数的栈
题目描述
用一个栈data保存数据,用另外一个栈min保存依次入栈最小的数
比如,data中依次入栈,
5
,
4
,
3
,
8
,
10
,
11
,
12
,
1
则min依次入栈,
5
,
4
,
3
,no,no, no, no,
1
no代表此次不入栈
每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不入栈。
import java.util.Stack; public class Solution { Stack<Integer> stack = new Stack<Integer>(); Stack<Integer> minstack = new Stack<Integer>(); public void push(int node) { stack.push(node); if(minstack.empty() || node < minstack.peek()) minstack.push(node); } public void pop() { if(stack.peek() == minstack.peek()) minstack.pop(); stack.pop(); } public int top() { return stack.peek(); } public int min() { return minstack.peek(); } }
21.栈的压入、弹出序列
题目描述
思路:借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
举例:
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈,此时栈顶1≠4,继续入栈2
此时栈顶2≠4,继续入栈3
此时栈顶3≠4,继续入栈4
此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
此时栈顶3≠5,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
….
import java.util.ArrayList; import java.util.Stack; public class Solution { public boolean IsPopOrder(int [] pushA,int [] popA) { if(pushA.length == 0 || popA.length == 0) return false; int popIndex = 0; Stack <Integer> s = new Stack<Integer>(); for(int i=0;i<pushA.length;i++){ s.push(pushA[i]); while(!s.empty() && popA[popIndex] == s.peek()){ s.pop(); popIndex++; } } return s.empty(); } }
22.从上向下打印二叉树
题目描述
public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { LinkedList<TreeNode> q = new LinkedList<TreeNode>(); ArrayList <Integer> list = new ArrayList<Integer>(); if(root == null) return list; q.offer(root); while(!q.isEmpty()){ TreeNode p = q.poll(); list.add(p.val); if(p.left != null) q.offer(p.left); if(p.right != null) q.offer(p.right); } return list; } }
递归解法:
import java.util.ArrayList; public class Solution { ArrayList<Integer> list=new ArrayList<Integer>(); public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { if(root!=null) list.add(root.val); print(root); return list; } public void print(TreeNode root){ if(root!=null){ if(root.left!=null) list.add(root.left.val); if(root.right!=null) list.add(root.right.val); print(root.left); print(root.right); } } }
23.二叉搜索树的后序遍历
题目描述
import java.util.*; public class Solution { public static boolean VerifySquenceOfBST(int [] sequence) { int length = sequence.length; if(sequence == null || sequence.length == 0) return false;
//在二叉搜索树中左子树节点的值小于根节点的值,找到最后一个左子树节点 int i = 0; for(;i<sequence.length-1;i++){ if(sequence[i]>sequence[length-1]) break; } //在二叉搜索树中右子树节点的值大于根节点的值 int j = i; for(;j<sequence.length-1;j++){ if(sequence[j]<sequence[length-1]) return false; } boolean left = true; if(i>0) left = VerifySquenceOfBST(Arrays.copyOfRange(sequence, 0, i)); boolean right = true; if(i<length-1) right = VerifySquenceOfBST(Arrays.copyOfRange(sequence,i,length-1)); return left && right; } }
24.二叉树中和为某一值的路径
题目描述
public class Solution { private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>(); private ArrayList<Integer> list = new ArrayList<Integer>(); public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) { if(root == null) return listAll; list.add(root.val); target -= root.val; if(target == 0 && root.left == null && root.right == null) listAll.add(new ArrayList<Integer>(list)); FindPath(root.left, target); FindPath(root.right, target); //回退的时候删去当前节点 list.remove(list.size()-1); return listAll; } }
25.复杂链表的复制
题目描述
/* 1、复制每个节点,如:复制节点A得到A1,将A1插入节点A后面 2、遍历链表,A1->random = A->random->next; 3、将链表拆分成原链表和复制后的链表 */ public class Solution { public RandomListNode Clone(RandomListNode pHead) { if(pHead == null) return null; RandomListNode node = pHead; while(node != null){ RandomListNode cloneNode = new RandomListNode(node.label); cloneNode.next = node.next; node.next = cloneNode; node = cloneNode.next; } node = pHead; while(node != null){ if(node.random != null) node.next.random = node.random.next; node = node.next.next; } RandomListNode head = pHead.next; RandomListNode cloneNode = head; node = pHead; while(node != null){ node.next = node.next.next; if(node.next != null) cloneNode.next = cloneNode.next.next; node = node.next; cloneNode = cloneNode.next; } return head; } }