オファーを指す - 配列と文字列


剣はオファー04を指します。二次元配列で検索

n * m2D 配列:

  • 各行は左から右へ非降順でソートされます。
  • 各列は上から下へ非降順でソートされます。
    効率的な関数を完成させて、このような2 次元配列と整数を入力し、配列に整数が含まれているかどうかを判断してください。

例:

  • 既存の行列行列は次のとおりです。
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

与えられた場合target = 5、 を返しますtrue
与えられた場合target = 20、返すfalse

制限:
0 <= n <= 1000
0 <= m <= 1000


コード

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        row=len(matrix)-1
        col=0
        while( row>=0 and col<len(matrix[0]) ):
            if(matrix[row][col]==target):
                return True
            else:
                if(matrix[row][col]>target):
                    row=row-1
                else:
                    col=col+1
        return False

問題解決計画+アイデア

  • タグ: 配列トラバース
  • 行列の左下から上にある数字はそれより小さく、右にある数字はそれより大きいので、この規則に従って数字が存在するかどうかを判断します
    • 現在の数値を としcur、目標の数値を とします。target
      • target < curのとき、cur はその上の数値に更新されます
      • target > curのとき、cur はその右側の数字に更新されます
    • 等しくなるまで戻り、等しくない場合は行列の境界true戻りますfalse
  • 時間計算量:O(m+n)

アルゴリズムのステップ

ここに画像の説明を挿入

その他の実装アイデア - 二分探索

  • 順序を見ると、最初の反応は二分探索です。最も直接的な方法は、行ごとに二分探索を実行することです。

また、秩序ある性質と相まって、状況によっては早期に終了する可能性があります。

  • たとえば、行の最初の要素がターゲットより大きい場合、現在の行と後続のすべての行は無視され、直接 false が返されます。
  • 行の最後の要素がターゲットより小さい場合、現在の行は考慮されず、次の行が置き換えられます。

この質問では、「各行の最初の整数が前の行の最後の整数より大きい」ことが保証されないため、「2 回の二分法」アプローチを取ることはできません。

  • 唯一の解決策は、行/列を走査し、列/行を 2 つに分割することです。

時間計算量の観点から言えば、m 行 n 列であれば、そうですO(mlog(n))

import numpy as np

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        matrix = np.array(matrix)
        row = matrix.shape[0]
        col = matrix.shape[1]

        flag =0
        for row in matrix:
            left = 0
            right = col-1
            while(left<=right):
                mid = (left+right+1)/2
                if row[mid]<target:
                    left=mid+1
                elif row[mid]>target:
                    right=mid-1
                else:
                    return True
        return False

その他の実装アイデア_変形した二分法

二分法の考え方は、ターゲット値と中間値を比較し、要素の半分を破棄できるというものです。

  • この質問はマトリックスです
  • 行列の中心が見つかったら、ターゲット値と比較して、一部の要素を破棄できるかどうかを確認します。
如下图,中心位置是 9
[1,   4,  7, 11, 15],
[2,   5,  8, 12, 19],
[3,   6, /9/,16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]

通过中心位置, 我们可以把原矩形分成四个矩形, 左上, 右上, 左下, 右下
[1,   4,  7   [11, 15  
 2,   5,  8    12, 19  
 3,   6, /9/]  16, 22] 
 
[10, 13, 14   [17, 24
[18, 21, 23]   26, 30]

如果 target = 10,
此时中心值小于目标值,左上角矩形中所有的数都小于目标值,我们可以丢弃左上角的矩形,继续从剩下三个矩形中寻找

如果 target = 5,
此时中心值大于目标值,右下角矩形中所有的数都大于目标值,那么我们可以丢弃右下角的矩形,继续从剩下三个矩形中寻找

要素を破棄する原理を見つけたので、コードを書くことができます。

ここで、長方形は左上隅と右下隅の座標で表され、分割された長方形の座標は次の図のようになります。
ここに画像の説明を挿入

再帰的に書くこともできますが、

  • 再帰的終了の場合、行列の要素が 1 つだけの場合は、現在の要素がターゲット値であるかどうかを直接判断すれば十分です。

分割する際に境界線を越える可能性もあります。

  • たとえば、元の行列には 1 行しかなく、左下隅と右下隅の行列は実際には存在しません。
  • 上記の座標式に従って計算した後、範囲外かどうかを判断する必要があります。


Sword Pointer Offer 05. スペースを置き換える

トピックの説明

s文字列内の各スペースを「%20」に置き換える関数を実装してください。

例 1:

  • 入力: s = 「私たちは幸せです。」
  • 出力: 「私たちは %20 %20 幸せです。」

制限:
0 <= s 的长度<= 10000


コード

Pythonの簡単な方法:

class Solution:
    def replaceSpace(self, s: str) -> str:
        return "%20".join(s.split(' '))

Python メソッド:

class Solution {
    
    
public:
    string replaceSpace(string s) {
    
    
        for(int i = 0; i < s.length(); i++){
    
    
            if(s.find(" ") == i){
    
    
                s.erase(i, 1);
                s.insert(i, "%20");
            }
        }
        return s;
    }
};

class Solution(object):
    def replaceSpace(self, s):
        """
        :type s: str
        :rtype: str
        """
        s1=""
        for c in s:
            if c == ' ': 
                s1 += '%20'
            else:
                s1 +=c
        return s1

C++:

class Solution {
    
    
public:
    string replaceSpace(string s) {
    
    
        for(int i = 0; i < s.length(); i++){
    
    
            if(s.find(" ") == i){
    
     # 查找到空格所在的位置
                s.erase(i, 1); # 先清除空格所占的一个字符
                s.insert(i, "%20"); # 在该位置插入%20
            }
        }
        return s;
    }
};

問題解決計画+アイデア

  • ラベル:文字列
  • 最も簡単な解決策は、当然のことながら、ライブラリ関数を直接使用することです。もちろん、タイトルは決してこれを望んでいるわけではありません。
  • 新しい文字列を追加し、走査プロセス中に 元の文字列を走査します。
    • スペースでない場合は、元の文字が新しい文字列に直接結合されます。
    • スペースが見つかった場合は、%20新しい文字列に結合されます
  • 時間計算量: O(n)、空間計算量:O(n)

アルゴリズムのステップ

ここに画像の説明を挿入

方法の概要

方法 1: トラバースと追加
Python や Java などの言語では、文字列は「不変」型として設計されています。つまり、文字列の特定の文字を直接変更することはできず、それを実装するには新しい文字列を作成する必要があります。 。

アルゴリズムの流れ:

  1. ;として示されるリスト (Python) / StringBuilder (Java) を初期化しますres
  2. sリスト内の各文字を反復処理しますc
    • cがスペースの場合:res後ろに文字列を追加します"%20"
    • cがスペースでない場合: ;resの後に文字を追加します。c
  3. リストをres文字列に変換して返します。

複雑さの分析:

  • 時間計算量 O(N): O(N) はトラバーサルに使用され、O(1) は文字の追加 (変更) 操作の各ラウンドに使用されます。
  • スペースの複雑さ O(N): Python の新しいリストと Java の新しい StringBuilder はどちらも線形サイズの追加スペースを使用します。

ここに画像の説明を挿入

方法 2: インプレース変更
C++ 言語では、文字列は「変数」型 (参照) として設計されているため、新しい文字列を作成することなくインプレース変更が実現できます。

  • スペースを「%20」に置き換える必要があるため、文字列の総文字数が増加し、元の文字列 s の長さを長くする必要があります。
  • 計算式は次のとおりです。新しい文字列の長さ = 元の文字列の長さ + 2 * スペースの数、
  • 以下の図に例を示します。
    ここに画像の説明を挿入
    アルゴリズムの流れ:
  1. 初期化: スペースの数count、文字列 s の長さlen
  2. 空白の数を数えます。空白が見つかった場合は s を走査しますcount++
  3. s の長さを変更します。「%20」を追加した後の文字列の長さはlen + 2 * count;になります。
  4. 逆順トラバーサル変更:i 元の文字列の末尾要素を指し、j新しい文字列の末尾要素を指します。
    • i = j の場合に飛び出します (左側にスペースがないため、トラバースを続ける必要がないことを意味します)。
    • s[i] がスペースでない場合: 実行しますs[j] = s[i]
    • s[i]がスペースの場合:[j-2, j]文字列の閉区間の要素を に変更します"%20"; 3つの要素が変更されているので、 が必要ですj -= 2
  5. 戻り値: 変更された文字列s

ここに画像の説明を挿入
複雑さの分析:

  • 時間計算量 O(N): トラバーサル統計とトラバーサル変更は両方とも O(N) 時間を使用します。
  • スペース複雑さ O(1): s の長さがその場で拡張されるため、O(1) 個の余分なスペースが使用されます。
class Solution {
    
    
public:
    string replaceSpace(string s) {
    
    
        int count = 0, len = s.size();
        // 统计空格数量
        for (char c : s) {
    
    
            if (c == ' ') count++;
        }
        // 修改 s 长度
        s.resize(len + 2 * count);
        // 倒序遍历修改
        for(int i = len - 1, j = s.size() - 1; i < j; i--, j--) {
    
    
            if (s[i] != ' ')
                s[j] = s[i];
            else {
    
    
                s[j - 2] = '%';
                s[j - 1] = '2';
                s[j] = '0';
                j -= 2;
            }
        }
        return s;
    }
};


剣はオファー 11 を指します。回転配列の最小数 - 解決策

トピックの説明

配列の最初のいくつかの要素を配列の末尾に移動することを、配列の回転と呼びます。

  • 昇順にソートされた配列の回転を入力し、回転された配列の最小要素を出力します。
  • たとえば、配列 [3,4,5,1,2] は、最小値 1 を持つ [1,2,3,4,5] の回転です。
  • [a[0], a[1], a[2], ..., a[n-1]]配列を 1 回回転した結果が配列になることに注意してください[a[n-1], a[0], a[1], a[2], ..., a[n-2]]

例 1:

  • 入力: [3,4,5,1,2]
  • 出力: 1

例 2:

  • 入力: [2,2,2,0,1]
  • 出力: 0

ヒント:

  • n ==numbers.length
  • 1 <= n<= 5000
  • -5000 <= numbers[i]<= 5000
  • 数値は 1 ~ n 回転の昇順にソートされた配列であることが判明します

コード

  • Pythonトラバーサル
class Solution:
    def minArray(self, numbers: List[int]) -> int:
            j=numbers[0]
            for i in range(len(numbers)):
                if j<=numbers[i]:
                    j=j
                else:
                    j=numbers[i]
            return j

  • 二分法
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] > nums[right]: left = mid + 1
            elif nums[mid] < nums[right]: right = mid
            else: right = right - 1 # key
        return nums[left]

問題解決計画+アイデア

  • タグ:二分探索
  • 全体のアイデア:
    • まず、配列は順序付けられた配列を回転させたものであり、この条件から配列が通常のサイズであることがわかります
    • 二分検索を使用すると、既存のルールを使用して結果を迅速に見つけることができます。
  • 時間計算量: O(logn)、空間計算量: O(1)

アルゴリズムのステップ

  1. 添え字を初期化しleftright
  2. 中央の添字が計算されるたびにmid = (right + left) / 2、ここでの除算は四捨五入演算となり、小数点は表示されません。
  • numbers[mid] < numbers[right]の場合、最小値が[left, mid]​区間内にあることを意味し、right = midそれを次の計算に使用します。
  • の場合numbers[mid] > numbers[right]、最小値が[mid, right]​間隔内にあることを意味し、left = mid + 1それを次の計算ラウンドに使用します。
  • 【注意】 の場合numbers[mid] == numbers[right]、最小値がどの区間にあるのか判断できません。この時は区間を絞っright--次のラウンドで判断しましょう

なぜleft++ ではなくright-- が縮小するのでしょうか?

  • 配列は昇順であるため、最小値は右ではなく左に近くなければなりません。
  • たとえば、[1,2,2,2,2]、left = 0、right = 4、mid = 2 の場合、値はnumbers[mid] == numbers[right]この条件を満たしますが、left++ の場合、最小値は見つかりません。

ここに画像の説明を挿入

アルゴリズムの考え方(2つのポイント)

  • 回転ソート配列は、nums2 つのソート配列 nums1、nums2、および nums1 の任意の要素 >= nums2 の任意の要素に分割できます。したがって、2 つの配列の分割点 nums[i] (つまり、 2 番目の配列 ) の最初の要素。
  • 左ポインタと右ポインタを nums 配列の両端に設定します。mid は各二等分の中点です。
    • nums[mid] > nums[right]の場合、mid は最初にソートされた配列内になければならず、iii は Mid < i <= right を満たす必要があるため、 left = Mid + 1; を実行します。
    • このnums[mid] < nums[right]とき、mid は 2 番目にソートされた配列内になければならず、iii は left < i <= Mid を満たす必要があるため、right = Mid; を実行します。
    • nums[mid] == nums[right]当時 は、この問題を153問と比較するのが難しかった(理由は、この問題は配列の要素が重複する可能性があり、分割点iiiのポインタ間隔の判断が難しいため)。
      • たとえば、[1,0,1,1,1] の場合、left = 0、right = 4、mid = 2 の場合、ソートされた配列 Midmidmid がどの配列に含まれるかを判断することはできません。
      • この問題は次のことを証明することで解決しますright = right - 1
        1. この操作では配列が範囲外になることはありません。反復条件によって right > left >= 0 が保証されるためです。
        2. この操作では最小値は失われません。nums[right] が最小値であると仮定すると、次の 2 つのケースがあります。
          • nums[right] が唯一の最小値の場合:mid < right (left != right and mid = (left + right) // 2 であるため、判定条件 nums[mid] == nums[right] を満たすことができません。小数点以下切り捨て);
          • nums[right] が唯一の最小値ではない場合、mid < right および nums[mid] == nums[right] であるため、つまり、[left,right−1] 区間にはまだ最小値が存在します。最小値は失われません。

ここに画像の説明を挿入

上記は理論的な分析であり、思考を助けるために次の配列に代入できます:
[1,2,3]
[1,1,0,1]
[1,0,1,1,1]
[1,1] ,1,1]

  • 時間計算量 O(logN)、特殊な場合 ([1,1,1,1] など) では O(N) に縮退します。



Jianzhi Offer 17. 1 から最大までの n 桁の数を出力します。

トピックの説明

数値 n を入力し、1 から最大の n までの 10 進数を順番に出力します。

  • たとえば、「3」と入力すると、1、2、3 が最大 3 桁の数字 999 まで出力されます。

例 1:

  • 入力:n = 1
  • 出力:[1,2,3,4,5,6,7,8,9]

例証します:

  • 出力する代わりに整数のリストを返す
  • nは正の整数です

解決

アイデア 1

ラベル: Array
全体的なアイデア:

  1. まず、出力する数値の範囲を見つけます。
  2. 次に、1 から最大の数値まで出力を開始します
    。 時間計算量: O( 1 0 n 10 ^n1 0n )、空間計算量: O(1 0 n 10 ^n1 0n )

アルゴリズム処理

  1. 初期化sum = 1
  2. ループして 10 を乗算し、合計を境界値にします
  3. サイズが次の新しい res 配列を作成します。sum-1
  4. 1 から sum-1 までを出力します
class Solution(object):
    def printNumbers(self, n):
        """
        :type n: int
        :rtype: List[int]
        """
        max = 10**(n)
        list=[]
        for i in range(1,max):
            list.append(i)
        
        return list

アイデア 2

ラベル:文字列
全体的なアイデア:

  • 元の質問の意味は、実際には大きな数値の計算を調査することです。int 配列はオーバーフローする可能性があるため、文字列処理を使用すると、オーバーフローしないことを保証できます
  • しかし、戻り値は int 配列として指定されているため、実際には戻り値の観点からは絶対にオーバーフローすることはなく、非常に矛盾しています。
  • そこでアイデア 2 を与えます。文字列を使用して大きな数値を処理する方法を学ぶだけです。オーバーフローの時間の複雑さについては心配しないでください
    。 O( 1 0 n 10 ^n1 0n )、空間計算量: O(1 0 n 10 ^n1 0n )

アルゴリズム処理

  1. string をstr初期化します。その初期値はn-1です。 "0"
  2. 文字を使用してstr をインクリメントします。
    • インクリメント処理中にキャリーの有無を判定しキャリーがある場合はキャリーを+1し
    • 最大値に達するまでループを終了する
  3. 各値を取得した後、先頭の冗長な「0」をたどり、冗長な「0」を削除します。
    • int に変換して結果の配列に格納します

ここに画像の説明を挿入


大数問題!】実は、この問題の主なテストポイントは、大きな数が境界を越えたときの出力です。
次の 3 つの問題に対処する必要があります。

  1. 大きな数値を表す変数の型:

    • どの変数タイプであってもshort / int / long数値の値の範囲は制限されています
    • したがって、大きな数値の表現はString 型である必要があります。
  2. 数値の文字列セットを生成します

    • この型を使用する場合int、次の数値は+1によってラウンドごとに生成できますが、これはString 型には適用できません
    • また、String 型の数値は桁上げの効率が悪く、例えば「9999」から「10000」までは 1 から 1000 までの判定を 4 回行う必要があります。
    • n生成されたリストは実際にはビット 000 ~ 999 の完全な配列であることが観察からわかるため、桁上げ操作を回避でき、数値の文字列リストを再帰的に生成できます
  3. 完全な順列を再帰的に生成します

    • 分割統治アルゴリズムの考え方に基づいて、最初に上位ビットを修正し、次に下位ビットに再帰的に戻り、1 のビットが修正されたら、数値の文字列を追加します。
    • たとえばn=2、 (数値範囲が 1−991 - 991−99 )、10の位が 000 - 999 に固定され、順次再帰が開始されると、1の位が 000 - 999 に固定され、再帰が終了します。そしてデジタル文字列が追加されます。

ここに画像の説明を挿入
上記の方法に従って、完全な配置コードを最初に記述することができます。

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n: # 终止条件:已固定完所有位
                res.append(''.join(num)) # 拼接 num 并添加至 res 尾部
                return
            for i in range(10): # 遍历 0 - 9
                num[x] = str(i) # 固定第 x 位为 i
                dfs(x + 1) # 开启固定第 x + 1 位
        
        num = ['0'] * n # 起始数字定义为 n 个 0 组成的字符列表
        res = [] # 数字字符串列表
        dfs(0) # 开启全排列递归
        return ','.join(res)  # 拼接所有数字字符串,使用逗号隔开,并返回

この方法では、数値の文字列がカンマで区切られて長い文字列が形成されます
返される数字セット文字列は次のようになります。

输入:n = 1
输出:"0,1,2,3,4,5,6,7,8,9"

输入:n = 2
输出:"00,01,02,...,10,11,12,...,97,98,99"

输入:n = 3
输出:"000,001,002,...,100,101,102,...,997,998,999"

観察の結果、現在の生成方法にはまだ次の問題があることがわかりました。

  1. 00,01,02,⋯,⋯ などは 0,1,2,⋯ と表示されます。
    • つまり、上位の冗長な 0 は削除する必要があります
  2. このメソッドは0から始まり、質問リクエストリストは1から始まります。

上記 2 つの問題の解決策は次のとおりです。

  1. 上位ビットから余分な 0 を削除します。
  • 文字列の左境界の定義:

    • 変数を宣言すると、追加される数値文字列に上位の冗長な 0 が存在しないように、文字列の左側の境界startが指定されます。num[start:]
    • 例えば、n=2の場合、1〜9の場合はstart=1、10〜99の場合はstart=0となります。
  • 左境界開始の変化則:

    • 観察すると、出力数値のすべての桁が 9 の場合、次の桁は 1 を上位桁に繰り上げる必要があり、このとき、左境界の開始を 1 減らす必要があることがわかります (つまり、上位桁の余分な 0 は 1 減らす必要があります)。
    • たとえば、n=3 (数値範囲 1 ~ 999) の場合、左側の境界開始を 1 つ減らす必要があります。「009」は「010」に、「099」は「100」になります。
    • 各桁の 999 の数を 9 とすると、全桁が 9 であるという判定条件は、次式で表すことができます。
      n − start = ninen−start = ninen始めましょ_ _=ナイン_
  • 統計的nine手法:

    • 固定xxxビットの場合i = 9 の場合、i=9=9 则执行 n i n e = n i n e + 1 nine=nine+1 ナイン_=ナイン_+1
    • そして、バックトラックする前にnine = nine − 1 nine=nine−1を復元します。ナイン_=ナイン_1
  1. リストは 1 から始まります。
  • 上記の方法に基づき、デジタル列を追加する前に「0」かどうかを判定し、「0」の場合はスキップします

ここに画像の説明を挿入
複雑さの分析:

  • 時間計算量O ( 1 0 n ) O(10^n)O ( 1 0n ): 再帰的に生成される順列の数は1 0 n 10^n1 0n
  • 空間計算量O ( 1 0 n ) O(10^n)O ( 1 0n ): 結果リスト res の長さは1 0 n − 1 10^n - 11 0n1、各デジタル文字列の長さの間隔は1 、 2 、...、 n 1、2、...、n1 2 ... nなので、O ( 1 0 n ) O(10^n) がO ( 1 0n )の追加スペース。

大きな数値を正しく表すために、次のコードの戻り値は、一連の数値文字列を連結した長い文字列になります

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n:
                s = ''.join(num[self.start:])
                if s != '0': res.append(s)
                if n - self.start == self.nine: self.start -= 1
                return
            for i in range(10):
                if i == 9: self.nine += 1
                num[x] = str(i)
                dfs(x + 1)
            self.nine -= 1
        
        num, res = ['0'] * n, []
        self.nine = 0
        self.start = n - 1
        dfs(0)
        return ','.join(res)

この質問では、int 型の配列の出力が必要です。

  • 渡すには、数値文字列 s を追加する前に int に変換します

コードは次のようになります。

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n:
                s = ''.join(num[self.start:])
                if s != '0': res.append(int(s))
                if n - self.start == self.nine: self.start -= 1
                return
            for i in range(10):
                if i == 9: self.nine += 1
                num[x] = str(i)
                dfs(x + 1)
            self.nine -= 1
        
        num, res = ['0'] * n, []
        self.nine = 0
        self.start = n - 1
        dfs(0)
        return res


[その他] 完全順列解の簡易版

大きな数値の場合は、long 型で扱えない場合でも string に格納する必要があります

  • この問題では、実際には「09」という数字の全配列と、0~9のn桁の全配列になりますが、数字の先頭に0があってはならないことに注意してください。

完全な配置について簡単に説明します。たとえば、数字 1、2、および 3 の完全な配置は次のとおりです。

123、132、213、231、312、321

テストに合格できるようにするには、最後に文字列形式を int 形式に変更します。実際には、文字列配列が返される必要があります。

具体的な手順は以下の通りです

  1. 数字の先頭の0 0を避けるため0first最初の桁を最初に固定します。first値の範囲は1 9 1~91 9 
  2. digit生成される数値を表す桁数を使用します。この質問は1 1から始める必要があります。nnまで1桁生成n桁の場合、最初の桁が各桁に対して生成されるため、二重の for ループ
  3. 最初の数字を生成した後、再帰的に入力して残りの数字を生成します( 0 ~ 9 0 ~ 9)digit - 10 9の値
  4. 再帰の終了条件digitは、桁数が生成されたこと、つまりindex == digitこのときの数値 num をint に変換して結果の res に加算することです。
class Solution:
    def printNumbers(self, n: int) -> List[int]:
        def dfs(index, num, digit):
            if index == digit:
                res.append(int(''.join(num)))
                return
            for i in range(10):
                num.append(str(i))
                dfs(index + 1, num, digit)
                num.pop()

        res = []
        for digit in range(1, n + 1):
            for first in range(1, 10):
                num = [str(first)]
                dfs(1, num, digit)
        
        return res




Jianzhi Offer 21. 奇数が偶数の前になるように配列の順序を調整します。

トピックの説明

整数の配列を入力し、配列内の数値の順序を調整する関数を実装します

  • すべての奇数が配列の前半にあるように
  • すべての偶数は配列の後半にあります

例:

  • 入力:nums = [1,2,3,4]
  • 出力:[1,3,2,4]
    • 注:[3,1,2,4] これも正解の 1 つです。

ヒント:

  • 1 <= nums.length<= 50000
  • 1 <= nums[i]<= 10000

解決

一連の考え

  • タグ:ダブルポインター
  • 全体のアイデア:
    • まず前方ポインタstart後方ポインタを指定しますend
    • 次に、前部のポインタが偶数を見つけ、後部のポインタが奇数を見つけます。
    • 配置後、配列の走査が完了するまで 2 つの値を交換します
  • 時間計算量: O(n)、空間計算量:O(1)

アルゴリズム処理

  1. フロントポインタstart = 0バックポインタを初期化します。end = nums.length - 1
  2. このstart < end時点では、配列がまだ走査されていないことを意味し、奇数と偶数の交換を続けます。
  3. nums[start]が奇数の場合は、奇数でない添え字が見つかるまでstart++です。
  4. nums[end]が偶数の場合は、偶数以外の添え字が見つかるend--まで です。
  5. 交換nums[start]し、次の交換ラウンドにnums[end]進みます
  6. nums交換の結果である返品

ここに画像の説明を挿入

  • 通常のトラバース
class Solution(object):
    def exchange(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        List1 = []
        List2 = []
        for i in nums:
            if (i % 2) == 0:
                List1.append(i)
            else:
                List2.append(i)

        List2.extend(List1)

        return List2
  • ダブルポインタ方式
class Solution(object):
    def exchange(self, nums):
        start = 0
        end = len(nums)-1
        
        while(start < end):
            while(nums[start] %2 != 0) and start < end:
                start+=1
            while(nums[end]%2 ==0)and start < end:
                end-=1
            tmp = nums[start]
            nums[start] = nums[end]
            nums[end] = tmp
            start+=1
            end-=1
        
        return nums

問題解決のアイデア (ダブルポインタ):

ダブル ポインター iii、jjj を定義して配列の左端と右端を並べ替え、ループで実行することを検討してください。

  1. ポインタii 偶数を左から右に探します
  2. ポインタjjj は 右から左に奇数を探します
  3. nums [ i ] nums[i]も実行されますnu m s [ i ]と奇数nums [ j ] nums[j]num s [ j ]交換 _

===> 常に次のことを保証します: ポインタiii の左辺は奇数、ポインタjjj の右辺は偶数です。

ここに画像の説明を挿入

アルゴリズムの流れ:

  1. 初期化: i 、 j はダブル ポインターで、それぞれ配列 nums の左端と右端を指します。

  2. 循環交換:i = ji = jの場合=jで飛び出します。

    • ポインタ i が奇数に遭遇した場合、i = i + 1 i=i+1を実行します。=+1偶数がまでスキップします
    • ポインタ j が偶数に遭遇すると、j = j − 1 j=j−1を実行します。j=j1奇数がまでスキップします
    • スワップnums[i]nums[j]値。
  3. 戻り値: 変更された nums 配列を返します。

ここに画像の説明を挿入
複雑さの分析:

  • 時間計算量O ( N ) O(N)O ( N )NNNは配列の長さ nums、ダブル ポインタi、ji、jj は配列全体をまとめて反復します。
  • 空間複雑度O ( 1 ) O(1)O ( 1 ) : ダブルポインタi、ji、jj は一定サイズの追加スペースを使用します

コード:

x & 1 ビット演算はx % 2 剰余演算と同等であり、どちらも数値のパリティを判断するために使用できます。

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        i, j = 0, len(nums) - 1
        while i < j:
            while i < j and nums[i] & 1 == 1: i += 1
            while i < j and nums[j] & 1 == 0: j -= 1
            nums[i], nums[j] = nums[j], nums[i]
        return nums

チップ:

  • クイックソートの基本
    • つまり、クイックソートでは、基点より小さいものは前に、基点より大きいものは後ろに配置されます。

その他のメソッド - 高速および低速ダブル ポインター

  • fast と low のダブル ポインタを fast と low で定義します。前方は fast、後方は low です。
  • fast の役割は前方の奇数位置を検索すること、 low の役割は次の奇数が格納される位置を指すことです
  • 早く早くf a s t は前方に進み、奇数が見つかった場合はnums[low] nums[low]合計されます。num s [ low ]交換、現時点low low低速1 つ前に移動ます
  • fast が配列の末尾を指すまで、上記の操作を繰り返します。

ここに画像の説明を挿入

class Solution {
    
    
public:
    vector<int> exchange(vector<int>& nums) {
    
    
        int low = 0, fast = 0;
        while (fast < nums.size()) {
    
    
            if (nums[fast] & 1) {
    
    
                swap(nums[low], nums[fast]);
                low ++;
            }
            fast ++;
        }
        return nums;
    }
};




剣はオファー 29 を指します。マトリックスを時計回りに印刷します。

トピックの説明

行列を入力し、各数値を外側から内側に時計回りの順序で出力します

例 1:
ここに画像の説明を挿入

  • 入力:行列 = [ [ 1 , 2 , 3 ] , [ 4 , 5 , 6 ] , [ 7 , 8 , 9 ] ] 行列 = [[1,2,3],[4,5,6],[7] 、8、9]]マトリックス_ _ _ _=[[ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ]]
  • 出力: [ 1 、 2 、 3 、 6 、 9 、 8 、 7 、 4 、 5 ] [1,2,3,6,9,8,7,4,5][ 1 2 3 6 9 8 7 4 5 ]

例 2:
ここに画像の説明を挿入

  • 入力:行列 = [ [ 1 , 2 , 3 , 4 ] , [ 5 , 6 , 7 , 8 ] , [ 9 , 10 , 11 , 12 ] ] 行列 = [[1,2,3,4],[5] 、6、7、8]、[9、10、11、12]]マトリックス_ _ _ _=[[ 1 2 3 4 ] [ 5 6 7 8 ] [ 9 10 11 12 ]]
  • 出力: [ 1 、 2 、 3 、 4 、 8 、 12 、 11 、 10 、 9 、 5 、 6 、 7 ] [1,2,3,4,8,12,11,10,9,5,6, 7][ 1 2 3 4 8 12 11 10 9 5 6 7 ]

制限:

  • 0 < = 行列。長さ < = 100 0 <= 行列.長さ <= 1000<=マトリックス_ _ _ _ _ _ _ _<=100
  • 0 < = 行列 [ i ] 。長さ < = 100 0 <= 行列[i].length <= 1000<=行列[ i ] _ _ _ _ _ _ _ _<=100

解決

一連の考え

  • タグ: 2D 配列
  • 全体のアイデア:
    • 配列全体ループし、ループ内にさらに 4 つのループをネストします
    • それらは、左から右、上から下、右から左、および下から上です。
    • タイトルの意味に従って配列全体の走査を完了し、境界を制御します
  • んんmは行数、nnnは列数、時間計算量:O ( mn ) O(mn)O ( mn )、空間計算量:O ( 1 ) O(1)

アルゴリズム処理

  1. タイトルのマトリックス マトリックスmat r i x は空でも構いませ空の配列を返すだけです
  2. 境界 の左、右、上、下の左、右、上、下を初期化します。le f t right t top b o tt o m の4の値、結果配列res resを初期化しますresと配列の添字 xxバツ
  3. トラバーサルの方向に従って、数値を取り出し、結果の配列に入れます。
    • 左から右へ: 走査が完了した後++top上 > 下 上 > 下の場合トップ_ _>底値境界サイクルの終わり到達
    • 上から下へ: トラバースが完了した後--right左 > 右の場合、左 > 右_ _ _>、境界サイクル終わり到達
    • 右から左へ: 走査が完了した後--bottom上 > 下 上 > 下の場合トップ_ _>底値境界サイクルの終わり到達
    • 下から上へ: トラバース完了後++left左 > 右の場合、左 > 右_ _ _>、境界サイクル終わり到達

コード

  • 横断戦略:最後まで横断する

class Solution(object):
    def spiralOrder(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: List[int]
        """
        if len(matrix) ==0 : return None
        # if not matrix or not matrix[0]:
        #     return list()

        right = len(matrix[0])-1
        bottom = len(matrix)-1
        top =0
        left=0
        res = []
        maxSize = (right+1)*(bottom+1)
        
        while(maxSize>0):
            for i in range(left,right+1):
                if maxSize>0:
                    res.append(matrix[top][i])
                    maxSize-=1
            top+=1
            for j in range(top,bottom+1):
                if maxSize>0:
                    res.append(matrix[j][right])
                    maxSize-=1
            right-=1
            for k in range(right,left-1,-1):
                if maxSize>0:
                    res.append(matrix[bottom][k])
                    maxSize-=1
            bottom-=1
            for l in range(bottom,top-1,-1):
                if maxSize>0:
                    res.append(matrix[l][left])
                    maxSize-=1
            left+=1

        return res

メソッド分析 - レイヤーごとのシミュレーション

  • マトリックスは複数の層として見ることができます
    • まず、最も外側の要素を出力します
    • 次に、2 番目の外側層の要素を出力します。
    • 最も内側の要素が出力されるまで

行列の kk 番目を定義しますk層は、最も近い境界までの距離ですkすべての頂点

  • たとえば、次の図の行列の最も外側の要素はすべて 1 1です。レベル12 番目の外側要素は2 番目の 22 つのレイヤー、残りの要素は 3 番目の3 つ3階建て。

[[1, 1, 1, 1, 1, 1, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 2, 3, 3, 3, 2, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 1, 1, 1, 1, 1, 1]]
 

各レベルで、左上から時計回りにすべての要素を繰り返します。

  • 現在のレイヤーの左上隅が位置し(top,left)、右下隅が位置すると仮定すると(bottom,right)、現在のレイヤーの要素は次の順序で走査されます。
    ここに画像の説明を挿入
  1. 上部の要素を左から右に、(top, left) から (top, right) にトラバースします。
  2. 右側の要素を上から下に移動し、続いて (トップ+1、右)から(下、右)まで。
  3. 場合左 < 右、上 < 下 左 < 右、上 < 下_ _ _<_ _ _ _ _ _<b o tt o m、次に下の要素を 右から左に
    • (下、右−1) から (下、左+1)
    • そして、左側の要素を下から上に順番に走査します。
    • (下、左) ~ (トップ+1、左)。

現在のレイヤーの要素を走査した後、

  • 左と左上と上に置きますleft tto p は1ずつ増加します
  • 右と下と右と下を変更します1減ります_ _ _ _ _
  • 次の層に入って横断を続けます。
  • すべての要素を通過するまで。
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix or not matrix[0]:
            return list()
        
        rows, columns = len(matrix), len(matrix[0])
        order = list()
        left, right, top, bottom = 0, columns - 1, 0, rows - 1
        while left <= right and top <= bottom:
            for column in range(left, right + 1):
                order.append(matrix[top][column])
            for row in range(top + 1, bottom + 1):
                order.append(matrix[row][right])
            if left < right and top < bottom:
                for column in range(right - 1, left, -1):
                    order.append(matrix[bottom][column])
                for row in range(bottom, top, -1):
                    order.append(matrix[row][left])
            left, right, top, bottom = left + 1, right - 1, top + 1, bottom - 1
        return order

複雑さの分析

  • 時間計算量: O(mn)。ここで、m と n はそれぞれ入力行列の行数と列数です。マトリックスの各要素には 1 回アクセスされます。
  • 空間複雑度: O(1)。空間計算量は、出力配列を除いて一定です。



おすすめ

転載: blog.csdn.net/weixin_43338969/article/details/129336823