【面试高频题】值得仔细推敲的贪心及其证明

数字银月渡
• 阅读 215

题目描述

这是 LeetCode 上的 1846. 减小和重新排列数组后的最大元素 ,难度为 中等

Tag : 「贪心」

给你一个正整数数组 arr

请你对 arr 执行一些操作(也可以不进行任何操作),使得数组满足以下条件:

  1. arr 中 第一个 元素必须为 $1$
  2. 任意相邻两个元素的差的绝对值小于等于 $1$

    对于任意的 $1 <= i < arr.length$ ,都满足 abs(arr[i]-arr[i-1])<=1abs(x) 为 x 的绝对值。

你可以执行以下 $2$ 种操作任意次:

  • 减小 arr 中任意元素的值,使其变为一个更小的正整数
  • 重新排列 arr 中的元素,你可以以任意顺序重新排列

请你返回执行以上操作后,在满足前文所述的条件下,arr 中可能的最大值。

示例 1:

输入:arr = [2,2,1,2,1]

输出:2

解释:
我们可以重新排列 arr 得到 [1,2,2,2,1] ,该数组满足所有条件。
arr 中最大元素为 2 。

示例 2:

输入:arr = [100,1,1000]

输出:3

解释:
一个可行的方案如下:
1. 重新排列 arr 得到 [1,100,1000] 。
2. 将第二个元素减小为 2 。
3. 将第三个元素减小为 3 。
现在 arr = [1,2,3] ,满足所有条件。
arr 中最大元素为 3 。

示例 3:

输入:arr = [1,2,3,4,5]

输出:5

解释:数组已经满足所有条件,最大元素为 5 。

提示:

  • $1 <= arr.length <= 10^5$
  • $1 <= arr[i] <= 10^9$

基本分析 & 证明

根据题意,数组的第一位必须是 $1$,且每个数只能 减小不变,数值位置可以任意调整。

求解经过调整后,符合要求的数组中的最大值是多少。

首先符合条件的数组相邻位差值绝对值不超过 $1$,这限定了数组的必然是如下三种分布之一:

  • (非严格)单调递减
  • 存在波段
  • (非严格)单调递增

证明一:取得最优解对应的数组「必然是」或者「可调整为」(非严格)单调递增的形式。

我们使用反证法来证明另外两种分布不能取得最优解:

  • (非严格)单调递减:题目限定了数的范围为正整数,且第一位为 $1$,这种情况不用讨论了,跳过;
  • 存在波段:我们始终可以将波峰的右侧出现的值,纳入到波峰的左侧,从而消掉这个波峰,最终将整个分布调整为「(非严格)单调递增」的形式,结果不会变差:

【面试高频题】值得仔细推敲的贪心及其证明

多个波段的情况也是同理,可以自己在纸上画画。

都是利用 波峰右侧的点可以调整成波峰左侧的点,从而使分布变为(非严格)单调递增

至此,我们证明了最优解对应的数组必然符合(非严格)单调递增。

这启发我们可以先对原数组排个序,在此基础上进行分析。

对原数组排序得到的有序数组,不一定是符合「相邻位差值绝对值不超过 $1$」的,同时由于每个数值可以选择 减小不变

证明二:当必须要对当前位进行调整的时,优先选择调整为「与前一值差值为 $1$ 的较大数」不会比调整为「与前一差值为 $0$ 的较小数」更差。

这可以使用归纳推理,假设采取「优先调整为与前一值差值为 $1$ 的较大数」得到的序列为 a,采用「优先调整与前一差值为 $0$ 的较小数」得到的序列为 b

根据「$a[0] = b[0] = 1$」、「ab 长度一致」、「ab 均为(非严格)单调递增」以及「ab 均满足相邻位差值不超过 $1$」,可推导出 $sum(a) >= sum(b)$,和任意位置 $a[i] >= b[i]$,从而推导出 a 序列的最后一位必然大于等于 b 的最后一位。

b 不会比 a 更优。

证明三:调整大小的操作不会改变数组元素之间的相对位置关系。

在证明二的分析中,我们会对某些元素进行“减小”操作,使得整个数组最终满足「相邻位差值绝对值不超过 $1$」。

但该证明成立的还有一个很重要的前提条件,就是调整操作不会出发元素的位置重排。

那么该前提条件是否必然成立呢?答案是必然成立。

假设原排序数组中存在需要调整的点 $i$ 和点 $j$,且 $nums[i] <= nums[j]$。

为了让数组满足条件,它们都进行了“减少”操作的调整,分别变为了 $p$ 和 $q$,如果触发位置重排的话,必然有 $nums[p] >= nums[q]$。

此时,我们能够通过调整它们的变化关系:点 $i$ 变为点 $q$、点 $j$ 变成点 $p$ 来确保同样满足条件,且不触发元素在有序数组中的位置重排。

【面试高频题】值得仔细推敲的贪心及其证明

贪心

排序,限定第一位值为 $1$,从前往后处理,根据每一位是否「必须修改(与上一位差值是否大于 $1$)」做决策,如果必须被修改,则修改为与前一值差值为 $1$ 的较大数。

代码:

class Solution {
    public int maximumElementAfterDecrementingAndRearranging(int[] arr) {
        int n = arr.length;
        Arrays.sort(arr);
        arr[0] = 1;
        for (int i = 1; i < n; i++) {
            if (arr[i] - arr[i - 1] > 1) {
                arr[i] = arr[i - 1] + 1;
            }
        }
        return arr[n - 1];
    }
}
  • 时间复杂度:假定 Arrays.sort 使用的是双轴快排实现。复杂度为 $O(n\log{n})$
  • 空间复杂度:假定 Arrays.sort 使用的是双轴快排实现。复杂度为 $O(\log{n})$

最后

这是我们「刷穿 LeetCode」系列文章的第 No.1846 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文由mdnice多平台发布

点赞
收藏
评论区
推荐文章
胡哥有话说 胡哥有话说
4年前
面试官在“逗”你系列:数组去重你会几种呀?
前言数组去重是一个老生常谈的话题,也是前端童鞋在面试时的一道高频题。本文将深入的探索数组去重的原理及实现,为各位小伙伴提供多种可以反手“调戏”面试官的解决方案。话不多说,上去就来一梭子...数组去重核心原理价值100W的核心原理上来就给你了...,记得留言点赞鸭!1.一般我们都会创建临时变量tmp,存储不重复的元素(以数组元素存储或对
孤心独饮 孤心独饮
4年前
从零开始刷力扣(一)——485:最大连续1的个数
分类:数组的遍历题目描述:给定一个二进制数组,计算其中最大连续1的个数。示例1:输入:1,1,0,1,1,1输出:3解释:开头的两位和最后的三位都是连续1,所以最大连续1的个数是3.思路初始化count和maxCount,然后遍历数组,遇见1则count,并且更新与maxCount比较,若比maxCount更大,则更新m
Stella981 Stella981
3年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
LeetCode刷题实战61:旋转链表
算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选!今天和大家聊的问题叫做旋转链表,我们先来看题面:https://leetcodecn.com/problems/rotatelist/Give
Stella981 Stella981
3年前
LeetCode 226场周赛题解
❝【GiantPandaCV导语】这是LeetCode第226场周赛题解,本周考察的知识点有枚举,贪心,前缀和,Manacher回文算法,动态规划,图论等。❞比赛链接https://leetcodecn.com/contest/weeklycontest226/最终Rank:23
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
3年前
LeetCode:283.移动零——简单
题目:283.移动零:给定一个数组nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。示例:输入:0,1,0,3,12输出:1,3,12,0,0说明:1.必须在原数组上操作,不能拷贝额外的数组。2.尽量减少操作
美凌格栋栋酱 美凌格栋栋酱
4个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
贾蔷 贾蔷
1星期前
力扣1137题 解题思路和步骤 C++代码实现,力扣一共多少题
一、题目分析力扣1137题要求我们找到第N个泰波那契数。泰波那契数的定义是:T00,T11,T21,且在n0的条件下Tn3TnTn1Tn2。,当n4时,T4T3T2T14。这道题主要考查我们对递归或动态规划的理解和运用。在思考解题方法时,我们