day02 每日三题
1、将数组和减半的最少操作次数2208
给你一个正整数数组 nums
。每一次操作中,你可以从 nums
中选择 任意 一个数并将它减小到 恰好 一半。(注意,在后续操作中你可以对减半过的数继续执行操作)
请你返回将 nums
数组和 至少 减少一半的 最少 操作数。
示例 1:
输入:nums = [5,19,8,1]
输出:3
解释:初始 nums 的和为 5 + 19 + 8 + 1 = 33 。
以下是将数组和减少至少一半的一种方法:
选择数字 19 并减小为 9.5 。
选择数字 9.5 并减小为 4.75 。
选择数字 8 并减小为 4 。
最终数组为 [5, 4.75, 4, 1] ,和为 5 + 4.75 + 4 + 1 = 14.75 。
nums 的和减小了 33 - 14.75 = 18.25 ,减小的部分超过了初始数组和的一半,18.25 >= 33/2 = 16.5 。
我们需要 3 个操作实现题目要求,所以返回 3 。
可以证明,无法通过少于 3 个操作使数组和减少至少一半。
示例 2:
输入:nums = [3,8,20]
输出:3
解释:初始 nums 的和为 3 + 8 + 20 = 31 。
以下是将数组和减少至少一半的一种方法:
选择数字 20 并减小为 10 。
选择数字 10 并减小为 5 。
选择数字 3 并减小为 1.5 。
最终数组为 [1.5, 8, 5] ,和为 1.5 + 8 + 5 = 14.5 。
nums 的和减小了 31 - 14.5 = 16.5 ,减小的部分超过了初始数组和的一半, 16.5 >= 31/2 = 16.5 。
我们需要 3 个操作实现题目要求,所以返回 3 。
用到的知识点:
优先队列
优先队列是一种特殊的队列数据结构,它的每个元素都有一个相关的优先级。优先队列的特点是在添加元素时,会根据元素的优先级自动进行排序,使得优先级高的元素排在前面。当访问或删除元素时,会首先处理优先级最高的元素。
下面介绍一下优先队列的使用方法:
- 创建优先队列:可以使用现有的数据结构库提供的优先队列类,如 Java 中的
PriorityQueue
类。也可以手动实现一个优先队列数据结构。 - 添加元素:使用
offer()
或add()
方法向优先队列中添加元素。添加的元素会根据其优先级自动排序。 - 访问元素:使用
peek()
方法可以访问优先队列中优先级最高的元素,即队列头部的元素。注意,此操作并不会移除队列中的元素。 - 删除元素:使用
poll()
方法可以移除并返回优先队列中优先级最高的元素。 - 自定义优先级比较器:如果要根据元素的特定属性或规则确定优先级,可以通过自定义比较器来指定元素之间的优先级关系。比较器可以在创建优先队列时通过构造函数参数传入,或者在添加元素时指定。
需要注意的是,优先队列并不保证所有元素的排序顺序,只保证队列头部的元素具有最高的优先级。而对于其他元素的顺序,取决于具体实现和使用的比较器。
优先队列常见的应用场景包括任务调度、事件管理、最短路径算法等,它能够高效地处理按优先级排序的任务或数据。
在java中的使用
在 Java 中,可以使用 java.util.PriorityQueue
类来实现优先队列。以下是在 Java 中使用优先队列的一般方法:
-
导入类:
javaCopy Codeimport java.util.PriorityQueue;
-
创建优先队列对象:
javaCopy CodePriorityQueue<T> pq = new PriorityQueue<>();
其中
<T>
是要存储的元素类型,可以是任何具有可比较性的类型,例如整数、字符串或自定义对象。 -
添加元素:
javaCopy Codepq.offer(element);
或
javaCopy Codepq.add(element);
这将根据元素的默认排序顺序将元素添加到优先队列中,并根据其优先级进行排序。
-
访问元素:
javaCopy CodeT firstElement = pq.peek();
这将返回优先队列中优先级最高的元素,但不会将其从队列中移除。如果队列为空,返回
null
。 -
删除元素:
javaCopy CodeT removedElement = pq.poll();
这将删除并返回优先队列中优先级最高的元素。如果队列为空,返回
null
。 -
自定义优先级比较器: 如果要基于特定规则确定元素的优先级,可以通过自定义比较器来实现。比较器需要实现
Comparator
接口或使用 lambda 表达式。javaCopy CodePriorityQueue<T> pq = new PriorityQueue<>(new MyComparator());
或
javaCopy CodePriorityQueue<T> pq = new PriorityQueue<>((a, b) -> /* 比较逻辑 */);
其中
MyComparator
是自定义的比较器类。
注意:在 Java 中,优先队列是基于二叉堆或二叉树实现的,因此插入和删除元素的时间复杂度为 O(logN),其中 N 是队列中的元素个数。同时,队列中的元素并不保证有序,只有队头元素具有最高的优先级。
示例代码:
private static int solution(int[] nums) {
int res = 0;
double sum1 = 0;
double sum2 = 0;
//创建一个优先队列,降序排列,lambda表达式
PriorityQueue<Double> numQueue = new PriorityQueue<Double>((a,b) -> b.compareTo(a));
for (int i = 0; i < nums.length; i++) {
numQueue.add((double) nums[i]);
}
for (int i = 0; i < nums.length; i++) {
sum1 += nums[i];
}
while (sum2<=sum1/2) {
double index = numQueue.poll();
sum2 += index/2;
numQueue.add(index/2);
res++;
}
return res;
}
2、无重复字符的最长子串3
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
-
0 <= s.length <= 5 * 104
-
s
由英文字母、数字、符号和空格组成
用到的知识点:滑动窗口、双指针
滑动窗口是一种常用的算法思想,用于解决一些数组或字符串相关的问题。它通常用于在线性时间复杂度内解决子数组或子串的问题。
滑动窗口的基本思想是维护一个窗口,通过调整窗口的起始位置和结束位置来解决问题。窗口通常是一个连续的子数组或子串。
下面是滑动窗口算法的一般步骤:
- 初始化窗口的起始位置
start
和结束位置end
。 - 当窗口满足某个条件时,尝试缩小窗口的范围,即增加
start
的值。同时记录当前满足条件的结果。 - 当窗口不再满足条件时,尝试扩展窗口的范围,即增加
end
的值。 - 重复步骤 2 和 3,直到遍历完所有的元素。
滑动窗口的优点在于它只需遍历一次输入序列,因此可以在线性时间内解决问题。它适用于解决满足某种特定条件的子数组或子串的问题,例如求和、平均值、最大/最小值等。
以下是一个示例问题:给定一个整数数组和一个目标值,找到数组中和大于等于目标值的最短子数组的长度。可以使用滑动窗口算法来解决这个问题。
javaCopy Codeint minSubArrayLen(int target, int[] nums) {
int n = nums.length;
int start = 0;
int end = 0;
int sum = 0;
int minLength = Integer.MAX_VALUE;
while (end < n) {
sum += nums[end];
while (sum >= target) {
minLength = Math.min(minLength, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return minLength != Integer.MAX_VALUE ? minLength : 0;
}
在上述代码中,使用了两个指针 start
和 end
表示窗口的起始位置和结束位置。通过移动这两个指针,在满足条件的情况下缩小窗口范围,直到找到最短的子数组。时间复杂度为 O(n),其中 n 是数组的长度。
滑动窗口是一个非常有用的算法思想,在解决一些数组或字符串相关问题时具有较高的效率和灵活性。
示例代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s.length()==0) return 0;
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
int max = 0;
int left = 0;
for(int i = 0; i < s.length(); i ++){
if(map.containsKey(s.charAt(i))){
left = Math.max(left,map.get(s.charAt(i)) + 1);
}
map.put(s.charAt(i),i);
max = Math.max(max,i-left+1);
}
return max;
}
}
3、寻找两个正序数组的中位数4
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
用到的知识点:暴力、二分查找
作者采用暴力算法,首先将数组合并然后再用sort方法,再用数学基础知识将中位数查找出来即可。
示例代码:
//暴力解法
public static double solution(int[] num1,int[] num2) {
int len1 = num1.length;
int len2 = num2.length;
int[] nums = new int[len1+len2];
System.arraycopy(num1, 0, nums, 0, len1);
System.arraycopy(num2, 0, nums, len1, len2);
Arrays.sort(nums);
if (nums.length==0) return 0;
if (nums.length%2 !=0) {
return nums[nums.length/2];
}else {
return (double)(nums[nums.length/2]+nums[(nums.length/2)+1])/2;
}
}