剑指Offer三天挑战赛32~45

剑指 Offer 32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

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

3

/
9 20
/
15 7
返回其层次遍历结果:

[
[3],
[20,9],
[15,7]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 这道题硬写也能写出来, 但是使用技巧性就很方便, 比如对返回值ans判断是不是偶数, 如果是偶数,那么就不需要反转链表数据, 如果是奇数就需要使用Collections.reverse来反转链表中的数据,其他操作和层序遍历一样
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root == null) {
            return ans;
        }
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Integer> temp = new ArrayList<>();
            for (int i = queue.size(); i > 0; i--) {
                TreeNode cur = queue.poll();
                temp.add(cur.val);
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            if (ans.size() % 2 != 0) {
                Collections.reverse(temp);
            }
            ans.add(temp);
        }
        return ans;
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

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

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

 5
/ \

2 6
/
1 3
示例 1:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 这道题很有意思, 要清楚后序遍历结果的最后一个元素肯定是每一颗子树的根节点,它前面的数据最前边是左子树数据, 中间部分的数据是右子树数据
  • 那么我倒叙遍历数组中的数据, 并附加一个栈配合,如果此时数组下标数据大于根节点数据root说明有错误, 然后如果此时栈不为空,并且此时栈底数据大于数据下标数据,说明此时栈底数据是数组坐标的父节点,但是要找到最亲父节点所以要使用while循环,最后所有的数据都要入栈
class Solution {
    public boolean verifyPostorder(int[] postorder) {
        Stack<Integer> stack = new Stack<>();
        int root = Integer.MAX_VALUE;
        for (int i = postorder.length - 1; i >= 0; i--) {
            if (postorder[i] > root) {
                return false;
            }
            while (!stack.isEmpty() && (stack.peek() > postorder[i])) {
                root = stack.pop();
            }
            stack.push(postorder[i]);
        }
        return true;
    }
}

剑指 Offer 34. 二叉树中和为某一值的路径

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

返回:

[
[5,4,11,2],
[5,8,4,5]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 很机智的一道题, 采用先序遍历的思路, 每次只要节点不为空就加入到linkedList中, 只要此时sum为0并且此时这个节点是叶子节点, 那么说明这是一个结果,保存他, 否则先用先序遍历访问其左子树和右子树, 最后不要忘了删除一开始加进去的这个数据,
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    private List<List<Integer>> ans;
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        ans = new ArrayList<>();
        fun(new LinkedList<>(), sum, root);
        return ans;
    }

    private void fun(LinkedList<Integer> list, int sum, TreeNode root) {
        if (root == null) {
            return;
        }
        list.add(root.val);
        sum -= root.val;
        if (sum == 0 && root.left == null && root.right == null) {
            ans.add(new ArrayList<>(list));
        }
        fun(list, sum, root.left);
        fun(list, sum, root.right);
        list.removeLast();
    }
}

剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 使用一个map用于建立原先链表节点和新建链表节点的映射关系, 然后用原先链表节点通过map的get操作来设置新建链表节点的复杂联系关系.
  • 注意最后返回也是map.get后新建的链表节点
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        Map<Node, Node> map = new HashMap<>();
        Node cur = head;
        while (cur != null) {
            map.put(cur, new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

剑指 Offer 36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 这道题是原先将二叉搜索树改为双向链表, 那道题的话只需要使用中序遍历,然后保存head节点,处理per节点就可以
  • 这道题比上面说到的题难了一点, 就是他是双向循环式的, 你还得保存队尾节点, 而队尾节点也是就最大值的那个节点, 最后让头节点的left指向尾节点, 尾节点的rigth指向头节点即可
/*
// Definition for a Node.
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;
    }
};
*/
class Solution {

    private Node head, per, tail;
    public Node treeToDoublyList(Node root) {
        if (root == null) {
            return root;
        }
        fun(root);
        head.left = tail;
        tail.right = head;
        return head;
    }

    private void fun(Node root) {
        if (root == null) {
            return;
        }
        treeToDoublyList(root.left);
        if (head == null) {
            head = root;
        }
        if (per != null) {
            root.left = per;
            per.right = root;
        }
        per = root;
        treeToDoublyList(root.right);
        if (tail == null || tail.val < root.val) {
            tail = root;
        }
    }
}

剑指 Offer 37. 序列化与反序列化二叉树(非递归)

题目描述
请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
示例1
输入
复制
{8,6,10,5,7,9,11}
返回值
复制
{8,6,10,5,7,9,11}

  • 下面这是一种非递归的写法, 序列化的过程是使用一个队列,每次在队列中取一个节点, 如果不为null则拼接其val和, 并且将其左节点有右节点放入队列, 如果此时节点为null, 则拼接#和,
  • 对于反序列化,首先将字符串使用,分割, 然后初始化好节点数组用于存放节点,只要对应的parts位置不为# 就初始化该节点, 然后使用一个for循环构建二叉树,使用双下标,一个表示0开始, 一个在1开始, 1开的用于建立每个0开始下标左子树和右子树
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.*;
public class Solution {
    String Serialize(TreeNode root) {
        StringBuilder builder = new StringBuilder();
        if (root == null) {
            return builder.toString();
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur != null) {
                builder.append(cur.val);
                builder.append(',');
                queue.offer(cur.left);
                queue.offer(cur.right);
            } else {
                builder.append("#,");
            }
        }
        String str = builder.toString();
        return str.substring(0, str.length() - 1);
  }
    
    TreeNode Deserialize(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
       String[] parts = str.split(",");
        return fun(parts);
    }
    
    private TreeNode fun(String[] parts) {
        TreeNode[] nodes = new TreeNode[parts.length];
        for (int i = 0; i < parts.length; i++) {
            if (!parts[i].equals("#")) {
                nodes[i] = new TreeNode(Integer.parseInt(parts[i]));
            }
        }
        for (int i = 0, j = 1; j < parts.length; i++) {
            if (nodes[i] != null) {
                nodes[i].left = nodes[j++];
                nodes[i].right = nodes[j++];
            }
        }
        return nodes[0];
    }
}

剑指 Offer 37. 序列化与反序列化二叉树(递归)

  • 对于递归版本来说, 序列化的过程就是如果为null就拼接#, 如果不为null就拼接他的val, 然后在拼接他的左子树和右子树, 最后返回string
  • 对于反序列化的过程就是先将其分割,判断是否越界, 然后递归初始化节点
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    String Serialize(TreeNode root) {
        StringBuilder builder = new StringBuilder();
        if (root == null) {
            builder.append("#,");
            return builder.toString();
        }
        builder.append(root.val).append(",");
        builder.append(Serialize(root.left));
        builder.append(Serialize(root.right));
        return builder.toString();
  }
    
    private int index = 0;
    TreeNode Deserialize(String str) {
       String[] parts = str.split(",");
        return fun(parts);
  }
    
    private TreeNode fun(String[] parts) {
        if (index >= parts.length) {
            return null;
        }
        if (parts[index].equals("#")) {
            index++;
            return null;
        }
        TreeNode node = new TreeNode(Integer.parseInt(parts[index++]));
        node.left = fun(parts);
        node.right = fun(parts);
        return node;
    }
}

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 这是一道典型的全排列问题, 利用dfs思想来实现, 而且得使用HashSet保存交换过的字符,避免字符串重复
class Solution {

    public String[] permutation(String s) {
        List<String> list = new ArrayList<>();
        char[] chars = s.toCharArray();
        fun(chars, 0, list);
        Collections.sort(list);
        String[] ans = new String[list.size()];
        for (int i= 0; i < ans.length; i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }

    private void fun(char[] chars, int index, List<String> list) {
        if (index == chars.length) {
            list.add(String.valueOf(chars));
        }

        HashSet<Character> set = new HashSet<>();
        for (int i = index; i < chars.length; i++) {
            if (set.contains(chars[i])) continue;
            set.add(chars[i]);
            swap(chars, i, index);
            fun(chars, index + 1, list);
            swap(chars, i, index);
        }
    }

        private void swap(char[] arr, int index, int i) {
        char temp = arr[index];
        arr[index] = arr[i];
        arr[i] = temp;
    }
}

剑指 Offer 39. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 题意只是为了寻找众数一种是使用map统计找到数量大于一般的那个数据, 再就是使用众数的定义, 众数一定是在排序好的数组的中间
class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length >> 1];
    }
}

剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 使用全排序然后获取k个数字也可以,但是效率低所以使用我在这里使用堆排序
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int num : arr
             ) {
            queue.offer(num);
        }
        int[] ans = new int[k];
        for (int i = 0; i < k; i++) {
            ans[i] = queue.poll();
        }
        return ans;
    }
}

剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:

输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 维护俩个优先级队列和一个计数器, big优先级队列是大堆, 放置较小的数据, small是小堆,放置较大的数据,这样小堆的堆顶和大堆的堆顶就是中位数, 但是还要判断数据的个数, 如果是奇数那定在大堆中, 如果是偶数那就是俩个堆顶的数据的平均值
class MedianFinder {

    /** initialize your data structure here. */
    private PriorityQueue<Integer> big = new PriorityQueue<>((o1, o2) -> o2 - o1);
    private PriorityQueue<Integer> small = new PriorityQueue<>();
    private int size = 0;
    public MedianFinder() {

    }
    
    public void addNum(int num) {
        if (size % 2 == 0) {
            small.offer(num);
            big.offer(small.poll());
        } else {
            big.offer(num);
            small.offer(big.poll());
        }
        size++;
    }
    
    public double findMedian() {
        if (size % 2 == 0) {
            return (small.peek() + big.peek()) * 1.0 / 2;
        } else {
            return big.peek();
        }
    }
}

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

剑指 Offer 42. 连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 最经典的一道动态规划,ret初始化为数组下标0的数据,从数组1开始遍历数据, 如果前一个下标数据小于0那么加起来肯定是让和变小了,那么就加0即可, 最后使用ret保存最大累加和
//时间和空间复杂度全为O(N)
class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int ret = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
            ret = Math.max(ret, dp[i]);
        }
        return ret;
    }
}

//优化空间复杂度为O(1)
class Solution {
    public int maxSubArray(int[] nums) {
        int ret = nums[0];
        for (int i = 1; i < nums.length; i++) {
            nums[i] += Math.max(nums[i - 1], 0);
            ret = Math.max(ret, nums[i]);
        }
        return ret;
    }
}

剑指 Offer 43. 1~n 整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5
示例 2:

输入:n = 13
输出:6

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
    public int countDigitOne(int n) {
        int digit = 1, res = 0;
        int high = n / 10, cur = n % 10, low = 0;
        while(high != 0 || cur != 0) {
            if(cur == 0) res += high * digit;
            else if(cur == 1) res += high * digit + low + 1;
            else res += (high + 1) * digit;
            low += cur * digit;
            cur = high % 10;
            high /= 10;
            digit *= 10;
        }
        return res;
    }
}

剑指 Offer 45. 把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:

输入: [10,2]
输出: “102”
示例 2:

输入: [3,30,34,5,9]
输出: “3033459”

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 这个采用分治的思想, 每次对比俩个数据的正反拼接的大小, 使用compareTo判断是否需要互换位置, 我使用的是冒泡排序的思想,也就是最开始放的肯定是最小的数据
class Solution {
    public String minNumber(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                String num1 = "" + nums[i] + nums[j];
                String num2 = "" + nums[j] + nums[i];
                if (num1.compareTo(num2) > 0) {
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }
        StringBuilder ans = new StringBuilder();
        for (int num: nums) {
            ans.append(num);
        }
        return ans.toString();
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值