Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
一、前言
刷题啊!!!
开始刷 “剑指 Offer” 31天。刷完时间:2022.3.6 ~ 2022.3.20。
二、题目
题目:
- 剪绳子
- 和为s的连续正数序列
- 圆圈中最后剩下的数字
(1)剑指 Offer 14- I. 剪绳子
题目描述
给你一根长度为 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。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:2 <= n <= 58
题解
想起李永乐老师的一个视频,大概意思是接近 3 的数最大。
那么这道题就是尽可能切出长度为 3 的片段。
所以,切分方式有三种:
- 恰好全切分完成:长度均为 3
- 最后一段长度为 2 :保留,不再拆分
- 最后一段长度为 1 :应该把一份 3 + 1 拆分为 2 + 2, 2 * 2 > 3 * 1
算法思路:
- 当
n <= 3时,要剪成m > 1段,所以直接返回n - 1 - 当
n > 3时,设a = n / 3,b = n % 3,即n = 3 * a + b:- 当
b == 0时,说明刚好整除,直接返回3 ^ a - 当
b == 1时,需要将一个 3 + 1 拆分为 2 + 2,返回(3 ^ (a-1)) * 4 - 当
b == 2时,直接返回3 ^ a * 2
- 当
AC 代码如下:
// Faster: 100.00%
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int a = n / 3;
int b = n % 3;
if (b == 0) return (int) Math.pow(3, a);
if (b == 1) return (int) Math.pow(3, a - 1) * 4;
return (int) Math.pow(3, a) * 2;
}
}
(2)剑指 Offer 57 - II. 和为s的连续正数序列
题目描述
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:1 <= target <= 10^5
题解
这题可用 《滑动窗口》,双指针改版。
基本思路:设定 i 和 j 两指针,左右移动。
算法流程:
- 初始化:左指针
i = 1,右指针j = 2,局部和sum = 3 - 当
i >= j时跳出循环:- 当
sum > target时:移动左指针i += 1, 并更新sum -= i - 当
sum < target时:移动右指针j += 1, 并更新sum += j - 当
sum = target时:记录连续序列,并移动左指针i += 1,并更新sum -= i
- 当
AC 代码如下:
class Solution {
public int[][] findContinuousSequence(int target) {
int i = 1, j = 2, sum = 3;
List<int[]> result = new ArrayList<>();
while (i < j) {
if (sum > target) {
sum -= i;
++i;
} else if (sum < target) {
++j;
sum += j;
} else {
int [] ans = new int[j - i + 1];
for (int a = 0; a < j - i + 1; ++a) {
ans[a] = i + a;
}
result.add(ans);
sum -= i;
++i;
}
}
return result.toArray(new int[0][]);
}
}
(3)剑指 Offer 62. 圆圈中最后剩下的数字
题目描述
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
限制:
1 <= n <= 10^51 <= m <= 10^6
题解
很容易想到模拟法:模拟这个删除的过程。
算法流程:
- 初始化:生成数,并入列表中 (
LinkedList) - 循环:执行
n - 1次- 设定
idx为当前位置,每次删除idx = (idx + m - 1) % n - 再更新
n:--n
- 设定
AC 代码如下:
class Solution {
public int lastRemaining(int n, int m) {
// 改为 ArrayList,可以减少查询时间,但增加了删除元素后移动元素的时间。
List<Integer> arr = new ArrayList<>();
for (int i = 0; i < n; ++i) arr.add(i);
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;
arr.remove(idx);
--n;
}
return arr.get(0);
}
}
但这个方法容易超出时间。
试试数学方法:
class Solution {
public int lastRemaining(int n, int m) {
int ans = 0;
// 最后一轮剩下2个人,所以从2开始反推
for (int i = 2; i <= n; i++) {
// (ans + m) % i 是对应的数
ans = (ans + m) % i;
}
return ans;
}
}