16bit!

エンジニアじゃなくなっちゃった人が何かを書くブログ

エンジニアからコンサルタントにクラスチェンジしてました

しばらく振りに記事を書きます。
というのも、2015年の夏頃にエンジニアとして働いていたとある企業を退職したため、夏以降はプログラミング的なことに関するキャッチアップが皆無になり、記事書くネタがなかったのです。
ただ、せっかく転職したので、転職に際して考えたこととかを日記がてら記事に残しておくことにします。

転職理由について

「転職するよ」って話を同僚や知り合いにした時に一番聞かれたのはやっぱり退職理由だったので、とりあえず書ける範囲で書いておきます。

<個人的要因>
・比較的大きいプロジェクトがカットオーバーして、個人的にキリが良いタイミングだったので
・もともとプログラミングはMAX5年間くらい学んで、その後は開発もある程度分かった上でそれを活かせる別の仕事をするつもりだったので(学生の頃から情報学んでたわけじゃないのもあって、プログラミングで一流になれるとは思っていなかった)
・前職の社内での職種転換等も考えたが、それよりも会社自体を変えてしまった方が自分にとって良い経験になると思ったので
・技術をキャッチアップし続けるのも正直キツくなってくるだろうなと思っていたので
・学生の延長みたいな自由な仕事の仕方ばかりやってきていて、そろそろまともな社会人にならなきゃいけないのでは?と感じていたので

<社内要因>
・会社自体が戦略も含めて丁度過渡期で、かつ今後のビジョンにあまり共感できなかったので
・社内でキャリアアップするにしてもマネジメント的なことをやるようになっていくしかなかったが、それなら別のところでやる方が良いと考えたので

<外的要因>
アベノミクスの影響で中途採用の市場が活況だったので

転職先について

転職先もIT系で、しかも前職と同じエンタープライズの領域です。
ITエンジニアからITコンサルタントへのクラスチェンジなので、まぁ業界的にはよくある話というか、最も普通な転職の1つではないかと。
SHIROBAKOで言うなら絵麻ちゃんからみゃーもりへのクラスチェンジみたいな感じですね。

そこを選んだ理由としては、そもそもが前職も含め仕事に不満があったというわけではなく、せっかくならこれまでの経験を活かせるところが良いなぁと思ったから。
あと正直前職ではエンジニアとして会社に守られすぎているなぁと感じていたので、矢面に立たされるような職種になって、強化外骨格を身につけたいなぁと思っていたのもあります。
ざっくり言うと、IT業界で行きていく上で、プログラミング以外のことを身につけたいなぁというのがあったのが理由でしょうか。

プログラミングについて

就職するまでプログラミングをほとんどやったことがなかった自分ですが、エンジニアを最初の仕事にできたことは本当に良かったなぁと思います。
初めて自分の書いたコードが思った通りに動いた時は感動しましたし、メンテナンス性を意識してコードを書くことで生産性とは何なのかを学ぶこともできました。
もちろんプログラムがなぜ動くのかということや、ソースコードを読み書きできる能力を身につけたことは今後の仕事でも役に立つと思いますし。

あと、綺麗なクラス設計をできる人は組織設計も上手い気がします。
クラス設計すらうまくできない人間に組織設計なんかできないんじゃないの?と思うくらいですし、そういう「目的にあわせて綺麗に設計する」というのは今後のマネジメントとかでも役に立つんじゃないかなと何となく思ってます。
まぁクラスと人とは違うので、オブジェクト指向で人の役割決めるだけではうまく行かないと思いますが。

なお、おそらく今後の仕事人生においては自分でコーディングをすることはほぼなくなると思われますが、趣味としてはこれからもちょくちょくプログラミングは続けていくつもりですし、そういう意味で一生遊べる趣味を得られたのもすごく大きかったです。
BaaSを代表とするいろんなサービスのおかげで、趣味レベルで作れるプログラミングでできる範囲がどんどん広がっていますし。

今後について

現職も主にBtoBのIT系のお仕事なので、自分でコーディングをしなくなった以外は正直大きな違いはないのですが、少なくとも数年間はここで学ぼうかと思っています。
再度転職するかどうかは今の時点では何とも言えませんが、個人的にITが好きだし、ITは変革のための強力なツールだと思っているので、ITについて自分でプログラミングすること以外の色んなことを学びつつ、ITから離れることなく、色んなことができるようになればいいなと。

当ブログについて

もともとはプログラミングやら何やらのメモを残して共有するために始めたブログなので、正直今後ブログに何を書いていこうかよくわからなくなっています。
読んだ本の感想とかは最近は読書メーターの方に書くようにしていますし、最近興味のある「読んだニュース記事の言語化」に関しては、とりあえずNewsPicksでやっていこうかなと思っていますので、実際書くことはないんですよね。

現状では、とりあえずブログとしては残しておいて、書きたいことが出てきた時にゆるゆると書くことにしようかと思っています。


以上。

【乞食】Amazonがアプリストアのクーポン2,000円分をくれたので購入したアプリを晒す

ちょっと前に一部で話題になってましたが、Amazonのアプリストアがあんまり盛況じゃない*1のを何とかしたいのか、それとも野良アプリで個人情報を吸い上げたいのかそのあたりの理由は知りませんが、AmazonさんがAmazonのアプリストア内でのみ利用できる2,000円分のクーポンを色んな人に配ってくれました。
配布対象やクーポン残高の確認方法などは以下にリンク貼っておきますので、知りたい人はご確認ください。

Amazonが「Amazon Androidアプリストア」で使える2,000円分のクーポンを配布中 | オクトバ

なお、有効期限が10月末ということで、とりあえず今日適当にストアを見ながらジャケ買いみたいな感じで2,000円分アプリを買いましたので、参考になるかどうかわかりませんが載せておきます。

Monument Valley

Monument Valley

Monument Valley

  • ustwo Games ltd
Amazon

オススメ度:★★★
これは昨年話題になっていたゲームで、「最も美しいモバイルゲーム」とか言われていました。
エッシャーの騙し絵みたいな世界を謎解きしながら進んでいくゲームで、当時も買おうかどうか迷ったのですが、なんか暇がなくて買わなかったんだと思います。で、今回クーポン貰ったので買いました。

実際プレイしてみるとほんとに美しくて、それはもうグラフィックからストーリー、ゲームルール、世界観に至るまで全てが美しかったです。
難易度もちょうど良いくらいで、オススメです。
価格は400円ですが、追加ステージが200円であるので、それも合わせると600円になります。

LYNE

LYNE

LYNE

  • Thomas Bowker
Amazon

オススメ度:★☆☆
LINEじゃないよ。パズルゲームです。
同じ図形同士を線で繋ぐ、"Flow Free"の図形版みたいな感じです。
操作も簡単だし、それなりに面白いので、無料とか今回みたいにタダでもらったクーポンで買うとかなら全然アリなんですが、定価だと271円するのでお金出して買うのはちょっとアホらしいかとも思います。DotsやFlow Freeは無料ですしね。

Cosmophony

Cosmophony

Cosmophony

  • BentoStudio
Amazon

オススメ度:★★☆
簡単に言うと音楽がカッコいいシューティング(というか避けゲー)なのですが、ストーリーもちゃんとあって、グラフィックも綺麗でクオリティ高いです。
ちなみに難易度も結構高いです。
練習モードは死んでも途中から復帰できるのですが、ノーマルモードは死んだら最初からなので、結構大変です。
でも面白いと思います。

アニメスタジオ物語

オススメ度:★☆☆
懐かしのカイロソフト。他にもゲーム会社やら温泉観光地やら色々なテーマでよく似たシミュレーションゲームを出していますが、アニメスタジオを選んだのはSHIROBAKOが神アニメだからです。
昔PCでゲーム会社のシミュレーションの方をやったことあるのですが、基本的には変わりません。
個人的にはカイロソフトのゲームの面白さは会社を大きくすることではなく、社員の名前などの小ネタを楽しむところにあると思っています。
値段がまぁまぁ高いのでオススメ度は低め。

Backflip Madness

Backflip Madness

Backflip Madness

  • Gamesoul Studio
Amazon

オススメ度:★★☆
糞アプリかと思ったら意外と中毒性あってハマりそう。
基本的にはおっさんがバク宙して成功すればクリア、できなければ骨折などの重傷を負うだけのゲームなのですが、バク宙の仕方も姿勢を3段階で操作するのか2段階でするのか、伸身なのかくの字なのかみたいな微妙な切り替えができたりして細かいし、何よりおっさんが怪我する時の描写がシュールです。
安いのでオススメ度は高め。

ねんしょう!

[asin:B009FLCMCY:detail]

オススメ度:★☆☆
筋トレを習慣付けるための補助アプリ。
美少女が筋トレを手伝ってくれているという体で、筋トレを頑張るとポイントが溜まってエピソードが解放されていく模様。(まだ初日なのでほとんど進んでないです)
ちなみに189円で購入した初期段階でできるのは腹筋のみ。
今後進んでいったら他のもできるのか、それとも他のはアドオンで追加購入なのかは調べてないので知りませんが、まぁとりあえず腹筋だけでもできれば最悪OKです。


以上ここまでが実際に買ったもの。
以下は買おうかどうか迷ったもの等。

MOUNTAIN

[asin:B00MKKT720:detail]

山が生まれてから消えるまでの一生をただ眺めるだけのアプリ。
生き物が生まれて育って死んだり、火事になったり隕石が落ちてきたりするらしい。
面白そうだったんですが、PlayStoreの方のレビューにXPeriaだと落ちるみたいなのが散見されたので、XPeriaユーザーの僕としては買えませんでした。

Goat Simulator

[asin:B00NWLIW0E:detail]

プレイヤーがヤギになって好き勝手に暴れるというゲーム(これはMMOなのか?)です。
商品説明に「もはや、ヤギになる空想をする必要がありません。」と書いてありますが、そんな空想をしたことはありません。
これも面白そうだったんですが、予算の都合で駄目でした。

88星座図鑑

やけに評価が良い星座図鑑。天体好きの人は大抵入れてるイメージがあります。鉄板なんでしょうか?
安いので入れてもいいかなぁと思っていたのですが、意外とぴったり2,000円くらいになったので、滑り込む隙間がありませんでした。

CHAOS RINGS II

[asin:B00B23HKLQ:detail]

1,400円するので、これとMonument Valley買ったらちょうど2,000円です。
ちなみにAmazonのアプリストアにはファイナルファンタジー系はまぁまぁ*2ありますが、ドラクエ系はありません。
あ、あとクロノトリガーもありません。


以上です。
良かったらAmazonアプリストアで使える2,000円クーポンの使い道の参考にしてみてください。
とりあえずMonument Valleyはオススメです。

おわり。

*1:だってGoogleにPlayStoreあるもん

*2:タクティクスは無かった

【雑記】3乗して下3桁が999になる自然数を求める問題

最近Twitterを見ていたら、「超難問コロシアム」とかいう数学の問題が広告で出てきてやがったので、ちょっと解いてみました。
ただ普通に解くだけじゃ面白くないので、せっかくだから僕が頭を使って自力で解くのと、プログラム書いて解くのとでどちらが早いかを比べてみました。

http://www.zkai.co.jp/z1/www.zkai.co.jp

解いた問題はこちら

自然数のうち、3乗すると下3桁が999になるものを1つ挙げよ。

問題の時点で結果が見えている気がしますが、とりあえずやってみます。

自力編

まず下3桁の話なので、1000の位より上はどうでも良い。
ということで自然数n=100a+10b+c とする。

3乗して下1桁が9になるには、(c, n^2の下1桁)の組み合わせは(1,9),(3,3),(9,1)の3通りしかないが、cが1ならn^2の下1桁も必ず1になるので(1,9)はあり得ないし、cが3ならn^2の下1桁は9になるはずなのでこれもダメ。
ということでc=9で、ついでにいうとn^2の下1桁は1ということがわかる。

このc=9を当てはめて、n^2の10の位をかけ算の筆算っぽく求めてみると、9b+8+9b=18b+8 の下1桁が、n^2の10の位ということになる。
これを仮にb2と置くと、(b,b2)の組み合わせは(1,6),(2,4),(3,2),(4,0),(5,8),(6,6),(7,4),(8,2),(9,0),(0,8)の10通りのいずれかになる。

さらにb2を使ってn^3の10の位を筆算っぽく求めてみると、
9b2+b の下1桁が9 ということになり、上記の10通りの中からこの条件を満たすのは(b,b2)=(9,0)の時のみであることがわかる。
したがってb=9。

この辺でなんとなく答えは読めてきたけど、ちゃんと解く。
n=100a+99なので、n^3=(100a+99)^2 *(100a+99)
1000の位より上はどうでも良いので、それらを省きながら計算すると、9801(300a+99)。
これの100の位はと言うと、9801*99の百の位は2なので(これはがんばって計算した)、つまり3a+2の下1桁が9ということになる。
こんなaは9しかない。
したがって答えは999。回答にかかった時間は約15分

プログラム編
public class Main {
	public static void main(String args[]) throws Exception {
		int n = 0;
		while (true) {
			n++;
			
			if ((n * n * n) % 1000 == 999) {
				break;
			}
			if (n > 10000) {
				break;
			}
		}
		System.out.println(n);
	}
}

実行結果は999。コーディングならびにプログラムが回答をコンソールに吐くまでの時間は2分

結果

予想してた通りですが、プログラム書く方が圧倒的に早かったです。


おわり。

【雑記】【ネタバレ】心が叫びたがってるんだ。

心が叫びたがってるんだ。』を観てきたんですが、非常に良くて最高でただいま心が叫びたがってるので、感想を書きます。
勢いで書いているので全然まとまってませんが、参考程度に見ていただければ。
多少ネタバレもあります。

アニメ映画『心が叫びたがってるんだ。』

「言葉」の重さを伝える話

全体としてのテーマは「言葉」。

劇中のセリフにも直球で出てきますが、「言葉は人を傷つける(順)」けど、言葉で人は救われもする。
主人公の順は幼少期のトラウマで喋ることができないけど、別に順だけじゃなくて、誰だって言葉で人を傷つけるし、言葉で救われます。
ただ同時に「言わなきゃわかんないよな(拓実)」というセリフにもあるように、それでも言葉にしなきゃダメなんですよね。

ちなみに書いてて思ったんですが、順のトラウマが素直に喋りすぎたことに起因しているのに対して、拓実と菜月のすれ違いは素直に話せなかったことに起因しているという対比があって面白いです。
素直な言葉もそうじゃない言葉も、どちらも人を傷つけるということ。

そしてこの「素直」っていうのも結構大事な要素で、順も拓実も菜月も大樹も、みんなそれぞれ素直じゃありません。
というか、順以外の3人は喋ってはいるけど、心では喋っていない、口で喋っているだけです。
心が叫びたがっているのは、みんな同じ。
順はただそれがちょっと他の人よりわかりやすく表面に表れているだけなんですよね。

いずれにせよ、全体としてのメッセージがすごくシンプルで、言われてみれば当然のことなんですが、物語の途中まではずっと伏線や暗喩に留めておいて、最後にセリフとして表現させるっていうやり方は個人的に結構好きです。
シンプルなメッセージが、重みをもって伝わるので。

ハッピーエンドじゃないよ

物語は、地域ふれあい交流会で行うクラス劇の制作と本番を軸に進むのですが、
この劇中劇は、順自身の物語をモデルに順自身が脚本を書いているため、映画のストーリーの暗喩としてすごく機能しています。

当初、順は物語をサッドエンドにする予定でした。
ヒロインは死んでしまい、死んでしまった後になって心の叫び声が溢れてくる。
王子に「愛してる」と伝える頃にはもう遅い。
ですが途中で、順は結末をハッピーエンドに変えようとします。
ヒロインは順で、王子は拓実で、結末はハッピーエンド。
ここに順の意思のようなものも感じますが、ともあれ拓実はサッドエンドもハッピーエンドも、どちらも順の伝えたいこととして、両方とも取り入れたようなラストにします。

映画自体は、順は声を取り戻すし、母親との確執もなくなるし、拓実と菜月はよりを戻すし、順と大樹はくっつきそうだしで、なんとなくハッピーエンド感がありますが、個人的にはハッピーエンドではないよなと思います。
順は精神的にものすごく子どもだし、声を取り戻したところで、幼少期にトラウマが発生した頃と何が違うのか、声を取り戻せばハッピーエンドなのかというと、そうではないはずです。
劇中劇のラストで、取り戻した声で歌うのがハッピーエンド用の曲、心の声が歌うのがサッドエンド用の曲というのが、その辺を表しているのかなぁと。
ただ、「自分のおしゃべりのせいにしないとどうしていいのかわからない」と言っていた順が現実と向き合ったことで、ようやく前に進めるというのはあると思いますが。

割とリアル

映画の冒頭はラブホのシーンです。
幼少期の順が「お城」として憧れていた世界ですが、このラブホが映画全体を通してちょいちょいメタファーとして出てきて、しかも割とリアルです。
例えば劇中劇の、「そのお城が罪を犯した人を踊らせ続けている場所だと知ってもなお、少女はお城に行きたいという思いを止められない」という脚本なんかは、お城がラブホから来ていることを考えるとものすごく生々しい話だなぁとも思いますし、劇の当日に順が逃げ込んで、追いかけてきた拓実とぶつかり合うシーンでは、背景にガラス張りのシャワールームがわざわざ描かれていたりして、そこがラブホであることを強調してきます。
それ以外にも、チア部の誰かと野球部の誰かが校内の物陰でキスしているシーンがあるんですが、セリフも音も描写もプリヤ並にリアルだったり、野球部の先輩後輩の確執や順の母親が保険の営業で苦労しているあたりなんかも、サブストーリーでありながらやけにリアリティのある表現だなぁと。

テーマ自体が普遍的なものなので、作品全体にリアリティを出すための表現は結構多かったと思います。

『あの花』ではないよ

ここまでの文章でわかると思いますが、本作はテーマもストーリーも世界観も、「あの花」とは全く異なります
リアルだし、魔法の玉子なんて無いし、メッセージも現実的だし。

感想の速報に「true tearsに似ている」という意見があったのですが、話自体は確かにそうかもしれません。
順は声が出せなくて、乃絵は涙を流せない。
菜月も比呂美も優等生で、主人公に好意を寄せているけど、それを表現しない。
テーマは違いますが、3人の関係のドロドロ感とか、話の雰囲気は似ていると思います。
アニメばりばりではなくて、普通の青春恋愛映画をアニメで表現している感じですかね。
「あの花」ファンがアニメと同じ雰囲気を期待して観ると、確実に肩透かしを喰らうと思います。

音楽

最後に音楽について。

劇中劇はミュージカルなので、作中にも歌がちょくちょく挟まります。
一番の見所はやっぱり劇のラストで、ベートーベンの「悲愴」とオズの魔法使いの「Over the Rainbow」にそれぞれ歌詞を付けてマッシュアップみたいにして歌うシーンがあるのですが、それがもう最高でした。
この曲のためだけにサントラが欲しいレベル。

なお、エンドロールがなぜか乃木坂なんですが、これは本当になんでなんだろう?
電通の仕業としか言いようがないのか。
悲愴とOver the Rainbowでいいやん。

まとめ

やっぱり長井龍雪と岡田マリーはすごいなぁと思います。
まぁ脚本は今回王道と言えば王道なんですけど。

声優さんも、メイン4人は割とバッチリでした。
雨宮さんは順役のオーディションも受けたとかいう話でしたが、まぁ菜月だよなぁと思います。

あと思ったのは、1クールとかでアニメをやった後に作られた劇場版と、最初から2時間の映画として作られたものとはやっぱり全然違うなと。
この作品を1クールとかのアニメにしちゃうと、キャラを立てるのが大事になって一番重要なメッセージがぼやけてしまうと思うので、せっかく普遍的でアニメファン以外にも響くテーマなんだから、映画という形で、しかもバリバリのアニメ作品としてではなく、一般にも受け入れられるような作品として作ったのはすごく良いと思います。
できれば普段アニメ映画を観ないという人や、ジブリ細田守くらいしか観ないという方にも観てほしいなと思いました。


おわり。

【アルゴリズム】CodeIQの「ホリエモンからの挑戦状」を解いて1,200円貰いました

気付けば1ヶ月以上もブログを放置していました。
というわけで、ちょっと前にCodeIQでやっていた、「ホリエモンからの挑戦状」というキャンペーン問題を解いて1,200円くらいもらった話をリハビリがてら書きます。

https://codeiq.jp/lp/horiemon/

と言っても企画自体は7月頭くらいだったので、もはやほとんど忘れてるんですが、2ヶ月前に自分が書いたコードを思い出しながら頑張って書きたいと思います。

問題

問題は短い棒を3本繋いで決まった長さの棒を作る場合の組み合わせパターン数を求めろというものだったのですが、単純化すると以下のようになります。

自然数Lと、N個(1≦N≦5000)の自然数とが入力値として与えられる。
N個の自然数のうち3つを選び、その和がLになるような組み合わせはいくつあるかを求めるプログラムを書け。
なお、N個の自然数の大きさは全て異なるとする。

回答晒し

で、例のごとく回答を晒します。ちょっと長いですね。

public class Main {
	
	List<Long> list = new ArrayList<Long>();
	int startIdx;
	int endIdx;
	
	public static void main(String args[]) throws IOException {
		Main self = new Main();
		self.execute();
	}
	
	private void execute() throws IOException {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
        
		final long L = Long.parseLong(br.readLine());
		final int N = Integer.parseInt(br.readLine());
       	
		//とりあえずリスト作成
		for (int i=0; i < N; i++) {
			list.add((long) Long.parseLong(br.readLine()));
 		}
		//数の大きい順にソートしておく
		Collections.sort(list);
		Collections.reverse(list);
        
		//ここがメイン
		try {
			long ans = 0;
			for (int i=0; i<N-2; i++) {
				//i番目の要素を1つ目の要素として選択した場合の合計
				if (isLengthTooMuch(i+1, L-list.get(i), 2)) {
					break;
				}
				ans += solve2(i+1, L-list.get(i), 2);
			}
			System.out.println(ans);
		} catch (Error e) {
			System.out.println("cause=" + e.getCause());
			System.out.println("class=" + e.getClass());
			System.out.println("message=" + e.getLocalizedMessage());
		}
	}
	
	private long solve2(int idx, long length, int num) {
		long res = 0;
		
		if (!isLengthEnough(length, num)) {
			return 0;
		}
		
		//探索用endIdxの初期化
		endIdx = list.size()-1;
		startIdx = idx;
		
		for (int j=idx; j<list.size()-1; j++) {
			if (startIdx < j+1) {
				startIdx = j+1;
			}
			if (containsAfter(j+1, length-list.get(j))) {
				res++;
			}
		}
		return res;
	}
	
	private boolean containsAfter(int idx, long length) {
		if (length <= 0) {
			return false;
		}
		if (list.get(idx) < length) {
			return false;
		}
		if (list.get(list.size()-1) > length) {
			return false;
		}
		
		while (true) {
			if (Math.abs(startIdx - idx) <= 2) {
				startIdx = idx;
				break;
			} else if (list.get(startIdx) < length) {
				startIdx = startIdx-10;
			} else {
				break;
			}
		}
		
		while (true) {
			if (Math.abs(endIdx - startIdx) <= 2) {
				break;
			}
			
			int halfIdx = endIdx - Math.round((endIdx - startIdx)/2);
			long halfVal = list.get(halfIdx);
			if (halfVal == length) {
				return true;
			} else if (halfVal > length) {
				startIdx = halfIdx;
			} else {
				endIdx = halfIdx;
			}
		}
		
		for (int i = startIdx; i <= endIdx; i++) {
			if (list.get(i) < length) {
				break;
			}
			if (list.get(i).longValue() == length) {
				return true;
			}
		}
		return false;
	}

	private boolean isLengthEnough(long length, int num) {
		if (num == 1) {
			return (length >= list.get(list.size() - 1));
		} else if (num == 2) {
			return (length >= list.get(list.size() - 1) 
					+ list.get(list.size() - 2));
		} else {
			return (length >= list.get(list.size() - 1) 
					+ list.get(list.size() - 2)
					+ list.get(list.size() - 3));
		}
	}
	
	private boolean isLengthTooMuch(int idx, long length, int num) {
		if (num == 1) {
			return (length > list.get(idx));
		} else if (num == 2) {
			return (length > list.get(idx) + list.get(idx+1));
		} else {
			return (length > list.get(idx) 
					+ list.get(idx+1)
					+ list.get(idx+2));
		}
	}
}

ポイントとしては、まず最初にN個の数字を大きい順にソートしておく。
これによって、ループ処理の過程で選ばれていく組み合わせは必ず大きいものから順に並ぶことになるし、i+1番目以降の数値を使って作成できる最大の和は、最初に取れるr個の数値の和になる
これを利用して無駄な処理を省いているのがisLengthTooMuchを呼んでいる部分で、最初の数値としてi番目のものを選んだとして、i番目とi+1番目とi+2番目の和がLに届かないなら、それ以降のループは回す必要がないよね、ということ。
これで1重目のループを回す回数を抑えます。

で、最初に選ぶのがi番目の数値ということが決まったら、次はsolve2というメソッドで、残りの要素を探す。
ここではまずisLengthEnoughというメソッドを呼んで、i番目を選んだ時点で、残りの和を最小となるような組み合わせで選んだとしてもLを超えてしまうようになっていないかどうかを事前に調査する。
リストは大きい順に並んでいるので、リストの末尾の要素2個の和がi+1番目以降の要素2つで作れる和の最小となる。
どう頑張ってもLを超えてしまうような場合はこの時点で次のループへ。
そうでない場合は、2つ目の要素としてj番目の数値を選んだとして、大きさが「L - (i番目の数値) - (j番目の数値)」の数値がj+1番目以降に存在するかどうかを調べる。
存在すれば組み合わせパターンを+1する。

で、その「大きさが「L - (i番目の数値) - (j番目の数値)」の数値がj+1番目以降に存在するかどうかを調べる」ためのメソッドがcontainsAfterなわけですが、ここでもソートしてあることが効いている。
まずi番目とj番目を選んだ時点でLを超えているようなケースはスキップするとして、次にsolve2と同様に、j+1番目以降の最大の数値(要するにj+1番目の数値)が残りの必要な長さに達していなかったスキップだし、最小の数値(要するに最後の要素)が残りの長さよりも長くてもスキップする。

で、containsAfterはさらにちょっとクセのあることをやっていて、それがwhile(true)でループしている2カ所の部分。
何をやっているかというと、擬似的な2分探索をやることで、残りの長さlengthが存在するとしたら、startIdx〜endIdxの間にしかあり得ないということを絞り込んで、実際にループを回して探す回数を抑えている。(2個目のwhile(true))
これもリストがソートされているからできること。
しかも、このstartIdxは毎回初期化されるわけではなく、「i番目とj番目を選んだ時のstartIdxがkだったなら、i番目とj+1番目を選んだ場合のstartIdxもkと同じか、それよりちょっと手前くらいだろ」という推測のもと、2分探索自体の試行回数も少なくなるよう工夫してある。(1個目のwhile(true))

こういったアルゴリズムで、一応合格レベルの速度は満たすことができた。
総額100万円を合格者の数で山分けという話だったので、1,200円貰えたということは合格者は800人くらいということですね。
いずれにせよ、こういう趣味プログラミングで問題解くだけでおこづかい貰えるのはハッピー。
時給換算すると800円くらいなのでちょっと辛いですが、まぁ趣味だし。数独解いてお小遣い貰えるみたいな感じだし。

おまけ

メソッド名がsolve2とかになっているのは、当然もともとはsolveがあったから。
最初はsolveを「idx番目以降の要素をnum個使って、合計lengthとなる組み合わせの数を返す」というメソッドとして定義し、再起的に呼び出すことでコーディングの省力化を図っていたのですが、上記の通り最後の1つを探す時には2分探索した方が良いとか色々あってできませんでした。
isLengthTooMuchやisLengthEnoughが無駄に汎用的に書かれているのもその名残です。
なお、C++とかの高速言語ならもしかしたら再起呼び出しのもともとのソースでも合格できたかもしれません。
そしたら時給1,000円超えるね。


以上、おわり。