剑指offer

55.输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
返回 false 。

限制:

0 <= 树的结点个数 <= 10000
注意:本题与主站 110 题相同:https://leetcode-cn.com/problems/balanced-binary-tree/

class Solution {
    public boolean isBalanced(TreeNode root) {
        int height=dfs(root);
        if(height==-1){
            return false;
        }else{
            return true;
        }
    }
    //浅写一个dfs
    public int dfs(TreeNode root){
        if(root==null){
            return 0;
        }
        int left=dfs(root.left);
        int right=dfs(root.right);
        if(left==-1||right==-1||Math.abs(left-right)>1){
            return -1;
        }else{
            return Math.max(left,right)+1;
        }
    }
}

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

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3
    / \
   4   5
  / \
 1   2
给定的树 B:

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

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:

0 <= 节点个数 <= 10000

 鬼畜想法有一个莫里斯:把俩都转成一条,类似于链表问题。

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //递归呢?任何一个为null都是false,判断是不是当前结点的子,或者左右节点的子
       return (A!=null)&&(B!=null)&&(recur(A,B)||isSubStructure(A.right,B)||isSubStructure(A.left,B));
    }
    public boolean recur(TreeNode A,TreeNode B){
        if(B==null){
            return true;
        }
        if(A==null||A.val!=B.val){
            return false;
        }
        return recur(A.right,B.right)&&recur(A.left,B.left);
    }
}

33.输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3
示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:

输入: [1,3,2,6,5]
输出: true
 

提示:

数组长度 <= 1000

 注意题目是二叉搜索树,即左子树中所有节点的值都小于根节点的值,右子树中所有节点的值都大于根节点的值,其中,左右子树也是二叉搜索树。

1、递归分治法

每次都划分根左右,并判断是不是二叉搜索树。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        int length=postorder.length;
        return recur(postorder,0,length-1);
    }
    public boolean recur(int[] postorder,int left,int right){
        if(left>=right){
            return true;
        }
        int root=postorder[right];
        int index=left;
        while(postorder[index]<root){
            index++;
        }
       //前面全是左子树满足
       int temp=index;
       while(temp<right){
           if(postorder[temp]<root){
               //右子树只要有比根节点小的都不是二叉搜索树
               return false;
           }
           temp++;
       }
       return recur(postorder,left,index-1)&&recur(postorder,index,right-1);
    }
}

2、单调栈

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        //浅写一个这种类型题目的思路
        //以本题为例,倒叙来看,如果是升序,则后者是前者的右子,否则,后者是前者某个爹的左子,所以爹要存在栈里
        //爹要更新,最后弹出的就是爹,如果当前的值大于前一个弹出的爹,出大问题,因为弹出意味着已经在处理爹的左儿子
        Deque<Integer> dq=new ArrayDeque<>();
        int length=postorder.length;
        //初始化一下爹
        int root=Integer.MAX_VALUE;
        for(int i=length-1;i>=0;i--){
            if(postorder[i]>root){
                return false;
            }
            while(!dq.isEmpty()&&postorder[i]<dq.peekLast()){
                root=dq.pollLast();
            }
            dq.addLast(postorder[i]);
        }
        return true;
    }
}

34.给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
示例 2:

输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3:

输入:root = [1,2], targetSum = 0
输出:[]
 

提示:

树中节点总数在范围 [0, 5000] 内
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000
注意:本题与主站 113 题相同:https://leetcode-cn.com/problems/path-sum-ii/

递归回溯搞一哈,没搞出来,太多遗漏知识需要补

class Solution {
    List<List<Integer>> res=new LinkedList<>();
    Deque<Integer> temp=new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        //递归回溯肯定可以搞一哈
        recur(root,target);
        return res;
    }
    public void recur(TreeNode root,int target){
        if(root==null){
            return;
        }
        temp.offerLast(root.val);
        target-=root.val;
        if(root.left==null&&root.right==null&&target==0){
            //这里只能采取这种初始化的方式无形中进行一个转换,这里是不能改动temp的
            res.add(new LinkedList<>(temp));
        }
        recur(root.left,target);
        recur(root.right,target);
        //删掉最后一个元素
        temp.pollLast();
    }
}

不用递归应该可以bfs一下

这个哈希表记录你爹也太他妈的绝了把。。

class Solution {
    List<List<Integer>> res=new LinkedList<>();
    //记录你爹
    Map<TreeNode,TreeNode> hashMap=new HashMap<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        if(root==null){
            return res;
        }
        Queue<TreeNode> queueNode=new LinkedList<>();
        Queue<Integer> queueSum=new LinkedList<>();
        queueNode.offer(root);
        queueSum.offer(0);
        while(!queueNode.isEmpty()){
            TreeNode curr=queueNode.poll();
            int sum=queueSum.poll()+curr.val;
            if(curr.left==null&&curr.right==null){
                if(sum==target){
                    getPath(curr);
                }
            }else{
                if(curr.left!=null){
                    queueNode.offer(curr.left);
                    queueSum.offer(sum);
                    //存爹
                    hashMap.put(curr.left,curr);
                }
                if(curr.right!=null){
                    queueNode.offer(curr.right);
                    queueSum.offer(sum);
                    hashMap.put(curr.right,curr);
                }
            }
        }
        return res;
    }
    //找你祖宗然后写道族谱里
    public void getPath(TreeNode root){
        List<Integer> temp=new LinkedList<>();
        while(root!=null){
            temp.add(root.val);
            root=hashMap.get(root);
        }
        Collections.reverse(temp);
        res.add(new LinkedList<>(temp));
    }
}

68.给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
 

说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。
注意:本题与主站 235 题相同:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode curr=root;
        while(true){
            if(curr.val>p.val&&curr.val>q.val){
                curr=curr.left;
            }else if(curr.val<p.val&&curr.val<q.val){
                curr=curr.right;
            }else{
                break;
            }
        }
        return curr;
    }
}

63.假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
 

限制:

0 <= 数组长度 <= 10^5

注意:本题与主站 121 题相同:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

class Solution {
    public int maxProfit(int[] prices) {
       //min存储目前为止最低的买入价
       //max存储目前为止最高的利润
       int length=prices.length;
       if(length==0){
           return 0;
       }
       int min=prices[0];
       int max=0;
       for(int i=0;i<length;i++){
           min=Math.min(prices[i],min);
           max=Math.max(max,prices[i]-min);
       } 
       return max;
    }
}

59.给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

提示:

你可以假设 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

注意:本题与主站 239 题相同:力扣

这题目做过,有一个机智的做法

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //两次遍历,以k为一个切割点
        int n=nums.length;
        if(n==0){
            return new int[0];
        }
        int[] t1=new int[n];
        int[] t2=new int[n];
        int[] res=new int[n-k+1];
        for(int i=0;i<n;i++){
            if(i%k==0){
                t1[i]=nums[i];
            }else{
                t1[i]=Math.max(t1[i-1],nums[i]);
            }
        }
        
        for(int i=n-1;i>=0;i--){
            if(i==n-1||(i+1)%k==0){
                t2[i]=nums[i];
            }else{
                t2[i]=Math.max(t2[i+1],nums[i]);
            }
        }
        for(int i=0;i<=n-k;i++){
            res[i]=Math.max(t1[i+k-1],t2[i]);
        }
        return res;
    }
}
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //整点阳间做法--优先队列
        //1.创建优先队列,重写comparator比较器里的compare方法
        PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] pair1,int[] pair2){
                //前面放值,后面是索引
                    return pair1[0]!=pair2[0]?pair2[0]-pair1[0]:pair2[1]-pair1[1];
                }
            }
        );
        //2.初始化优先队列
        for(int i=0;i<k;i++){
            pq.offer(new int[]{nums[i],i});
        }
        //3.处理滑动窗口
        int length=nums.length;
        if(length==0){
            return new int[0];
        }
        int[] res=new int[length-k+1];
        res[0]=pq.peek()[0];
        for(int i=1;i<length-k+1;i++){
            pq.offer(new int[]{nums[i+k-1],i+k-1});
            //如何抛弃?不在范围内的索引所对应的数组被抛弃
            while(pq.peek()[1]<i){
                pq.poll();
            }
            res[i]=pq.peek()[0];
        }
        return res;
    }
}
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] num1,int[] num2){
                return num1[0]!=num2[0]?num2[0]-num1[0]:num2[1]-num1[1];
            }
        });
        int length=nums.length;
        if(length==0){
            return new int[0];
        }
        for(int i=0;i<k;i++){
            pq.offer(new int[]{nums[i],i});
        }
        int[] res=new int[length-k+1];
        res[0]=pq.peek()[0];
        for(int i=1;i<length-k+1;i++){
            pq.offer(new int[]{nums[i+k-1],i+k-1});
            while(pq.peek()[1]<i){
                pq.poll();
            }
            res[i]=pq.peek()[0];
        }
        return res;
    }
}

单调队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> dq=new LinkedList<>();
        int length=nums.length;
        if(length==0){
            return new int[0];
        }
        int[] res=new int[length-k+1];
        for(int i=0;i<length;i++){
            while(!dq.isEmpty()&&nums[i]>nums[dq.peekLast()]){
                dq.pollLast();
            }
            dq.addLast(i);
            if(dq.peek()<=i-k){
                dq.poll();
            }
            if(i>=k-1){
                res[i-k+1]=nums[dq.peek()];
            }
        }
        return res;
    }
}

add和offer方法是默认加在最后面,其他都默认前面,需要注意!!! 

51.在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5
 

限制:

0 <= 数组长度 <= 50000 

1、归并排序

class Solution {
    int[] temp;
    int[] nums;
    public int reversePairs(int[] nums) {
        //浅写一个归并排序的思路
        //因为两个已经各自排好序的数组的拼接逆序数比较容易获得,
        //即:如果右边要插到左边前面,哪怕是最后一个数,都构成逆序数,逆序数为左边数组的大小减去比右边小的数的下标+1
        //所以可以通过一边归并一边获取逆序数
        
        //需要注意的是,需要实时修改nums数组使左右数组各自是有序的,所以需要一个中介数组存储原数组的值
        this.nums=nums;
        int length=nums.length;
        temp=new int[length];
        return mergeSort(0,length-1);
    }
    public int mergeSort(int left,int right){
        //写出结束条件
        if(left>=right){
            return 0;
        }
        //否则分
        int m=(left+right)/2;
        int res=mergeSort(left,m)+mergeSort(m+1,right);
        //存储当前的nums到临时数组
        for(int i=left;i<=right;i++){
            temp[i]=nums[i];
        }
        int j=left;
        int k=m+1;
        for(int i=left;i<=right;i++){
            //实时修改nums数组
            if(j==m+1){
                //左边走完了,右边直接构成新的nums数组
                nums[i]=temp[k++];
            }else if(k==right+1||temp[j]<=temp[k]){//防止数组越界
                nums[i]=temp[j++];
            }else{
                res+=(m-j+1);
                nums[i]=temp[k++];
            }
        }
        return res;
    }
}

2.树状数组

Binary Indexed Tree,二叉索引树,设计初衷是解决数据压缩里的累积频率的计算问题,现多用于高效计算数列的前缀和,区间和。

低位运算:非负整数再二进制表示下最后面的1及后面所有的0构成的数的数值。

计算构成n的所有二次幂数 :一个一个减去lowbit(n)

树状数组

树状数组基于此思想,用途是维护序列的前缀和。

 

 

 看不懂啊,坏了啊。。。

class Solution {
    public int reversePairs(int[] nums) {
        int n = nums.length;
        int[] tmp = new int[n];
        System.arraycopy(nums, 0, tmp, 0, n);
        // 离散化
        Arrays.sort(tmp);
        for (int i = 0; i < n; ++i) {
            nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1;
        }
        // 树状数组统计逆序对
        BIT bit = new BIT(n);
        int ans = 0;
        for (int i = n - 1; i >= 0; --i) {
            ans += bit.query(nums[i] - 1);
            bit.update(nums[i]);
        }
        return ans;
    }
}

class BIT {
    private int[] tree;
    private int n;

    public BIT(int n) {
        this.n = n;
        this.tree = new int[n + 1];
    }

    public static int lowbit(int x) {
        return x & (-x);
    }

    public int query(int x) {
        int ret = 0;
        while (x != 0) {
            ret += tree[x];
            x -= lowbit(x);
        }
        return ret;
    }

    public void update(int x) {
        while (x <= n) {
            ++tree[x];
            x += lowbit(x);
        }
    }
}

59.请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
 

限制:

1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
通过次数127,621提交次数267,。

class MaxQueue {
    //底层用双向队列模拟
    Deque<Integer> dq;
    //队列存储最大值
    Deque<Integer> max;
    public MaxQueue() {
        dq=new LinkedList<>();
        max=new  LinkedList<>();
    }
    
    public int max_value() {
        if(max.isEmpty()){
            return -1;
        }else{
            return max.peekFirst();
        }
    }
    
    public void push_back(int value) {
        while(!max.isEmpty()&&value>max.peekLast()){
            max.pollLast();
        }
        max.addLast(value);
        dq.addLast(value);
    }
    
    public int pop_front() {
        if(dq.isEmpty()){
            return -1;
        }else{
            //为什么必须写成poll再比较,否则的话就意味着弹出两次!!
            int ans=dq.pollFirst();
            if(ans==max.peekFirst()){
                max.pollFirst();
            }
            return ans;
        }
    }
}

 58.输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"
示例 2:

输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
 

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
注意:本题与主站 151 题相同:https://leetcode-cn.com/problems/reverse-words-in-a-string/

注意:此题对比原题有改动

class Solution {
    public String reverseWords(String s) {
        StringBuilder sb = trimSpaces(s);

        // 翻转字符串
        reverse(sb, 0, sb.length() - 1);

        // 翻转每个单词
        reverseEachWord(sb);

        return sb.toString();
    }

    public StringBuilder trimSpaces(String s) {
        int left = 0, right = s.length() - 1;
        // 去掉字符串开头的空白字符
        while (left <= right && s.charAt(left) == ' ') {
            ++left;
        }

        // 去掉字符串末尾的空白字符
        while (left <= right && s.charAt(right) == ' ') {
            --right;
        }

        // 将字符串间多余的空白字符去除
        StringBuilder sb = new StringBuilder();
        while (left <= right) {
            char c = s.charAt(left);

            if (c != ' ') {
                sb.append(c);
            } else if (sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }

            ++left;
        }
        return sb;
    }

    public void reverse(StringBuilder sb, int left, int right) {
        while (left < right) {
            char tmp = sb.charAt(left);
            sb.setCharAt(left++, sb.charAt(right));
            sb.setCharAt(right--, tmp);
        }
    }

    public void reverseEachWord(StringBuilder sb) {
        int n = sb.length();
        int start = 0, end = 0;

        while (start < n) {
            // 循环至单词的末尾
            while (end < n && sb.charAt(end) != ' ') {
                ++end;
            }
            // 翻转单词
            reverse(sb, start, end - 1);
            // 更新start,去找下一个单词
            start = end + 1;
            ++end;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值