三、剑指 Offer(10~14)


一、10 斐波那契数列 I

1.算法描述

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2),其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

2.算法题解

  • 当 n = 0 时 F(0) = 0,当 n = 1 时 F(1) = 1
  • 当 n > 1 时 F(n) = F(n - 1) + F(n - 2), 其中 n > 1
  • 本地中需要注意大数取模的问题
class Solution {
    public int fib(int n) {
        if (n == 0 || n == 1) {
            return n;
        }
        int len = n + 1;
        List<Integer> list = new ArrayList<>(len);
        list.add(0);
        list.add(1);
        for (int i = 2; i < len; i++) {
            Integer res = list.get(i - 1) + list.get(i - 2);
            list.add(res % 1000000007);
        }
        return list.get(n);
    }
}

二、10 青蛙跳台阶问题 ||

1.算法描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

2.算法题解

设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。

  • 当为 1 级台阶:剩 n−1 个台阶,此情况共有 f(n−1) 种跳法;
  • 当为 2 级台阶:剩 n−2 个台阶,此情况共有 f(n-2) 种跳法。

f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2),以上递推性质为斐波那契数列。本题可转化为求斐波那契数列第 n 项的值。

本题的初始值和斐波那契不同

  • 青蛙跳台阶问题:f(0)=1,f(1)=1,f(2)=2;
  • 斐波那契数列问题:f(0)=0,f(1)=1,f(2)=1。
class Solution {
    public int numWays(int n) {
        if (n <= 1) {
            return 1;
        }
        int[] nums = new int[n + 1];
        nums[1] = 1;
        nums[2] = 2;
        for (int i = 3; i < nums.length; i++) {
            nums[i] = nums[i - 1] + nums[i - 2];
            nums[i] %= 1000000007;
        }
        return nums[n];
    }
}

三、11 旋转数组的最小数字

1.算法描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

2.算法题解

旋转点的元素一定小于左排序数组的所有元素,一定大于右排序数组的所有元素。

  • 声明 left、right 双指针分别指向 numbers 数组左右两端。
  • 设 mid = left + (right - left) / 2 为每次二分的中点,因此恒有 left < mid < right,可分为以下三种情况:
  • 当 numbers[mid] > numbers[right] 时:mid 一定在左排序数组中,即旋转点 x 一定在 [mid + 1, right] 闭区间内。
  • 当 numbers[mid] < numbers[right] 时:mid 一定在右排序数组中,即旋转点 x 一定在 [left, mid] 闭区间内。
  • 当 numbers[mid] = numbers[right] 时:无法判断 mid 在哪个排序数组中,即无法判断旋转点 x 在 [left, mid] 还是 [mid + 1, right] 区间中。解决方案: 执行 right = right - 1 缩小判断范围。
  • 当 left = right 时跳出二分循环,并返回 旋转点的值 numbers[left]即可。
class Solution {
    public int minArray(int[] numbers) {
        int left = 0, right = numbers.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (numbers[mid] < numbers[right]) {
                right = mid;
            } else if (numbers[mid] > numbers[right]) {
                left = mid + 1;
            } else {
                right -= 1;
            }
        }
        return numbers[left];
    }
}

四、12 矩阵中的路径

1.算法描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

2.算法题解


五、13 机器人的运动范围

1.算法描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。

2.算法题解


六、14 剪绳子 I

1.算法描述

给你一根长度为 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。

2.算法题解

根据(题怪推到过程)数学推导可知:一个数被分的份数越多乘积越大,但份数会有一个极值,当每份的数值等于 3 的时候,乘积达到最大。

切分规则:

  • 最优:把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0、1、2 三种情况。
  • 次优:若最后一段绳子长度为 2 则保留,不再拆为。
  • 最差:若最后一段绳子长度为 1 则应把一份 3 + 1 替换为 2 + 2,因为 2 × 2 > 3 × 1。

算法流程:

  • 当 n ≤ 3 时,按照规则应不切分,但由于题目要求必须剪成 m > 1 段,因此必须剪出一段长度为 1 的绳子,即返回 n - 1 。
  • 当 n > 3 时,求 n 除以 3 的 整数部分 a 和余数部分 b (即 n = 3a + b),并分为以下三种情况:
  • 当 b = 0 时直接返回 3^a
  • 当 b = 1 时要将一个 1 + 3 转换为 2 + 2,因此返回 3^(a-1) × 4
  • 当 b = 2 时返回 3^a × 2
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }
        int a = n / 3, 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;
    }
}

七、14 剪绳子 II

1.算法描述

给你一根长度为 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。

2.算法题解

本题基于剪绳子 I,主要考察数据取模。首先求 n 除以 3 的 整数部分 a 和余数部分 b (即 n = 3a + b),然后计算 3^(a-1) 的值 ret,b 分为以下三种情况:

  • 当 b = 0 时直接返回 ret × 3
  • 当 b = 1 时要将一个 1 + 3 转换为 2 + 2,因此返回 ret × 4
  • 当 b = 2 时返回 ret × 6
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }
        int a = n / 3, b = n % 3;
        long ret = 1;
        for (int i = 1; i < a; i++) {
            ret = ret * 3 % 1000000007;
        }
        if (b == 0) {
            return (int) (ret * 3 % 1000000007);
        }
        if (b == 1) {
            return (int) (ret * 4 % 1000000007);
        }
        return (int) (ret * 6 % 1000000007);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值