剑指offer算法题分析与整理(六)

1、判断平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

思路一:对每个节点都要判断其左右子树的高度,递归从底层往上检测每个节点,避免在计算左右子树高度时重复遍历,所以递归的同时要返回高度信息

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root==null)return true;
        return f(root)!=-1;
    }
    //具有一定程度的剪枝效果,当发现某个节点B不符合要求,向上递归时B是A的左节点,那A就不用计算右节点的高度了,直接返回-1。但若B是A的右节点,A还是会先计算其左子树的高度
    int f(TreeNode t){
        if(t==null)return 0;
        int l=f(t.left);
        if(l==-1)return -1;
        int r=f(t.right);
        if(r==-1)return -1;
        if(l-r>1||r-l>1)return -1;
        else return l<r?r+1:l+1;
    }
}

为了既返回是不是平衡二叉树的Boolean值,又同时能保存每个结点的深度避免重复计算,C++里面基本类型有指针传递,Java没有,可以用一下几种方法。
来自牛客网@Aurora1

//对象成员变量
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return f(root, new Holder());
    }

    private class Holder {
        int n;
    }

    boolean f(TreeNode root, Holder h) {
        if (root == null) {
            h.n = 0;
            return true;
        }

        Holder l = new Holder(), r = new Holder();

        if (f(root.left, l) && f(root.right, r)) {
            if (l.n - r.n > 1 || r.n - l.n > 1)
                return false;

            h.n += (l.n > r.n ? l.n : r.n) + 1;
            return true;
        }

        return false;
    }
}

来自牛客网@guo_庆

//数组,这里只需要一个长为1的数组
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return isBalance(root,new int[1]);
    }
    public boolean isBalance(TreeNode root,int []depth){
        if(root==null){
            depth[0]=0;
            return true;
        }

        boolean left=isBalance(root.left,depth);
        if(!left)return false;
        int leftdepth=depth[0];

        boolean right=isBalance(root.right,depth);
        if(!right)return false;
        int rightdepth=depth[0];

        if(Math.abs(leftdepth-rightdepth)>1)return false;
        depth[0]=Math.max(leftdepth+1,rightdepth+1);

        return true;
    }
}
//只用一个类变量就行
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return f(root);
    }

    static int dep=0;//类变量,全局变量

    boolean f(TreeNode t) {
        if (t == null) {
            dep=0;
            return true;
        }

        int l=0,r=0;

        if(f(t.left))l=dep;
        else return false;

        if(f(t.right))r=dep;
        else return false;

        if(l-r>1||r-l>1)return false;

        dep=(l>r?l:r)+1;
        return true;
        }   
    }

Java变量之间传值- 值传递还是引用传递的讨论

2、二进制中1的个数

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

思路一:循环32次,验证每位上是0还是1

public class Solution {
    public int NumberOf1(int n) {
        int c=0;
        for(int i=0;i<32;i++){
            //if((n&1<<i)!=0)c++;//两种都可以
            if((n>>>i&1)==1)c++;
        }
        return c;
    }
}

思路二:循环到最高的为1的位就不循环,不必循环32位

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n!= 0){
            count++;
            n = n & (n - 1);//把最低的1变成了0
         }
        return count;
    }
}

思路三:利用Integer.bitCount(int i)原理
来自牛客网@Holiday_12138

public class Solution {
public int  NumberOf1(int n) {
          int temp = n;
 temp = (temp & 0x55555555) + ((temp & 0xaaaaaaaa) >>> 1);//两位两位看
 temp = (temp & 0x33333333) + ((temp & 0xcccccccc) >>> 2);//四位四位看
 temp = (temp & 0x0f0f0f0f) + ((temp & 0xf0f0f0f0) >>> 4);
 temp = (temp & 0x00ff00ff) + ((temp & 0xff00ff00) >>> 8);
 temp = (temp & 0x0000ffff) + ((temp & 0xffff0000) >>> 16);
 return temp;
     }
}

3、矩形覆蓋

我们可以用2*1的小矩形横着或者竖着去覆蓋更大的矩形。请问用n个2*1的小矩形无重叠地覆蓋一个2*n的大矩形,总共有多少种方法?

思路一:设横着放的有a个,竖着放的有b个,它们的混合有 Caa+b=Cba+b C a + b a = C a + b b 种可能(等同于a个相同的球放入b+1个不同的盒子里,盒子可以为空)

public class Solution {
    public int RectCover(int target) {
        if(target==0)return 0;
        int s=target/2;
        int a,b,m,n,sum=0;
        for(int i=0;i<=s;i++){//横着放的最多有s个
            a=i;
            b=target-2*a;
            if(a==0||b==0){sum+=1;}
            else{
                m=a<b?a:b;
                n=a+b;
                sum+=C(n,m);
            }
        }
        return sum;
    }

    int C(int n,int m){
        if(m==1)return n;
        long p=1,q=1;//n太大了可能会超出Integer范围
        for(int i=n;i>n-m;i--){
            p*=i;
        }
        for(int i=1;i<=m;i++){
            q*=i;
        }
        return (int)(p/q);
    }
}

思路二:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2),斐波那契数列,神奇的发现!

4、连续子数组的最大和

例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和。
思路一:数组全为负数也没关系

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int len=array.length;
        if(len==0)return 0;
        int max=Integer.MIN_VALUE,sum=0;
        for(int i=0;i<len;i++){
            sum+=array[i];
            max=sum<max?max:sum;
            if(sum<0)sum=0;
        }
        return max;
    }
}

思路二:DP

public int FindGreatestSumOfSubArray(int[] array) {
        int res = array[0]; //记录当前所有子数组的和的最大值
        int max=array[0];   //包含array[i]的连续数组最大值
        for (int i = 1; i < array.length; i++) {
            max=Math.max(max+array[i], array[i]);
            res=Math.max(max, res);
        }
        return res;
}

5、逻辑符的短路特性求累加

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路一:不能循环那就递归,用逻辑符的短路特性来判断终止递归条件

public class Solution {
    public int Sum_Solution(int n) {
        return sum(n,n+1)>>1;
    }
    //快速幂的思想,不用乘法用递归求n(n+1)/2
    int sum(int a,int b){
        int result=0;
        boolean f=  (a&1)==0||(result+=b)>0;
        a>>=1;b<<=1;
        boolean f1=  a==0||(result+=sum(a,b))>0;
        return result;
    }
}
//利用递归累加
public class Solution {
    public int Sum_Solution(int n) {
        int result=n;
        boolean f=  n==0||(result+=Sum_Solution(--n))>0;     
        return result;
    }   
}

思路二:Java的异常
来自牛客网@无极暴走夜之子

//用异常退出递归
public class Solution {
    public int Sum_Solution(int n) {
        return sum(n);
    }
    int sum(int n){
        try{
            int i = 1%n;
            return n+sum(n-1);
        }
        catch(Exception e){
            return 0;
        }
    }
}

6、构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]*A1*…*A[i-1]*A[i+1]*…*A[n-1]。不能使用除法。
思路:结果很明显是两部分的乘积,先遍历一遍将这两部分保存起来,再相乘。线性时间复杂度。
来自牛客网@老石基

public class Solution {
    public int[] multiply(int[] A) {
        if(A==null||A.length==0)
            return A;
        int[] left = new int[A.length];//记录除了自己,左边的乘积
        int[] right = new int[A.length];//记录除了自己,右边的乘积
        right[A.length-1] = 1;
        for(int i = A.length-2;i>=0;i--){
            right[i] = right[i+1]*A[i+1];
        }
        left[0] = 1;
        for(int i = 1;i<A.length;i++){
            left[i] = left[i-1]*A[i-1];
        }
        int[] B = new int[A.length];
        for(int i = 0;i<A.length;i++){
            B[i] = left[i]*right[i];
        }
        return B;
    }
}

实际上没必要构造两个新数组,利用B的空间就行

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
         int n=A.length;
         int[] B=new int[n];
         B[0]=1;
         for(int i=1;i<n;i++){
             B[i]=B[i-1]*A[i-1];      
         }
        int temp=1;
        for(int i=n-2;i>=0;i--){
            temp*=A[i+1];
            B[i]*=temp;
        }
        return B;
    }
}

7、求二叉树的深度

思路一:递归

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root==null)return 0;
        return (TreeDepth(root.left)>TreeDepth(root.right)?TreeDepth(root.left):TreeDepth(root.right))+1;
    }
}

思路二:层序遍历

class Solution {
public:
    int TreeDepth(TreeNode* pRoot) {
        if (!pRoot) return 0;
        queue<TreeNode*> que;
        que.push(pRoot);int depth=0;
        while (!que.empty()) {
            int size=que.size();
            depth++;
            for (int i=0;i<size;i++) {      //一次处理一层的数据
                TreeNode *node=que.front();
                que.pop();
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return depth;
    }
};
点赞