LeetCode 7, 703, 287


7. 整数反转

题目链接

7. 整数反转

标签

数学

思路

反转操作

反转实际上很简单,假设要反转数字 n u m num num,反转的结果存储在 r e v rev rev 中,反转的流程如下:

  • 先获取 n u m num num 的最后一位。
  • 然后给 r e v rev rev 10 10 10,再给 r e v rev rev 加上 n u m num num 的最后一位。
  • 最后给 n u m num num 除以 10 10 10
  • 重复以上流程,直到 n u m num num 0 0 0

反转的数的范围

如果使用 long 类型来进行反转,那么就不需要对 r e v rev rev 限制范围了,直接在最后判断 r e v rev rev 是否超过 int 的范围 [Integer.MIN_VALUE, Integer.MAX_VALUE] 即可, 如果超过了,则返回 0 0 0

然而,本题不允许使用 long,所以就不能在 r e v rev rev 溢出之后再进行判断,而是 r e v rev rev 溢出之前进行判断

Integer.MIN_VALUE M I N V MINV MINVInteger.MAX_VALUE M A X V MAXV MAXV

如果 r e v rev rev 溢出,则它的取值范围是 r e v < M I N V rev \lt MINV rev<MINV r e v > M A X V rev \gt MAXV rev>MAXV

在溢出前,也就是还没有进行第二步反转操作之前, r e v rev rev 比之后小 10 10 10 倍,如果想要在溢出之前就进行判断,则 r e v rev rev 溢出前的取值范围应该是 r e v × 10 < M I N V rev \times 10 \lt MINV rev×10<MINV r e v × 10 > M A X V rev \times 10 \gt MAXV rev×10>MAXV

由于 r e v rev rev 再乘 10 10 10 就溢出了,所以应该给 M I N V , M A X V MINV, MAXV MINV,MAXV 除以 10 10 10,此时 r e v rev rev 的溢出范围是 r e v < M I N V 10 rev \lt \frac{MINV}{10} rev<10MINV r e v > M A X V 10 rev \gt \frac{MAXV}{10} rev>10MAXV

分别用 M I N , M A X MIN, MAX MIN,MAX 来记录 M I N V 10 , M A X V 10 \frac{MINV}{10}, \frac{MAXV}{10} 10MINV,10MAXV,则 r e v rev rev 的溢出范围是 r e v < M I N rev \lt MIN rev<MIN r e v > M A X rev \gt MAX rev>MAX

代码

class Solution {
    public int reverse(int num) {
        final int MAX = Integer.MAX_VALUE / 10;
        final int MIN = Integer.MIN_VALUE / 10;

        int rev = 0;
        while (num != 0) {
            if (rev < MIN || rev > MAX) { // 先判断 rev 是否有可能溢出
                return 0; // 如果可能溢出,则返回 0
            }

			// 反转操作
            int digit = num % 10;
            rev = rev * 10 + digit;
            num /= 10;
        }
        return rev;
    }
}

703. 数据流中的第 K 大元素

题目链接

703. 数据流中的第 K 大元素

标签

树 设计 二叉搜索树 二叉树 数据流 堆(优先队列)

思路

本题的要求实现的数据结构能够找到第 k k k 大的元素,这个需求和 LeetCode 215. 数组中的第K个最大元素 的需求相似,所以考虑使用 小顶堆 来作为底层的数据结构。对于新增的数,仍旧分类讨论:

  • 如果堆未满,则直接将数字添加到堆中。
  • 如果堆已满,则考虑 新增的数 是否比 堆顶的数 大,如果大,则用新数替换堆顶的数;否则抛弃新增的数。

代码

class KthLargest {
    public KthLargest(int k, int[] nums) {
        heap = new MinHeap(k); // 构造大小为 k 的堆
        for (int num : nums) { // 将 nums 中的所有数 新增 到堆中
            add(num);
        }
    }
    
    public int add(int val) {
        if (!heap.isFull()) { // 如果堆未满
            heap.offer(val); // 则直接添加
        } else if (val > heap.peek()) { // 如果 新增的数 > 堆顶的数
            heap.replace(val); // 则进行替换
        }
        return heap.peek(); // 返回堆顶的数
    }

    private MinHeap heap;
    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];
        }
        // 检查堆是否已满
        public boolean isFull() {
            return (size == data.length);
        }
        // 上浮操作
        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;
    }
}

287. 寻找重复数

题目链接

287. 寻找重复数

标签

位运算 数组 双指针 二分查找

思路

题目中给定了一个包含 n + 1 n + 1 n+1 个整数的数组 n u m s nums nums ,其数字都在 [ 1 , n ] [1, n] [1,n] 范围内(包括 1 1 1 n n n),也就是说 n u m s [ i ] nums[i] nums[i] 的值 可以作为索引,所以可以考虑使用 算法——Floyd判圈算法 的思想解决。

先举个例子表明本题的 n u m s nums nums 可以形成 循环链表 的形式:
alt text
上图中的 n u m s = [ 1 , 2 , 3 , 2 ] nums = [1, 2, 3, 2] nums=[1,2,3,2],如果用 n u m s [ i ] nums[i] nums[i] 作为 n u m s nums nums 中下一个元素的索引,那么就可以将数组转化为“链表”,只看“链表”的属性则可以得到下面的图,这正好可以使用 Floyd 判圈算法 解决,步骤如下:

  1. 使用快慢指针从头开始遍历“链表”,快指针每次走两步,慢指针每次走一步,直到快慢指针相遇,即快慢指针的值相等。
  2. 让慢指针从头开始遍历,快指针从相遇点开始遍历,两个指针分别每次只走一步,直到相遇,相遇后返回结果即可。

在链表中,用 slow = slow.next; fast = fast.next.next; 表示移动;在本题中,用 slow = nums[slow]; fast = nums[nums[fast]] 表示移动。

代码

class Solution {
    public int findDuplicate(int[] nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);

        slow = 0; // 慢指针回到原点
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
}
  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值