C++で積がK未満となる部分配列の個数を数える方法
問題の概要
正の整数からなる配列 nums が与えられます。この中から、部分配列内の全要素の積が k 未満となる(連続した)部分配列の個数を数えて出力します。
例えば、入力が [10,5,2,6] で k = 100 の場合、出力は 8 になります。条件を満たす部分配列は以下の8つです。
[[10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6]]
解法のアプローチ
この問題はスライディングウィンドウ(二ポインタ)の手法を使うことで、O(n) の計算量で効率的に解くことができます。右端を固定しながら配列を走査し、積が k 以上になった時点で左端を縮めていくのがポイントです。
具体的な手順は以下の通りです。
- temp := 1、j := 0、ans := 0 で初期化します
- i を 0 から配列のサイズまで繰り返します
- temp := temp * nums[i] とします
- temp >= k かつ j <= i の間、以下を繰り返します
- temp := temp / nums[j] とします
- j を 1 増やします
- ans := ans + (i - j + 1) とします
- ans を返します
ここで ans に加算する (i - j + 1) は、「右端が i である条件を満たす部分配列の個数」を表しています。ウィンドウ [j, i] 内のすべての部分配列が積 k 未満を保証されているためです。
C++の実装例
理解を深めるために、以下の実装例を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
typedef long long int lli;
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
lli temp = 1;
int j = 0;
int ans = 0;
for(int i = 0; i < nums.size(); i++){
temp *= nums[i];
while(temp >= k && j <= i) {
temp /= nums[j];
j++;
}
ans += (i - j + 1);
}
return ans;
}
};
main(){
Solution ob;
vector<int> v = {10,5,2,6};
cout << (ob.numSubarrayProductLessThanK(v, 100));
}
入力
[10,5,2,6] 100
出力
8
計算量の評価
時間計算量は O(n) です。ポインタ i と j はそれぞれ最大で配列の長さ分しか進まないため、全要素を高々2回ずつ処理するだけで済みます。空間計算量は O(1) で、追加のデータ構造は不要です。また、積のオーバーフローを防ぐために temp を long long 型としている点にも注目してください。要素がすべて正の整数であるため、積は単調に増加し、この手法が正しく機能します。
-
C++でn以下のすべての階乗数を効率的に求める方法
本記事では、C++を使ってn以下のすべての階乗数を出力する方法を解説します。 階乗数とは 階乗数(factorial number)とは、ある正の整数の階乗として表せる数のことです。たとえば、1! = 1、2! = 2、3! = 6、4! = 24、5! = 120 となるため、1、2、6、24、120 はいずれも階乗数に該当します。 アルゴリズムの考え方 n以下の階乗数を求める際、毎回ゼロから階乗を計算し直す必要はありません。初期値として fact = 1 を用意し、変数 i を 2 から順に増やしながら fact に i を掛けていくだけで、1!、2!、3!、… と次々に求められます。fa
-
C++で解く積配列パズル ― 除算なし・O(1)の追加メモリで実現する方法
問題の概要今回は配列に関する興味深いパズルを取り上げます。n個の要素を持つ配列が与えられたとき、同じくn個の要素を持つ別の配列を作成します。ただし、新しい配列のi番目の要素には、元の配列のi番目の要素を除いた残りすべての要素の積を格納する必要があります。この問題には次の2つの制約があります。除算演算子(/)を使用してはならない出力用の配列以外、追加のメモリ領域はO(1)に抑えることもし除算が許されるなら話は簡単です。配列全体の積を事前に計算しておき、それを各要素で割った値を順に格納すればよいからです。しかし、配列に0が含まれると除算が使えない、積が大きくなるとオーバーフローの恐れがあるといった