Second Largest Number
整数のリスト nums が与えられます。2 番目に大きい異なる値、つまり最大値より厳密に小さい最大の値を返してください。値は重複することがあるため、[5, 5, 3] の場合、答えは 5 ではなく 3 です。リストには常に異なる値が少なくとも 2 つ含まれています。
関数
- numsinteger-array
- 少なくとも2つの異なる値を含む整数のリスト
- 戻り値integer
- 最大値より小さい値の中で最も大きい値
制約
2 ≤ nums.length ≤ 5000-109 ≤ nums[i] ≤ 109numsには、少なくとも2つの異なる値が含まれています。
例
- 入力
- nums = [4, 9, 2, 7, 9]
- 出力
- 7
- 説明
- 最大値は
9です。これは2回出現しますが、最大値の2つ目は数えないため、答えは次に大きい値の7です。
- 入力
- nums = [-5, -1, -8]
- 出力
- -5
- 説明
- 値を大きい順に並べると、
-1、-5、-8です。2番目に大きい値は、負の数ですが-5です。
- 入力
- nums = [6, 6, 6, 3]
- 出力
- 3
- 説明
- 異なる値は
6と3の2つだけです。6が何度繰り返されても、2番目に大きい値は3です。
提出時に隠しテスト+15件
発展問題
ソートせずに、3つの変数を使って1回の走査で3番目に大きい異なる値を返せますか?
ヒント
1つずつ開いてください。開くたびに少しずつ答えに近づきます。
最大値を見つけるには変数が1つ必要です。リストを読み進めるとき、2つ目の変数があれば何を覚えておけるでしょうか?
最大値と2番目に大きい異なる値を追跡します。新しい値は、最大値を上回るか、2つの値の間に入るか、何も変えない可能性があります。
両方の変数を、以下のすべての許容値で初期化します。
x > largestの場合は、largestをsecondに移し、xを格納します。そうでなく、xがそれらの値の間に厳密にある場合は、secondに格納します。
解説
2つの点が、最大値を見つけるよりも難しくしています。最大値は複数回現れることがあり、その重複値を2番目に大きい値として報告してはいけません。答えが負の値になることもあるため、0から始まる変数では、すべての要素が負のリストに対して誤った答えになります。厳密な比較を使って、1回の走査で異なる上位2つの値を追跡すれば、どちらの問題にも対処できます。
最大値で並べ替えて、その先まで進む
考え方
コピーを小さい順に並べ替えます。最大値は末尾にあり、連続して複数回現れることもあります。末尾から左へ向かって最大値と同じ値をすべて通り過ぎると、最初に見つかる異なる値が2番目に大きい値です。[6, 6, 6, 3]の場合、並べ替えたコピーは[3, 6, 6, 6]です。6を3つ通り過ぎて、3にたどり着きます。
ここでよくある間違いは、後ろから2番目の要素を返すことです。[4, 9, 2, 7, 9]の場合、それでは最大値と同じ9が返されます。リストには少なくとも2つの異なる値が含まれているため、走査が先頭を通り越すことはありません。
答えは正しいものの、上位2つだけが必要なのに、並べ替えではすべての値を順序付けます。時間計算量はO(n log n)で、コピーにはO(n)のメモリが必要です。
アルゴリズム
numsをコピーし、そのコピーを小さい順に並べ替えます。- インデックス
iを最後の位置に設定します。 iの位置の値が最大値と等しい間、iを1つ左に移動します。iの位置の値を返します。
def secondLargest(nums):
ordered = sorted(nums)
i = len(ordered) - 1
# Step left past every copy of the maximum.
while ordered[i] == ordered[-1]:
i -= 1
return ordered[i]2回のパス
考え方
処理を2回に分けます。1回目の走査では、「最大の数を見つける」と同じように最大値を見つけます。2回目の走査では、その最大値より厳密に小さい値のうち、最も大きいものを探します。[4, 9, 2, 7, 9]の場合、1回目の走査で9が見つかり、2回目の走査では2つの9を飛ばして、4、2、7の中で最も大きい7を保持します。
secondは、リストに入る可能性のあるすべての値より小さい値(たとえば、その言語で扱える最小の整数)で初期化します。リストには少なくとも2つの異なる値があるため、最大値より小さい値があり、必ずその初期値が置き換えられます。
それぞれの走査では最大値を更新していくため、全体の計算量は時間がO(n)、空間がO(1)です。ただし、リストを2回読み取る必要があります。そのため、値が1つずつ届き、読み取った後に失われる場合には、この方法は使えません。
アルゴリズム
numsを一度ループして、最大値をlargestに格納します。secondを、許可されるすべての値より小さい値に設定します。- もう一度ループします。
x < largestかつx > secondである各xについて、secondをxに設定します。 secondを返します。
def secondLargest(nums):
largest = nums[0]
for x in nums:
if x > largest:
largest = x
second = float("-inf") # below every allowed value
for x in nums:
if x < largest and x > second:
second = x
return second上位 2 つを追跡する 1 回の走査
考え方
これまでに見た値のうち、大きい順に上位2つの異なる値を保持するため、largestとsecondの2つの変数を使います。新しい値xは、次の3つのケースのいずれかに当てはまります。xがlargestより大きければ、以前のlargestが2番目になり、xが最大の値になります。xがsecondとlargestの間に厳密に位置する場合、新しいsecondになります。それ以外の場合は何も変わりません。
重複を処理するのは、厳密な比較です。[4, 9, 2, 7, 9]の場合、largestは4になり、次にsecond = 4として9になります。2では何も変わりません。7は4と9の間にあるため、second = 7となります。最後の9はlargestと等しいため、スキップされます。答えは7です。
両方の変数を、あり得るすべての値より小さい値で初期化します。両方を0で初期化すると、どの値も0を上回らないため、[-5, -1, -8]に対して0が返されます。リストには異なる値が2つ含まれているため、最終的にsecondには必ずリスト内の実際の値が入ります。
アルゴリズム
largestとsecondを、許容されるすべての値より小さく設定します。nums内のすべての値xを順に処理します。x > largestの場合、largestをsecondに移し、largestをxに設定します。- そうでなく、
x < largestかつx > secondの場合、secondをxに設定します。 - ループの後、
secondを返します。
def secondLargest(nums):
# Both start below every allowed value.
largest = second = float("-inf")
for x in nums:
if x > largest:
second = largest # the old maximum drops to second place
largest = x
elif largest > x > second:
second = x
return second
落とし穴と境界ケース
よくある誤答の原因は、最大値の重複か負の値です。
- ソート済みリストの最後から2番目の要素を返す。最大値が重複している場合、
[4, 9, 2, 7, 9]のように、最大値を再び返すことになります。 - 変数を
0で初期化する。[-5, -1, -8]では、どの値も0を上回らないため、リストに含まれていない数値である0を返してしまいます。 - 最初の条件で
x >= largestと書く。2つ目の9によって、最初の9がsecondに入るため、9を返してしまいます。 - 新しい最大値が現れたときだけ
secondを更新する。[10, 20, 15]では、15がsecondに入らず、10を返してしまいます。 - setで重複を取り除いてからソートする。正しく動きますが、1回の走査で済む処理に
O(n)のメモリとO(n log n)の時間を費やします。
よくある質問4
配列内で2番目に大きい数値を、1回の走査でどのように見つけますか?
これまでに確認した最大値と、それと異なる2番目に大きい値を保持します。ある値が最大値を上回ると、以前の最大値が2番目に移ります。ある値がこの2つの値の間に厳密に入ると、2番目の値と置き換わります。1回走査すると、2番目の変数に答えが格納されます。
2番目に大きい要素を見つける時間計算量はどれくらいですか?
1 パス法と 2 パス法はいずれも、O(n) 時間、O(1) 追加領域で実行できます。先にソートすると、O(n log n) 時間がかかります。すべての値を少なくとも 1 回は読み取る必要があるため、O(n) より速くすることはできません。
重複は2番目に大きい要素にどのような影響を与えますか?
この問題では2番目に大きい異なる値を求めるため、最大値の重複は飛ばします。[9, 9, 7]の場合、答えは7です。問題によっては位置を数えるため、答えが9になることもあります。コードを書く前に、どちらの意味か確認してください。
2番目に大きい値がない場合、何を返すべきですか?
ここではそのようなことは起こりません。リストには常に異なる値が2つ含まれています。一般に、[4, 4, 4]のようなリストには答えがなく、-1やnullのようなマーカーを返すか、エラーを発生させます。ループの後もsecondに初期値が入ったままかどうかで、このケースを検出できます。
似た問題
同じ考え方を使う問題です。2〜3問解くとパターンが身につきます。
Python
def secondLargest(nums):
# ここにコードを書いてくださいケース1
ケース2
ケース3
入力
nums = [4, 9, 2, 7, 9]
期待値
7