文章目录
一、认识田忌赛马背后的贪心思想
本文讲的是田忌赛马思想。其中,我们也运用到了双指针的思想。假如我们用nums1和nums2分别代表两组选手。此时我们需要通过合理分配顺序来实现nums1的利用最大化。nums2是对方的选择,我们无法进行重新排列,但我们可以推算出性价比最高的排列。我们可以通过将nums2进行降序排列,将nums1进行升序排列,以便我们将两者进行比较,并将最优化的结果存到res[]数组中。其思想就是,如果nums1的第一个最大值大于nums2当前的第一个最大值,那么,我们就将nums1的这个值存到res数组中,并将nums1的右指针往左移动,把nums2的值从栈中一个一个得提取出来作为当前的最大值进行比较;如果nums1的第一个最大值并没有大于nums2的最大值,那么,我们就将nums1的左指针当前的值(也就是nums1的最小值)存到res数组中,此时,我们相当于牺牲了一个最小值,确保我们在后面的比较中有更大的胜算比nums2的值要高。
二、算法运用
1.优势洗牌
下面选用力扣的870. 优势洗牌练习,详情请读者自动跳转至原题。
该题的解题思路在于将nums2进行降序排列,这里我们用到了优先队列来生成二叉堆来保持当前的值是nums2的最大值,同时我们存储了其对应的最开始的索引。因为我们这里将nums2进行了重新排序,这样一种理想化的排序有利于我们进行比较,而在实际比较中,它们的实际位置是不动的,所以我们需要记住它们的索引,这样就可以正确将我们推算出来的优先值存到与其对应的值的索引位置,而不是有序得将拿出来的值存入res中。因为nums1我们不需要记住索引,所以我们可以调用sort函数来进行升序排列。比较的过程,这里我们用到了双指针的方法。最后整个算法的时间复杂度为O(nlogn)。
代码如下(示例):
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums2.length;
//利用优先队列对nums2进行降序排列
PriorityQueue<int[]> max = new PriorityQueue<>(
(int[] pair1,int[] pair2) -> {
return pair2[1] - pair1[1];//[0]是原数组的索引,[1]是原数组的值。比较前后两个值,若结果>0,则pair2的值优先于pair1的值
}
);
//遍历nums2,生成最大堆
for(int i = 0; i<n; i++){
max.offer(new int[]{i,nums2[i]});
}
//将nums1升序排列
Arrays.sort(nums1);
//将res作为最优结果集
int left=0,right= n-1;
int[] res = new int[n];
//生成最优结果集
while(!max.isEmpty()){
int[] pair=max.poll();//弹出当前的堆顶,也就是最大值
int i = pair[0],maxVal = pair[1];//[0]是原数组的索引,用于正确配对;[1]是原数组的值,用于比较
//调用最大值
if(maxVal<nums1[right]){
res[i] = nums1[right];
right--;
}
//调用最小值
else if(maxVal>=nums1[right]){
res[i]= nums1[left];
left++;
}
}
//返回结果集
return res;
}
}
总结
对于优先决策类型的题目,解决思路如下:
1.利用优先队列数据结构来实现二叉堆,这样我们可以把比较对象理想化便于我们进行对比。这里我们需要注意的是,我们将比较对象重新排列,所以我们需要同时存储其对应的原本的索引,否则会出现配对错误。
2.其次我们也要将被优化的对象进行一个理想化的排列,通常我们只要进行数值的比较和存储,所以这里可以直接使用sort方法来简化代码。
3.最后在比较过程中简单运用双指针,来实现对比和存储。对于二叉堆调用poll()方法即可。