paiza スキルチェック過去問題セット「日別訪問者数の最大平均区間 PHP編」解答例

kue

あなたは、とあるウェブサイトを管理していました。
ある連続したk日間、このウェブサイトでキャンペーンを行ったのですが、いつからいつまでの期間に行ったかを忘れてしまいました。

幸い、ウェブサイトを運営していた全n日分のアクセスログが残っており、1日ごとの訪問者数が分かっています。

とりあえず、連続するk日の中で、1日あたりの平均訪問者数が最も多い期間を、キャンペーンを行った期間の候補だと考えることにしました。
n日分の訪問者数のリストとキャンペーンの日数kが入力されるので、キャンペーンを行った期間の候補数と、候補の中で最も早い開始日を出力してください。

paiza スキルチェック過去問題より

この手の問題を考えるとき、頭に浮かぶ図がこれです。

入力例
5 3
1 2 3 2 1

ざっくりしたスプレッドシートで申し訳ありません。

範囲内に連続したかたまりがあり、それが1つずつ横にスライドしている。
スライドできなくなったらそこでおわり。
そういうイメージです。

kue
<?php
    // 自分の得意な言語で
    // Let's チャレンジ!!
    
    /*
    n日分の訪問者数のリストとキャンペーンの日数kが入力されるので、
    キャンペーンを行った期間の候補数と、候補の中で最も早い開始日を出力してください。
    */

    $s = explode(" ",trim(fgets(STDIN)));
    $num = $s[0];//全体の日数
    $cam_num = $s[1];//キャンペーンの日数
    
    $days = explode(" ",trim(fgets(STDIN)));//日ごとの訪問数
    //print_r($day);
    

    //キャンペーン期間の訪問者合計をだす
    for ($i = 0; $i < $num-$cam_num+1; $i++) {
        $sum[$i]=0;//reset 連続するキャンペーン日数の訪問数の合計
        
        for ($j = 0, $k=$i; $j < $cam_num; $j++,$k++) {
            $sum[$i]=$sum[$i]+$days[$k];
        }
    }
    
    //print_r($sum);

    $max = max($sum);
    //echo($max);
    
    $max_num = 0;//reset キャンペーンを行った期間の候補数
    
    //最大値をもっている日程を探す
    foreach($sum as $key=>$value){
        if($value==$max){
            $max_num++;
            $answer[]=$key;
        }
    }
    echo($max_num." ");
    //print_r($answer);
    
    $first_answer = $answer[0]+1;//候補の中で最も早い開始日/配列が0はじまりのため1を足す
    echo($first_answer."\n");
    

?>

構造としては、

  • 日にちの訪問者を配列に入れておく($days)
  • キャンペーン日数(この場合3日)のかたまり【黄色部分】を1つずつ横にずらして取得したい
  • よってforの始まりは$i = 0;  ループ範囲は$i < $num-$cam_num+1;(全日程numからキャンペーン日数$cam_numを引き1を足す) $i++
  • その合計値を配列に入れておく($sum) (なお$sumのキーはキャンペーン候補開始日にもなっています)
  • $sumの最大値をもっているものが「もっともキャンペーンの3日というかたまりのなかで訪問者を取得している」ということになる
  • 『キャンペーンを行った期間の候補数』が解答で求められているため、候補日は複数あると仮定し、最大値を持っているものの数を変数に格納($max_num)
  • 同時に最大値をもっているもののキー(すなわち開始日候補)を$answerに格納
  • 『候補の中で最も早い開始日を出力してください。』ということなので、$answer[0](配列のトップ)を出したいが、配列は0始まりで日にちは1始まりのため+1して出力

そういう流れになっています。

あぶらぼうず

二重forで処理するのですが内側のforにはちょっと変数に仕掛けがいります。
$j =$i; でループを始めてしまうと「連続した3日」が、2回目のループの時には「連続した2日」、さらに「連続した1日」と目減りします。これはよくない。
よって$kをかませる必要がありました。

ご参考になれば幸いです。

paiza スキルチェック過去問題セット

https://paiza.jp/works/mondai/skillcheck_archive/problem_index?language_uid=php

3+

paiza スキルチェック過去問題セット「日別訪問者数の最大平均区間 PHP編」解答例」への2件のフィードバック

  1. あやぼう 返信

    初コメ失礼します。
    PHP学習中なのですが、
    for ($j = 0, $k=$i; $j < $cam_num; $j++,$k++) {
    $sum[$i]=$sum[$i]+$days[$k];
    }
    ここの部分ですが、なぜ$kを噛ませる事で、連続した三日を維持出来るのでしょうか?
    結構、難しいなあと感じてしまいました。

    0
  2. karasumaru 投稿者返信

    あやぼうさんお疲れ様です。

    for ($j = 0, $k=$i; $j < $cam_num; $j++,$k++) {

    この部分ですが、最初、
    for ($j=$i; $j < $cam_num; $j++) {

    と記述したんですよ。
    結果どうなったかというと、
    「外側のfor文のカウント」が進み、$i=1、$i=2とすすむたび、
    ちょうどこう記述した形になってしまったんです。

    for ($j=2; $j < $cam_num; $j++) {

    お気づきでしょうか?
    こうなると$cam_numは定数の3ですので、
    この内側のループは本来3日あるはずのキャンペーン期間を、
    「2からはじめて3より小でおわる」1日分で済ませて終了しまいます。

    内側のループは「キャンペーン期間」3日分でちゃんと回してほしい。
    よって$jは「常に0から始めて」「3より小で」ループを終わらせる。

    一方スライドする日付(開始日)も欲しい。それは$iに連動しています。
    よって2つめの$kが登場し、$k=$i;となっています。

    (そしてforループの終了条件は$jが持っています。$kは影響を受けません。 これによって3日が維持されます。)

    もっとスマートで分かりやすい記述があるかもしれません。 私も万事勉強中ですのでご容赦ください。 ほんのご参考になれば幸いです!

    1+

karasumaru へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です