程序员的算法趣题2
上QQ阅读APP看书,第一时间看更新

Q 01 少数服从多数

IQ 70
目标时间:20 分钟

少数服从多数的方式常在人们意见不一致时使用。该方式简单明了,所 以除了政界,学校和公司也常通过它来表决。这里,我们来思考一个使用了猜拳的少数服从多数的方式,具体规则如下。

每个人只能出“石头”“剪刀”“布”中的 1 种手势,出哪一种的人数最多,则哪些人获胜。比如,当参加者有 6 人时,既可能出现表 1.1 那种一次定胜负的情况,也可能出现表 1.2 那种胜负未决的情况。

表 1.1 胜负可定的情况

表 1.2 胜负未决的情况

当一定数量的人猜拳时,一次就能定胜负的人数组合方式有多少种呢?以 4 人为例,表 1.3 列出了所有可能的组合方式,一共有 12 种。

表 1.3 当 4 个人猜拳时,组合方式有 12 种

问题

当 100 人猜拳时,一次就能定胜负的人数组合方式有多少种呢?

思路

猜拳的手势有石头、剪刀、布这 3 种,我们可以数一下出各种手势的人数。一次定胜负指的就是,出的人数最多的手势只有 1 种的情况。

代码清单 01.01q01_1.rb

N = 100

cnt = 0
0.upto(N) do |rock|               # 出“石头”的人数
  0.upto(N - rock) do |scissors|  # 出“剪刀”的人数
    paper = N - rock - scissors   # 出“布”的人数
    all = [rock, scissors, paper]
    cnt += 1 if all.count(all.max) == 1
  end
end
puts cnt

代码清单 01.02q01_1.js

N = 100;

var cnt = 0;
for (var rock = 0; rock <= N; rock++){ // 出“石头”的人数
  for (var scissors = 0; scissors <= N - rock; scissors++){
    // 出“剪刀”的人数
    var paper = N - rock - scissors; // 出“布”的人数
    if (rock > scissors){
      if (rock != paper)
        cnt++;
    } else if (rock < scissors) {
      if (scissors != paper)
        cnt++;
    } else {
      if (rock < paper)
        cnt++;
    }
  }
}
console.log(cnt);

关键点

以 4 人为例,我们可以在石头、剪刀、布之间放置分隔符来表示各种组合。假设用“○”表示人,第 1 个“|”之前是出“石头”的人,第 1 个“|”和第 2 个“|”之间是出“剪刀”的人,这之后是出“布”的人。于是,这个问题就可以转换成“求 4 个 ○ 和 2 个|有多少种组合方式”的问题(图 1.2)。

在研究组合方式时经常会用到这种方法,大家要学会这种解题思路。

图 1.2  用分隔符来思考组合方式

我们用 l(left)表示左边放分隔符的地方,用 r(right)表示右边放分隔符的地方(代码清单 01.03 和代码清单 01.04)。

代码清单 01.03q01_2.rb

N = 100

cnt = 0
0.upto(N) do |l|    # 左边的分隔符的位置
  l.upto(N) do |r|  # 右边的分隔符的位置
    all = [l, r - l, N - r] # 分别表示出石头、剪刀、布的人数
    cnt += 1 if all.count(all.max) == 1
  end
end
puts cnt

代码清单 01.04q01_2.js

N = 100;

var cnt = 0;
for (l = 0; l <= N; l++){   // 左边的分隔符的位置
  for (r = l; r <= N; r++){ // 右边的分隔符的位置
    if (l > r - l){         // 当出“石头”的人数多于出“剪刀”的人数时
      if (l != N - r)       // 当出“石头”的人数和出“布”的人数不同时
        cnt++;
    } else if (l < r - l){  // 当出“剪刀”的人数多于出“石头”的人数时
      if (r - l != N - r)   // 当出“剪刀”的人数和出“布”的人数不同时
        cnt++;
    } else {                // 当出“石头”的人数和出“剪刀”的人数一样时
      if (l < N - r)        // 当出“布”的人数最多时
        cnt++;
    }
  }
}
console.log(cnt);

答案 5100 种