LoginSignup
1
0

More than 1 year has passed since last update.

paizaラーニング レベルアップ問題集 Aランクレベルアップメニュー JavaScript 最短の区間

Last updated at Posted at 2022-09-27

最短の区間 (paizaランク B 相当)

毎回区間和をループで求めて解くと、計算量が多すぎて、タイムオーバーになります。

そこで、しゃくとり法というアルゴリズムを使います。
しゃくとり法のアルゴリズムの理解は難しくないのですが、
実装が慣れないと難しく感じるので、コードを書いて慣れると良いようです。

解答例(C++の場合を参考に)

javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const lines = input.split("\n");

const [N, M] = lines[0].split(" ").map(Number);
let sum = 0;//区間和
ans = Infinity;//最短の区間の長さ
let l = 0;//左端
let u = 0;//右端

const A = lines[1].split(" ").map(Number);

sum += A[0];

while (1) {
    if (M <= sum) {
        sum -= A[l];
        ans = Math.min(ans, u-l+1);
        l++;
    }else{ // (sum < M)
        u++;
        if (u === N) {//右端が全区間の右端に到達したら抜ける
            break;
        }else{
            sum += A[u];    
        }
        
    } 
}//while
console.log(ans === Infinity ? -1 : ans);

解答例(Python3の場合を参考に)

javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const lines = input.split("\n");

const [N, M] = lines[0].split(" ").map(Number);
const A = lines[1].split(" ").map(Number);

let sums = [0];//累積和
for (let i = 0; i < N; i++) {
    sums.push(sums[i] + A[i]);
}

let count = Infinity;//最短の区間の長さ
let start = 0;//左端
let end = 0;//右端

while (true) {
    if (end >= N) {//右端がN以上になったら
        break;
    }
    //区間和を累積和で計算
    if (sums[end + 1] - sums[start] >= M) {
        if (end - start + 1 < count) {
            count = end - start + 1;
        }
        start += 1;//左端を進める
    } else {
        end += 1;
    }
}//while
if (count === Infinity) {
    count = -1;//解がないなら-1
}
console.log(count);

解答例(Ruby の場合を参考に)

javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const lines = input.split("\n");

const [n, m] = lines[0].split(" ").map(Number);
const a = lines[1].split(" ").map(Number);

let [head, tail] = [0, 0];//右端、左端
let [sum, ans] = [0, n + 1];//区間和sum、答えans初期値n+1

while (true) {
    if (sum >= m) {
        ans = Math.min(ans, head - tail);
        sum -= a[tail];
        tail += 1;
    } else {
        if (head === n) {
            break;
        }
        sum += a[head];
        head += 1;
    }
}//while
//条件を満たす部分列がなかった場合
if (ans === n + 1) {
    ans = -1;
}    
console.log(ans);

解答例(累積和)

累積和を導入すれば計算量が落ちて、時間内に処理ができます。

区間和の長さlを最短の1からNまで順に調べていきます。
長さlの区間和を、先頭から順に、累積和で求めます。
S >= Mを満たすlが見つかったら、それが最短の長さなので、lを出力して、処理を終了します。
最後まで見つからなかったら、-1を出力します。

const fs = require("fs");
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const lines = input.split("\n");
//要素数N,M
const [N,M] = lines[0].split(" ").map(Number);
//数列A
const A = lines[1].split(" ").map(num => Number(num));
//累積和
let sums = [0];
for (let i = 0; i < N; i++) {
    sums.push(sums[i] + A[i]);
}

//最短の長さが存在するか
let flag = false;

//区間和の長さl,1からNまで、短い方から長くしていく
for (let l = 1; l <= N; l++) {
    //区間和の始まりのインデックス
    for (let j = 0; j <= A.length - l; j++) {
        
        //jからj+l-1の区間和Sを累積和sumsで求める
        let S = sums[j+l] - sums[j];
        
        //区間和Sが M 以上か
        if (S >= M) {
            //長さを出力
            console.log(l);
            //最短の長さが存在したら
            flag = true;
            break;
        }              
    } //forj    
    if (flag) break;//最短の長さが存在したらbreak
} //for l

//そのような部分列が存在しない場合は -1 を出力
if (!flag) console.log(-1);
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0