文章目录
带来第216,217场双周赛和第40场双周赛的题目解析
1665. 完成所有任务的最少初始能量H 贪心
核心思路是贪心,有一个特点是贪心的题目都是要求什么任选之类,并且数据大。对于一般的任选可以采用状态压缩dp的方法。对于数据比较大的一般就是采用贪心的思路了。
class Solution {
public int minimumEffort(int[][] tasks) {
// 贪心的思路,我们需要首先完成那些门槛值和消耗值差距大的任务。
// 原因是,
Arrays.sort(tasks, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return -(a[1]-a[0])+(b[1]-b[0]);
}
});
int need = 0;
for (int[] t:tasks){
need += t[0];
}
int ans = need; // 最基本的完成任务需要的能量
int add = 0; // 额外需要补充的能力达到门槛的
for (int[] t:tasks){
if (need<t[1]){
add += t[1]-need;
need += t[1]-need;
}
need -= t[0];
}
return ans+add;
}
}
1669. 合并两个链表M 链表
java对于链表的题目还是经常的出现错误,这里一定要注意每次的ListNode dummy = list1
其实是指向了一个地址。
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode ans=new ListNode();
b -= a-1;
ListNode tmp1 = new ListNode();
tmp1.next = list1;
ans = tmp1; // ans这个东西,指向tmp1当前的地址
while((a--)>0) tmp1 = tmp1.next; // 找到链表的尾部,这里一会进行删除
ListNode tmp2 = tmp1; // temp这个东西,指向tmp1当前的地址
while((b--)>=0) tmp2 = tmp2.next; // 这个是一会的接在list2后面的
tmp1.next = list2; // 这一行不能放到空行那里,因为temp2还是会继续使用的
while(tmp1.next != null) tmp1 = tmp1.next; // 找到list2的尾部
tmp1.next = tmp2; // list2的尾部拼接上list1
return ans.next;
}
}
1671. 得到山形数组的最少删除次数H 左右两次遍历
左右两边进行遍历的思路,注意最后在选择山峰的时候,不能是第一个和最后一个。
class Solution {
public int minimumMountainRemovals(int[] nums) {
int n = nums.length;
// 从左侧开始遍历
int[] dp = new int[n];
for (int i = 0; i<n;i++){
int cur = nums[i];
dp[i] = i; // 有一个最小值,是为了考虑如果都是清一色的上升的情况。
for (int j = i-1;j>=0;j--){
if(cur>nums[j]){
dp[i] = Math.min(dp[j]+i-j-1, dp[i]);
}
}
}
// 从右侧开始进行遍历
int[] dp1 = new int[n];
for (int i = n-1; i>=0;i--){
int cur = nums[i];
dp1[i] = n-1-i; // 同样,考虑都是下降的情况
for (int j = i+1;j<n;j++){
if(cur>nums[j]){
dp1[i] = Math.min(dp1[j]+j-i-1,dp1[i]);
}
}
}
int ans = n;
// 这里选择山峰,山峰不可以是第一个,也不可以是最后一个
for (int i = 1;i<n-1;i++){ // 注意循环范围,不可以是单调的。这里是选择最高的山峰
if(dp[i]!=i && dp1[i]!=n-i-1)
ans = Math.min(ans,dp[i]+dp1[i]);
}
return ans;
}
}
1673. 找出最具竞争力的子序列M 单调栈
class Solution {
public int[] mostCompetitive(int[] nums, int k) {
// 单调栈
int[] ans = new int[k];
int index = -1;
int n = nums.length;
for (int i = 0; i<n;i++){
while (index>= 0 && index+n-i >= k && ans[index] > nums[i]){// 栈不空,且后面足够填满,当前的小于栈顶的
index--;
}
if (index<k-1) {
index++;
ans[index] = nums[i];
}
}
return ans;
}
}
1674. 使数组互补的最少操作次数M 差分数组
class Solution {
// 差分的思路。
// 为什么使用差分? 因为差分可以知道对于这个和,每个对子为了实现这个目标需要的次数。
// 并且这个具有明显的区间分段的性质
public int minMoves(int[] nums, int limit) {
// diff 维护了整个数组实现这个i这个加和需要的操作数量
int[] diff = new int[2+2*limit];
int n = nums.length;
for (int i = 0;i<n/2;i++){// 每次枚举两侧的数字
int l = nums[i];
int r = nums[n-1-i];
// 开始讨论: 假设l小,r大
// 1. 对于[2,l]的显然需要修改两次
// 2. 对于[l+1, r+limit]需要修改一次
// 3. 对于[r+limit+1, 2limit] 需要两次
// 4. 对于r+l,完全不需要调整,0次
// 因此使用差分数组时候,0->2->1—>0->1->2->0
diff[2] += 2;
diff[1+Math.min(l,r)] -= 1;
diff[l+r] -= 1;
diff[l+r+1] += 1;
diff[limit+Math.max(l,r)+1] += 1;
diff[2*limit+1] -= 2;
}
int ans = Integer.MAX_VALUE;
int now = 0;
// 最后枚举每种情况,找到最小值。
for (int i = 2; i<1+2*limit;i++){
now += diff[i];
ans = Math.min(ans, now);
}
return ans;
}
}
1675. 数组的最小偏移量 H 优先队列
其实关键在于理解题目,我们首先把数组退化为一个最基础的形式,然后题目给了我们进化的最大次数,很容易想到用优先队列去维护每次该进化哪一个,并且在这个过程中维护最小偏移量。
class Solution {
public int minimumDeviation(int[] nums) {
// 题目有一个特点,奇数只能变大,偶数只能变小。因此我们首先都变为最小然后依次增大最小的数字,每次维护最小偏移量
int n = nums.length;
int[][] list = new int[n][2]; // 第一项是底,第二项是可以乘多少次2.如,8->[1,3]
for (int i = 0; i<nums.length;i++){
if (nums[i]%2 == 0){
int cnt =0;
while (nums[i]%2 == 0){
cnt++;
nums[i] /= 2;
}
list[i] = new int[]{nums[i], cnt};
}
else {
list[i] = new int[]{nums[i], 1};
}
}
// 类似最小区间问题。
PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() { // Comparator里面的泛型填写比较的泛型
@Override
public int compare(int[] o1, int[] o2) {
return o1[0]-o2[0];
}
});
int max = 0;
for (int i = 0;i<n;i++){
pq.offer(list[i]);
max = Math.max(max, list[i][0]);
}
int ans = Integer.MAX_VALUE;;
while(true){
int[] cur = pq.poll();
ans = Math.min(ans, max-cur[0]);
if(cur[1]<1) return ans; // 跳出位置,某一个已经是0,无法再增大
cur[0] *= 2;
max = Math.max(max, cur[0]);
cur[1]--;
pq.offer(cur);
}
}
}