刷题记录15---前K个高频元素+根据身高重建队列+路径总和Ⅲ+找到所有数组中消失的数+二叉树的之字形遍历

前言

所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习

347.前K个高频元素

题目:
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
解体思路:
【小根堆】统计各个元素出现的个数比较简单,我们可以用字典和遍历数组达到目的;那如何取得次数最多的K个元素呢,比较容易想到先排序再取即可,如果调用系统排序方法则是Onlogn,这就回到了如果On的进行排序呢,也就是堆排序,所以我们可以维护一个小根堆,如果小根堆的个数小于K,则直接往堆里加值,如果达到了K就先比较堆顶元素的个数与当前元素个数,若当前的个数大于堆顶的个数就弹出堆顶元素将当前值加入,就这样遍历一遍字典中的元素,得到的小根堆就是前K个高频元素;其实思路很容易想到,难得是小根堆得用法
代码(python):


#导入小根堆算法
import  heapq

class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        # 定义字典,存储各个元素出现的个数
        counts = dict()
        for num in nums:
            if num in counts:
                counts[num] = counts[num] + 1
            else:
                counts[num] = 1
        # 定义列表 用来当小根堆的容器
        queue = []
        # 对于字典中每个元素
        for key in counts.keys():
            # 如果当前小根堆的个数小于 K,直接将 元素个数 和 元素 以列表的形式添加到小根堆
            # 注意是 【个数 和 元素】 而不是 【元素 和 个数】,因为heapq算法再构建小根堆时是将列表的第一个元素当的键 
            if len(queue) < k:
                heapq.heappush(queue, [counts[key], key])
            # 如果小根堆个数达到了K,就要对比一下堆顶元素个数是否小于当前个数,若小于,则弹出栈顶值,将当前个数和元素添加到小根堆
            else:
                if queue[0][0] < counts[key]:
                    heapq.heappop(queue)
                    heapq.heappush(queue, [counts[key], key])
        # 最后将小根堆中的值依次弹出将元素添加到结果列表即可
        result = []
        for i in range(0, k):
            result.append(heapq.heappop(queue)[1])
        return result

代码(java):

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> counts = new HashMap<>();
        for(int num: nums){
            counts.put(num, counts.getOrDefault(num, 0) + 1);
        }

        PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] m, int[] n){
                return m[1] - n[1];
            }
        });

        for(Map.Entry<Integer, Integer> entry: counts.entrySet()){
            int num = entry.getKey();
            int count = entry.getValue();
            if(k == queue.size()){
                if(queue.peek()[1] < count){
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            }else{
                 queue.offer(new int[]{num, count});
            }
        }

        int[] res = new int[k];
        for(int i = 0; i < k; i++){
            res[i] = queue.poll()[0];
        }
        return res;
    }
}

知识点:

  • python可以用heapq算法构建小根堆,使用前要import heapq,新建一个列表list当作容器;添加元素是heapq.heappush(list, val),需要注意得是若val是一个列表,那会以列表得第一个元素当比较的键;弹出堆顶元素是heap.heappop(list);而得到堆顶元素直接list[0]即可
  • python中若想构建大根堆,可以再小根堆的基础上,添加和取值都将key变为-key即可
  • java中小根堆和大根堆都可以用优先级队列PriorityQueue,传入比较器就好,添加就是PriorityQueue.offer(val);弹出堆顶元素就i是PriorityQueue.poll(val);取得堆顶元素是PriorityQueue.peek()

原题链接:前K个高频元素

406.根据升高重建队列

题目:
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
解体思路:
【排序】很重要的一个思想是:矮个子站好位置了对高个子没影响(这个影响指的是前面有几个比他高的人),所以我们要按个子到高进行排队,也就是说要对原队列进行排序,按身高从小到大排序,若身高相同怎么办呢?看前面人的个数,假设一个前面人的个数是0,一个是1,那排队后的结果是0在1前面,也可以变相理解为0的身高比1的身高了一丢丢(这样才能使1认为0比它高一点,他才能是1),又因为按身高从小到大排序,所以1要排在0前面,也就意味着身高相同是要按照前面有几个比他高的人的个数从大到小排序;我们已经知道了排队的顺序,那每个人要如何排队呢?就是每个人都找到他对应的位置,那怎么确认其对应位置呢?我们只要根据其前面有几个比他高的人,把他们的位置给预留出来(此时队列的状态应该是一些位置已经被比当前人矮的人占了,但是我们不用管他,因为矮的人对高的人没有影响,我们只需关注后面高个子对当前人的影响,所以要把他们的位置预留出来),也就是基于现在的队列找到第 预留数+1(自己)个空位置,那就是他的位置
代码(python):

class Solution(object):
    def reconstructQueue(self, people):
        """
        :type people: List[List[int]]
        :rtype: List[List[int]]
        """
        # 按身高从小到达排序,如果身高相同 按前面比他高的人数 从大到小排序
        # 这么做的原因是 高个子插入位置比影响矮个子, 而矮个子插入不影响高个子,所要先排矮个子
        people.sort(key=lambda x: (x[0], -x[1]))
        # 定义结果列表
        res = [None] * len(people)
        # 对于排好序的每个人
        for p in people:
            # 代表着 当前人应该排在 第几个空位置 也就是  前面有几个比他高的(要预留出来) + 1 (自己本身的空位置)
            pos = p[1] + 1
            # 遍历现在的结果列表 找到 第pos个空位置,并将当前人放到这个位置,那这个人就排好了
            for i in range(0, len(res)):
                if not res[i]:
                    pos -= 1
                if pos == 0:
                    res[i] = p
                    break

        return res

代码(java):

class Solution {
    /***
    先排个子小的人,因为个子小的人排哪对后面个子大的人没影响,所以按照身高从小到大排序
    个子相同的人,先排k值大的
    综上,按照h从小到大排序,h相等,按照k从大到小排序
    每一个人的位置是 第k+1个空位置记为 pos
    遍历结果数组,若i位置有人了,跳过;没人则将pos-1,当pos到0时,i就是第pos个没人的空位置,放入即可
     */
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, new Comparator<int[]>(){
            public int compare(int[] x1, int[] x2){
                if(x1[0] != x2[0]){
                    return x1[0] - x2[0];
                }else{
                    return x2[1] - x1[1];
                }
            }
        });

        int len = people.length;
        int[][] res = new int[len][];

        for (int i = 0; i < len; i++){
            int pos = people[i][1] + 1;
            for(int j = 0; j < len; j++){
                if(res[j] == null){
                    pos --;
                    if(pos == 0){
                        res[j] = people[i];
                        break;
                    }
                }
            }
        }
        return res;
    }
}

知识点:

原题链接:根据身高重建队列

437.路径总和Ⅲ

题目:
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:
刷题记录15---前K个高频元素+根据身高重建队列+路径总和Ⅲ+找到所有数组中消失的数+二叉树的之字形遍历_第1张图片

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
解体思路:
【深度优先搜索】由于路径的结尾不一定是哪个节点,我们可以计算以每个节点为结尾的路径个数,全部相加在一起就是这个数中路径的个数,这也就是遍历的思想,先计算当前节点为结尾的路径数,在分别递归计算左子树和右子树中的路径个数,最后相加即整个树中的个数;那么如果计算以某个节点为结尾的路径数呢?还是递归的思想,我们可以先看当前节点的值是不是目标值,若是则说明当前节点自己就是一个路径,在看如果向左子树或右子树延伸的情况下有多少条路径,问题也就变成了以左节点或右节点为路径结尾,且和为target-root.val的路径个数,这不就是递归调用嘛,最后将自己本身、向左子树延伸、向右子树延伸三种情况相加,便是以当前节点为结尾的路径数
代码(python):

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):


    def pathSum(self, root, targetSum):
        """
        :type root: TreeNode
        :type targetSum: int
        :rtype: int
        """
        # 如果当前节点为空,直接返回0个结果
        if not root:
            return 0
        # 通过深度优先搜索计算以当前节点为路径结尾的结果个数
        res = self.dfs(root, targetSum)
        # 通过递归调用计算 左子树 右子树 中结果个数 并相加  这个过程有点像先序遍历
        res += self.pathSum(root.left, targetSum)
        res += self.pathSum(root.right, targetSum)
        return res
    # 通过深度优先搜索计算以当前节点为路径结尾的结果个数
    def dfs(self, root, targetSum):
        #定义结果值
        res = 0
        # 如果当前节点为空,直接返回0
        if not root: 
            return res
        #如果当前节点是目标值 则结果值+1 说明找到一条路径
        if root.val == targetSum:
            res += 1
        # 通过递归调用查看以左右节点为路径结尾 且和是 targetSum-当前节点值 的路径个数
        # 并加到结果值上,此时说明通过左右节点延伸 还能拼成目标路径
        res += self.dfs(root.left, targetSum - root.val)
        res += self.dfs(root.right, targetSum - root.val)
        # 返回结果值
        return res

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int  num = 0;
    public int pathSum(TreeNode root, int targetSum) {
        test(root, targetSum);
        return num;
    }
    public ArrayList<Integer> test(TreeNode root, int targetSum){
        ArrayList<Integer> res = new ArrayList<>();
        if (root == null){
            return res;
        }
        res.add(root.val);
        ArrayList<Integer> leftRes = test(root.left, targetSum);
        for(int val : leftRes){
            res.add(val + root.val);
        }
        ArrayList<Integer> rightRes = test(root.right, targetSum);
        for(int val : rightRes){
            res.add(val + root.val);
        }
        for(int val : res){
            if (val == targetSum){
                num ++;
            }
        }
        return res;
    }
}

知识点:

原题链接:路径总和Ⅲ

448.找到所有数组中消失的数字

题目:
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:
输入:nums = [1,1]
输出:[2]
解体思路:
【原地修改】比较容易想到的是,我们遍历一遍数组,将出现过的数字做一个标记,那既然不允许使用额外空间,我们可以考虑在当前数组做标记,索引是0到n-1 值是1到n 所以索引和值对应关系是 值-1=索引 ,这就将值和索引联系在一起了,所以当我们遍历到某个值时就可以在值-1位置做标记了;那怎么标记呢?有两种选择,一是将该索引的值取反,最后判断哪个索引的值不是负数说明对应该索引的值不存在,又因为数组中某个值可能会出现多次即索引也会标记多次,所以应该是该索引的绝对值再取反,并且在对应值和索引关系时要取该值的绝对值,因为可能此时该值已经被置为负数了;二是将该索引的值加n,最后判断哪个索引的值小于等于n说明对应该索引的值不存在,同样在对应值和索引关系时要先还原即除n取余数,为啥除法而不是减法,也是因为刚刚提到的数组中某个值可能会出现多次即索引也会标记多次
代码(python):

class Solution(object):
    def findDisappearedNumbers(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        # 计算当前数组长度
        n = len(nums)
        # 对于每个值,找到他对应的索引,将该索引的位置 加上 n
        # 索引是0到n-1 值是1到n 所以正常对应关系是 值-1=索引 
        # 但又因为我们可能会对值加n,所以我们在转化为索引时要先除n取余,这才是原来的值,那为什么是除不是减呢,因为该索引可能不只加一次n
        for num in nums:
            nums[num % n - 1] += n
        # 定义结果列表
        res = list()
        # 对于每个位置,如果当前索引的值小于n 说明刚刚没有找到与该索引对应的值,也就是缺少的值,将该值(索引+1)添加到结果列表
        for i in range(0, n):
            if nums[i] <= n:
                res.append(i + 1)
        # 返回结果列表
        return res

代码(java):

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        ArrayList<Integer> arrayList = new ArrayList<>();

        for(int num : nums){
            nums[Math.abs(num) - 1] = -Math.abs(nums[Math.abs(num) - 1]);
        }

        for(int i = 0; i < nums.length; i ++){
            if (nums[i] > 0){
                arrayList.add(i + 1);
            }
        }
        return arrayList;
    }
}

知识点:

  • python中求数字的绝对值是abs(val)
  • java中求数字的绝对值是Math.abs(val)

原题链接:找到所有数组中消失的数字

二叉树的之字形遍历

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

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

3

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

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

解题思路:
【双端队列】大体思路和二叉树的层序遍历类似,不同的是单数层和偶数层先后顺序不同,那怎么知道哪层是哪种顺序呢?可以用一个标识位标志,每层都取反更新标识位即可;那如何做正序遍历和逆序遍历呢?可以用双端队列结果,双端队列即可以在头位置添加和弹出元素,也可以在尾位置添加和删除元素,如果是正序我们就在尾位置追加依次取出的层元素,如果是倒序我们就在头位置追加即可
代码(python):

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

# 导入双端队列,类似于双向链表,可在两侧添加和弹出元素
from Queue import deque

class Solution(object):
    def levelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        # 定义结果链表
        res = []
        # 如果树为空直接返回
        if not root:
            return res
        # 定义标志位,True代表从左向右遍历,False反之
        is_left = True
        # 定义双端队列 并将初始值即根节点添加到队列中
        my_queue = deque()
        my_queue.append(root)
        # 当队列不为空的时
        while my_queue:
            # 定义当前层队列
            item = deque()
            # 计算一下当前层的节点个数
            size = len(my_queue)
            # 根据节点个数 依次将当前层节点取出
            for i in range(0, size):
                # 取出当前节点
                cur_node = my_queue.popleft()
                # 如果是从左向右遍历,就将该节点的值添加到当前层队列后面
                if is_left:
                    item.append(cur_node.val)
                # 如果是从右向左遍历,就将该节点的值添加到当前层队列前面
                else:
                    item.appendleft(cur_node.val)
                # 将当前节点的左右节点添加到双端队列中,用于下一层遍历
                if cur_node.left:
                    my_queue.append(cur_node.left)
                if cur_node.right:
                    my_queue.append(cur_node.right)
            # 将当前层队列添加到结果列表中
            res.append(list(item))
            # 把标志位取反
            is_left = not is_left

        return res
        

代码(java):

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> ans = new LinkedList<List<Integer>>();
        if (root == null) {
            return ans;
        }

        Queue<TreeNode> nodeQueue = new ArrayDeque<TreeNode>();
        nodeQueue.offer(root);
        boolean isOrderLeft = true;

        while (!nodeQueue.isEmpty()) {
            Deque<Integer> levelList = new LinkedList<Integer>();
            int size = nodeQueue.size();
            for (int i = 0; i < size; ++i) {
                TreeNode curNode = nodeQueue.poll();
                if (isOrderLeft) {
                    levelList.offerLast(curNode.val);
                } else {
                    levelList.offerFirst(curNode.val);
                }
                if (curNode.left != null) {
                    nodeQueue.offer(curNode.left);
                }
                if (curNode.right != null) {
                    nodeQueue.offer(curNode.right);
                }
            }
            ans.add(new LinkedList<Integer>(levelList));
            isOrderLeft = !isOrderLeft;
        }

        return ans;
    }
}

知识点:

  • python中双端队列用deque,类似双向链表的结构,需要导入from Queue import duque。deque.append(val)和deque.appendleft(val)分别代表在右侧(末尾)和左侧(前面)添加元素,deque.pop()和deque.popleft()分别代表在右侧和左侧弹出元素
  • java中可以将LinkedList结构当作双端队列,linkedlist.offerFirst()和linkelist.offerLast分别代表在右侧(末尾)和左侧(前面)添加元素,linkedlist.poll()表示出队列(前面),linkedlist.offer(val)表示入队列(末尾)

原题链接:剑指 Offer 32 - III. 从上到下打印二叉树 III

你可能感兴趣的:(算法和数据结构,算法,数据结构)