【剑指 Offer】第 24 天

132 阅读3分钟

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 的片段。

所以,切分方式有三种:

  1. 恰好全切分完成:长度均为 3
  2. 最后一段长度为 2 :保留,不再拆分
  3. 最后一段长度为 1 :应该把一份 3 + 1 拆分为 2 + 2, 2 * 2 > 3 * 1

算法思路:

  1. n <= 3 时,要剪成 m > 1 段,所以直接返回 n - 1
  2. n > 3 时,设 a = n / 3b = 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

题解


这题可用 《滑动窗口》,双指针改版。

基本思路:设定 ij 两指针,左右移动。

算法流程:

  1. 初始化:左指针 i = 1,右指针 j = 2,局部和 sum = 3
  2. 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^5
  • 1 <= m <= 10^6

题解


很容易想到模拟法:模拟这个删除的过程。

算法流程:

  1. 初始化:生成数,并入列表中 (LinkedList
  2. 循环:执行 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;
    }
}