实习面试之算法准备:数学题

目录

  • [1 技巧](#1 技巧)
  • [2 例题](#2 例题)
    • [2.1 Nim 游戏](#2.1 Nim 游戏)
    • [2.2 石子游戏](#2.2 石子游戏)
    • [2.3 灯泡开关](#2.3 灯泡开关)

1 技巧

稍加思考,找到规律

2 例题

2.1 Nim 游戏

你和你的朋友,两个人一起玩 Nim 游戏:

桌子上有一堆石头。

你们轮流进行自己的回合, 你作为先手 。

每一回合,轮到的人拿掉 1 - 3 块石头。

拿掉最后一块石头的人就是获胜者。

假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。

示例 1:

输入:n = 4

输出:false

解释:以下是可能的结果:

  1. 移除1颗石头。你的朋友移走了3块石头,包括最后一块。你的朋友赢了。
  2. 移除2个石子。你的朋友移走2块石头,包括最后一块。你的朋友赢了。
    3.你移走3颗石子。你的朋友移走了最后一块石头。你的朋友赢了。
    在所有结果中,你的朋友是赢家。

示例 2:

输入:n = 1

输出:true

示例 3:

输入:n = 2

输出:true

思路以及代码:
我们解决这种问题的思路一般都是反着思考:

如果我能赢,那么最后轮到我取石子的时候必须要剩下 1~3 颗石子,这样我才能一把拿完。

如何营造这样的一个局面呢?显然,如果对手拿的时候只剩 4 颗石子,那么无论他怎么拿,总会剩下 1~3 颗石子,我就能赢。

如何逼迫对手面对 4 颗石子呢?要想办法,让我选择的时候还有 5~7 颗石子,这样的话我就有把握让对方不得不面对 4 颗石子。

如何营造 5~7 颗石子的局面呢?让对手面对 8 颗石子,无论他怎么拿,都会给我剩下 5~7 颗,我就能赢。

这样一直循环下去,我们发现只要踩到 4 的倍数,就落入了圈套,永远逃不出 4 的倍数,而且一定会输。所以这道题的解法非常简单:

java 复制代码
class Solution {
boolean canWinNim(int n) {
    // 如果上来就踩到 4 的倍数,那就认输吧
    // 否则,可以把对方控制在 4 的倍数,必胜
    return n % 4 != 0;
    }
}

2.2 石子游戏

Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子,排成一行;每堆都有 正 整数颗石子,数目为 piles[i] 。

游戏以谁手中的石子最多来决出胜负。石子的 总数 是 奇数 ,所以没有平局。

Alice 和 Bob 轮流进行,Alice 先开始 。 每回合,玩家从行的 开始 或 结束 处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中 石子最多 的玩家 获胜 。

假设 Alice 和 Bob 都发挥出最佳水平,当 Alice 赢得比赛时返回 true ,当 Bob 赢得比赛时返回 false 。

示例 1:

输入:piles = [5,3,4,5]

输出:true

解释:

Alice 先开始,只能拿前 5 颗或后 5 颗石子 。

假设他取了前 5 颗,这一行就变成了 [3,4,5] 。

如果 Bob 拿走前 3 颗,那么剩下的是 [4,5],Alice 拿走后 5 颗赢得 10 分。

如果 Bob 拿走后 5 颗,那么剩下的是 [3,4],Alice 拿走后 4 颗赢得 9 分。

这表明,取前 5 颗石子对 Alice 来说是一个胜利的举动,所以返回 true 。

示例 2:

输入:piles = [3,7,2,3]

输出:true

思路以及代码:

注意,石头的堆的数量为偶数,所以你们两人拿走的堆数一定是相同的。石头的总数为奇数,也就是你们最后不可能拥有相同多的石头,一定有胜负之分。

而且注意,并不是简单的挑数字大的选

这道题又涉及到两人的博弈,也可以用动态规划算法暴力试,比较麻烦。但我们只要对规则深入思考,就会大惊失色:只要你足够聪明,你是必胜无疑的,因为你是先手。

java 复制代码
class Solution {
    public boolean stoneGame(int[] piles) {
        return true;
    }
}

动态规划解法:

dp[i][j] 表示当剩下的石子堆为下标 i到下标 j时,即在下标范围 [i,j] 中,在双方都做最好选择的情况下,先手与后手的最大得分差值为多少。

那么 f[1][n]为考虑所有石子,先手与后手的得分差值:

f[1][n]>0,则先手必胜,返回 True

f[1][n]<0,则先手必败,返回 False

不失一般性的考虑 f[l][r] 如何转移,根据题意,只能从两端取石子(令 piles 下标从 1 开始),共两种情况:

1.Alice从左端取,那么取到的价值为piles[l-1],这时,Alice变为了后手,Bob为先手,从f[l-1][r]之间做决策,那么基于这种情况,双方差值为:piles[l−1]−f[l+1][r]

2.Alice从右端取,同理,双方的差值为:piles[r−1]−f[l][r−1]

好,在双方都做出最优决策的情况下,f[l][r]为上述两种情况中的最大值。

因此,我们可以的到状态转移方程为
dp[i][j]=max(piles[i]−dp[i+1][j],piles[j]−dp[i][j−1])

对于区间 dp 来说,将 i 从 n−1 往前遍历到 0,而 j从 i 位置往后遍历到 n−1,这样能够方便 i<j ,将大区间划分成小区间。从小区间开始判断,不断的扩大我们的判断范围看会不会赢

java 复制代码
class Solution {

    public boolean stoneGame(int[] piles) {
        int n = piles.length;
        if (n == 0) return false;
        int[][] dp = new int[n][n];
        for (int i=0;i<n;i++) {
            dp[i][i] = piles[i];
        }

        for (int i=n-1;i>=0;i--) {
            for (int j=i+1;j<n;j++) {
                dp[i][j] = Math.max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
            }
        }
        return dp[0][n - 1] >= 0;
    }
}

2.3 灯泡开关

初始时有 n 个灯泡处于关闭状态。第一轮,你将会打开所有灯泡。接下来的第二轮,你将会每两个灯泡关闭第二个。

第三轮,你每三个灯泡就切换第三个灯泡的开关(即,打开变关闭,关闭变打开)。第 i 轮,你每 i 个灯泡就切换第 i 个灯泡的开关。直到第 n 轮,你只需要切换最后一个灯泡的开关。

找出并返回 n 轮后有多少个亮着的灯泡。

示例1:

输入:n = 3

输出:1

解释:

初始时, 灯泡状态 [关闭, 关闭, 关闭].

第一轮后, 灯泡状态 [开启, 开启, 开启].

第二轮后, 灯泡状态 [开启, 关闭, 开启].

第三轮后, 灯泡状态 [开启, 关闭, 关闭].

你应该返回 1,因为只有一个灯泡还亮着。

示例 2:

输入:n = 0

输出:0

示例 3:

输入:n = 1

输出:1

题目解读:

第 1 轮操作是把每一盏电灯的开关按一下(全部打开)。

第 2 轮操作是把每两盏灯的开关按一下(就是按第 2,4,6... 盏灯的开关,它们被关闭)。

第 3 轮操作是把每三盏灯的开关按一下(就是按第 3,6,9... 盏灯的开关,有的被关闭,比如 3,有的被打开,比如 6)...

如此往复,直到第 n 轮,即只按一下第 n 盏灯的开关。

代码以及思路:

首先,因为电灯一开始都是关闭的,所以某一盏灯最后如果是点亮的,必然要被按奇数次开关。

我们假设只有 6 盏灯,而且我们只看第 6 盏灯。需要进行 6 轮操作对吧,请问对于第 6 盏灯,会被按下几次开关呢?这不难得出,第 1 轮会被按,第 2 轮,第 3 轮,第 6 轮都会被按。

为什么第 1、2、3、6 轮会被按呢?因为 6=16=23

一般情况下,因子都是成对出现的,也就是说开关被按的次数一般是偶数次。

但是有特殊情况,比如说总共有 16 盏灯,那么第 16 盏灯会被按几次?

16 = 116 = 28 = 4*4

其中因子 4 重复出现,所以第 16 盏灯会被按 5 次,奇数次。现在你应该理解这个问题为什么和平方根有关了吧?

所以这道的base case就是找 1-n之间有多少个平方数

因此代码如下:

java 复制代码
int bulbSwitch(int n) {
    return (int)Math.sqrt(n);
}
相关推荐
香菜大丸2 分钟前
链表的归并排序
数据结构·算法·链表
jrrz08282 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time14 分钟前
golang学习2
算法
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara2 小时前
函数对象笔记
c++·算法
测试19982 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
泉崎2 小时前
11.7比赛总结
数据结构·算法
你好helloworld3 小时前
滑动窗口最大值
数据结构·算法·leetcode
马剑威(威哥爱编程)3 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试