数据结构和算法三十三

剑指 Offer 14- II. 剪绳子 II

题目:给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
       输入: 2
       输出: 1
       解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
       输入: 10
       输出: 36
       解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
       2 <= n <= 1000
解题思路:
此题与 面试题14- I. 剪绳子 主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题”
建议先做上一道题,在此基础上再研究此题目的大数求余方法。
在这里插入图片描述

以下数学推导总体分为两步:① 当所有绳段长度相等时,乘积最大。② 最优的绳段长度为 3 。

数学推导:
在这里插入图片描述

推论一: 将绳子 以相等的长度等分为多段 ,得到的乘积最大。

在这里插入图片描述
在这里插入图片描述

推论二: 尽可能将绳子以长度 3 等分为多段时,乘积最大。

切分规则:
在这里插入图片描述
算法流程:
在这里插入图片描述
在这里插入图片描述
大数求余解法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
复杂度分析:

以下为二分求余法的复杂度。

  • 时间复杂度O(log2N) :其中N=a,二分法为对数级别复杂度,每轮仅有求整、求余、次方运算。
    • 求整和求余运算:资料提到不超过机器数的整数可以看作是O(1);
    • 幂运算:查阅资料,提到浮点取幂为O(1) 。
  • 空间复杂度 O(1): 变量 a, b, p, x, rem 使用常数大小额外空间。

代码:
在这里插入图片描述

class Method{
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        int b = n % 3, p = 1000000007;
        long rem = 1, x = 3;
        for(int a = n / 3 - 1; a > 0; a /= 2) {
            if(a % 2 == 1) rem = (rem * x) % p;
            x = (x * x) % p;
        }
        if(b == 0) return (int)(rem * 3 % p);
        if(b == 1) return (int)(rem * 4 % p);
        return (int)(rem * 6 % p);
    }
}

贪心思路:

设一绳子长度为 n(n>1 ),则其必可被切分为两段n=n1+n2。
根据经验推测,切分的两数字乘积往往原数字更大,即往往有n1×n2>n1+n2=n 。

  • 例如绳子长度为 6 :6=3+3<3×3=9 ;
  • 也有少数反例,例如 2 : 2=1+1>1×1=1 。
  • 推论一: 合理的切分方案可以带来更大的乘积。

设一绳子长度为n(n>1),切分为两段n=n1+n2,切分为三段n=n1+n2+n3。
根据经验推测,三段 的乘积往往更大,即往往有 n1n2n3>n1n2。

  • 例如绳子长度为 9 : 两段 9=4+5 和 三段 9=3+3+3,则有 4×5<3×3×3 。
  • 也有少数反例,例如 6 : 两段 6=3+3 和 三段 6=2+2+2,则有 3×3>2×2×2 。
  • 推论二: 若切分方案合理,绳子段切分的越多,乘积越大。

总体上看,貌似长绳子切分为越多段乘积越大,但其实到某个长度分界点后,乘积到达最大值,就不应再切分了。
问题转化: 是否有优先级最高的长度 x 存在?若有,则应该尽可能把绳子以 x 长度切为多段,以获取最大乘积。

  • 推论三: 为使乘积最大,只有长度为 2 和 3 的绳子不应再切分,且 3 比 2 更优 (详情见下表)
    在这里插入图片描述

剑指 Offer 39. 数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
       输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
       输出: 2
限制:
       1 <= 数组长度 <= 50000
解题思路:

本文将 “数组中出现次数超过一半的数字” 简称为 “众数” 。
需要注意的是,数学中众数的定义为 “数组中出现次数最多的数字” ,与本文定义不同。

本题常见的三种解法:

  • 哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N)。
  • 数组排序法: 将数组 nums 排序,数组中点的元素 一定为众数。
  • 摩尔投票法: 核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为O(N)和O(1),为本题的最佳解法。

摩尔投票法:

设输入数组 nums 的众数为 x ,数组长度为 n 。

推论一: 若记 众数 的票数为+1 ,非众数 的票数为−1 ,则一定有所有数字的 票数和>0 。

推论二: 若数组的前 a 个数字的 票数和 =0 ,则 数组剩余 (n−a) 个数字的 票数和一定仍>0 ,即后(n−a) 个数字的 众数仍为 x 。
在这里插入图片描述
根据以上推论,记数组首个元素为n1,众数为 x,遍历并统计票数。当发生 票数和=0 时,剩余数组的众数一定不变 ,这是由于:

  • 当n1=x : 抵消的所有数字,有一半是众数 x 。
  • 当n1!=x : 抵消的所有数字,众数 x 的数量为一半或 0 个。

利用此特性,每轮假设发生 票数和 = 0 都可以 缩小剩余数组区间 。当遍历完成时,最后一轮假设的数字即为众数。

算法流程:
1、初始化: 票数统计 votes = 0 , 众数 x;
2、循环: 遍历数组 nums 中的每个数字 num ;
(1)当 票数 votes 等于 0 ,则假设当前数字 num 是众数;
(2)当 num = x 时,票数 votes 自增 1 ;当 num != x 时,票数 votes 自减 1 ;
3、返回值: 返回 x 即可;

复杂度分析:

  • 时间复杂度 O(N): N为数组 nums 长度。
  • 空间复杂度 O(1): votes 变量使用常数大小的额外空间。

代码:

class Method{
    public int majorityElement(int[] nums) {
        int x = 0, votes = 0;
        for(int num : nums){
            if(votes == 0) x = num;
            votes += num == x ? 1 : -1;
        }
        return x;
    }
}

拓展: 由于题目说明 给定的数组总是存在多数元素 ,因此本题不用考虑 数组不存在众数 的情况。若考虑,需要加入一个 “验证环节” ,遍历数组 nums 统计 x 的数量。

  • 若 x 的数量超过数组长度一半,则返回 x ;
  • 否则,返回未找到众数;

时间和空间复杂度不变,仍为 O(N)和 O(1)。

class Method{
    public int majorityElement(int[] nums) {
        int x = 0, votes = 0, count = 0;
        for(int num : nums){
            if(votes == 0) x = num;
            votes += num == x ? 1 : -1;
        }
        // 验证 x 是否为众数
        for(int num : nums)
            if(num == x) count++;
        return count > nums.length / 2 ? x : 0; // 当无众数时返回 0
    }
}

总结:

      今天又过去了,从早上一直忙到下午!帮同学修改他的毕业设计项目!是一个JSP写的Servlet项目!项目当中也用到了很多的零碎知识点!比如:页面的转发重定向、EL表达式如何获取数据库的值、如何上传文件和下载文件等等,最重要的是如何将tomcat上的静态资源放到本地避免服务器宕机以后挂载的数据全部丢失!当然这些知识点也让我学到了很多!
      学习就是靠一点一点积累起来的!万丈高楼平地起!如果没有很好的根基,一切都就不复存在!明天先去找几个公司面试去!看看是否能够满足条件!先不求有多少工资,主要是锻炼自己的能力!并让其实现自己的价值!我的眼里只有技术!技术迭代更新快!也不得不使我加快自己的脚步学习了!渐渐要养成看书的习惯!只有这样才能彻底地学到东西!加油吧!
      最后,愿我们都能在各行各业中能够取得不同的成就,不负亲人、朋友、老师、长辈和国家的期望!能够用自身的所学知识为国家贡献出自己的一份力量!一起加油!
                                                                                                                       2021年5月19日夜

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值