LeetCode12. 整数转罗马数字 / 剑指 Offer 40. 最小的k个数 / 剑指 Offer 41. 数据流中的中位数
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode12. 整数转罗马数字 / 剑指 Offer 40. 最小的k个数 / 剑指 Offer 41. 数据流中的中位数相关的知识,希望对你有一定的参考价值。
LeetCode12. 整数转罗马数字
2021.5.14 每日一题
题目描述
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。
示例 1:
输入: 3
输出: "III"
示例 2:
输入: 4
输出: "IV"
示例 3:
输入: 9
输出: "IX"
示例 4:
输入: 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.
示例 5:
输入: 1994
输出: "MCMXCIV"
解释: M = 1000, CM = 900, XC = 90, IV = 4.
思路
经典问题了,思路就是模拟吧,从高位到低位依次处理,我也是这样写的:
class Solution {
public String intToRoman(int num) {
//经典问题了,必须拿下
//数字最大3999,因此先除以1000,等于几,添加几个M
//再除以100,等于9 加 CM ,等于4 加CD,其余如果大于等于5,先加D,再加几个C;小于5,加C
//下面的处理类似,
//想想怎么循环起来
StringBuilder sb = new StringBuilder();
//用一个Map来存储当前位置(个十百千)对应的罗马数字
Map<Integer, Character> map = new HashMap<>(){
{
put(1, 'I');
put(5, 'V');
put(10, 'X');
put(50, 'L');
put(100, 'C');
put(500, 'D');
put(1000, 'M');
}
};
int base = 1000;
//统计每位的个数
while(num > 0){
int count = num / base;
num = num - count * base;
char ge = map.get(base);
char wu = ' ';
char shi = ' ';
if(map.containsKey(base * 5))
wu = map.get(base * 5);
if(map.containsKey(base * 10))
shi = map.get(base * 10);
if(count == 9){
sb.append(ge);
sb.append(shi);
count = 0;
}
else if(count == 4){
sb.append(ge);
sb.append(wu);
count = 0;
}
else if(count >= 5){
sb.append(wu);
count = count - 5;
}
for(int i = 0; i < count; i++){
sb.append(ge);
}
base /= 10;
}
return sb.toString();
}
}
没啥问题,看了题解吧,发现也是模拟,只不过把数分的更加详细了,贴一个weiwei哥的代码:
public class Solution {
public String intToRoman(int num) {
// 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中,并且按照阿拉伯数字的大小降序排列
int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
while (index < 13) {
// 特别注意:这里是等号
while (num >= nums[index]) {
stringBuilder.append(romans[index]);
num -= nums[index];
}
index++;
}
return stringBuilder.toString();
}
}
剑指 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个数,如果堆顶大于新来的数,就弹出,加入当前数,最后堆中就是k个最小的数
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
//最小的数可以求,最小的两个数可以求,那么最小的四个数其实也可以写,只不过有点复杂,判断条件多了
//或者排序
//用最大堆,堆中存储4个最小的数字,如果新的数字小于堆顶,进行替换
if(k == 0)
return new int[]{};
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a, Integer b){
return b - a;
}
});
for(int i = 0; i < arr.length; i++){
if(pq.size() < k){
pq.offer(arr[i]);
}else{
if(pq.peek() > arr[i]){
pq.poll();
pq.offer(arr[i]);
}
}
}
int[] res = new int[k];
for(int i = 0; i < k; i++){
res[i] = pq.poll();
}
return res;
}
}
第二种:快排思想
先来复习一下快排,快排思路就是将数组划分为小于基准数和大于基准数两部分,最重要的是细节
刚开始写了下面的代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
//来写一下快排
quickSort(arr, 0, arr.length - 1);
return Arrays.copyOf(arr, k);
}
public void quickSort(int[] arr, int l, int r){
if(l >= r)
return;
//基准值取最左边的值
int base = arr[l];
int i = l;
int j = r;
while(i < j){
while(i < j && arr[i] <= base)
i++;
while(i < j && arr[j] >= base)
j--;
swap(arr, i, j);
}
swap(arr, i, l);
quickSort(arr, l, i - 1);
quickSort(arr, i + 1, r);
}
public void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
这个报错了,[0,1,2,1] 1 这个例子倒下了,我输出的是1,答案应该是0
然后我把下面两个循环的顺序颠倒了,就过了
while(i < j && arr[j] >= base)
j--;
while(i < j && arr[i] <= base)
i++;
这说明第一种写法,排序结果不对,为什么呢,看一下错误的例子,
第一轮排序,基准是0,第一轮两个循环结束,i == 1,j == 1,交换不变,最后交换基准,把0和1的位置交换了,所以发生错误
第二种写法,先移动j,第一轮两个循环结束,j = 0,i = 0,交换不变,最后交换基准,也不变,即0排在最前面,正确
因此,第一种快排模板,很简洁明了,背过
第二种模板,也还行吧,也顺带记一下
private int partition(int[] nums, int lo, int hi) {
int v = nums[lo];
int i = lo, j = hi + 1;
while (true) {
while (++i <= hi && nums[i] < v);
while (--j >= lo && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[lo] = nums[j];
nums[j] = v;
return j;
}
然后针对这个问题,对快排进行改进,因为要返回的是最小的k个数,因此不需要对数组进行排序,只需要将数组划分为长度为k和剩余部分两个部分就可以了。
因此只需要找到基准位置的下标为k就可以了,此时,左边的数正好是k个
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
//来写一下快排——> 快排思想
//如果k比数组长度还大,就直接返回数组
if(k >= arr.length)
return arr;
return quickSort(arr, 0, arr.length - 1, k);
}
public int[] quickSort(int[] arr, int l, int r, int k){
//基准值取最左边的值
int base = arr[l];
int i = l;
int j = r;
while(i < j){
while(i < j && arr[j] >= base)
j--;
while(i < j && arr[i] <= base)
i++;
swap(arr, i, j);
}
swap(arr, i, l);
//如果当前基准数的位置大于k,说明在左边
if(i > k) return quickSort(arr, l, i - 1, k);
if(i < k) return quickSort(arr, i + 1, r, k);
return Arrays.copyOf(arr, k);
}
public void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
剑指 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]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
还是想办法优化时间复杂度,因为如果用数组每次插入找到位置,然后移动的话,时间复杂度为O(n)
然后就想起之前做过的用两个对顶堆求中位数的方法;中位数把一组数分成了大数部分和小数部分,用一个小顶堆存放大数部分,堆顶就是大数部分最小数;大顶堆存放小数部分,堆顶就是小数部分最大数
主要是怎么维护这两个堆,首先插入元素,如果大于小顶堆堆顶,放入小顶堆;小于大顶堆堆顶,放在大顶堆;介于中间,也放在小顶堆
然后调整两个堆的大小,使得大小之差小于等于1
仔细想了一下后,写了下面代码:
class MedianFinder {
//怎么快速计算中位数,
//如果之前是偶数个数,中位数是m,m = (l + r)/ 2
//现在添加了一个t,如果t大于r,中位数变成r;小于l,中位数变成l;l和r之间,中位数为t
//现在是奇数,中位数是m,记录左边的数l和右边的数r,添加一个数,如果大于r,更新mid = (r + mid) / 2,l = mid,r = r;
//同理处理小于r和在l和r之间的情况,不行。。。。。。。。。。。
//那么怎么办呢,突然想起来之前做过的一道题,两个堆找中位数,就是一个大根堆,一个小跟堆,两个顶部的数就是中位数
//想想逻辑怎么实现,小根堆里存大数,大根堆里存小数
//先把第一个数放进任意一个堆中,例如小根堆中,
//然后放第二个数,如果第二个数大于第一个数,那么就把第一个数弹出,放入大根堆中,再把第二个数放在小根堆中
//放第三个数,和两个堆顶的数进行比较,如果大于小根堆堆顶,放在小根堆中;小于大根堆堆顶,放在大根堆里;介于之间,放在小根堆
//放入以后,调整两个堆的数个数,使得两个堆个数之差不大于1
//这样,如果奇数,就取小根堆堆顶的数;如果偶数,就两个堆堆顶的数平均
//放一个数的时间复杂度为O(logn),计算中位数的时间复杂度为O(1)
/** initialize your data structure here. */
PriorityQueue<Integer> small;
PriorityQueue<Integer> big;
public MedianFinder() {
small = new PriorityQueue<>(); //小根堆,放大数
big = new PriorityQueue<>(new Comparator<Integer>(){ //大根堆,放小数
public int compare(Integer a, Integer b){
return b - a;
}
});
}
public void addNum(int num) {
//刚开始放在小根堆里
if(small.isEmpty() && big.isEmpty()){
small.add(num);
return;
}
//如果大于小根堆堆顶,说明是大数部分的,放在小根堆里面
if(!small.isEmpty() && num > small.peek())
small.offer(num);
//如果小于大根堆堆顶,说明是小数部分的,放在大根堆里面
else if(!big.isEmpty() && num < big.peek())
big.offer(num);
//介于中间的,也放在小根堆堆顶
else
small.offer(num);
//调整两个堆元素的数量
while(small.size() - big.size() > 1){
big.offer(small.poll());
}
while(big.size() - small.size() > 1){
small.offer(big.poll());
}
}
public double findMedian() {
if(small.size() > big.size())
return small.peek();
else if(small.size() < big.size())
return big以上是关于LeetCode12. 整数转罗马数字 / 剑指 Offer 40. 最小的k个数 / 剑指 Offer 41. 数据流中的中位数的主要内容,如果未能解决你的问题,请参考以下文章