剑指Offer31—40题(leetcode 剑指Offer第二版)

31,从上到下打印二叉树 III

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFbENpfo-1651452081525)(%E5%89%91%E6%8C%87Offer.assets/1650274643656.png)]

思路分析

在上一个道题的基础上修改一个添加的方法。如果是偶数层就从尾部添加,反之就头部添加

注意:是修改的temp的添加方式,而不是队列的

代码实现

首先先了解一下linkedList的添加结果顺序

public class Test {
    public static void main(String[] args) {
        //add的加入顺序 1234
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        for (Integer integer : list) {
            System.out.print(integer);
        }
        //addFirst 4321
        LinkedList<Integer> list2 = new LinkedList<>();
        list2.addFirst(1);
        list2.addFirst(2);
        list2.addFirst(3);
        list2.addFirst(4);
        for (Integer integer2 : list2) {
            System.out.print(integer2);
        }

        //addLast  1234
        LinkedList<Integer> list3 = new LinkedList<>();
        list3.addLast(1);
        list3.addLast(2);
        list3.addLast(3);
        list3.addLast(4);
        for (Integer integer3 : list3) {
            System.out.print(integer3);
        }
    }
}

下面是代码的实现

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution27
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/4/18 16:41
 */
public class Solution27 {
    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    public static List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        int leve = 0;
        if (root != null) {
            queue.add(root);
        }
        while (!queue.isEmpty()) {
            //linkedList是通过node节点实现的,所以如果我们加入1,2,3,
            //从开头加1先进去,然后为了从头部加,2占领1的位置,让1后退,一次类推
            //从尾部加也是如此
            LinkedList<Integer> temp = new LinkedList<Integer>();
            for (int i = queue.size(); i > 0; i--) {
                TreeNode treeNode = queue.poll();
                if (list.size() % 2 == 0) {
                    temp.addLast(treeNode.val);
                }else {
                    temp.addFirst(treeNode.val);
                }
                if (treeNode.left != null) {
                    queue.add(treeNode.left);
                }
                if (treeNode.right != null) {
                    queue.add(treeNode.right);
                }
            }
            list.add(temp);
        }
        return list;
    }

    public static void main(String[] args) {
        TreeNode treeNode1 = new TreeNode(1);
        TreeNode treeNode2 = new TreeNode(2);
        TreeNode treeNode3 = new TreeNode(3);
        TreeNode treeNode4 = new TreeNode(4);
        TreeNode treeNode5 = new TreeNode(5);
        treeNode1.left = treeNode2;
        treeNode1.right = treeNode3;
        treeNode2.left = treeNode4;
        treeNode3.right = treeNode5;
        List<List<Integer>> list = levelOrder(treeNode1);

    }
}

32, 二叉搜索树的后序遍历序列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vx4oh8Is-1651452081526)(%E5%89%91%E6%8C%87Offer.assets/1650281124323.png)]

思路分析

  • 做递归思考三步:

    1. 递归的函数要干什么?
    • 因为后续遍历的特殊性,所以最后一个节点是根节点,且搜索二叉树的性质是根节点的左边都比他小,右边都比他大
    • 所以我们要通过递归判断对于每个根节点是否都满足左子树节点都小于根节点,右子树都大于根节点
    • 输入:int[] postorder, int i, int j,i表示数组0位置,j表示数组的最后位置
    • 输出:是:true,不是:false
    1. 递归停止的条件是什么?
    • 从左到右遍历,如果小于最后一个数,就让一直右移动
    • 当不符合条件时,就记录当前位置,之前的部分为左子树的后序遍历,剩余的部分判断是否大于最后一个数
    • 当俩指针相等时,就说明是一个搜索二叉树,返回true,反之false
    1. 调用递归函数,到下一层就是在判断左子树是否是搜索二叉树,右子树是否是搜索二叉树

代码分析

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution28
 * @Description: 二叉搜索树的后序遍历序列
 * @Author: ming
 * @Date: 2022/4/18 19:12
 */
public class Solution28 {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder, 0, postorder.length - 1);
    }

    /**
     * 通过后序遍历判断是否是一个搜索二叉树
     * @param postorder 后序遍历的数组
     * @param i 是数组开头位置
     * @param j 是数组最后位置
     * @return
     */
    boolean recur(int[] postorder, int i, int j) {
        //如果i=>j的时候就说明我们已经遍历完了
        if (i >= j) {
            return true;
        }
        int p = i;
        //因为后续遍历的特殊性,所以最后一个节点是根节点,且搜索二叉树的性质是根节点的左边都比他小,右边都比他大
        while (postorder[p] < postorder[j]) {
            p++;
        }
        //此时m-1就是我们左字树的后续遍历的数组,
        int m = p;
        while (postorder[p] > postorder[j]) {
            p++;
        }
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j-1);
    }
}

33,二叉搜索树与双向链表

在这里插入图片描述

思路分析:

首先它是要求是一个从小到大的排序,所以中序遍历没有问题吧,这里不要求我们输出值,所以直接在需要输出的位置进行修改,将其变成双向环形链表,left为左指针,right为右指针。

代码实现

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution30
 * @Description: 二叉搜索树与双向链表
 * @Author: ming
 * @Date: 2022/4/21 18:22
 */
public class Solution30 {

    Node head, pre;

    /**
     * 题目是要求他按照从小到大的顺序改成双向链表,所以进行中序递归,在中间进行修改值
     * @param root
     * @return
     */
    public Node treeToDoublyList(Node root) {
        if(root==null) return null;
        dfs(root);
        //题目的要求是最后一个节点指向第一个节点,所以让pre.right指向head,且head的左指针指向pre
        head.left = pre;
        pre.right = head;

        return head;

    }

    /**
     * 中序遍历+附加操作
     * @param cur
     */
    public void dfs(Node cur){
        if(cur==null) {
            return;
        }
        dfs(cur.left);
        //是双向链表,所以我们必须要有前一次递归的值,pre就当作前一次递归的节点,但是我们后面又需要头节点
        //所以现在head是用记录这个,后面在进行微调
        if (pre == null) {
            head = cur;
        }else {
            //pre是一个前驱节点,也就是上一轮的cur节点
            pre.right = cur;
        }
        //双向链表:现在需要修改的是当前节点指向前一个节点
        cur.left = pre;
        pre = cur;
        //全部迭代完成后,pre指向双向链表中的尾节点
        dfs(cur.right);
    }
}
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
}

34,序列化二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5fqPipx-1651452081530)(%E5%89%91%E6%8C%87Offer.assets/1650540493680.png)]

思路分析:

序列化和反序列化是指:

  • 序列化:就是将树结构变成一个string字符串
  • 反序列化:就是将字符串在变成树结构

这里是用的左程云老师的代码思路。具体实现在代码中标明了。

代码实现:

import java.util.LinkedList;
import java.util.Queue;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution30
 * @Description: 序列化
 * @Author: ming
 * @Date: 2022/4/21 18:22
 */
public class Solution30 {
    /**
     * 先序序列化
     * 进行分割运算,如果是空就添加#,反之就将val加入,用!分割
     * @param head
     * @return
     */
    public static String serialByPre(Node head) {
        //确立规则,如果是null就返回一个#当占位符
        if (head == null) {
            return "#!";
        }
        //如果不是null,就返回当前值加上一个分隔符!,进行先序遍历
        String res = head.val + "!";
        res += serialByPre(head.left);
        res += serialByPre(head.right);
        return res;
    }

    public static Node reconByPreString(String preStr) {
        //按照!进行分割将其变为字符串数组
        String[] values = preStr.split("!");
        //创建一个字符串的队列,通过递归进行还原
        Queue<String> queue = new LinkedList<String>();
        //将字符串放入队列中
        for (int i = 0; i != values.length; i++) {
            queue.offer(values[i]);
        }
        return reconPreOrder(queue);
    }

     public static Node reconPreOrder(Queue<String> queue) {
        //将字符串输出
        String value = queue.poll();
        //如果是#,就说明是空
        if (value.equals("#")) {
            return null;
        }
        //如果不是null就将string字符串变为integer值
        Node head = new Node(Integer.valueOf(value));
        //进行递归将其遍历出来,可以画图理解
        head.left = reconPreOrder(queue);
        head.right = reconPreOrder(queue);
        return head;
    }


    /**
     * 层序列化
     * @param head
     * @return
     */
    public static String serialByLevel(Node head) {
        //如果是null就返回#
        if (head == null) {
            return "#!";
        }
        String res = head.val + "!";
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(head);
        //宽度优先遍历+res的创建过程
        while (!queue.isEmpty()) {
            head = queue.poll();
            if (head.left != null) {
                res += head.left.val + "!";
                queue.offer(head.left);
            } else {
                res += "#!";
            }
            if (head.right != null) {
                res += head.right.val + "!";
                queue.offer(head.right);
            } else {
                res += "#!";
            }
        }
        return res;
    }

    public static Node reconByLevelString(String levelStr) {
        String[] values = levelStr.split("!");
        //index:当到下一层的时候index指向的是下一层的第一个元素
        int index = 0;
        Node head = generateNodeByString(values[index++]);
        Queue<Node> queue = new LinkedList<Node>();
        if (head != null) {
            queue.offer(head);
        }
        Node node = null;
        while (!queue.isEmpty()) {
            node = queue.poll();
            node.left = generateNodeByString(values[index++]);
            node.right = generateNodeByString(values[index++]);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        return head;
    }

    public static Node generateNodeByString(String val) {
        if (val.equals("#")) {
            return null;
        }
        return new Node(Integer.valueOf(val));
    }
}
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
}

先序序列化会快一点。

35,字符串的排列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFSbDL1n-1651452081531)(%E5%89%91%E6%8C%87Offer.assets/1650550138698.png)]

思路分析:

import java.util.ArrayList;
import java.util.Arrays;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution31
 * @Description: 字符串的排列
 * @Author: ming
 * @Date: 2022/4/21 21:22
 */
public class Solution31 {
    public static String[] permutation(String s) {
        ArrayList<String> list = new ArrayList<>();
        char[] chars = s.toCharArray();
        process(chars, 0, list);
        String[] res = list.toArray(new String[list.size()]);
        for (String re : res) {
            System.out.println(re);
        }
        Arrays.sort(res);
        return res;
    }

    public static void swap(char[] chars, int i, int j) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }

    public static void process(char[] chars, int i, ArrayList list) {
        //当i等于数组的长度-1时,就说明我们已经遍历完这个数组了
        if (i == chars.length - 1) {
            list.add(String.valueOf(chars));
        }
        //26个位置代表26字母,用于判断是否使用过
        boolean[] visit = new boolean[26];
        for (int j = i; j < chars.length; j++) {
            if (!visit[chars[j] - 'a']) {
                // 把i赋值给j,这样在回退的时候,j可以++,而i在j前面一位,可以将其交换,达到我们省空间的想法,当然
                // 交换并执行完base case且回退到这一步的时候,我们需要将其进行回复原本的模样,
                visit[chars[j] - 'a'] = true;
                swap(chars, i, j);
                process(chars, i + 1, list);
                //之前进行交换过,破坏了原有的顺序,这里再换回来
                swap(chars, i, j);
            }
        }
    }

    public static void main(String[] args) {
        String a = "abb";
        permutation(a);
    }
}

36,数组中出现次数超过一半的数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BHsKa71Y-1651452081531)(%E5%89%91%E6%8C%87Offer.assets/1650978533118.png)]

思路分析:

方法一

可以创建一个新的数组,用来存放每个位置的下标表示数组中的数字,下标对应的值是指他在数组中出现的次数,将输入的数组遍历一次之后,再去我们放出现次数的数组中找即可。(也可也在放入的过程中发现有一个值的大小已经超过了数组的一半,或者等于数组的长度,就直接返回他的下表)。

方法二

将数组进行排序,首先排除数组只用1个值的情况,如果是一个值就返回这个值。然后开始遍历数组,每出现一个数字,就将其记录,然后再记录他出现的次数,如果超过数组的一半就返回。

public static int majorityElement(int[] nums) {
    int count = nums.length/2;
    if (nums.length == 1) {
        return nums[0];
    }
    Arrays.sort(nums);
    int value = 1;int a = nums[0];
    for (int i = 1; i < nums.length; i++) {
        if (a == nums[i]) {
            value++;
            if (value > count) {
                return nums[i];
            }
        }else {
            a = nums[i];
            value = 1;
        }
    }
    return 0;
}

方法三

因为又一个值是超过数组的一半的,所以我们就可以

  • 如果当前数和前一个数不同,就将之前数出现的次数减一,然后判断存放次数的数是否为=0,如果是,就用当前值代替之前的值,否则就不变。
  • 如果当前数和前一个数相同,则将出现的次数加一,直到结束为止
  • 返回存放前一个数的变量
public static int majorityElement1(int[] nums) {
    int value = 0;
    int pre = 0;
    for (int num : nums) {
        if (value == 0) {
            pre = num;
            value++;
        }else {
            if (pre == num) {
                value++;
            }else {
                pre = --value == 0 ? num : pre;
            }
        }
    }
    return pre;
}

37,最小的k个数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0MIBtJE-1651452081531)(%E5%89%91%E6%8C%87Offer.assets/1650979462846.png)]

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        Arrays.sort(arr);
        int[] res = new int[k];
        for(int i = 0; i < k; i++) {
            res[i] = arr[i];
        }
        return res;
    }
}

注意:

这道题其实考验的是你是否会快排。我偷奸耍滑了。。。

方法二

创建一个小顶堆,然后把数组的数放进去,然后创建一个大小为k的数组,然后放入即可

小顶堆是用优先队列+顺序排序的比较器构成

public static class MinCostComparator implements Comparator<Integer> {

    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}

public static int[] getLeastNumbers1(int[] arr, int k) {
    PriorityQueue<Integer> queue = new PriorityQueue<>(new MinCostComparator());
    for (int i : arr) {
        queue.add(i);
    }
    int[] res = new int[k];
    int flag = 0;
    while (flag != k) {
        res[flag] = queue.poll();
        flag++;
    }
    return res;
}

38,数据流中的中位数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V8D4Rusa-1651452081531)(%E5%89%91%E6%8C%87Offer.assets/1650984007222.png)]

思路分析:

我们创建一个大顶堆一个小顶堆,大顶堆存放的数值小的那部分,小顶堆存放的是数值大 的那部分,且要求两棵树的高度相差不能超过1.这样子就变成了大顶堆存放的是排好顺序的前半部分,小顶堆存放的是后半部分。

如果两个顶堆的大小一样,就返回俩顶堆头节点的值的平均值,如果不一样,就返回较大的顶堆的头节点。

添加的话,先往大顶堆中加入,如果加入的值大于大顶堆的头节点,就加在小顶堆中,然后如果又高度相差大于1,修改一下高度。

class MedianFinder {

    public static class MaxHeapComparator implements Comparator<Double> {


        @Override
        public int compare(Double o1, Double o2) {
            return (int) (o2 - o1);
        }
    }

    public static class MinHeapComparator implements Comparator<Double> {


        @Override
        public int compare(Double o1, Double o2) {
            return (int) (o1 - o2);
        }
    }

    private PriorityQueue<Double> maxHeap;
    private PriorityQueue<Double> minHeap;
    /** initialize your data structure here. */
    public MedianFinder() {
        this.maxHeap = new PriorityQueue<>(new MaxHeapComparator());
        this.minHeap = new PriorityQueue<>(new MinHeapComparator());
    }

    /**
     * 维持两个堆的大小,让其始终相等,或者差一
     * 如果一个堆的大小比另外一个堆的大小大2,那么就将较大堆堆顶元素移动到较小堆中。
     */
    private void modifyTowHeapsSize() {
        if (this.maxHeap.size() == this.minHeap.size() + 2) {
            this.minHeap.add(this.maxHeap.poll());
        }
        if (this.maxHeap.size() + 2 == this.minHeap.size()) {
            this.maxHeap.add(this.minHeap.poll());
        }
    }

    public void addNum(int num) {
        if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
            maxHeap.add((double) num);
        }else {
            minHeap.add((double) num);
        }
        modifyTowHeapsSize();
    }

    public double findMedian() {
        int maxHeapSize = this.maxHeap.size();
        int minHeapSize = this.minHeap.size();
        if (maxHeapSize + minHeapSize == 0) {
            return Double.parseDouble(null);
        }
        Double maxHeapHead = this.maxHeap.peek();
        Double minHeapHead = this.minHeap.peek();
        if (((maxHeapSize + minHeapSize) % 2) == 0) {
            return (maxHeapHead + minHeapHead)/2.0;
        }
        return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

39, 1~n 整数中 1 出现的次数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPiWxeeE-1651452081532)(%E5%89%91%E6%8C%87Offer.assets/1651420874099.png)]

思路分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z59fTZ00-1651452081532)(%E5%89%91%E6%8C%87Offer.assets/1651421016800.png)]

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution34
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/5/1 23:15
 */
public class Solution34 {
    public static int countDigitOne(int n) {
        int count = 0;
        //要从个位开始计算
        long i = 1;
        while (n / i != 0) {
            //高位
            long high = n / (10 * i);
            //当前位
            long cur = (n / i) % 10;
            //低位
            long low = n - (n / i) * i;
            if(cur == 0) {
                count += high * i;
            }else if (cur == 1) {
                count += high * i + (low + 1);
            }else {
                count += (high + 1) * i;
            }
            i *= 10;
        }
        return count;
    }

    public static void main(String[] args) {
        System.out.println(countDigitOne(12));
    }
}

40,连续子数组的最大和

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oa65eky-1651452081532)(%E5%89%91%E6%8C%87Offer.assets/1651421052953.png)]

思路分析:

我们将每个位置的最优解放在一个数组中,先将第一个位置的值设置位nums[0],然后再与0进行比较,如果大于0的话,res += nums[i].

class Solution {
    public int maxSubArray(int[] nums) {
        //这里必须设置为nums[0],因为有数组有时候只有一个值
        int res = nums[0];
        for(int i = 1; i < nums.length; i++) {
            nums[i] += Math.max(nums[i - 1], 0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值