Problem: 1793. 好子数组的最大分数
文章目录
- 思路
- Code:

思路
法一: 基于快排的划分思想
1.即开始为拿到数组全部 计算分数 后来对每次找到的min值的下标左右两侧进行划分 即将min去掉 从而可以构建出两个新数组;
2.对新数组继续计算分数 与前一次比较取最大 接着继续找到min值的下标 继续划分;
3.直到划分的所有数组均结束或下标出现非法继而停止则结束此过程 则此时max即为最大;
法二: 基于快排的划分思想的优化过程
1.优化划分过程 对多个min可同时划分 而不是一个一个划分;
2.即如5 5 1 4 5 4 1 —> 5 5 | 1 | 4 5 4 | 1 故只需检查5 5 和 4 5 4 即可;
法三: 基于贪心的双指针操作
1.思路即注意到好子数组一定是包括nums[k] 因此我们可以以其作为起点开始枚举;
2.若其左方为>=nums[k] 则左方可直接延长; 若其右方为>=nums[k] 则右方同理可直接延长;
3.之所以可直接延长 是因分数中min恒为nums[k] 且长度变长 因此分数是一定为增加方向的;
4.但是上述只是一种可能 因可能左右继续延长使得min为小于nums[k] 但长度增大很多 同样也可使得分数为增加方向;
5.因此我们需要枚举这几种可能 即发现只需要遍历一遍数组即可;
具体思路见代码注释;
Code:
// 法一: 基于快排的划分思想
// 即开始为拿到数组全部 计算分数 后来对每次找到的min值的下标左右两侧进行划分 即将min去掉 从而可以构建出两个新数组
// 对新数组继续计算分数 与前一次比较取最大 接着继续找到min值的下标 继续划分
// 直到划分的所有数组均结束或下标出现非法继而停止则结束此过程 则此时max即为最大
// class Solution {
// int max = -1;
// public int maximumScore(int[] nums, int k) {
// int len = nums.length;
// int start = 0;
// int end = len-1;
// cutNums(nums,start,end,k);
// return max;
// }
// public void cutNums(int[] nums,int start,int end,int k){
// // k做下标检查工作
// if(start <= end && start <= k && end >= k){
// int[] message = calNumsOfMinAndIndexAndSum(nums,start,end);
// int min_temp = message[0];
// int min_index = message[1];
// int temp_sum = message[2];
// max = Math.max(max,temp_sum);
// cutNums(nums,start,min_index-1,k);
// cutNums(nums,min_index+1,end,k);
// }
// // 一旦下标已经非法 则此段数组直接舍弃即可 因其再怎么移动也无法合法 因其移动为缩小方式
// return ;
// }
// // 实际上 可以将每次得到的所有最小值都去除 这里只是模拟找到一个最小值下标 依据这一个进行切分的情况
// public int[] calNumsOfMinAndIndexAndSum(int[] nums,int start,int end){
// int[] mes = new int[3];
// // mes[0] = nums[start];
// // mes[1] = start;
// // for(int i=start+1;i<=end;i++){
// // if(nums[i] < nums[i-1]){
// // mes[1] = i;
// // mes[0] = nums[i];
// // }
// // }
// // 下述方式避免了此段数组只有一个元素的情况 因当start==end时 使用上述则不会进入循环
// mes[0] = Integer.MAX_VALUE;
// mes[1] = -1;
// for(int i=start;i<=end;i++){
// if(nums[i] < mes[0]){
// mes[1] = i;
// mes[0] = nums[i];
// }
// }
// mes[2] = mes[0]*(end-start+1);
// return mes;
// }
// }
// 法二: 基于快排的划分思想的优化过程
// 优化划分过程 对多个min可同时划分 而不是一个一个划分
// 即如5 5 1 4 5 4 1 ---> 5 5 | 1 | 4 5 4 | 1 故只需检查5 5 和 4 5 4 即可
// class Solution {
// int max = -1;
// public int maximumScore(int[] nums, int k) {
// int len = nums.length;
// int start = 0;
// int end = len-1;
// cutNums(nums,start,end,k);
// return max;
// }
// public void cutNums(int[] nums,int start,int end,int k){
// // k做下标检查工作
// if(start <= end && start <= k && end >= k){
// List<Integer> list = calNumsOfMinAndIndexAndSum(nums,start,end);
// int list_len = list.size();
// max = Math.max(max,nums[list.get(0)]*(end-start+1));
// for(int i=0;i<list_len;i++){
// int index = list.get(i);
// cutNums(nums,start,index-1,k);
// cutNums(nums,index+1,end,k);
// }
// }
// // 一旦下标已经非法 则此段数组直接舍弃即可 因其再怎么移动也无法合法 因其移动为缩小方式
// return ;
// }
// // 实际上 可以将每次得到的所有最小值都从数组中去除 根据去除的"豁口"划分出新的数组
// // 即如5 5 1 4 5 4 1 ---> 5 5 | 1 | 4 5 4 | 1 故只需检查5 5 和 4 5 4 即可
// // 因一旦某段数组中带有此min 则其所得分数必定是还不如原先分数! 因其min和原先的相同 且长度又缩短了 故定不会实现分数增加
// public List<Integer> calNumsOfMinAndIndexAndSum(int[] nums,int start,int end){
// int min = Integer.MAX_VALUE;
// for(int i=start;i<=end;i++){
// min = Math.min(min,nums[i]);
// }
// List<Integer> list = new ArrayList<>();
// for(int i=start;i<=end;i++){
// if(nums[i] == min){
// list.add(i);
// }
// }
// return list;
// }
// }
// 法三: 基于贪心的双指针操作
// 思路即注意到好子数组一定是包括nums[k] 因此我们可以以其作为起点开始枚举
// 若其左方为>=nums[k] 则左方可直接延长; 若其右方为>=nums[k] 则右方同理可直接延长;
// 之所以可直接延长 是因分数中min恒为nums[k] 且长度变长 因此分数是一定为增加方向的
// 但是上述只是一种可能 因可能左右继续延长使得min为小于nums[k] 但长度增大很多 同样也可使得分数为增加方向
// 因此我们需要枚举这几种可能 即发现只需要遍历一遍数组即可
class Solution {
public int maximumScore(int[] nums, int k) {
int len = nums.length;
// 已经认为把nums[k]为置入状态 故l和r要枚举k的左右两方
int l = k-1;
int r = k+1;
int score = 0;
// i即维护此时数组中min值 初始即为nums[k] 因好子数组一定为包含nums[k]
for(int i=nums[k];;){
// 若能移动则移动 因此时min未变化 且长度为增大 故分数也为增大
while(l >=0 && nums[l] >= i){
l--;
}
// 若能移动则移动 因此时min未变化 且长度为增大 故分数也为增大
while(r < len && nums[r] >= i){
r++;
}
// 求出移动后的分数
score = Math.max(score,i*((r-1)-(l+1)+1));
// 边界判断 若l r均出界则直接停止
if(l < 0 && r >= len)
break;
// 此时此种情况下的最大分数已经计算完毕 因此需要试探下一种情况
// 下一种情况即min进行变化(选择移动到不太小于他的元素) 再使长度增加 此时也有可能超过原先的分数
// 由于之前的两个while操作 故此时l和r下标对应的两个元素值都是大于之前的标准i的
// 我们选择较大的让min值移动
if(l >=0 && r < len)
i = Math.max(nums[l],nums[r]);
else if(l == -1)
i = nums[r];
else
i = nums[l];
}
return score;
}
}