LeetCode 66, 169, 215


66. 加一

题目链接

66. 加一

标签

数组 数学

思路

本题十分简单,如果会 高精度 的解决方法,则本题更是小菜一碟。核心思想是:给低位加一,如果低位小于 10,则可以直接返回;否则就将低位的 10 变成 0,然后继续判断更高位,直到将整个数组都遍历完;如果遍历完整个数组都没有返回,则说明 最高位 的值为 10,此时需要构建一个新的数字返回,新的数字的长度为 原数字的长度 加 一,且最高位为一。

解释:

  • 为何将低位的 10 变成 0:由于是加一操作,所以某一位上加一得到的最大数字是 10,即 9 + 1,此时让 1010 取余就是 0,是一个确定的数,为了避免取余操作浪费时间,直接赋值 0
  • 为何遍历完整个数组还没有返回?:例如 9, 99, 999 这样的数,遍历完整个数组后最高位都是 10。而不是小于 10 的数,所以没有在循环中返回,而是需要构建新的数字进行返回。

代码

class Solution {
    public int[] plusOne(int[] digits) {
        int n = digits.length;
        for (int i = n - 1; i >= 0; i--) { // 从 最低位 到 最高位 遍历
            digits[i]++; // 给这一位加一
            if (digits[i] < 10) { // 如果这一位加上一的结果小于 10
                return digits; // 则无需对更高位进行加一的操作,直接返回结果即可
            }
            digits[i] = 0; // 否则需要对更高位进行加一的操作,先将这一位的 10 变成 0
        }

        // 如果能到这一步,则说明 最高位 的值为 10,其余位都是 0
        digits = new int[n + 1]; // 构建一个 n + 1 位的数字
        digits[0] = 1; // 让最高位为 1
        return digits; // 返回构建的新数
    }
}

169. 多数元素

题目链接

169. 多数元素

标签

数组 哈希表 分治 计数 排序

法一:计数

思路

本题可以使用最简单的思路来解决,使用 Map 统计数组中每个数字出现的次数,然后选出出现次数最多的数字,这个数字就是题目中要求的 出现次数大于 n / 2 的数字。

代码

class Solution {
    public int majorityElement(int[] nums) {
        // 统计每个数字出现的次数,key 为数字,value 为其出现的次数
        Map<Integer, Integer> count = new HashMap<>();
        for (int num : nums) {
            count.put(num, count.getOrDefault(num, 0) + 1);
        }

        // 在整个 Map 中选出出现次数最多的数字
        Map.Entry<Integer, Integer> resEntry = null;
        for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
            if (resEntry == null || resEntry.getValue() < entry.getValue()) {
                resEntry = entry;
            }
        }
        return resEntry.getKey();
    }
}

法二:排序

思路

如果将数组排序,那么出现次数大于 n / 2 的数字势必会出现在数组的中间位置。例如对于数组 nums = [2, 2, 1, 1, 1, 2, 2],它排序的结果为 [1, 1, 1, 2, 2, 2, 2],数字 2 正好在数组的中间位置上。

以下是对这个结论的文字论证:

  • 如果所求数字是数组中的最值(最小值 或 最大值),那么它肯定会有一部分位于数组的左子区间,另一部分位于数组的右子区间,所以数组的中间位置必定是所求的数字。例如:[1, 1, 1, 1, 2, 2, 2][1, 1, 1, 2, 2, 2, 2]
  • 如果所求数字不是数组中的最值,那么它的两边会有其他值,但是仍然有一部分位于数组的左子区间,另一部分位于数组的右子区间,所以数组的中间位置还是所求的数字。例如:[1, 2, 2, 2, 2, 3, 4]

代码

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums); // 排序
        return nums[nums.length / 2]; // 获取中间的数字
    }
}

215. 数组中的第K个最大元素

题目链接

215. 数组中的第K个最大元素

标签

数组 分治 快速选择 排序 堆(优先队列)

思路

本题可以维护一个长度为 k 的小顶堆,数值越小,越靠近顶部,将数组中所有的元素都“添加”到优先队列中,注意此处的“添加”不是真正的添加,需要分情况讨论:

  • 对于数组的前 k 个元素,直接 添加 即可。
  • 对于数组的剩余元素,先判断它是否大于堆顶元素,如果大于,则用它 替换 堆顶元素。

这样一来,小顶堆中就存储着数组中 从 第 1 大 到 第 k 大 的元素,并且堆顶是这些元素中最小的元素,即第 k 大的元素。

如果对 小顶堆、大顶堆、优先队列 不熟悉,可以看 数据结构——优先队列,本文使用大顶堆实现了优先队列,理解大顶堆实现的优先队列后,也就能理解小顶堆的实现了,区别只有 上浮操作下潜操作 的判断。

代码

class Solution {
    public int findKthLargest(int[] nums, int k) {
        MinHeap heap = new MinHeap(k); // 构建一个长度为 k 的小顶堆

        // 先将数组中的前 k 个数加入小顶堆
        for (int i = 0; i < k; i++) {
            heap.offer(nums[i]);
        }

        for (int i = k; i < nums.length; i++) { // 对于剩余的数
            if (heap.peek() < nums[i]) { // 如果 剩余的数 大于 小顶堆中最小的数
                heap.replace(nums[i]); // 则使用 剩余的数 替换 小顶堆中最小的数
            }
        }

        return heap.peek(); // 返回堆顶元素
    }

    private static class MinHeap { // 小顶堆
        public MinHeap(int capacity) {
            data = new int[capacity];
        }
        // 添加新数
        public void offer(int value) {
            int child = up(value);
            data[child] = value;
            size++;
        }
        // 将原先堆顶的数字替换成 newValue
        public void replace(int newValue) {
            data[0] = newValue; // 先替换
            down(0); // 后将其下潜到合适的位置
        }
        // 取出最小的数
        public int peek() {
            return data[0];
        }
        // 上浮操作
        private int up(int value) {
            int child = size;
            int parent = getParent(child);
            // 类似 插入排序
            while (child > 0
            		&& value < data[parent]) { // 只有 当前数 小于 父节点的数 才进行“交换”
                data[child] = data[parent];
                child = parent;
                parent = getParent(parent);
            }
            return child;
        }
        // 下潜操作
        private void down(int parent) {
            int left = getLeft(parent);
            int right = left + 1;
            int min = parent; // min 是 在父节点和两个子节点中,最小值的 索引
            if (left < size && data[min] > data[left]) {
                min = left;
            }
            if (right < size && data[min] > data[right]) {
                min = right;
            }

            if (min == parent) {
                return;
            }

            swap(min, parent);
            down(min);
        }
        // 根据 子节点的索引 获取 父节点的索引
        private int getParent(int child) {
            return (child - 1) >> 1;
        }
        // 根据 父节点的索引 获取 左子节点的索引
        private int getLeft(int parent) {
            return (parent << 1) + 1;
        }
        // 交换指定索引的两个元素
        private void swap(int i, int j) {
            int temp = data[j];
            data[j] = data[i];
            data[i] = temp;
        }
        private int[] data;
        private int size;
    }
}
  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值