文章目录
- 7. 整数反转
- 题目链接
- 标签
- 思路
- 反转操作
- 反转的数的范围
- 代码
- 703. 数据流中的第 K 大元素
- 题目链接
- 标签
- 思路
- 代码
- 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
MINV,Integer.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 可以形成 循环链表 的形式:
上图中的
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 判圈算法 解决,步骤如下:
- 使用快慢指针从头开始遍历“链表”,快指针每次走两步,慢指针每次走一步,直到快慢指针相遇,即快慢指针的值相等。
- 让慢指针从头开始遍历,快指针从相遇点开始遍历,两个指针分别每次只走一步,直到相遇,相遇后返回结果即可。
在链表中,用 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;
}
}