序文
この問題解決策の Go 言語部分はLeetCode-Go に基づいています。
他の部分は私の実践的な学習に基づいています。
個人的な問題解決策の GitHub リンク: LeetCode-Go-Python-Java-C
Go-Python-Java-C-LeetCode High解決方法 - 1週目収集
Go-Python-Java-C-LeetCode高分解法 - 2週目収集
Go-Python-Java-C-LeetCode高分解法 - 3週目収集
Go-Python-Java-C-LeetCode高分解法- 4週目のコレクション
この記事の一部 オンラインコレクションと個人練習から。情報に誤りが含まれている場合は、読者の皆様は批判や修正を歓迎します。この記事は学習とコミュニケーションのみを目的としており、商業目的ではありません。
29. 2 つの整数の除算
トピック
2 つの整数dividend
と が与えられた場合divisor
、乗算、除算、mod 演算子を使用せずに 2 つの整数を除算します。
dividend
で割った後の商を返しますdivisor
。
整数の除算はゼロに向かって切り捨てる必要があります。
例 1:
Input: dividend = 10, divisor = 3
Output: 3
例 2:
Input: dividend = 7, divisor = -3
Output: -2
注記:
- 被除数と除数は両方とも 32 ビットの符号付き整数になります。
- 約数が 0 になることはありません。
- Assume we are dealing with an environment which could only store integers within the 32-bit signed integer
range: [−2^31, 2^31 − 1]. For the purpose of this problem, assume that your function returns 2^31 − 1 when the
division result overflows.
题目大意
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数
divisor 得到的商。
说明:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1]。本题中,如果除法结果溢出,则返回 2^31 − 1。
解题思路
- 给出除数和被除数,要求计算除法运算以后的商。注意值的取值范围在 [−2^31, 2^31 − 1] 之中。超过范围的都按边界计算。
- 这一题可以用二分搜索来做。要求除法运算之后的商,把商作为要搜索的目标。商的取值范围是 [0, dividend],所以从 0
到被除数之间搜索。利用二分,找到(商 + 1 ) * 除数 > 被除数并且 商 * 除数 ≤ 被除数 或者 (商+1)* 除数 ≥ 被除数并且商 *
除数 < 被除数的时候,就算找到了商,其余情况继续二分即可。最后还要注意符号和题目规定的 Int32 取值范围。 - 二分的写法常写错的 3 点:
- low ≤ high (注意二分循环退出的条件是小于等于)
- 中 = 低 + (高-低)>>1 (オーバーフローを防ぐため)
- low = mid + 1; high = mid - 1 (low と high の値の更新に注意してください。更新が正しくない場合、無限ループが発生します。) 以下は問題の詳細な紹介です
。 - 各バージョンのアイデアを解決:
Goのバージョン
Go バージョンの解決策は次のとおりです。
-
まず、被除数が 0 で除数が 1 であるなどの特殊なケースを処理し、オーバーフロー状況を回避します (たとえば、被除数が math.MinInt32 で除数が -1 など)。
-
最終結果の符号 (正または負) を決定し、ビット単位の演算のために被除数と除数の両方を正の数に変換します。
-
二分探索を使用して商を見つけます。
(x + 1) * divisor > dividend
二分探索の目的は、 andx * divisor ≤ dividend
、 or(x + 1) * divisor ≥ dividend
and となるような整数 x を見つけることですx * divisor < dividend
。この x は、探している商です。 -
二分検索プロセス中は、オーバーフローの可能性がある条件を処理しながら、必ず検索範囲と値を更新してください。
-
最後に、符号と境界の場合に基づいて最終的な商が返されます。
Pythonのバージョン
問題解決アイデアの Python バージョンは次のとおりです。
-
被除数が 0 で除数が 1 であるなどの特殊なケースを処理し、オーバーフロー状況を回避します (例: 被除数が -2^31、除数が -1)。
-
最終結果の符号 (正または負) を決定し、ビット単位の演算のために被除数と除数の両方を正の数に変換します。
-
再帰二分探索法を使用して商を見つけます。
(x + 1) * divisor > dividend
再帰関数の目的は、 andx * divisor ≤ dividend
、または(x + 1) * divisor ≥ dividend
and となるような整数 x を見つけることですx * divisor < dividend
。この x は、探している商です。 -
再帰中は、オーバーフローの可能性がある条件を処理しながら、必ず検索範囲と値を更新してください。
-
最後に、符号と境界の場合に基づいて最終的な商が返されます。
Javaのバージョン
Java 版の解決策は次のとおりです。
-
被除数が 0 で除数が 1 であるなどの特殊なケースを処理し、オーバーフロー状況を回避します (例: 被除数が Integer.MIN_VALUE で除数が -1)。
-
最終結果の符号 (正または負) を決定し、ビット単位の演算のために被除数と除数の両方を正の数に変換します。
-
再帰二分探索法を使用して商を見つけます。
(x + 1) * divisor > dividend
再帰関数の目的は、 andx * divisor ≤ dividend
、または(x + 1) * divisor ≥ dividend
and となるような整数 x を見つけることですx * divisor < dividend
。この x は、探している商です。 -
再帰中は、オーバーフローの可能性がある条件を処理しながら、必ず検索範囲と値を更新してください。
-
最後に、符号と境界の場合に基づいて最終的な商が返されます。
C++バージョン
C++ バージョンの解決策は次のとおりです。
-
被除数が 0、除数が 1 などの特殊なケースを処理し、オーバーフロー状況を回避します (例: 被除数が INT_MIN、除数が -1)。
-
最終結果の符号 (正または負) を決定し、ビット単位の演算のために被除数と除数の両方を正の数に変換します。
-
再帰二分探索法を使用して商を見つけます。
(x + 1) * divisor > dividend
再帰関数の目的は、 andx * divisor ≤ dividend
、または(x + 1) * divisor ≥ dividend
and となるような整数 x を見つけることですx * divisor < dividend
。この x は、探している商です。 -
再帰中は、オーバーフローの可能性がある条件を処理しながら、必ず検索範囲と値を更新してください。
-
最後に、符号と境界の場合に基づいて最終的な商が返されます。
つまり、これらのソリューションの中心となるアイデアは、特殊なケースやオーバーフロー状況を処理して最終的な計算結果が正しいことを確認しながら、二分探索を使用して商を見つけることです。再帰関数は、検索プロセスの実装で広く使用されています。
コード
行く
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// 解法一 递归版的二分搜索
func divide(dividend int, divisor int) int {
sign, res := -1, 0
// low, high := 0, abs(dividend)
if dividend == 0 {
return 0
}
if divisor == 1 {
return dividend
}
if dividend == math.MinInt32 && divisor == -1 {
return math.MaxInt32
}
if dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 {
sign = 1
}
if dividend > math.MaxInt32 {
dividend = math.MaxInt32
}
// 调用二分搜索函数计算商
res = binarySearchQuotient(0, abs(dividend), abs(divisor), abs(dividend))
// 处理溢出情况
if res > math.MaxInt32 {
return sign * math.MaxInt32
}
if res < math.MinInt32 {
return sign * math.MinInt32
}
return sign * res
}
// 二分搜索函数,用于计算商
func binarySearchQuotient(low, high, val, dividend int) int {
quotient := low + (high-low)>>1
if ((quotient+1)*val > dividend && quotient*val <= dividend) || ((quotient+1)*val >= dividend && quotient*val < dividend) {
if (quotient+1)*val == dividend {
return quotient + 1
}
return quotient
}
if (quotient+1)*val > dividend && quotient*val > dividend {
return binarySearchQuotient(low, quotient-1, val, dividend)
}
if (quotient+1)*val < dividend && quotient*val < dividend {
return binarySearchQuotient(quotient+1, high, val, dividend)
}
return 0
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// 解法二 非递归版的二分搜索
func divide(dividend int, divisor int) int {
if dividend == math.MinInt32 && divisor == -1 {
return math.MaxInt32
}
result := 0
sign := -1
if dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 {
sign = 1
}
dividendAbs, divisorAbs := abs(dividend), abs(divisor)
for dividendAbs >= divisorAbs {
temp := divisorAbs
multiplier := 1
for temp<<1 <= dividendAbs {
temp <<= 1
multiplier <<= 1
}
dividendAbs -= temp
result += multiplier
}
return sign * result
}
パイソン
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
# 递归版
def recursive_divide(dividend, divisor):
if dividend < divisor:
return 0
quotient = 1
div = divisor
while (div + div) <= dividend:
div += div
quotient += quotient
return quotient + recursive_divide(dividend - div, divisor)
if dividend == 0:
return 0
if dividend == -2 ** 31 and divisor == -1:
return 2 ** 31 - 1
sign = 1 if (dividend > 0) == (divisor > 0) else -1
dividend, divisor = abs(dividend), abs(divisor)
result = recursive_divide(dividend, divisor)
return sign * result
class Solution:
# 非递归版
def divide(self, dividend: int, divisor: int) -> int:
if dividend == 0:
return 0
if dividend == -2 ** 31 and divisor == -1:
return 2 ** 31 - 1
sign = 1 if (dividend > 0) == (divisor > 0) else -1
dividend, divisor = abs(dividend), abs(divisor)
result = 0
while dividend >= divisor:
temp, m = divisor, 1
while dividend >= (temp << 1):
temp <<= 1
m <<= 1
dividend -= temp
result += m
return sign * result
ジャワ
class Solution {
public int divide(int dividend, int divisor) {
// 递归版
if (dividend == 0) {
return 0;
}
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
int sign = (dividend > 0) == (divisor > 0) ? 1 : -1;
long dvd = Math.abs((long) dividend);
long dvs = Math.abs((long) divisor);
int result = recursiveDivide(dvd, dvs);
return sign * result;
}
private int recursiveDivide(long dividend, long divisor) {
if (dividend < divisor) {
return 0;
}
int quotient = 1;
long div = divisor;
while ((div + div) <= dividend) {
div += div;
quotient += quotient;
}
return quotient + recursiveDivide(dividend - div, divisor);
}
}
class Solution {
// 非递归版
public int divide(int dividend, int divisor) {
if (dividend == 0) {
return 0;
}
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
int sign = (dividend > 0) == (divisor > 0) ? 1 : -1;
long dvd = Math.abs((long) dividend);
long dvs = Math.abs((long) divisor);
int result = 0;
while (dvd >= dvs) {
long temp = dvs;
int m = 1;
while (dvd >= (temp << 1)) {
temp <<= 1;
m <<= 1;
}
dvd -= temp;
result += m;
}
return sign * result;
}
}
CPP
class Solution {
public:
int divide(int dividend, int divisor) {
// 处理特殊情况
if (dividend == 0) {
return 0;
}
if (divisor == 1) {
return dividend;
}
if (dividend == INT_MIN && divisor == -1) {
return INT_MAX;
}
int sign = (dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0) ? 1 : -1;
// 处理溢出情况
if (dividend > INT_MAX) {
dividend = INT_MAX;
}
return sign * binarySearchQuotient(0, abs((long)dividend), abs((long)divisor), abs((long)dividend));
}
private:
int binarySearchQuotient(long low, long high, long val, long dividend) {
long quotient = low + (high - low) / 2;
if (((quotient + 1) * val > dividend && quotient * val <= dividend) ||
((quotient + 1) * val >= dividend && quotient * val < dividend)) {
if ((quotient + 1) * val == dividend) {
return quotient + 1;
}
return quotient;
}
if ((quotient + 1) * val > dividend && quotient * val > dividend) {
return binarySearchQuotient(low, quotient - 1, val, dividend);
}
if ((quotient + 1) * val < dividend && quotient * val < dividend) {
return binarySearchQuotient(quotient + 1, high, val, dividend);
}
return 0;
}
};
class Solution {
public:
int divide(int dividend, int divisor) {
// 处理特殊情况
if (dividend == INT_MIN && divisor == -1) {
return INT_MAX;
}
int result = 0;
int sign = (dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0) ? 1 : -1;
long dvd = abs((long)dividend);
long dvs = abs((long)divisor);
while (dvd >= dvs) {
long temp = dvs;
long m = 1;
while (temp << 1 <= dvd) {
temp <<= 1;
m <<= 1;
}
dvd -= temp;
// 处理溢出情况
if (result > INT_MAX - m) {
return sign == 1 ? INT_MAX : INT_MIN;
}
result += m;
}
return sign * result;
}
};
ソリューションの各バージョンについて説明する際に、必要な詳細な基本については個別に説明します。
Goのバージョン
-
Go 言語の基本: Go バージョンのソリューションを理解する前に、変数、関数、条件文、ループなどを含む Go 言語の基本を理解しておく必要があります。
-
再帰: Go バージョンのソリューションでは再帰を使用して二分探索を実装するため、再帰の概念と使用法を理解する必要があります。
-
ビット操作: ビット操作は、シンボル、エッジ ケースなどの詳細を処理するために Go バージョンで使用されます。したがって、Go のビット演算子 (例
<<
、>>
) とビット演算の基本原理を理解する必要があります。 -
エッジ ケースの処理: 入力と出力が制限されているため、エッジ ケースの処理方法を知ることが、この問題を解決するための鍵の 1 つです。最小および最大の 32 ビット符号付き整数を考慮する必要があります。
Pythonのバージョン
-
Python 言語の基本: 変数、関数、条件ステートメント、ループ、整数オーバーフロー処理など、Python 言語の基本を理解している必要があります。
-
Recursive : Python バージョンの再帰ソリューションは、再帰関数を使用して二分探索を実装します。再帰関数の書き方と再帰の仕組みを理解する必要があります。
-
ビットごとの操作: ビットごとの操作は、シンボルとエッジ ケースを処理するために Python バージョンでも使用されます。
<<
Python のビット演算子 ( 、など>>
) とビット演算の基本原理を理解すると、解決策を理解するのに役立ちます。
Javaのバージョン
-
Java 言語の基本: ソリューションの Java バージョンを理解する前に、クラス、メソッド、条件文、ループ、整数オーバーフロー処理などの Java 言語の基本を理解しておく必要があります。
-
再帰: Java バージョンのソリューションでは、再帰関数を使用してバイナリ検索を実装します。再帰関数の書き方と再帰の仕組みを理解する必要があります。
-
ビット操作: ビット操作は、Java バージョンでシンボルとエッジ ケースを処理するために使用されます。Java のビット演算子 (
<<
、など) とビット演算の基本原理を理解すると、解決策を理解するのに役立ちます。>>
-
整数オーバーフローの処理: Java バージョンでは、整数オーバーフローの状況が考慮され、オーバーフローを防止するための手段が講じられます。Java における整数オーバーフローの特性とその対処方法を理解する必要があります。
C++バージョン
-
C++ 言語の基本: C++ バージョンのソリューションを理解する前に、クラス、関数、条件ステートメント、ループ、整数オーバーフロー処理などの C++ 言語の基本を理解しておく必要があります。
-
再帰: C++ バージョンのソリューションでは、再帰関数を使用してバイナリ検索を実装します。再帰関数の書き方と再帰の仕組みを理解する必要があります。
-
ビット操作: ビット操作は、C++ バージョンでシンボルとエッジ ケースを処理するために使用されます。
<<
C++ のビット演算子 ( 、など>>
) とビット演算の基本原理を理解すると、解決策を理解するのに役立ちます。 -
整数オーバーフローの処理: C++ バージョンでは、整数オーバーフローの状況を考慮し、オーバーフローを防止するための措置を講じます。C++ における整数オーバーフローの特性とその対処方法を理解する必要があります。
要約すると、ソリューションの各バージョンを理解するには、プログラミング言語の基礎、再帰、ビットごとの演算、および整数オーバーフロー処理に関するある程度の知識が必要です。さらに、問題の特別な要件と境界条件を理解することも、この問題を解決する鍵となります。
30. すべての単語を連結した部分文字列
トピック
文字列 s と、すべて同じ長さの単語のリスト word が与えられます。
間に文字を入れずに、words 内の各単語を 1 回だけ連結した s 内の部分文字列の開始インデックスをすべて検索します。
例 1:
Input:
s = "barfoothefoobarman",
words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.
例 2:
Input:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
Output: []
トピックの一般的な考え方
ソース文字列 s と文字列配列を指定すると、ソース文字列内の文字列配列のさまざまな組み合わせで構成される連続文字列の開始添え字を見つける必要があります。複数ある場合は、それらを出力する必要があります。結果です。
問題解決のアイデア
この質問は難しいように思えるかもしれませんが、2 つの制限があるため、特に難しいわけではありません。1. 文字列配列内の文字列の長さはすべて同じです。2.
文字列配列内の文字列は連続して接続されている必要があり、順序は任意の順列および組み合わせにすることができます。
この問題を解決するためのアイデアは、まず文字列配列内のすべての文字列をマップに格納し
、出現回数を累積することです。次に、ソース文字列の先頭からスキャンし、文字列配列内の文字列を判断するたびに、すべての文字列が使い果たされ (カウントが 0 であっても)、すべて使い果たされていれば、その長さはちょうど合計の長さになります
。文字列配列の順列と組み合わせの場合、この組み合わせの開始インデックスを記録します。一致しない場合は、ソース文字列全体がスキャンされるまで、ソース文字列の次の文字の検査を続けます。
以下に、各バージョンの問題解決のアイデアを紹介します。
Goのバージョン:
-
変数を初期化します。
s
入力文字列の長さls
、単語配列words
の長さm
、および単語の長さを取得しますn
。- 空の整数スライスを初期化して
ans
結果を保存します。
-
メインループ:
s
文字列の最初の n 文字から開始して、開始位置までループします。differ
現在のウィンドウ内の各単語の出現数を追跡するマップを作成します。
-
単語リストを反復処理します
words
。- 単語リスト内の単語ごとに、
differ
の出現数を増やします。 - 出現回数が増えて0になったら、その単語
differ
を から。
- 単語リスト内の単語ごとに、
-
メインループ:
- 現在の開始位置から開始して、一度に 1 単語の長さを移動し
n
、部分文字列にすべての単語が含まれているかどうかを確認します。 - そうでない場合は、ウィンドウを移動し続けます。
- 単語が含まれていない場合
differ
、部分文字列にはすべての単語が含まれ、現在の開始位置が結果に追加されます。
- 現在の開始位置から開始して、一度に 1 単語の長さを移動し
-
結果を返す: 結果を格納するスライスを返します
ans
。
Pythonのバージョン:
-
変数を初期化します。
- 空のリストを初期化して
ans
結果を保存します。 - 単語の長さ
word_len
、単語配列の長さtotal_words
、単語配列の要素数word_count
、および入力文字列s
の長さを計算しますs_len
。
- 空のリストを初期化して
-
単語の開始位置をループします。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
word_len-1
。 - 内部的には 2 つのポインタ
left
とj
、およびカウンタcount
とワード カウンタを維持しますcurrent_count
。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
-
文字列を反復処理します
s
。- 内側のループは
s
、現在の開始位置から開始して一度に 1 ワード長ずつ移動しながら、文字列を反復処理しますword_len
。 - 内側のループで、現在の部分文字列が単語配列の有効な組み合わせであるかどうかを確認します。
- 有効な組み合わせの場合、開始位置が
left
結果リストに追加されます。
- 内側のループは
-
結果を返す: 保存された結果のリストを返します
ans
。
Javaのバージョン:
-
変数を初期化します。
- 空のリストを初期化して
res
結果を保存します。 - 単語とその出現を保存するマップを作成します
map
。 - ワード配列の長さを取得します
m
。
- 空のリストを初期化して
-
考えられる単語の開始位置をループします。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
len(words[0])-1
。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
-
文字列を反復処理します
s
。- 内側のループは
s
、現在の開始位置から開始して一度に 1 ワード長ずつ移動しながら、 string を反復処理します。 - 内側のループで、現在の部分文字列が単語配列の有効な組み合わせであるかどうかを確認します。
- 有効な組み合わせの場合、開始位置が結果リストに追加されます
res
。
- 内側のループは
-
結果を返す: 保存された結果のリストを返します
res
。
C++ バージョン:
-
変数を初期化します。
- 空のベクトルを初期化して
ans
結果を保存します。 - 単語配列の長さ
m
と単語の長さを取得しますwordsize
。 - 単語とその出現を保存するマップを作成します
mp
。 s
入力文字列の長さを取得しますn
。
- 空のベクトルを初期化して
-
考えられる単語の開始位置をループします。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
wordsize-1
。
- 外側のループは、単語の開始位置を 0 から まで繰り返します
-
文字列を反復処理します
s
。- 内側のループは
s
、現在の開始位置から開始して一度に 1 ワード長ずつ移動しながら、 string を反復処理します。 - 内側のループで、現在の部分文字列が単語配列の有効な組み合わせであるかどうかを確認します。
- 有効な組み合わせの場合は、開始位置を結果のベクトルに追加します
ans
。
- 内側のループは
-
結果を返す: 結果を格納するベクトルを返します
ans
。
これらは、各バージョンの問題解決の基本的なアイデアです。これらはすべてスライディング ウィンドウ手法を使用し、異なる開始位置から開始してウィンドウを徐々に移動させ、部分文字列が条件を満たしているかどうかを確認します。同時に、データ構造 (マップやカウンターなど) を利用して、比較のために単語の出現をカウントします。プログラミング言語が異なれば実装の詳細は異なりますが、全体的な考え方は同じです。
コード
行く
func findSubstring(s string, words []string) (ans []int) {
// 获取输入字符串 `s` 的长度
ls, m, n := len(s), len(words), len(words[0])
// 遍历字符串 `s` 的前 n 个字符
for i := 0; i < n && i+m*n <= ls; i++ {
// 使用 map `differ` 来跟踪子串中每个单词的出现次数
differ := map[string]int{}
// 遍历单词列表 `words`
for j := 0; j < m; j++ {
// 将子串中的每个单词加入到 `differ` 中,并统计出现次数
differ[s[i+j*n:i+(j+1)*n]]++
}
// 遍历单词列表 `words`
for _, word := range words {
// 减少 `differ` 中对应单词的出现次数
differ[word]--
// 如果出现次数减少到 0,从 `differ` 中删除这个单词
if differ[word] == 0 {
delete(differ, word)
}
}
// 从当前位置 `i` 开始,每次移动一个单词长度 `n`,检查子串是否包含所有单词
for start := i; start < ls-m*n+1; start += n {
if start != i {
// 更新 `differ`,增加新单词的出现次数,减少旧单词的出现次数
word := s[start+(m-1)*n : start+m*n]
differ[word]++
if differ[word] == 0 {
delete(differ, word)
}
word = s[start-n : start]
differ[word]--
if differ[word] == 0 {
delete(differ, word)
}
}
// 如果 `differ` 中不包含任何单词,说明子串包含了所有单词
if len(differ) == 0 {
ans = append(ans, start)
}
}
}
return
}
パイソン
from collections import Counter
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
if not s or not words:
return []
ans = []
word_len = len(words[0])
total_words = len(words)
word_count = Counter(words)
s_len = len(s)
for i in range(word_len):
left = i
count = 0
current_count = Counter()
for j in range(i, s_len - word_len + 1, word_len):
word = s[j:j + word_len]
if word in word_count:
current_count[word] += 1
count += 1
while current_count[word] > word_count[word]:
left_word = s[left:left + word_len]
current_count[left_word] -= 1
count -= 1
left += word_len
if count == total_words:
ans.append(left)
else:
current_count.clear()
count = 0
left = j + word_len
return ans
ジャワ
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
public List<Integer> findSubstring(String s, String[] words) {
Map<String, Integer> map = new HashMap<>();
// 将单词数组中的单词以及它们的出现次数存储在 map 中
for (String str : words) {
map.put(str, map.getOrDefault(str, 0) + 1);
}
int len = words[0].length(); // 单词的长度
List<Integer> res = new ArrayList<>(); // 存储结果的列表
// 遍历字符串 s 中每个可能的起始位置
for (int i = 0; i < len; i++) {
Map<String, Integer> newmap = new HashMap<>(); // 存储当前窗口内的单词出现次数
int count = 0; // 记录窗口内匹配的单词数量
for (int j = i; j <= s.length() - len;) {
String cur = s.substring(j, j + len); // 当前窗口内的单词
// 如果当前单词在单词数组中且未超出其出现次数限制
if (map.containsKey(cur) && (!newmap.containsKey(cur) || newmap.get(cur) < map.get(cur))) {
newmap.put(cur, newmap.getOrDefault(cur, 0) + 1); // 更新窗口内单词出现次数
count++; // 增加匹配的单词数量
// 如果窗口内匹配的单词数量等于单词数组的长度,表示找到一个满足条件的子串
if (count == words.length) {
res.add(j - len * (words.length - 1)); // 记录子串的起始位置
count--;
String pre = s.substring(j - len * (words.length - 1), j - len * (words.length - 2));
newmap.put(pre, newmap.get(pre) - 1); // 更新窗口内单词出现次数
}
j += len; // 移动窗口
}
// 如果当前单词不在单词数组中
else if (!map.containsKey(cur)) {
count = 0;
newmap.clear(); // 清空窗口内的单词记录
j += len;
}
// 如果当前单词在单词数组中但超出其出现次数限制
else {
String pre = s.substring(j - len * count, j - len * (count - 1));
newmap.put(pre, newmap.get(pre) - 1); // 更新窗口内单词出现次数
count--; // 减少匹配的单词数量
}
}
}
return res; // 返回结果列表
}
}
CPP
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
const int n = s.length(); // 输入字符串的长度
const int m = words.size(); // 单词数组的大小
const int wordsize = words.front().length(); // 单词的长度
unordered_map<string, int> mp; // 用于存储单词以及它们的出现次数
for (auto &word : words)
mp[word]++;
vector<int> ans; // 存储结果的向量
for (int i = 0; i < wordsize; ++i) { // 对于每个可能的起始位置
unordered_map<string, int> cnt; // 用于存储当前窗口内的单词出现次数
int start = i; // 记录符合条件的字符串的起始位置
for (int j = i; j < n; j += wordsize) { // 遍历字符串 s 中所有单词
string word = s.substr(j, wordsize);
if (!mp.count(word)) { // 如果遇到不在单词数组中的单词,直接清空前面所有的计数
cnt.clear();
start = j + wordsize;
} else {
cnt[word] += 1;
while (cnt[word] > mp[word]) { // 某个单词的计数超过了在单词数组中的出现次数,从左边减
cnt[s.substr(start, wordsize)]--;
start += wordsize;
}
if (j - start == (m - 1) * wordsize) // 如果窗口内匹配的单词数量等于单词数组的长度,表示找到一个满足条件的子串
ans.push_back(start);
}
}
}
return ans;
}
};
コードの各バージョンに必要な基本的な知識。
Goのバージョン:
-
Go 言語の基本:
- 変数、ループ、条件文、関数など、Go 言語の基本的な構文を理解します。
-
文字列処理:
- 文字列のスライスと文字列の連結操作を使用して文字列を処理する方法を学びます。
- 文字列の長さがどのように計算されるかを理解します。
-
地図:
- Go のマップ データ構造、つまり
map
マップから要素を追加、削除、取得する方法を理解します。 - マッピングを使用して単語のカウントと比較を実装する方法を理解してください。
- Go のマップ データ構造、つまり
-
ループと条件文:
for
ループと条件文を使用して反復と条件判断を実装する方法を学びますif
。
-
スライス操作:
- スライスを使用して配列または文字列のサブセットを操作する方法を学びます。
- スライスを反復処理する方法を理解します。
Pythonのバージョン:
-
Python 言語の基本:
- 変数、ループ、条件文、関数など、Python 言語の基本的な構文に精通している。
-
文字列処理:
- 文字列のスライスと文字列の連結操作を使用して文字列を処理する方法を学びます。
- 文字列の長さを取得する方法を理解します。
-
カウンタークラス:
collections.Counter
要素の出現数をカウントするために使用されるPython のクラスを理解します。- 単語カウントにオブジェクトを使用する方法を理解します
Counter
。
-
ループと条件文:
for
ループとif
条件文を使用して反復と条件判断を実装する方法を理解します。
-
リスト:
- Python のリスト データ構造と、リストを使用してデータを保存および操作する方法について理解します。
Javaのバージョン:
-
Java 言語の基本:
- 変数、ループ、条件文、関数など、Java 言語の基本的な構文に精通していること。
-
文字列処理:
- 文字列のスライスと文字列の連結操作を使用して文字列を処理する方法を学びます。
- 文字列の長さを取得する方法を理解します。
-
地図:
- Java のインターフェースと、それを使用してマッピングを実装する
java.util.Map
方法を理解します。HashMap
- マップに要素を追加、削除、取得する方法を理解します。
- Java のインターフェースと、それを使用してマッピングを実装する
-
ループと条件文:
for
ループと条件文を使用して反復と条件判断を実装する方法を学びますif
。
-
リスト:
- Java のリスト データ構造、つまり
java.util.List
、リストを使用してデータを保存および操作する方法についてよく理解してください。
- Java のリスト データ構造、つまり
C++ バージョン:
-
C++ 言語の基本:
- 変数、ループ、条件文、関数などの C++ 言語の基本構文を理解します。
-
文字列処理:
- 文字列のスライスと文字列の連結操作を使用して文字列を処理する方法を学びます。
- 文字列の長さを取得する方法を理解します。
-
STL (標準テンプレート ライブラリ) :
- マッピングを実装するためのC++ STL を理解します
std::unordered_map
。 - マップに要素を追加、削除、取得する方法を理解します。
- マッピングを実装するためのC++ STL を理解します
-
ループと条件文:
for
ループと条件文を使用して反復と条件判断を実装する方法を学びますif
。
-
ベクトル:
- C++ のベクトル データ構造、つまり
std::vector
、およびベクトルを使用してデータを保存および操作する方法についてよく理解してください。
- C++ のベクトル データ構造、つまり
これらは、提供されているさまざまな言語バージョンでコードを理解し、記述するために必要な基本です。各バージョンは同様のアルゴリズムとデータ構造を使用しますが、特定の構文とライブラリ関数は異なる場合があります。
31. 次の順列
トピック
next permutationを実装します。これは、数値を辞書編集上で次に大きい数値の順列に並べ替えます。
このような配置が不可能な場合は、可能な限り低い順序で再配置する必要があります (つまり、昇順でソートされます
)。
置換は**所定の位置に**行われ、一定の追加
メモリのみを使用する必要があります。
例 1:
Input: nums = [1,2,3]
Output: [1,3,2]
例 2:
Input: nums = [3,2,1]
Output: [1,2,3]
例 3:
Input: nums = [1,1,5]
Output: [1,5,1]
例 4:
Input: nums = [1]
Output: [1]
制約:
1 <= nums.length <= 100
0 <= nums[i] <= 100
トピックの一般的な考え方
次の順列を取得する関数を実装すると、アルゴリズムは、指定された数値シーケンスを辞書編集順で次に大きな順列に並べ替える必要があります。次に大きな順列がない場合は、数値を最小の順列に並べ替えます (つまり、昇順)。適切な場所で変更する必要があります
。追加の定数スペースのみが許可されます。
問題解決のアイデア
Go バージョンの問題解決のアイデア:
-
次の順列を見つける: まず、次の順列を見つけます。つまり、辞書編集順で次に大きい順列になるように、指定された数値シーケンスを並べ替えます。
-
小さい方の数値の位置を見つけます。整数スライスを右から左にたどり
nums
、条件を満たす最初のnums[i] < nums[i+1]
添字を見つけますi
。この位置は小さい方の数字の位置を表します。 -
大きい方の数値の位置を見つける: 見つかった場合は
i
、降順区間で右から左に満たす[i+1, n)
最初のnums[i] < nums[j]
添字を見つけますj
。この位置は、大きい方の数値の位置を表します。 -
小さい数値と大きい数値を入れ替えます: swap
nums[i]
とnums[j]
. このステップが完了すると、間隔は[i+1, n)
降順の間隔になる必要があります。 -
部分配列を反転する:
[i+1, n)
範囲内の要素を所定の位置で反転して昇順にし、次の配列を生成できるようにします。
Python バージョンの問題解決のアイデア:
Python バージョンの問題解決のアイデアは、Python のリストとオブジェクトが使用される点を除いて Go バージョンと似ています。
-
次の順列を見つける: Go バージョンと同じように、最初に次の順列を見つける必要があります。つまり、辞書編集順で次に大きな順列になるように、指定された数値シーケンスを並べ替えます。
-
小さい方の数値の位置を見つける: 整数のリストを右から左にたどって、小さい方の数値の位置を表す を満たす
nums
最初のnums[i] < nums[i+1]
添字を見つけます。i
-
大きい方の数値の位置を見つけます。見つかった場合は、大きい方の数値の位置を表す、
i
降順区間で右から左に満たす[i+1, n)
最初のnums[i] < nums[j]
添え字を見つけます。j
-
小さい数値と大きい数値を入れ替えます: swap
nums[i]
とnums[j]
. このステップが完了すると、間隔は[i+1, n)
降順の間隔になる必要があります。 -
部分配列を反転する:
[i+1, n)
範囲内の要素を所定の位置で反転して昇順にし、次の配列を生成できるようにします。
Java バージョンのソリューションのアイデア:
Java バージョンの問題解決のアイデアは、Java の配列とクラスが使用されることを除いて、Go および Python バージョンのものと似ています。
-
次の順列を見つける: 他のバージョンと同様に、最初に次の順列を見つけます。つまり、与えられた一連の数値を並べ替えて、辞書編集順で次に大きい順列にします。
-
小さい方の数値の位置を見つける: 整数配列を右から左にたどって、小さい方の数値の位置を表す を満たす
nums
最初のnums[i] < nums[i+1]
添え字を見つけます。i
-
大きい方の数値の位置を見つけます。見つかった場合は、大きい方の数値の位置を表す、
i
降順区間で右から左に満たす[i+1, n)
最初のnums[i] < nums[j]
添え字を見つけます。j
-
小さい数値と大きい数値を入れ替えます: swap
nums[i]
とnums[j]
. このステップが完了すると、間隔は[i+1, n)
降順の間隔になる必要があります。 -
部分配列を反転する:
[i+1, n)
範囲内の要素を所定の位置で反転して昇順にし、次の配列を生成できるようにします。
C++ バージョンの問題解決のアイデア:
C++ バージョンの問題解決のアイデアは、C++ の配列とクラスが使用されることを除いて、Go、Python、および Java バージョンのものと似ています。
-
次の順列を見つける: 他のバージョンと同様に、最初に次の順列を見つけます。つまり、与えられた一連の数値を並べ替えて、辞書編集順で次に大きい順列にします。
-
小さい方の数値の位置を見つける: 整数配列を右から左にたどって、小さい方の数値の位置を表す を満たす
nums
最初のnums[i] < nums[i+1]
添え字を見つけます。i
-
大きい方の数値の位置を見つけます。見つかった場合は、大きい方の数値の位置を表す、
i
降順区間で右から左に満たす[i+1, n)
最初のnums[i] < nums[j]
添え字を見つけます。j
-
小さい数値と大きい数値を入れ替えます: swap
nums[i]
とnums[j]
. このステップが完了すると、間隔は[i+1, n)
降順の間隔になる必要があります。 -
部分配列を反転する:
[i+1, n)
範囲内の要素を所定の位置で反転して昇順にし、次の配列を生成できるようにします。
コード
行く
// 解法一
// 定义一个函数 nextPermutation,用于生成下一个排列
func nextPermutation(nums []int) {
// 定义两个变量 i 和 j,并初始化为 0
i, j := 0, 0
// 从倒数第二个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[i] < nums[i+1] 的 i
for i = len(nums) - 2; i >= 0; i-- {
if nums[i] < nums[i+1] {
break
}
}
// 如果找到了 i,表示存在下一个排列
if i >= 0 {
// 从最后一个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[j] > nums[i] 的 j
for j = len(nums) - 1; j > i; j-- {
if nums[j] > nums[i] {
break
}
}
// 交换 nums[i] 和 nums[j]
swap(&nums, i, j)
}
// 对从 i+1 到末尾的部分进行翻转,以获得下一个排列
reverse(&nums, i+1, len(nums)-1)
}
// 定义一个函数 reverse,用于翻转整数切片 nums 中从位置 i 到 j 的元素
func reverse(nums *[]int, i, j int) {
// 使用双指针将元素从两端向中间逐个交换
for i < j {
swap(nums, i, j)
i++
j--
}
}
// 定义一个函数 swap,用于交换整数切片 nums 中位置 i 和 j 的元素
func swap(nums *[]int, i, j int) {
// 使用指针访问和交换切片中的元素值
(*nums)[i], (*nums)[j] = (*nums)[j], (*nums)[i]
}
パイソン
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# Step 1: Find the first decreasing element from right to left (i)
i = len(nums) - 2
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
# Step 2: Find the first element larger than nums[i] from right to left (j)
if i >= 0:
j = len(nums) - 1
while j > i and nums[j] <= nums[i]:
j -= 1
# Step 3: Swap nums[i] and nums[j]
nums[i], nums[j] = nums[j], nums[i]
# Step 4: Reverse the subarray to the right of i
left, right = i + 1, len(nums) - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
ジャワ
class Solution {
public void nextPermutation(int[] nums) {
// Step 1: Find the first decreasing element from right to left (i)
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// Step 2: Find the first element larger than nums[i] from right to left (j)
if (i >= 0) {
int j = nums.length - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
// Step 3: Swap nums[i] and nums[j]
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// Step 4: Reverse the subarray to the right of i
int left = i + 1, right = nums.length - 1;
while (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
}
CPP
class Solution {
public:
void nextPermutation(vector<int>& nums) {
// Step 1: Find the first decreasing element from right to left (i)
int i = nums.size() - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// Step 2: Find the first element larger than nums[i] from right to left (j)
if (i >= 0) {
int j = nums.size() - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
// Step 3: Swap nums[i] and nums[j]
swap(nums[i], nums[j]);
}
// Step 4: Reverse the subarray to the right of i
int left = i + 1, right = nums.size() - 1;
while (left < right) {
swap(nums[left], nums[right]);
left++;
right--;
}
}
};
基本知識
Goのバージョン:
-
Go 言語の基礎: Go 言語の基本的な構文、データ型、関数の定義と使用法、スライス、およびその他の関連知識を理解します。
-
ポインター: ポインターの概念と Go でのポインターの使用方法について学びます。
-
関数: 関数の定義と呼び出し方法、およびそのパラメータと戻り値を理解します。
-
配列のスライス: スライスの作成と変更を含む、Go でのスライスとスライス操作の概念を理解します。
Python のバージョン:
-
Python の基本: Python の基本的な構文、リスト、条件文、ループを理解します。
-
クラスとオブジェクト: クラスを定義し、オブジェクトを作成する方法 (Python クラスでメソッドを定義する) を理解します。
-
リスト操作: リスト要素のインデックス付け、スライス、反復、変更など、リストを操作する方法を学びます。
Java バージョン:
-
Java の基礎: Java の基本的な構文、配列、ループ、条件文に精通しています。
-
クラスとメソッド: クラスとメソッドを定義する方法、およびクラス内でメンバー変数とメソッドを使用する方法を学びます。
-
配列操作: Java での配列の作成、走査、および変更操作に精通します。
C++ バージョン:
-
C++ の基礎: C++ の基本的な構文、配列、ループ、条件文を理解します。
-
関数: 関数の定義と呼び出し方法、およびそのパラメータと戻り値を理解します。
-
配列操作: 配列要素のインデックス付け、走査、変更など、配列を操作する方法を学びます。
32. 有効な最長括弧
トピック
'('
と の文字だけを含む文字列を指定して')'
、最長の有効な (整形式の)
括弧部分文字列の長さを見つけます。
例 1:
Input: s = "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()".
例 2:
Input: s = ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()".
例 3:
Input: s = ""
Output: 0
制約:
0 <= s.length <= 3 * 104
s[i]
'('
、または')'
。
トピックの一般的な考え方
'(' と ')' のみを含む文字列を指定して、最長の有効な (整形式で連続した) 括弧部分文字列の長さを見つけます。
問題解決のアイデア
以下に、各バージョンの問題解決のアイデアを詳しく紹介します。
Go バージョンの問題解決のアイデア:
Go バージョンの問題解決のアイデアは、スタックを使用して括弧の一致問題を処理し、ダブル ポインター メソッドを使用して最長の有効な括弧の部分文字列の長さを計算することです。主な手順は次のとおりです。
-
max(a, b int) int
2 つの整数のうち大きい方を返すヘルパー関数を定義します。 -
初期化
left
、right
およびmaxLength
変数は、それぞれ左括弧の数、右括弧の数、および最長の有効な括弧部分文字列の長さを記録するために使用されます。初期値は 0 です。 -
入力文字列を
s
左から右に繰り返します。- 現在の文字が「(」の場合、
left
カウンターを増やします。 - 現在の文字が「)」の場合、
right
カウンターをインクリメントします。 left
とright
カウンタが等しい場合、有効な括弧部分文字列が見つかり、現在の有効な括弧部分文字列の長さが計算され、更新されることを意味しますmaxLength
。right
が より大きい場合left
、現在の括弧文字列と一致しないため、left
とカウンタが 0 にリセットされます。right
- 現在の文字が「(」の場合、
-
left
変数、right
および変数を 0 にリセットしmaxLength
、右から左へのトラバーサルを再度実行して、右括弧の数が左括弧の数よりも多い状況に対処します。 -
maxLength
最も長い有効なブラケット部分文字列の長さを返します。
Python バージョンの問題解決のアイデア:
Python バージョンの問題解決の考え方は Go バージョンと似ており、括弧の一致問題を処理するためにスタックを使用し、有効な括弧部分文字列の最長を計算するためにダブル ポインター メソッドを使用します。主な手順は次のとおりです。
-
max(a, b)
2 つの数値のうち大きい方を返すヘルパー関数を定義します。 -
初期化
left
、right
およびmaxLength
変数は、それぞれ左括弧の数、右括弧の数、および最長の有効な括弧部分文字列の長さを記録するために使用されます。初期値は 0 です。 -
入力文字列を
s
左から右に繰り返します。- 現在の文字が「(」の場合、
left
カウンターを増やします。 - 現在の文字が「)」の場合、
right
カウンターをインクリメントします。 left
とright
カウンタが等しい場合、有効な括弧部分文字列が見つかり、現在の有効な括弧部分文字列の長さが計算され、更新されることを意味しますmaxLength
。right
が より大きい場合left
、現在の括弧文字列と一致しないため、left
とカウンタが 0 にリセットされます。right
- 現在の文字が「(」の場合、
-
left
変数、right
および変数を 0 にリセットしmaxLength
、右から左へのトラバーサルを再度実行して、右括弧の数が左括弧の数よりも多い状況に対処します。 -
maxLength
最も長い有効なブラケット部分文字列の長さを返します。
Java バージョンのソリューションのアイデア:
Java バージョンの問題解決の考え方は Go バージョンや Python バージョンと似ており、スタックを使用して括弧の一致問題を処理し、ダブル ポインタ メソッドを使用して最長の有効な括弧の部分文字列の長さを計算します。 。主な手順は次のとおりです。
-
初期化
left
、right
およびmaxLength
変数は、それぞれ左括弧の数、右括弧の数、および最長の有効な括弧部分文字列の長さを記録するために使用されます。初期値は 0 です。 -
入力文字列を
s
左から右に繰り返します。- 現在の文字が「(」の場合、
left
カウンターを増やします。 - 現在の文字が「)」の場合、
right
カウンターをインクリメントします。 left
とright
カウンタが等しい場合、有効な括弧部分文字列が見つかり、現在の有効な括弧部分文字列の長さが計算され、更新されることを意味しますmaxLength
。right
が より大きい場合left
、現在の括弧文字列と一致しないため、left
とカウンタが 0 にリセットされます。right
- 現在の文字が「(」の場合、
-
left
変数、right
および変数を 0 にリセットしmaxLength
、右から左へのトラバーサルを再度実行して、右括弧の数が左括弧の数よりも多い状況に対処します。 -
maxLength
最も長い有効なブラケット部分文字列の長さを返します。
C++ バージョンの問題解決のアイデア:
C++ バージョンの問題解決の考え方は Go、Python、Java バージョンと似ており、スタックを使用して括弧一致問題を処理し、ダブル ポインタ メソッドを使用して有効な最長の長さを計算します。括弧部分文字列。主な手順は次のとおりです。
-
初期化
left
、right
およびmaxLength
変数は、それぞれ左括弧の数、右括弧の数、および最長の有効な括弧部分文字列の長さを記録するために使用されます。初期値は 0 です。 -
入力文字列を
s
左から右に繰り返します。- 現在の文字が「(」の場合、
left
カウンターを増やします。 - 現在の文字が「)」の場合、
right
カウンターをインクリメントします。 left
とright
カウンタが等しい場合、有効な括弧部分文字列が見つかり、現在の有効な括弧部分文字列の長さが計算され、更新されることを意味しますmaxLength
。right
が より大きい場合left
、現在の括弧文字列と一致しないため、left
とカウンタが 0 にリセットされます。right
- 現在の文字が「(」の場合、
-
left
変数、right
および変数を 0 にリセットしmaxLength
、右から左へのトラバーサルを再度実行して、右括弧の数が左括弧の数よりも多い状況に対処します。 -
maxLength
最も長い有効なブラケット部分文字列の長さを返します。
コード
行く
func max(a, b int) int {
// 返回两个整数中的较大值
if a > b {
return a
}
return b
}
// 解法二 双指针
func longestValidParentheses(s string) int {
// 初始化左右指针和最大有效括号子串长度
left, right, maxLength := 0, 0, 0
for i := 0; i < len(s); i++ {
// 如果当前字符是左括号 '(',增加左括号计数
if s[i] == '(' {
left++
} else {
// 如果当前字符是右括号 ')',增加右括号计数
right++
}
// 如果左右括号计数相等,说明找到了一个有效的括号子串
if left == right {
// 计算当前有效括号子串的长度并更新最大长度
maxLength = max(maxLength, 2*right)
} else if right > left {
// 如果右括号计数大于左括号计数,重置左右指针
left, right = 0, 0
}
}
// 重置左右指针
left, right = 0, 0
for i := len(s) - 1; i >= 0; i-- {
// 从右向左遍历字符串,处理与上面相同的逻辑
if s[i] == '(' {
left++
} else {
right++
}
if left == right {
maxLength = max(maxLength, 2*left)
} else if left > right {
left, right = 0, 0
}
}
// 返回最大有效括号子串的长度
return maxLength
}
パイソン
class Solution:
def longestValidParentheses(self, s: str) -> int:
def max(a, b):
return a if a > b else b
left, right, maxLength = 0, 0, 0
# 从左向右遍历字符串
for char in s:
if char == '(':
left += 1
else:
right += 1
if left == right:
maxLength = max(maxLength, 2 * right)
elif right > left:
left, right = 0, 0
left, right = 0, 0
# 从右向左遍历字符串
for i in range(len(s) - 1, -1, -1):
char = s[i]
if char == '(':
left += 1
else:
right += 1
if left == right:
maxLength = max(maxLength, 2 * left)
elif left > right:
left, right = 0, 0
return maxLength
ジャワ
class Solution {
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxLength = 0;
// 从左向右遍历字符串
for (char c : s.toCharArray()) {
if (c == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxLength = Math.max(maxLength, 2 * right);
} else if (right > left) {
left = 0;
right = 0;
}
}
left = 0;
right = 0;
// 从右向左遍历字符串
for (int i = s.length() - 1; i >= 0; i--) {
char c = s.charAt(i);
if (c == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxLength = Math.max(maxLength, 2 * left);
} else if (left > right) {
left = 0;
right = 0;
}
}
return maxLength;
}
}
CPP
class Solution {
public:
int longestValidParentheses(string s) {
int left = 0, right = 0, maxLength = 0;
// 从左向右遍历字符串
for (char c : s) {
if (c == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxLength = max(maxLength, 2 * right);
} else if (right > left) {
left = 0;
right = 0;
}
}
left = 0;
right = 0;
// 从右向左遍历字符串
for (int i = s.length() - 1; i >= 0; i--) {
char c = s[i];
if (c == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxLength = max(maxLength, 2 * left);
} else if (left > right) {
left = 0;
right = 0;
}
}
return maxLength;
}
};
Goのバージョン:
-
Go 言語の基本:
- 変数の宣言と初期化
- ループ(forループ)
- 条件文(if-else)
- 関数の宣言と呼び出し
- 配列とスライスの基本操作
-
スタックの概念:
- Go でスライスを使用してスタックの動作をシミュレートできます。
Python のバージョン:
-
Python 言語の基本:
- 変数の宣言と初期化
- ループ(forループ)
- 条件文(if-else)
- 関数の宣言と呼び出し
- 文字列に対する基本的な操作
-
スタックの概念:
- Python でリストを使用してスタックの動作をシミュレートできます
Java バージョン:
-
Java 言語の基本:
- クラスとオブジェクトの概念
- メソッドの宣言と呼び出し
- ループ(forループ)
- 条件文(if-else)
- 文字列に対する基本的な操作
-
スタックの概念:
- Java でコレクション クラス (ArrayList や LinkedList など) を使用して、スタックの動作をシミュレートできます。
C++ バージョン:
-
C++ 言語の基本:
- 変数の宣言と初期化
- 関数の宣言と呼び出し
- ループ(forループ)
- 条件文(if-else)
- 文字列に対する基本的な操作
-
スタックの概念:
- C++ では、標準ライブラリ (std::vector や std::deque など) のコンテナーを使用して、スタックの動作をシミュレートできます。
33. 回転ソート配列での検索
トピック
昇順にソートされた配列が、事前に未知のピボットで回転されたとします。
(つまり、[0,1,2,4,5,6,7]
になる可能性があります[4,5,6,7,0,1,2]
)。
検索するターゲット値が与えられます。配列内で見つかった場合はそのインデックスを返し、それ以外の場合は を返します-1
。
配列には重複が存在しないと想定できます。
アルゴリズムの実行時の複雑さはO (log n ) のオーダーである必要があります。
例 1:
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4
例 2:
Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1
トピックの一般的な考え方
昇順にソートされた配列が、事前に不明な点で回転されたとします。(たとえば、配列 [0,1,2,4,5,6,7] は [4,5,6,7,0,1,2] になる可能性があります)。指定されたターゲット値を検索し、配列内に存在する場合はそのインデックスを返し、存在しない場合は -1 を返します。配列内に重複する要素はないと仮定できます。
アルゴリズムの時間計算量は O(log n) レベルである必要があります。
問題解決のアイデア
以下に、各バージョンの問題解決のアイデアを紹介します。
Go バージョンの問題解決のアイデア:
- 2 つのポインター
low
とを初期化しhigh
、それぞれ配列の先頭と末尾を指します。 - 二分探索ループを使用してターゲット値を検索します。
- 各反復で、中間要素のインデックスが計算されます
mid
。 - 中間要素とターゲット値の関係を確認します。
- 中間要素がターゲット値と等しい場合、中間要素のインデックスが直接返されます。
- 中央の要素が左端の要素より大きい場合 (左半分が順序付けされていることを示します)、ターゲット値と中央要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が右端の要素以下である場合 (右半分が順序付けされていることを示します)、ターゲット値と中央の要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が左端の要素と等しい場合、要素が重複している可能性があることを示し、左端のポインタを
low
1 つ右に移動できます。 - 中央の要素が右端の要素と等しい場合、要素が重複している可能性があることを示し、右端のポインタを
high
1 つ左の位置に移動できます。
low
が より大きくなるまで手順 3 と 4 を繰り返しますhigh
。この時点でターゲット値が見つからない場合は、-1 が返されます。
Python バージョンの問題解決のアイデア:
- 2 つのポインター
low
とを初期化しhigh
、それぞれ配列の先頭と末尾を指します。 - 二分探索ループを使用してターゲット値を検索します。
- 各反復で、中間要素のインデックスが計算されます
mid
。 - 中間要素とターゲット値の関係を確認します。
- 中間要素がターゲット値と等しい場合、中間要素のインデックスが直接返されます。
- 中央の要素が左端の要素より大きい場合 (左半分が順序付けされていることを示します)、ターゲット値と中央要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が右端の要素以下である場合 (右半分が順序付けされていることを示します)、ターゲット値と中央の要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が左端の要素と等しい場合、要素が重複している可能性があることを示し、左端のポインタを
low
1 つ右に移動できます。 - 中央の要素が右端の要素と等しい場合、要素が重複している可能性があることを示し、右端のポインタを
high
1 つ左の位置に移動できます。
low
が より大きくなるまで手順 3 と 4 を繰り返しますhigh
。この時点でターゲット値が見つからない場合は、-1 が返されます。
Java バージョンのソリューションのアイデア:
- 2 つのポインター
low
とを初期化しhigh
、それぞれ配列の先頭と末尾を指します。 - 二分探索ループを使用してターゲット値を検索します。
- 各反復で、中間要素のインデックスが計算されます
mid
。 - 中間要素とターゲット値の関係を確認します。
- 中間要素がターゲット値と等しい場合、中間要素のインデックスが直接返されます。
- 中央の要素が左端の要素より大きい場合 (左半分が順序付けされていることを示します)、ターゲット値と中央要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が右端の要素以下である場合 (右半分が順序付けされていることを示します)、ターゲット値と中央の要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が左端の要素と等しい場合、要素が重複している可能性があることを示し、左端のポインタを
low
1 つ右に移動できます。 - 中央の要素が右端の要素と等しい場合、要素が重複している可能性があることを示し、右端のポインタを
high
1 つ左の位置に移動できます。
low
が より大きくなるまで手順 3 と 4 を繰り返しますhigh
。この時点でターゲット値が見つからない場合は、-1 が返されます。
C++ バージョンの問題解決のアイデア:
- 2 つのポインター
low
とを初期化しhigh
、それぞれ配列の先頭と末尾を指します。 - 二分探索ループを使用してターゲット値を検索します。
- 各反復で、中間要素のインデックスが計算されます
mid
。 - 中間要素とターゲット値の関係を確認します。
- 中間要素がターゲット値と等しい場合、中間要素のインデックスが直接返されます。
- 中央の要素が左端の要素より大きい場合 (左半分が順序付けされていることを示します)、ターゲット値と中央要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が右端の要素以下である場合 (右半分が順序付けされていることを示します)、ターゲット値と中央の要素の間のサイズ関係を比較します。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
low = mid + 1
。 - それ以外の場合、ターゲット値はセグメントの左半分の順序付けされていない部分で更新されます
high = mid - 1
。
- ターゲット値が中央の要素より大きく、右端の要素以下の場合、ターゲット値が順序付けされた部分の右半分にあり、更新されることを意味します
- 中央の要素が左端の要素と等しい場合、要素が重複している可能性があることを示し、左端のポインタを
low
1 つ右に移動できます。 - 中央の要素が右端の要素と等しい場合、要素が重複している可能性があることを示し、右端のポインタを
high
1 つ左の位置に移動できます。
low
が より大きくなるまで手順 3 と 4 を繰り返しますhigh
。この時点でターゲット値が見つからない場合は、-1 が返されます。
これら 4 つのバージョンの問題解決の考え方は基本的に同じです。これらはすべて、二分探索の変形を使用して、回転順序付けされた配列内のターゲット値を検索します。重要なのは、ポインターの更新と境界条件の処理を正しく処理することです。さまざまな状況で。各反復で検索範囲が半分になるため、アルゴリズムの時間計算量は O(log n) です。コードのバージョンが異なれば実装方法も異なりますが、コアロジックは似ています。
コード
行く
func search(nums []int, target int) int {
// 检查数组是否为空,如果是空数组则直接返回-1
if len(nums) == 0 {
return -1
}
// 初始化两个指针,分别指向数组的开头和结尾
low, high := 0, len(nums)-1
// 使用二分查找的循环来搜索目标值
for low <= high {
// 计算中间元素的索引
mid := low + (high-low)>>1
// 如果中间元素等于目标值,则直接返回中间元素的索引
if nums[mid] == target {
return mid
} else if nums[mid] > nums[low] { // 如果中间元素在数值大的一部分区间里
// 检查目标值是否在左半部分区间内,如果是则更新高指针
if nums[low] <= target && target < nums[mid] {
high = mid - 1
} else {
// 否则更新低指针
low = mid + 1
}
} else if nums[mid] < nums[high] { // 如果中间元素在数值小的一部分区间里
// 检查目标值是否在右半部分区间内,如果是则更新低指针
if nums[mid] < target && target <= nums[high] {
low = mid + 1
} else {
// 否则更新高指针
high = mid - 1
}
} else {
// 处理中间元素等于边界元素的情况,移动边界指针以去除重复元素
if nums[low] == nums[mid] {
low++
}
if nums[high] == nums[mid] {
high--
}
}
}
// 如果未找到目标值,则返回-1
return -1
}
パイソン
class Solution:
def search(self, nums, target: int):
# 如果数组为空,直接返回-1
if len(nums) == 0:
return -1
# 如果数组只有一个元素,分两种情况判断
elif len(nums) == 1:
if nums[0] != target:
return -1
else:
return 0
# 找到旋转点的位置
for i in range(len(nums) - 1):
if nums[i] > nums[i + 1]:
flag = i + 1
break
# 在旋转点左边进行二分查找
left = self.binary_search(nums, target, 0, flag - 1)
if left != -1:
return left
else:
# 如果左边没有找到,就在旋转点右边进行二分查找
right = self.binary_search(nums, target, flag, len(nums) - 1)
if right == -1:
return -1
else:
return right
def binary_search(self, nums, target, left, right):
l, r = left, right
while l <= r:
mid = l + (r - l) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return -1
ジャワ
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > nums[low]) {
if (nums[low] <= target && target < nums[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
} else if (nums[mid] < nums[high]) {
if (nums[mid] < target && target <= nums[high]) {
low = mid + 1;
} else {
high = mid - 1;
}
} else {
if (nums[low] == nums[mid]) {
low++;
}
if (nums[high] == nums[mid]) {
high--;
}
}
}
return -1;
}
}
CPP
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.empty()) {
return -1;
}
int low = 0, high = nums.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > nums[low]) {
if (nums[low] <= target && target < nums[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
} else if (nums[mid] < nums[high]) {
if (nums[mid] < target && target <= nums[high]) {
low = mid + 1;
} else {
high = mid - 1;
}
} else {
if (nums[low] == nums[mid]) {
low++;
}
if (nums[high] == nums[mid]) {
high--;
}
}
}
return -1;
}
};
さまざまなプログラミング言語でアルゴリズムを実装する場合は、次の基本知識を習得する必要があります。
Goのバージョン:
-
関数の定義と呼び出し: パラメーターと戻り値を含む関数を定義して呼び出す方法を学びます。
-
配列とスライス: Go には従来の配列はなく、代わりに動的配列を処理するためにスライスが使用されます。スライスを宣言、初期化、操作する方法を知る必要があります。
-
ループと条件文
for
:と文を使用してループと条件判断を実装する方法を学びますif
。 -
二分探索: 中間要素のインデックスの計算方法やポインタの更新方法など、二分探索の原理と実装方法を理解します。
-
アルゴリズムの複雑さ: 時間計算量と空間計算量を含むアルゴリズムの複雑さの概念を理解し、アルゴリズムのパフォーマンスを分析する方法を理解します。
Python のバージョン:
-
クラスとメソッド: クラスとクラス メソッドを定義する方法、およびクラスのインスタンスを作成する方法を学びます。
-
リスト: Python のリストは動的配列に似ており、リストの宣言、初期化、操作方法に関する知識が必要です。
-
ループと条件文
for
:と文を使用してループと条件判断を実装する方法を学びますif
。 -
二分探索: 中間要素のインデックスの計算方法やポインタの更新方法など、二分探索の原理と実装方法を理解します。
-
関数再帰: 関数再帰の概念と再帰の問題を解決する方法を理解します。
Java バージョン:
-
クラスとメソッド: クラスとクラス メソッドを定義する方法、およびクラスのインスタンスを作成する方法を学びます。
-
配列: Java には静的配列があり、配列を宣言、初期化、操作する方法を知る必要があります。
-
ループと条件文
for
:と文を使用してループと条件判断を実装する方法を学びますif
。 -
二分探索: 中間要素のインデックスの計算方法やポインタの更新方法など、二分探索の原理と実装方法を理解します。
-
アルゴリズムの複雑さ: 時間計算量と空間計算量を含むアルゴリズムの複雑さの概念を理解し、アルゴリズムのパフォーマンスを分析する方法を理解します。
C++ バージョン:
-
クラスとメソッド: クラスとクラス メソッドを定義する方法、およびクラスのインスタンスを作成する方法を学びます。
-
ベクトル: C++ のベクトルは動的配列に似ており、ベクトルの宣言、初期化、操作方法に関する知識が必要です。
-
ループと条件文
for
:と文を使用してループと条件判断を実装する方法を学びますif
。 -
二分探索: 中間要素のインデックスの計算方法やポインタの更新方法など、二分探索の原理と実装方法を理解します。
-
アルゴリズムの複雑さ: 時間計算量と空間計算量を含むアルゴリズムの複雑さの概念を理解し、アルゴリズムのパフォーマンスを分析する方法を理解します。
上記は、検索回転ソート配列アルゴリズムを実装するために必要な基本的な知識であり、言語の特徴、データ構造の操作、ループと条件判断、二分探索アルゴリズム、アルゴリズムの複雑さの分析などの知識が含まれます。プログラミング言語が異なれば構文やライブラリ関数も異なりますが、核となるアルゴリズムの考え方とロジックは似ています。
34. ソートされた配列内の要素の最初と最後の位置を検索する
トピック
nums
昇順にソートされた整数の配列を指定して、指定されたtarget
値の開始位置と終了位置を見つけます。
アルゴリズムの実行時の複雑さはO (log n ) のオーダーである必要があります。
ターゲットが配列内に見つからない場合は、 を返します[-1, -1]
。
例 1:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]
例 2:
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]
トピックの一般的な考え方
昇順に並べられた整数 nums の配列と、ターゲット値 target が与えられます。配列内の指定されたターゲット値の開始位置と終了位置を見つけます。アルゴリズムの時間計算量は O(log n) レベルである必要があります。対象の値が配列に存在しない場合は、[-1, -1] が返されます。
問題解決のアイデア
問題解決のアイデアの Go バージョン:
-
まず、配列の先頭と末尾をそれぞれ指す2 つのポインター
l_ptr
とを初期化します。r_ptr
-
ターゲット値の範囲を格納する整数スライスを
ans
初期値 で初期化します[-1, -1]
。 -
二分探索の考え方を利用し、目的の値の範囲が見つかるか、目的の値が存在しないと判断されるまで、ループ内で探索範囲を絞り続けます。
-
各反復で、中間位置のインデックスが計算されます
mid
。 -
中央の要素がターゲット値と等しい場合、
ans[0]
と が更新ans[1]
されますmid
。次に、左右にループすることで、目標値の最初と最後の位置が見つかります。 -
中央の要素が目標値より大きい場合は、右ポインタを に移動して
r_ptr
検索mid - 1
範囲を左半分に絞ります。 -
中央位置の要素が目標値より小さい場合は、左ポインタを に移動して
l_ptr
検索mid + 1
範囲を右半分に絞ります。 -
最後に、ターゲット値の範囲を含む配列が返されます
ans
。
Python 版の問題解決アイデア:
Python バージョンの問題解決のアイデアは Go バージョンと似ていますが、コードを整理するために関数とモジュールの構造が使用されます。
-
find_left
まず、2 つの補助関数および が定義されておりfind_right
、それぞれターゲット値の左側と右側の境界を見つけるために使用されます。 -
main 関数で
searchRange
、ターゲット値の左右の境界を格納する 2 つの整数を初期化します。これらの値は、ヘルパー関数を呼び出すことで見つかります。 -
補助機能
find_left
とは、find_right
二分探索の考え方を利用して、目標値の境界が見つかるか、目標値が存在しないと判断されるまで探索範囲を絞り続けます。 -
左側の境界が見つかったら、右に向かって検索を続けて、右側の境界を見つけます。
-
最後に、ターゲット値の範囲を含む結果リストが返されます。
Java バージョンの問題解決アイデア:
Java バージョンの問題解決の考え方は Go バージョンと似ていますが、コードを整理するためにクラスとメソッドの構造が使用されています。
-
クラスでは、問題を解決するための
Solution
メソッドが定義されます。searchRange
-
このメソッドでは
searchRange
、ターゲット値の左右の境界を格納する 2 つの整数を初期化します。 -
ループを使用して
while
、ターゲット値の範囲が見つかるまで、またはターゲット値が存在しないと判断されるまで、検索範囲を絞り込みます。 -
在每次迭代中,计算中间位置的索引
mid
。 -
如果中间位置的元素等于目标值,则更新左边界和右边界,然后通过循环向左和向右扩展,找到目标值的第一个和最后一个位置。
-
如果中间位置的元素大于目标值,则缩小搜索范围到左半部分,将右指针
r_ptr
移动到mid - 1
。 -
如果中间位置的元素小于目标值,则缩小搜索范围到右半部分,将左指针
l_ptr
移动到mid + 1
。 -
最终,返回包含目标值范围的整数数组。
C++ 版本的解题思路:
C++ 版本的解题思路与 Java 版本类似,但使用了类和方法的结构来组织代码:
-
在
Solution
类中,定义了一个方法searchRange
来解决问题。 -
在
searchRange
方法中,初始化两个整数,用于存储目标值的左边界和右边界。 -
使用
while
循环,在循环中不断缩小搜索范围,直到找到目标值的范围或确定目标值不存在。 -
在每次迭代中,计算中间位置的索引
mid
。 -
如果中间位置的元素等于目标值,则更新左边界和右边界,然后通过循环向左和向右扩展,找到目标值的第一个和最后一个位置。
-
如果中间位置的元素大于目标值,则缩小搜索范围到左半部分,将右指针
r_ptr
移动到mid - 1
。 -
如果中间位置的元素小于目标值,则缩小搜索范围到右半部分,将左指针
l_ptr
移动到mid
。 -
最终,返回包含目标值范围的整数数组。
这些是每个版本的解题思路的详细说明,希望这能帮助您更好地理解每个解决方案的工作原理。如果您有任何进一步的问题,请随时提问。
代码
Go
func searchRange(nums []int, target int) []int {
// 获取数组的长度
n := len(nums)
// 初始化左指针为 0
l_prt := 0
// 初始化右指针为数组长度减一
r_prt := n - 1
// 初始化答案数组,用于存储目标值的范围
ans := []int{-1, -1}
// 在左指针小于等于右指针的条件下进行循环
for l_prt <= r_prt {
// 计算中间位置的索引
mid := ((r_prt - l_prt) >> 1) + l_prt
// 如果中间位置的元素等于目标值
if nums[mid] == target {
// 更新答案的左边界和右边界为中间位置
ans[0] = mid
ans[1] = mid
// 从中间位置向左扩展,找到目标值的第一个位置
for ans[0] > 0 && nums[ans[0]-1] == target {
ans[0]--
}
// 从中间位置向右扩展,找到目标值的最后一个位置
for ans[1] < n - 1 && nums[ans[1] + 1] == target {
ans[1]++
}
// 跳出循环,因为已经找到了目标值的范围
break
} else if nums[mid] > target {
// 如果中间位置的元素大于目标值,缩小搜索范围到左半部分
r_prt = mid - 1
} else {
// 如果中间位置的元素小于目标值,缩小搜索范围到右半部分
l_prt = mid + 1
}
}
// 返回包含目标值范围的答案数组
return ans
}
Python
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 辅助函数:查找目标值的左边界
def find_left():
start, end = 0, len(nums) - 1
res = -1 # 初始化结果为-1,表示未找到
while start <= end:
mid = (start + end) // 2 # 计算中间位置的索引
if nums[mid] < target:
start = mid + 1 # 如果中间值小于目标值,缩小搜索范围到右半部分
elif nums[mid] > target:
end = mid - 1 # 如果中间值大于目标值,缩小搜索范围到左半部分
else:
res = mid # 找到目标值,更新结果为当前位置
end = mid - 1 # 继续向左搜索更左边的位置
return res
# 辅助函数:查找目标值的右边界
def find_right():
start, end = 0, len(nums) - 1
res = -1 # 初始化结果为-1,表示未找到
while start <= end:
mid = (start + end) // 2 # 计算中间位置的索引
if nums[mid] < target:
start = mid + 1 # 如果中间值小于目标值,缩小搜索范围到右半部分
elif nums[mid] > target:
end = mid - 1 # 如果中间值大于目标值,缩小搜索范围到左半部分
else:
res = mid # 找到目标值,更新结果为当前位置
start = mid + 1 # 继续向右搜索更右边的位置
return res
# 返回包含目标值范围的结果数组,左边界和右边界分别由辅助函数找到
return [find_left(), find_right()]
Java
class Solution {
public int[] searchRange(int[] nums, int target) {
// 获取数组的长度
int n = nums.length;
// 初始化左指针为0
int l_ptr = 0;
// 初始化右指针为数组长度减一
int r_ptr = n - 1;
// 初始化答案数组,用于存储目标值的范围
int[] ans = {-1, -1};
// 在左指针小于等于右指针的条件下进行循环
while (l_ptr <= r_ptr) {
// 计算中间位置的索引
int mid = (r_ptr - l_ptr) / 2 + l_ptr;
// 如果中间位置的元素等于目标值
if (nums[mid] == target) {
// 更新答案的左边界和右边界为中间位置
ans[0] = mid;
ans[1] = mid;
// 从中间位置向左扩展,找到目标值的第一个位置
while (ans[0] > 0 && nums[ans[0] - 1] == target) {
ans[0]--;
}
// 从中间位置向右扩展,找到目标值的最后一个位置
while (ans[1] < n - 1 && nums[ans[1] + 1] == target) {
ans[1]++;
}
// 跳出循环,因为已经找到了目标值的范围
break;
} else if (nums[mid] > target) {
// 如果中间位置的元素大于目标值,缩小搜索范围到左半部分
r_ptr = mid - 1;
} else {
// 如果中间位置的元素小于目标值,缩小搜索范围到右半部分
l_ptr = mid + 1;
}
}
// 返回包含目标值范围的答案数组
return ans;
}
}
Cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int ret; // 用于存储目标值的起始位置
int l = 0, r = nums.size() - 1; // 初始化左右指针,表示搜索范围
// 第一个循环:查找目标值的起始位置
while (l < r) {
int mid = (l + r) / 2; // 计算中间位置的索引
if (nums[mid] < target) {
l = mid + 1; // 如果中间值小于目标值,缩小搜索范围到右半部分
} else {
r = mid; // 否则,缩小搜索范围到左半部分
}
}
// 如果找到的位置超出数组范围或者该位置的值不等于目标值,返回{-1, -1}
if (l == nums.size() || nums[l] != target) {
return {-1, -1};
}
ret = l; // 记录目标值的起始位置
l = 0;
r = nums.size() - 1;
// 第二个循环:查找目标值的结束位置
while (l < r) {
int mid = (l + r + 1) / 2; // 计算中间位置的索引
if (nums[mid] > target) {
r = mid - 1; // 如果中间值大于目标值,缩小搜索范围到左半部分
} else {
l = mid; // 否则,缩小搜索范围到右半部分
}
}
// 返回包含目标值范围的结果数组,包括起始位置和结束位置
return {ret, l};
}
};
每个版本的所需基础知识:
Go 版本的基础知识:
-
切片(Slices):Go 语言中的切片是动态数组,非常常用于处理数组数据。在这个解决方案中,
nums
是一个整数切片,而ans
也是一个整数切片,用于存储目标值的范围。 -
循环和条件语句:解决方案中使用了
for
循环和if
条件语句来实现逻辑控制,例如,在二分搜索中不断缩小搜索范围。 -
二分探索: この問題の鍵は、二分探索アルゴリズムを使用することです。二分探索の基本原理を理解する必要があります。つまり、中間要素のサイズとターゲットの値を比較し、ターゲットが見つかるか、ターゲットが存在しないと判断されるまで、検索範囲が半分に縮小されます。
Python バージョンの基本:
-
リスト: Python のリストは配列に似ており、要素のセットを格納するために使用されます。このソリューションでは、
nums
は整数のリストであり、ans
ターゲット値の範囲を格納する整数のリストでもあります。 -
関数とモジュール
find_left
: 2 つの補助関数と がソリューションで定義されておりfind_right
、関数はコードを編成するために使用されます。List
また、モジュールへの型のインポートなど、Python モジュールの概念を理解する必要もありますtyping
。 -
ループと条件文: Go バージョンと同様、Python バージョンでも
for
ループとif
条件文を使用してロジック制御を実装します。 -
二分探索: 二分探索の基本原理と実装方法も理解する必要があります。
Java バージョンの基本:
-
配列: Java は配列を使用して要素のセットを格納します。このソリューションでは、
nums
は整数配列であり、ans
ターゲット値の範囲を格納する整数配列でもあります。 -
クラスとメソッド: Java バージョンでは、問題を解決する
Solution
メソッドを含むクラスを使用します。searchRange
クラスとメソッドを定義し、それらを呼び出す方法を知る必要があります。 -
ループと条件文: 他のバージョンと同様、Java バージョンでも
while
ループとif
条件文を使用して論理制御を実装します。 -
二分探索:二分探索の基本原理と実装方法を理解します。
C++ バージョンの基本:
-
ベクトル: C++ のベクトルは動的配列に似ており、要素のセットを格納するために使用されます。このソリューションでは、
nums
は整数のベクトルであり、ans
ターゲット値の範囲を格納する整数ベクトルでもあります。 -
クラスとメソッド: Java バージョンと同様に、C++ バージョンでは、問題を解決する
Solution
メソッドを含むクラスを使用します。searchRange
クラスとメソッドを定義し、それらを呼び出す方法を知る必要があります。 -
ループと条件ステートメント: 他のバージョンと同様、C++ バージョンでも
while
ループとif
条件ステートメントを使用してロジック制御を実装します。 -
二分探索:二分探索の基本原理と実装方法を理解します。
35. 挿入位置の検索
トピック
ソートされた配列とターゲット値を指定すると、ターゲットが見つかった場合はインデックスを返します。
そうでない場合は、順番に挿入された場合のインデックスを返します。
配列内に重複はないと想定できます。
例 1:
Input: [1,3,5,6], 5
Output: 2
例 2:
Input: [1,3,5,6], 2
Output: 1
例 3:
Input: [1,3,5,6], 7
Output: 4
例 4:
Input: [1,3,5,6], 0
Output: 0
トピックの一般的な考え方
ソートされた配列とターゲット値を指定すると、配列内でターゲット値を検索し、そのインデックスを返します。ターゲット値が配列内に存在しない場合は、その値が順番に挿入される位置を返します。
配列内に重複する要素はないと仮定できます。
問題解決のアイデア
各バージョンの問題解決のアイデアは次のとおりです。
Go バージョンの問題解決のアイデア:
-
2 つのポインター
low
とを定義しhigh
、それぞれ配列の先頭と末尾を指します。 -
ループを使用して二分探索を実行します。
- 中間要素のインデックスを計算するには
mid
、(low + (high - low) >> 1)
Calculate を使用します。 - 中央の要素が
nums[mid]
ターゲット値以上の場合はtarget
、high
ポインタを に移動しmid - 1
、左半分で検索が継続されることを示します。 - それ以外の場合、中央の要素がターゲット値より小さい場合
target
:- 中央の要素の次の要素がターゲット値以上であるかどうか、
target
または配列の末尾に到達しているかどうかをチェックします。 - そうであれば、ターゲット値を現在位置の後に挿入する必要があることを示し、
mid + 1
挿入位置のインデックスとして返されます。 - それ以外の場合は、
low
ポインタを に移動して、mid + 1
右半分の検索を続けます。
- 中央の要素の次の要素がターゲット値以上であるかどうか、
- 中間要素のインデックスを計算するには
-
ループの最後でも適切な位置が見つからない場合は、ターゲット値を配列の先頭に挿入する必要があることを意味し、0 が返されます。
Python バージョンの問題解決のアイデア:
-
2 つのポインター
low
とを定義しhigh
、それぞれ配列の先頭と末尾を指します。 -
while
ループを使用して二分探索を実行します。- 中間要素のインデックスを計算するには
mid
、(low + (high - low) // 2)
Calculate を使用します。 - 中央の要素が
nums[mid]
ターゲット値以上の場合はtarget
、high
ポインタを に移動しmid - 1
、左半分で検索が継続されることを示します。 - それ以外の場合、中央の要素がターゲット値より小さい場合
target
:- 中央の要素の次の要素がターゲット値以上であるかどうか、
target
または配列の末尾に到達しているかどうかをチェックします。 - そうであれば、ターゲット値を現在位置の後に挿入する必要があることを示し、
mid + 1
挿入位置のインデックスとして返されます。 - それ以外の場合は、
low
ポインタを に移動して、mid + 1
右半分の検索を続けます。
- 中央の要素の次の要素がターゲット値以上であるかどうか、
- 中間要素のインデックスを計算するには
-
ループの最後でも適切な位置が見つからない場合は、ターゲット値を配列の先頭に挿入する必要があることを意味し、0 が返されます。
Java バージョンのソリューションのアイデア:
-
2 つのポインター
low
とを定義しhigh
、それぞれ配列の先頭と末尾を指します。 -
while
ループを使用して二分探索を実行します。- 中間要素のインデックスを計算するには
mid
、(low + (high - low) / 2)
Calculate を使用します。 - 中央の要素が
nums[mid]
ターゲット値以上の場合はtarget
、high
ポインタを に移動しmid - 1
、左半分で検索が継続されることを示します。 - それ以外の場合、中央の要素がターゲット値より小さい場合
target
:- 中央の要素の次の要素がターゲット値以上であるかどうか、
target
または配列の末尾に到達しているかどうかをチェックします。 - そうであれば、ターゲット値を現在位置の後に挿入する必要があることを示し、
mid + 1
挿入位置のインデックスとして返されます。 - それ以外の場合は、
low
ポインタを に移動して、mid + 1
右半分の検索を続けます。
- 中央の要素の次の要素がターゲット値以上であるかどうか、
- 中間要素のインデックスを計算するには
-
ループの最後でも適切な位置が見つからない場合は、ターゲット値を配列の先頭に挿入する必要があることを意味し、0 が返されます。
C++ バージョンの問題解決のアイデア:
-
2 つのポインター
low
とを定義しますhigh
。これらはそれぞれ、ベクトル (動的配列) の開始と終了を指します。 -
while
ループを使用して二分探索を実行します。- 中間要素のインデックスを計算するには
mid
、(low + (high - low) / 2)
Calculate を使用します。 - 中央の要素が
nums[mid]
ターゲット値以上の場合はtarget
、high
ポインタを に移動しmid - 1
、左半分で検索が継続されることを示します。 - それ以外の場合、中央の要素がターゲット値より小さい場合
target
:- 中間要素の次の要素がターゲット値以上であるかどうか、
target
またはベクトルの終端に到達しているかどうかをチェックします。 - そうであれば、ターゲット値を現在位置の後に挿入する必要があることを示し、
mid + 1
挿入位置のインデックスとして返されます。 - それ以外の場合は、
low
ポインタを に移動して、mid + 1
右半分の検索を続けます。
- 中間要素の次の要素がターゲット値以上であるかどうか、
- 中間要素のインデックスを計算するには
-
ループの最後でも適切な位置が見つからない場合は、ターゲット値をベクトルの先頭に挿入する必要があり、0 が返されることを意味します。
low
一般に、問題解決のアイデアの各バージョンは、とポインタを常に更新することによってhigh
ターゲット値の挿入位置に近づく、二分探索アルゴリズムの変形に基づいています。プログラミング言語によって構文やデータ構造の詳細は異なりますが、基本的な考え方は同じです。
コード
行く
func searchInsert(nums []int, target int) int {
// 定义两个指针,low 指向数组的起始位置,high 指向数组的末尾位置
low, high := 0, len(nums)-1
// 使用循环执行二分查找
for low <= high {
// 计算中间元素的索引
mid := low + (high-low)>>1
// 如果中间元素大于等于目标值
if nums[mid] >= target {
// 将 high 指针移动到中间元素的前一个位置
high = mid - 1
} else {
// 如果中间元素小于目标值
// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾
if (mid == len(nums)-1) || (nums[mid+1] >= target) {
// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引
return mid + 1
}
// 否则,将 low 指针移动到中间元素的下一个位置
low = mid + 1
}
}
// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
return 0
}
パイソン
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置
low, high = 0, len(nums) - 1
# 使用循环执行二分查找
while low <= high:
# 计算中间元素的索引
mid = low + (high - low) // 2
# 如果中间元素大于等于目标值
if nums[mid] >= target:
# 将high指针移动到中间元素的前一个位置
high = mid - 1
else:
# 如果中间元素小于目标值
# 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾
if mid == len(nums) - 1 or nums[mid + 1] >= target:
# 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引
return mid + 1
# 否则,将low指针移动到中间元素的下一个位置
low = mid + 1
# 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
return 0
ジャワ
class Solution {
public int searchInsert(int[] nums, int target) {
// 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置
int low = 0, high = nums.length - 1;
// 使用循环执行二分查找
while (low <= high) {
// 计算中间元素的索引
int mid = low + (high - low) / 2;
// 如果中间元素大于等于目标值
if (nums[mid] >= target) {
// 将high指针移动到中间元素的前一个位置
high = mid - 1;
} else {
// 如果中间元素小于目标值
// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾
if (mid == nums.length - 1 || nums[mid + 1] >= target) {
// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引
return mid + 1;
}
// 否则,将low指针移动到中间元素的下一个位置
low = mid + 1;
}
}
// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
return 0;
}
}
CPP
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
// 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置
int low = 0, high = nums.size() - 1;
// 使用循环执行二分查找
while (low <= high) {
// 计算中间元素的索引
int mid = low + (high - low) / 2;
// 如果中间元素大于等于目标值
if (nums[mid] >= target) {
// 将high指针移动到中间元素的前一个位置
high = mid - 1;
} else {
// 如果中间元素小于目标值
// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾
if (mid == nums.size() - 1 || nums[mid + 1] >= target) {
// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引
return mid + 1;
}
// 否则,将low指针移动到中间元素的下一个位置
low = mid + 1;
}
}
// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
return 0;
}
};
コードの各バージョンを理解して作成するときは、次の基本を習得する必要があります。
- Goのバージョン:
- スライス: 配列とスライスは Go において非常に重要なデータ構造です。スライスは、同じタイプの要素のシーケンスを格納するために使用される動的配列です。
- ループと条件文
for
: コード内にループとif
条件判定を記述するには、Go でのループと条件文の使用法を理解する必要があります。 - 関数: 関数の定義と呼び出し方法、およびそのパラメータと戻り値を知る必要があります。
- 二分探索アルゴリズム: このコードを理解するには、二分探索アルゴリズムがどのように機能するかを理解することが重要です。
- Python のバージョン:
- リスト: Python のリストは、複数のデータ型を格納できる順序付けされたデータ構造です。この問題では、整数要素を格納するために list が使用されます。
- ループと条件文
while
: コードでループや条件判定を記述するには、 Python でのループとif
条件文の使い方を理解する必要があります。 - クラスとメソッド: Python で
class
メソッドとメソッドを定義する方法。このバージョンでは、コードはクラスにカプセル化されます。 - 二分探索アルゴリズム: このコードを理解するには、二分探索アルゴリズムがどのように機能するかを理解することが重要です。
- Java バージョン:
- 配列: Java の配列は静的データ構造であるため、配列要素の宣言、初期化、アクセス方法を知る必要があります。
- ループと条件文
while
: コードでループや条件判定を記述するには、 Java でループとif
条件文を使用する方法を理解する必要があります。 - クラスとメソッド: Java でクラスとメソッドを定義する方法。このバージョンでは、コードはクラスにカプセル化されます。
- 二分探索アルゴリズム: このコードを理解するには、二分探索アルゴリズムがどのように機能するかを理解することが重要です。
- C++ バージョン:
- ベクトル: C++ のベクトルは、Python のリストや Go のスライスに似た動的配列です。
- ループと条件文
while
: コードでループと条件if
判断を記述するには、C++ でのループと条件文の使用法を理解する必要があります。 - クラスとメソッド: C++ でクラスとメソッドを定義する方法。このバージョンでは、コードはクラスにカプセル化されます。
- 二分探索アルゴリズム: このコードを理解するには、二分探索アルゴリズムがどのように機能するかを理解することが重要です。
一般に、どのバージョンのプログラミング言語を選択する場合でも、配列、ループ、条件文、および二分探索アルゴリズムに精通している必要があります。さらに、コードを正しく記述して理解できるようにするには、特定のプログラミング言語の構文と標準ライブラリ関数を理解することも非常に重要です。特定の言語に慣れていない場合は、対応するバージョンを理解してコーディングする前に、言語の基本を学ぶことをお勧めします。