C++で最長ウィグル(振動)サブシーケンスの長さを求める方法
ウィグルシーケンスとは
隣り合う数値の差が正と負で厳密に交互に現れる数列を「ウィグルシーケンス」と呼びます。最初の差は正でも負でも構いません。また、要素が2つ未満の数列は自明にウィグルシーケンスとみなされます。
例えば [1,7,4,9,2,5] はウィグルシーケンスです。隣接する数値の差が (6, -3, 5, -7, 3) となり、正と負が交互に現れているためです。一方、[1,4,7,2,5] は最初の2つの差がどちらも正であるため、[1,7,4,5,5] は最後の差が0になってしまうため、それぞれウィグルシーケンスではありません。
問題の概要
整数列が与えられたとき、ウィグルシーケンスとなっている最長の部分列(サブシーケンス)の長さを求めます。部分列とは、元の数列からいくつかの要素(0個の場合も含む)を削除し、残りの要素を元の順序のまま保ったものです。
例えば入力が [1,7,4,9,2,5] の場合、数列全体がすでにウィグルシーケンスであるため、出力は6になります。
解法のアプローチ
この問題は動的計画法(DP)の考え方を使うことで、線形時間で効率的に解けます。手順は以下の通りです。
- n を nums のサイズとします。
- n が 0 の場合は 0 を返します。
- up := 1、down := 1 と初期化します。up は「直前の差が正(上昇)で終わる最長ウィグル部分列」の長さ、down は「直前の差が負(下降)で終わる最長ウィグル部分列」の長さを表します。
- i を 1 から n-1 まで繰り返します。
- nums[i] > nums[i-1] の場合は up := down + 1 と更新します。
- nums[i] < nums[i-1] の場合は down := up + 1 と更新します。
- 最後に up と down の大きい方を返します。
このアルゴリズムの時間計算量は O(n)、追加のメモリ使用量は O(1) と非常に効率的です。等しい要素が続く場合は状態を更新しないため、重複値も正しく処理できます。
C++での実装例
以下の実装を見ると、理解がより深まるでしょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if(!n) return 0;
int up = 1;
int down = 1;
for(int i = 1; i < n; i++){
if(nums[i] > nums[i - 1]){
up = down + 1;
}
else if(nums[i] < nums[i - 1]){
down = up + 1;
}
}
return max(up, down);
}
};
main(){
Solution ob;
vector<int> v = {1,7,4,9,2,5};
cout << (ob.wiggleMaxLength(v));
}入力
[1,7,4,9,2,5]
出力
6
-
C++でk番目の順列シーケンスを効率的に求める方法
問題の概要 集合 [1, 2, 3, ..., n] には、合計 n! 通りの異なる順列が存在します。すべての順列を辞書順に並べてラベルを付けると、n = 3 の場合は次のシーケンスが得られます。 [123, 132, 213, 231, 312, 321] このとき、n と k が与えられた場合、k 番目の順列シーケンスを返すのが本問題の目的です。制約として、n は 1 以上 9 以下、k は 1 以上 n! 以下の範囲にあります。 アルゴリズムの考え方 すべての順列を生成して k 番目を探す方法は非効率です。そこで、階乗の性質を利用したアプローチを用います。 先頭の桁にどの数字を置くかを決
-
【C++】アリコット数列の求め方と実装例をわかりやすく解説
アリコット数列とは アリコット数列(Aliquot Sequence)は、特殊な性質をもった数列です。数列はある整数から始まり、次の項は直前の項の真の約数(その数自身を除く約数)の総和として定義されます。 具体的な例で確認してみましょう。 入力 : 8 出力 : 8 7 1 0 解説 : 8 の真の約数は 4, 2, 1。その和は 7 7 の真の約数は 1。その和は 1 1 の真の約数は存在しないため、その和は 0 完全数・友愛数・社交数との関係 アリコット数列は、以下の3種類の特別な数と深い関わりがあります。 完全数:数列の長さが1(自分自身に戻る)となる数。例:6