剑指offer 打卡计划 | 每日进步一点点 | 第二十四天

117 阅读2分钟

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

剑指 Offer 60. n个骰子的点数

思路

(动态规划) O(n^2)

计算所有点数出现的概率,点数x出现的概率为: P(x) = x出现的次数 / 总次数​

投掷 n个骰子,所有点数出现的总次数是 6^n。

掷1个骰子,点数之和范围为1 - 6。

掷2个骰子,点数之和范围为2 - 6 * 2。

因此,掷n个骰子,点数之和范围为n - 6 * n。

状态表示: f[i][j]表示投掷i个骰子,点数之和为j出现的次数。那么f[n][x]就表示投掷n个筛子,点数之和为x出现的次数。

状态计算: 我们依据最后一次投掷的点数划分集合,那么 f[i][j] += f[i - 1][j - k]k 属于 {1, 2, 3, 4, 5, 6}并且 j >= k

初始化: 初始化 f[1][1,2,3,4,5,6] = 1 = f[0][0] 。投掷0个骰子,点数之和为0只有一种方案。

时间复杂度分析: O(n^2)

c++代码

 class Solution {
 public:
     vector<double> dicesProbability(int n) {
         vector<vector<int>> f(n + 1, vector<int>(6 * n + 1, 0));
         f[0][0] = 1;
         for(int i = 1; i <= n; i++){
             for(int j = i; j <= 6 * i; j++)
                 for(int k = 1; k <= 6; k++){
                     if(j >= k)
                         f[i][j] += f[i - 1][j - k]; //6次累加起来
                 }
         }
         vector<double> res;
         int total = pow(6, n);
         for(int i = n; i <= 6 * n; i++){
             res.push_back(f[n][i] * 1.0/ total);
         }
         return res;
     }
 };

剑指 Offer 61. 扑克牌中的顺子

思路

(数组,排序) O(n)

能组成顺子需要满足的两个条件是:

  1. 除了0以外不能出现两个相同的数字。
  2. 排序后两个相邻数字的差值不能大于0的个数。

具体过程如下:

  • 1、将数组排序。

  • 2、统计数组中0的个数。

  • 3、遍历整个数组:

    • 如果在遍历过程中,出现了0以外的两个相同的数字或者相邻数字的差值大于0的个数,则返回false
    • 维护0的个数,用总的0的个数减去用0填补空位的数量。
  • 4、如果可以遍历到数组结尾,说明当前序列合法,返回true

时间复杂度分析: O(n)

c++代码

 class Solution {
 public:
     bool isStraight(vector<int>& nums) {
         int cnt = 0;
         sort(nums.begin(), nums.end());
         for(int x : nums){
             if(!x) cnt++; //统计0的个数
         } 
         for(int i = 0; i < nums.size() - 1; i++){
             if(!nums[i]) continue;
             if(nums[i + 1] - nums[i] - 1> cnt) return false;
             if(nums[i + 1] == nums[i])  return false;
             cnt -= nums[i + 1] - nums[i] - 1;
         }
         return true;
     }
 };  

剑指 Offer 62. 圆圈中最后剩下的数字

思路

(递推,动态规划) O(n)

状态表示: f[n][m]表示n个人报数,每次报m的人被杀掉,最终胜利者的下标位置。

状态计算: 每杀掉一个人,则下一个人成为第一个报数的人,相当于把数组向前移动m位。若已知n - 1个人时,胜利者的下标位置为f[n - 1][m],则n个人的时候,就是往后移动m,(因为有可能数组越界,超过的部分会被接到头上,所以还要模n),即:f[n][m] = (f[n - 1][m] + m) % n,去掉报数的一维,则f[i] = (f[i - 1] + m) % n;

下图表示11个人从1开始报数,每次报3的被杀掉。

图片.png

时间复杂度分析: O(n)。

文章链接:blog.csdn.net/u011500062/…

c++代码

 class Solution {
 public:
     int lastRemaining(int n, int m){
         if (n == 1) return 0;
         vector<int> f(n + 1);
         f[1] = 0;
         for(int i = 2; i <= n ; i ++){ //枚举人数
             f[i] = (f[i - 1] + m) % i; //核心递推关系式
         }
         return f[n];
     }
 };