刷题笔记(剑指offer-67题,Java版)

文章目录


点击下方目录,可跳转查看:

本文包括刷题的总体思想要义、以及剑指offer67题的全部题解。

《剑指Offer》刷题笔记(67题全)

01. 二维数组中的查找 【中值出发无分支】

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

【思路】
左上最小、右下最大。右上or左下出发,快速切边查找;每一步都只有一个选择=》不产生分支

public class Solution {
   
    public boolean Find(int target, int [][] array) {
   
        if(array.length==0 || array[0].length==0)return false;
      //尽量都写成左闭右闭区间的风格,在一开始就减一,上下界都是能够达到的值
        int row = array.length -1;
        int col = array[0].length -1;
        //这里从右上开始,左下也可以。  //(不能从左上开始,不然不知道移动的方向。更不能从任意位置开始)
        int i = row;
        int j =0;
        while(i>=0 && j<=col){
   //范围用>=和<=,这样配合左闭右闭区间
            if(array[i][j]>target)--i;//【每次判断都能剔除一整行或一整列】
            else if(array[i][j]<target)++j;//这里的else if 不能用else,因为上面的语句可能会影响array[i][j]的值(改变了i的值)
            else return true;//将==放在最后,因为这个情况的概率最小,这样效率更高
        }
        return false;
    }
}
//时间复杂度:O(col+row)
//空间复杂度:O(1)

02. 替换空格 【从后往前扩充】

1) 原版(给StringBuffer):

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

【思路】
如果给StringBuffer就直接在上面改;从后往前,进行扩张。
如果给String则需要新开辟空间,然后从前往后即可。

return str.toString().replace(" ","%20");    //一句搞定
public class Solution {
   
    public String replaceSpace(StringBuffer str) {
     //str的类型是StringBuffer,最后要转换成String//一共两轮,第一轮是扫描得到space个数
        int space=0;
        int L1=str.length();//str需要length();数组一般用length
        for (int i=0;i<L1;i++)
        {
   
            if (str.charAt(i)==' ')space++;  //【str.charAt(i)】
        }
        int L2=L1+2*space;
        str.setLength(L2);   //【str.setLength(L2)】一定要修改(加长)str的长度
        L1--;L2--; //一定要一次性减1,来对齐数组下标
        while (L1>=0&&L2>L1){
   
            if (str.charAt(L1)!=' '){
   
                str.setCharAt(L2--,str.charAt(L1));   //【str.setCharAt(下标,值)】
            }
            else{
   
                str.setCharAt(L2--,'0');
                str.setCharAt(L2--,'2');
                str.setCharAt(L2--,'%');
            }
            L1--;
        }
        return str.toString();   //【str.toString()】
    }
}
//时间复杂度:O(N)
//空间复杂度:O(space) =>直接在原来的StringBuffer上面改

2) 新版(给String):

import java.util.*;

public class Solution {
   
    public String replaceSpace (String s) {
   
        StringBuilder res = new StringBuilder();
        int len =s.length()-1;
        for(int i=0;i<=len;++i){
   
            if(s.charAt(i)!=' '){
     //单引号代表char, 双引号代表String
                res.append(s.charAt(i));  //String可用charAt()
            }
            else res.append("%20");//StringBuilder的append可以是几乎所有类型
        }
        return res.toString();
    }
}
//时间:O(N)
//空间:O(N)

03. 从尾到头打印链表值【无需反转链表,直接倒叙输出val】

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

【思路】
1)递归方法(系统栈):类似于树只有单分支,持续向下到末端后,后续遍历往上
2)用栈反转:注意,从尾到头打印值,可能只是反转值,而没有反转链表。
3)头插法直接存到ArrayList res,这个效率最低,所以说ArrayList尽量不要用头插法。

1)递归方法(系统栈)

import java.util.ArrayList;
public class Solution {
   
    ArrayList<Integer> res = new ArrayList<Integer>();  //一定要在函数之前定义
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
   
        if(listNode!=null){
   
            printListFromTailToHead(listNode.next);    //没有用到printListFromTailToHead的返回值
            res.add(listNode.val);    //这个在递归后面,则可以做到倒序;如果在递归前就是正序
        }
        return res;
    }
}//空间O(N)  时间O(N)

2)用栈反转

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
   
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
   
        ArrayList<Integer> res = new ArrayList<Integer>();
        Stack<Integer> stack = new Stack<Integer>();    //posh + pop 搞定
        while(listNode != null){
   
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while(!stack.isEmpty()){
   
            res.add(stack.pop());//这里只是反转了val, 如果要反转链表可以新建Node
        }
        return res;
    }
}//空间O(N)  时间O(N)

3)ArrayList中用 - 头插法

import java.util.ArrayList;
public class Solution {
   
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
   
        ArrayList<Integer>mylist=new ArrayList<>();//含有<>和();别忘了new
        while(listNode!=null){
   //直接用null对应listNode就行
            mylist.add(0,listNode.val);//list.add(0,value)在list的头部插入值,单次耗时O(N)
            listNode=listNode.next;//Java这样就不用到->指针了,只会用到STL里面定义过的操作
        }
        return mylist;
    }
}//空间O(N)  时间O(N^2)
//时间效率最低,所以说ArrayList尽量不要用头插法

04. 重建二叉树 【先序pre[0]做根切割中序】

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

【思路】
前序+中序=》重建二叉树
先序pre[0]做根切割中序

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.Arrays;//Arrays.copyOfRange(,,);  //针对pre[]和in[]数组,功能:选择范围复制==>左闭右开区间
public class Solution {
   
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
    //返回的是根节点
        if(pre.length==0||in.length==0)return null;//【递归的终结】  //也可以简化为 pre.length==0 (只判断一个即可)   //不能写成 pre==null,因为pre==[]时,数组不是null但长度为零
        TreeNode node=new TreeNode(pre[0]);//先序的第一个pre[0]永远是根节点,也是分左右子树的关键
        for(int i=0;i<pre.length;i++){
    //pre和in的子数组永远是对应相同长度的
            if(pre[0]==in[i]){
   //每一次for循环,只有一次会执行if里面的语句
                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));
            }//在建设node.val后,再递归调用获取node.left和node.right,这样3步后,一个node就完整建立起来了
        }
        return node;
    }
}
//复杂度方面:最坏情况下(树是一条直线)每一层递归从O(n)直到O(1),因为每一层都会至少减少1个复制的。
//最坏情况下,N层递归,此时时间空间复杂度都是O(N^2)
//平均情况下,log(N)层递归,此时时间空间复杂度都是O(NlogN)

05. 用两个栈实现队列 【等栈2排空,栈1全到2】

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

【思路】
如果不控制好栈1栈2的数据流动,可能会造成数据顺序错乱
只有栈2排空了,才会由1到2,必须一次性全部

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.isEmpty()){
    //【只有stack2排空了,才会由1到2,且必须一次性全部】
            while(!stack1.isEmpty())stack2.push(stack1.pop());
        }
        return stack2.pop();//(无论上面的if语句结果怎样,这里都会pop出一个)
    }
}

06. 旋转数组的最小数字 【二分探测- 3种情况,相等也要动】

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

1)O(n) 暴力扫描

import java.util.ArrayList;
public class Solution {
   
    public int minNumberInRotateArray(int [] array) {
   
        if (array.length==0)return 0;
        int min=array[0];
        for (int i=0;i<array.length;i++){
   
            if (array[i]<min)min=array[i]; 
        }
        return min;
    }
}  
//暴力查找,时间O(N)

2)二分法

import java.util.ArrayList;
public class Solution {
   
    public int minNumberInRotateArray(int [] array) {
   
        int len=array.length;
        if(len==0)return 0;
        int left=0;int right=len-1;//自己写区间的时候,尽量用“左闭右闭”区间,不然容易出错。(不要用左闭右开区间!!)
        while(left<right){
   
            if(array[left]<array[right]){
   //严格小于//说明区间里面没有“断层”,单调增/单调不减
                return array[left];
            }
            int mid=(left+right)/2;//左右区间内有“断层”的时候,需要探测mid位置的值
            //3种情况:大于小于等于(最好画个图)//最好是mid和right来比较;选mid和left来比较时,还需要再次分类判断(因为只有2个数时,mid和left重合)
            if(array[mid]>array[right])left=mid+1;
            else if(array[mid]<array[right])right=mid;
            else if(array[mid]==array[right])--right;//这种情况容易考虑不到,导致区间无法收敛
        }
        return array[right]; //此时只有一个元素,所以left==right
    }
}
//二分查找,平均时间O(logN)
//最坏情况(全部相等)时间复杂度O(N)

07. 斐波那契数列

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

public class Solution {
   
    public int Fibonacci(int n) {
   
        int[] fi=new int[40];//设置数组记录中间结果,不然重复计算太多   //根据题目,放心设置数组大小	 
        fi[0]=0;fi[1]=1;
        for(int i=2;i<=n;i++){
   
            fi[i]=fi[i-1]+fi[i-2];
        }
        return fi[n];
    }
}
//动态规划,时间复杂度O(N),空间复杂度O(N)
//如果用递归,时间复杂度O(1.618^N)【上网查的,略小于2^N】,空间复杂度O(1)【不包括系统栈空间】

08. 跳台阶

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

1)斐波拉切-O(N)动态规划

public class Solution {
   
    public int JumpFloor(int target) {
   
        int frog[]=new int[100];
        frog[1]=1;frog[2]=2;
        for (int i=3;i<=target;i++){
   
            frog[i]=frog[i-1]+frog[i-2];
        }
        return frog[target];
    }
}
//原理同:斐波那契数列
//【动态规划】时间O(N),空间O(N)
//如果只要最后的结果,那么可以撤销数组,使用a/b/c三个变量存储即可。空间复杂度减为O(1)

2)空间O(1)的方法

public class Solution {
   
    public int jumpFloor(int target) {
   
        if(target<=2)return target;
        int lastOne = 2;  //现在位置上一个,相当于fi[i-1]
        int lastTwo = 1;  //相当于fi[i-2]
        int res = 0;
        for(int i=3; i<=target; ++i){
   
            res = lastOne + lastTwo;
            lastTwo = lastOne;
            lastOne = res;
        }
        return res;
    }
}
//这种方法的空间复杂度为:O(1)
//时间复杂度虽然也为O(N),但是比上一种动态规划的方法耗时,因为循环里面操作较多
//相当于时间换空间,花费时间在不断倒腾地方

09. 跳台阶扩展问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
1)找出公式

public class Solution {
   
    public int JumpFloorII(int target) {
   
        int way=1;for(int i=1;i<target;i++)way*=2;return way;
    }
}
//【找出数学公式】2的n-1次方:类似向n个点之间的n-1个空画横线
// 其实不难找,在找递推公式时,前几项一写就知道了
// 时间  O(N)
// 空间  O(1)

2)(动态规划)硬算

public class Solution {
   
    public int jumpFloorII(int target) {
   
        int[] array =new int[100];
        array[1] = 1;
        for(int i=2; i<=target; ++i){
   
            int sum = 0;
            for(int j=1; j<=i-1; ++j)sum+=array[j];
            array[i] = sum +1;  //之前所有路径,再加上直接全部的1个跳法
        }
        return array[target];
    }
}
//时间  O(N^2)
//空间  O(N)

10. 矩形覆盖

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

public class Solution {
   
    public int rectCover(int target) {
   
        int fi[] = new int[100];
        for(int i= 0; i<=2; ++i)fi[i]=i;
        for(int i=3; i<=target; ++i)fi[i]=fi[i-1]+fi[i-2];
        return fi[target];
    }
}
//(除了初始少许不一样,后面是斐波拉切)
// 找递推关系:分解情况==》最右边只可能为竖或横两种情况,这两种情况无交集,分别占用1个块块和2个块块

11. 二进制中1的个数【0x01按位与】

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

方法一:按位与

public class Solution {
   
    public int NumberOf1(int n) {
   //可以直接拿int类型,当做二进制来进行位运算 (IDEA上亲测可用)
        int count =0;
        int mark = 0x01;
        for(int i=0;i<32;++i){
   //从最低位到最高位,一个个试   //或者用while(mark !=0)也可以
            if((n & mark)!=0)++count;//不能是if(bit&n!=0),少了括号后,先计算n!=0(判断优先于按位运算)
            mark<<=1;//mark中唯一的1,左移一位
        }
        return count;
    }
}
//时间复杂度O(1)==>O(32)
//空间复杂度O(1)
//C++要3ms,Java要13ms

方法二:神奇方法(由于规模限制,所以并无明显优势)

public class Solution {
   
    public int NumberOf1(int n) {
   
        int count=0;
        while(n!=0){
   
            n=n&(n-1);//神奇的方法:补码&反码  //&的含义是只有两个都是1,结果才是1
            count++;//跳过了补码中没有1的位,每一轮循环弹无虚发,都找到一个1
        }
        return count;
    }
}
//时间复杂度O(1)==>O(16),输入n的补码中1的个数,平均为O(16)
//空间复杂度O(1)
//C++要3ms,Java要13ms //和上面一样时间,白优化了hhh

12. 数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0

public class Solution {
   
    public double Power(double base, int exponent) {
   
        if(exponent == 0)return 1;
        double res = 1.0;
        if(exponent <0){
   
            base = 1.0/base;
            exponent *=(-1);
        }
        for(int i=1; i<=exponent; ++i)res*=base;
        return res;
  }
}
//时间  O(exponent)
//空间  O(1)

13. 调整数组顺序使奇数位于偶数前面 【保证顺序:新开空间;只区分奇偶:双指针】

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

【思路】
1)若是保证顺序:需要新开辟空间
2)若是只需要区分奇偶数,就直接在原数组上用双指针 (类似快排:一个条件划分两块)

import java.util.*;

public class Solution {
   
    public int[] reOrderArray (int[] array) {
   
        int len = array.length;
        //1个数组 + 1个下标:
        int [] res = new int[len];
        int k =0;
        
        for(int i=0; i<=len-1; ++i){
   
            if(array[i]%2 == 1)
                res[k++] = array[i];
        }
        for(int i=0; i<=len-1; ++i){
   
            if(array[i]%2 ==0)
                res[k++] = array[i];
        }
        return res;
    }
}
//在保证奇数偶数内部相对顺序的情况下,这种方法就是最优了,时间、空间都是O(n)
//书上的题目要求仅仅是将奇偶数分开,那么用类似快排的two pointer就行,时间O(n),空间是O(1)

14. 链表中倒数最后k个结点 【双指针,一个先走k步】

输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。

1)标准的 双指针法

public class Solution {
   
    public ListNode FindKthToTail (ListNode pHead, int k) {
   
        ListNode pre = pHead;//head是第一个节点
        ListNode res = pHead;
        while(--k >= 0){
   
            if(pre== null)return null;//倒数第k的k值大于链表总长度
            pre=pre.next;
        }
        while(pre!=null){
   
            pre=pre.next;
            res=res.next;
        }
        return res;
    }
}
//时间 O(N)
//空间 O(1)

2)朴素方法(可读性好,且性能相当)

public class Solution {
   
    public ListNode FindKthToTail (ListNode pHead, int k) {
   
        int len =0;
        ListNode p = pHead;
        while(p != null){
   
            p = p.next;
            ++len;
        }
        if(len<k)return null;
        p = pHead;
        for(int i=1; i<=len-k; ++i){
   
            p=p.next;
        }
        return p;
    }
}
//【个人认为】前后指针的方法,其实和朴素的方法没有实质的性能差别。==>感觉只能炫技而已,华而不实
//时间上:两种方法的遍历都是 一个指针完整遍历N跳,另一个/另一次遍历N-k跳;
//空间上:朴素方法只要一个节点指针,前后节点法占用两个节点指针;
//总的来说,就是【一个指针遍历两次,和两个指针遍历一次】这样子,性能上,并没有两倍的时间差别

15. 反转链表 【三指针法】

输入一个链表,反转链表后,输出新链表的表头。

public class Solution {
   
    public ListNode ReverseList(ListNode head) {
   
        //【设置3个指针三连排:p1/p2/p3】//两个指针可以是pre/cur,但多了用1/2/3比较好(清晰)
        //反转时候最怕的就是单链断掉,所以这里用几个节点指针来缓存“断链操作”处的信息
        ListNode p1=null;//第一次使head指向null
        ListNode p2=head;//【p2是head】
        ListNode p3=null;
        
        while(p2!=null){
   //选p2是因为p2第一次作为head、正好去遍历全部单链节点
            p3=p2.next;//p3的作用是在p2.next被翻转前,标记原始的p2.next
            p2.next=p1;//反转操作
            p1=p2;
            p2=p3;
        }
        return p1;//最后p1为翻转后的head
    }
}
//时间复杂度O(N),空间复杂度O(1)

16. 合并两个排序的链表 【虚拟头结点】

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

1)朴素方法

public class Solution {
   
    public ListNode Merge(ListNode list1,ListNode list2) {
   
        //没有虚拟头结点的坏处:要额外拎出来头部的过程,而不是统一
        if(list1 == null)return list2;
        if(list2 == null)return list1;
        ListNode p0 = null;//标记头部,不能动
        if(list1.val < list2.val){
   
            p0=list1;
            list1=list1.next;
        }
        else{
   
            p0=list2;
            list2=list2.next;
        }
        ListNode p=p0;
        while(list1 != null && list2 != null){
   //正式主体过程
            if(list1.val < list2.val){
   
                p.next=list1;
                p=p.next;//【千万别漏了这个】
                list1=list1.next;
            }
            else{
   
                p.next=list2;
                p=p.next;
                list2=list2.next;//直接修改题目变量,可节约空间
            }
        }
        if(list1==null)p.next=list2;
        if(list2==null)p.next=list1;
        return p0;
    }
}
// 时间 O(N),  空间 O(1)

2)虚拟头结点:统一过程&精简代码

public class Solution {
   
    public ListNode Merge(ListNode list1,ListNode list2) {
   
        //建立虚拟头结点,可以统一过程&精简代码,同时减少或不用考虑头部的情况
        //此代码和上面相比,只有函数里的第一行和最后一行是修改的
        ListNode vHead = new ListNode(Integer.MIN_VALUE);//建立虚拟头结点  //建立的时候不能用null,必须实例化
        ListNode p=vHead;
        while(list1 != null && list2 != null){
   
            if(list1.val < list2.val){
   
                p.next=list1;
                p=p.next;
                list1=list1.next;
            }
            else{
   
                p.next=list2;
                p=p.next;
                list2=list2.next;
            }
        }
        if(list1==null)p.next=list2;
        if(list2==null)p.next=list1;
        return vHead.next;//虚拟头结点一直在那不动,返回它的下一个即为第一个真实节点
    }
}

17. 树的子结构 【大树每一个节点node,和子树的root进行匹配; 注意:N*if + return(|| &&)】

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

ps:大的思路不算难,但这里面细节比较多【见下方代码及注释】

public class Solution {
   //分两步:
    //[1]遍历root1树,尝试每一个节点
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
   
        if(root1==null || root2==null)return false;//由题,root1或root2初试为null都会导致false
        if(judge(root1,root2)==true)return true;//必须要有if判断 ==>只有true才返回、并结束题目任务;false时不能返回,并进行下方的详细判别
        return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);//这里的关系是"或"
    }                                                                          //表示整个树的所有分支只要有一个末端分支满足即可。
    //[2]针对某一节点node,判断是否与root2匹配
    public boolean judge(TreeNode node, TreeNode root2){
   
        if(root2==null)return true;//在前,因为有:node和root2都为null的情况,root2为空node不为空的情况(本题允许在匹配时,子树比原树下方短)
        if(node==null)return false;//在后,相当与node==null&&root2!=null
        if(node.val != root2.val)return false;//不相等直接结束,否则继续向下详细检查
        return judge(node.left,root2.left) && judge(node.right,root2.right);//"与"的关系,表示子树所有分支全部都要满足。
    }
}
//judge()函数复杂度为O(root2) //root2是B树(子树)
//HasSubtree()由于每个root1树的节点都要试一下,调用次数O(root1)
//==>时间复杂度 O(root1*root2)

leetcode变种版本:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:

给定的树 A:
3
/
4 5
/
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

class Solution {
   
    public boolean isSubStructure(TreeNode A, TreeNode B) {
   
        if(B==null)return false;
        return tryTree(A,B);
    }
    public boolean tryTree(TreeNode A, TreeNode B){
   
        if(A!=null){
   
            if(judge(A,B)==true)return true;
            if(tryTree(A.left, B)==true || tryTree(A.right, B)==true)return true;//这里没有那么简洁,但是可读性好。
        }
        return false;
    }
    public boolean judge(TreeNode A, TreeNode B){
   
        if(B!=null){
   
            if(A==null || B.val != A.val)return false;
            return (judge(A.left,B.left)==true)&&(judge(A.right,B.right)==true);
        }
        return true;
    }
}

18. 二叉树的镜像 【借temp交换】

操作给定的二叉树,将其变换为源二叉树的镜像。

public class Solution {
   //总体思想:递归遍历+交换
    public TreeNode Mirror (TreeNode pRoot) {
   
        if(pRoot!=null){
   //递归终止条件:null时不再向下
            TreeNode temp = pRoot.left;
            pRoot.left = pRoot.right;
            pRoot.right = temp;//经典的三步交换:当前节点 交换 左右子树
            Mirror(pRoot.left);
            Mirror(pRoot.right);//向下传递左右子树根节点,向上无传递
        }
        return pRoot;
    }
}
//时间O(N)
//空间不是O(1)而是O(logN)==>先中后序dfs都占用logN层系统栈空间

19. 顺时针打印矩阵 【上下左右四界】

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

import java.util.ArrayList;
public class Solution {
   
    public ArrayList<Integer> printMatrix(int [][] matrix) {
   
        ArrayList<Integer> res = new ArrayList<Integer>();
        //定义上下左右的边界,共需要4个变量
        int up = 0;
        int down = matrix.length-1;//这里的4个边界全部做到可以达到
        int left = 0;
        int right = matrix[0].length-1;
        while(true){
   //while中有4个类似的结构,但是不适合合并,因为合并也不会减少代码
            if(up>down)break;//函数开头不用另外验matrix,因为while里面的判断 已经做得很完善了
            for(int i = left;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值