剑指 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日夜