Lituo アルゴリズム演習 (3)

目次

1. N字変形(6)

解決策 1 (Lituo の公式分析)

解決策 2 (公式解決策)

問題解決策 3 (電源ボタン)

2. 整数反転 (7)

解決策 1

解決策 2

解決策 3 (最初の解決策と同様の公式分析)

3. 文字列変換整数(atoi) (8)

解決策 1

解決策 2

問題解決3(最適解)

解決策 4


1. N字変形(6)

指定された文字列を、 s 指定された行数に従って numRows 上から下、左から右の順にジグザグに配置します。

たとえば、入力文字列が  行"PAYPALISHIRING" 数 の場合、次のようになります。3

パーン・
アプリシグ
・イル

その後、出力を左から右に 1 行ずつ読み取って、次のような新しい文字列を生成する必要があります"PAHNAPLSIIGYIR"

文字列を指定した行数に変換するには、この関数を実装してください。

文字列変換(文字列 s, int numRows);

例 1:

入力: s = "PAYPALISHIRING"、numRows = 3
出力: "PAHNAPLSIIGYIR"

例 2:

入力: s = "PAYPALISHIRING"、numRows = 4
出力: "PINALSIGYAHRPI"
説明: 
PIN 
ALSIG 
YAHR 
PI

例 3:

入力: s = "A"、numRows = 1
出力: "A"

解決策 1 (Lituoの公式分析)

class Solution {
    public String convert(String s, int numRows) {
        int n = s.length(), r = numRows;
        if (r == 1 || r >= n) {
            return s;
        }
        int t = r * 2 - 2;
        int c = (n + t - 1) / t * (r - 1);
        char[][] mat = new char[r][c];
        for (int i = 0, x = 0, y = 0; i < n; ++i) {
            mat[x][y] = s.charAt(i);
            if (i % t < r - 1) {
                ++x; // 向下移动
            } else {
                --x;
                ++y; // 向右上移动
            }
        }
        StringBuffer ans = new StringBuffer();
        for (char[] row : mat) {
            for (char ch : row) {
                if (ch != 0) {
                    ans.append(ch);
                }
            }
        }
        return ans.toString();
    }
}

解決策 2 (公式解決策)

方法 1 の行列には未使用のスペースがたくさんありますが、最適化できますか?

文字が行列の行に追加されるたびに、その行の文字の右側に追加され、最終的な回答の構成には各行の null 以外の文字のみが使用されることに注意してください。したがって、行列の各行を空のリストとして初期化し、文字が行に追加されるたびに、その行のリストの最後に追加されます。

 

class Solution {
    public String convert(String s, int numRows) {
        int n = s.length(), r = numRows;
        if (r == 1 || r >= n) {
            return s;
        }
        StringBuffer[] mat = new StringBuffer[r];
        for (int i = 0; i < r; ++i) {
            mat[i] = new StringBuffer();
        }
        for (int i = 0, x = 0, t = r * 2 - 2; i < n; ++i) {
            mat[x].append(s.charAt(i));
            if (i % t < r - 1) {
                ++x;
            } else {
                --x;
            }
        }
        StringBuffer ans = new StringBuffer();
        for (StringBuffer row : mat) {
            ans.append(row);
        }
        return ans.toString();
    }
}

問題解決策 3 (電源ボタン)

これは K 神の解決策です。読んだ後は、素直に感心するとしか言えません。文字バッファを使用すると、この問題は完全に解決されました。本当に少し惰性で考えてしまって、最初は 2 次元配列しか思いつきませんでした時間。文字バッファーを使用することは予期していませんでした。そして国旗がとても上手に使われています。

class Solution {
    public String convert(String s, int numRows) {
        if(numRows < 2) return s;
        List<StringBuilder> rows = new ArrayList<StringBuilder>();
        for(int i = 0; i < numRows; i++) rows.add(new StringBuilder());
        int i = 0, flag = -1;
        for(char c : s.toCharArray()) {
            rows.get(i).append(c);
            if(i == 0 || i == numRows -1) flag = - flag;
            i += flag;
        }
        StringBuilder res = new StringBuilder();
        for(StringBuilder row : rows) res.append(row);
        return res.toString();
    }
}

2. 整数反転 (7)

32 ビット符号付き整数を指定すると x 、 x その数値部分を反転した結果を返します。

反転された整数が 32 ビット符号付き整数の範囲を超える場合は [−231,  231 − 1] 0 を返します。

環境では 64 ビット整数 (符号付きまたは符号なし) の保存が許可されていないとします。

例 1:

入力: x = 123
出力: 321

例 2:

入力: x = -123
出力: -321

例 3:

入力: x = 120
出力: 21

解決策 1

毎回最後の番号を取得して、新しい番号を作成します。( Zili) 判定条件は、x自体がintで制限されるため、xが正の数で桁数がInteger.MAX_VALUEの桁数と等しい場合、最初の桁は最大でも2桁までしか取れないので、反転後は表示されません

res = Integer.MAX_VALUE / 10 && tmp > 2 の場合、res==214748364 && tmp > 7 を判定する必要はありません。逆も同様です。

class Solution {
    public int reverse(int x) {
        int res = 0;
        while(x!=0) {
            //每次取末尾数字
            int tmp = x%10;
            //判断是否 大于 最大32位整数
        if (res > Integer.MAX_VALUE / 10 || res < Integer.MIN_VALUE / 10)  {
            return 0;
        }
            res = res*10 + tmp;
            x /= 10;
        }
        return res;
    }
}	

解決策 2

最初に文字列に変換し、次に文字に変換し、最初にシンボルを判断し、次にサイクルの開始と終了を交換します。例外を処理します。

class Solution {
    public int reverse(int x) {
        String s=String.valueOf(x);
        char[] array=s.toCharArray();
        int i=0,right=s.length()-1;
        char temp=' ';
        if(s.charAt(i)=='+'||s.charAt(i)=='-'){
            i++;
        }
        while(i<right){
            temp=array[i];
            array[i]=array[right];
            array[right]=temp;
            i++;
            right--;
        }
        try{
            return Integer.parseInt(String.valueOf(array));
        }catch(NumberFormatException e){
            return 0;
        }
    }
}

解決策 3 (最初の解決策と同様の公式分析)

参考バックル

反転後の数値として rev を記録します。反転を完了するには、xxx が 000 になるまで、xxx の終了番号を「ポップ」し、rev の最後に「プッシュ」することを繰り返します。

補助スタックや配列の助けを借りずに数値を「ポップ」および「プッシュ」するには、次の数学的手法を使用できます。

// x 桁の最後の桁をポップします
digit = x % 10
x /= 10

// 数値の桁をrevの最後にプッシュします
rev = rev * 10 + 桁

class Solution {
    public int reverse(int x) {
        int rev = 0;
        while (x != 0) {
            if (rev < Integer.MIN_VALUE / 10 || rev > Integer.MAX_VALUE / 10) {
                return 0;
            }
            int digit = x % 10;
            x /= 10;
            rev = rev * 10 + digit;
        }
        return rev;
    }
}

3. 文字列変換整数(atoi) (8)

文字列を 32 ビット符号付き整数に変換する関数を実装してください (  C/C++ の関数myAtoi(string s) と同様 )。atoi

関数 のアルゴリズムはmyAtoi(string s) 次のとおりです。

  1. 文字列を読み取り、不要な先頭の空白を破棄します。
  2. 次の文字 (文字の終わりにまだ到達していないものと仮定して) がプラス記号かマイナス記号であるかを確認し、その文字 (存在する場合) を読み取ります。最終結果が陰性か陽性かを決定します。どちらも存在しない場合、結果は肯定的なものとみなされます。
  3. 次の非数字文字に到達するか、入力の終わりに到達するまで、次の文字を読み取ります。文字列の残りの部分は無視されます。
  4. 前の手順で読み取ったこれらの数値を整数に変換します (つまり、「123」 -> 123、「0032」 -> 32)。数値が読み込まれない場合、整数は です 0 。必要に応じてシンボルを変更します (手順 2 から)。
  5. 整数の数が 32 ビット符号付き整数の範囲を超える場合は [−231,  231 − 1] 、この範囲内に収めるために整数を切り捨てる必要があります。具体的には、 −231 より小さい整数は に固定し −231 、 231 − 1 より大きい整数は に固定する必要があります 231 − 1 。
  6. 最終結果として整数を返します。

知らせ:

  • この質問の空白文字にはスペース文字のみが含まれます ' ' 。
  • 先頭のスペース以外の 文字や数字の後の文字列の残りの部分は省略しないでください。

例 1:

入力: s = "42"
出力: 42
説明:太字の文字列は読み取られた文字、キャレットは現在読み取られている文字です。
ステップ 1: "42" (先頭にスペースがないため、現在は文字が読み取られません) 
         ^
ステップ 2: "42" ('-' または '+' がないため、現在は文字が読み取られません) 
         ^
ステップ 3: " 42 " (「42」と読み取られます) 
           ^
解析して整数 42 を取得します。
「42」は [-231, 231 - 1] の範囲内にあるため、最終結果は 42 になります。

例 2:

入力: s = "-42"
出力: -42
説明:
ステップ 1: "    -42" (先頭のスペースを読み取りますが、無視します) 
            ^
ステップ 2: "    -42 " ('-' 文字を読み取るため、結果は次のようになります)負の数) 
             ^
ステップ 3: "    -42 " (「42」と読み取ります) 
               ^
解析して整数 -42 を取得します。
「-42」は [-231, 231 - 1] の範囲内にあるため、最終結果は -42 になります。

例 3:

入力: s = "単語付き 4193"
出力: 4193
説明:
ステップ 1: "単語付き 4193" (先頭にスペースがないため、現在文字は読み取られません) 
         ^
ステップ 2: "単語付き 4193" (現在読み取られる文字はありません)ここには '-' または '+' がないため) 
         ^
ステップ 3: 「4193と単語」 (「4193」と読みます。次の文字は数字ではないため、読み取りは停止します) 
             ^
解析して整数 4193 を取得します。
「4193」は [-231, 231 - 1] の範囲内にあるため、最終結果は 4193 になります。

解決策 1

まず文字列を配列に分割して先頭のスペースを削除し、最初の文字列がシンボルの場合はトラバースして保存し、次に数値を照合し、数値でない場合はブレークします。その後、文字に対して例外処理を行い、境界や記号を判定します。

class Solution {
    public int myAtoi(String s)  {
        int result=0,i=0;
        String[] str=s.trim().split("");//去掉首部空格并分割成字符数组
        String flag="";//正负符号
        StringBuffer newStr=new StringBuffer();//新建一个临时存储区域
        while(i<str.length){
            if(str[i].equals("+")&&i==0||str[i].equals("-")&&i==0){//如果有符号则存储
                flag=str[i];
                i++;
                continue;
            }
            if(str[i].matches("[0-9]+")){//用正则匹配数字
                newStr.append(str[i]);
                i++;
            }else{
                break; 
            }
        }
        try{
            if(!newStr.toString().equals("")){
                if(!flag.equals("")){
                    result=Integer.parseInt(flag+newStr.toString());
                }else{
                    result=Integer.parseInt(newStr.toString());
                }
            }else{
                result=0;
            }
        }catch(NumberFormatException e){
            // System.out.println(e);
            if(flag.equals("-")){//判断范围 处理异常
                result=(int)Math.pow(-2,31);
            }else{
                result=(int)Math.pow(2,31);
            }
        }finally{
            return result;
        }
    }
}

解決策 2

これは Likou の公式分析です。アルゴリズムは非常に優れており、まず状態テーブルを作成し、それをマップに格納し、文字列を走査し、状態に応じてさまざまな判断を行います。

class Solution {
    public int myAtoi(String str) {
        Automaton automaton = new Automaton();
        int length = str.length();
        for (int i = 0; i < length; ++i) {
            automaton.get(str.charAt(i));
        }
        return (int) (automaton.sign * automaton.ans);
    }
}

class Automaton {
    public int sign = 1;
    public long ans = 0;
    private String state = "start";
    private Map<String, String[]> table = new HashMap<String, String[]>() {
   
   {
        put("start", new String[]{"start", "signed", "in_number", "end"});
        put("signed", new String[]{"end", "end", "in_number", "end"});
        put("in_number", new String[]{"end", "end", "in_number", "end"});
        put("end", new String[]{"end", "end", "end", "end"});
    }};

    public void get(char c) {
        state = table.get(state)[get_col(c)];
        if ("in_number".equals(state)) {
            ans = ans * 10 + c - '0';
            ans = sign == 1 ? Math.min(ans, (long) Integer.MAX_VALUE) : Math.min(ans, -(long) Integer.MIN_VALUE);
        } else if ("signed".equals(state)) {
            sign = c == '+' ? 1 : -1;
        }
    }

    private int get_col(char c) {
        if (c == ' ') {
            return 0;
        }
        if (c == '+' || c == '-') {
            return 1;
        }
        if (Character.isDigit(c)) {
            return 2;
        }
        return 3;
    }
}

問題解決3(最適解)

まず、この方法は最初のスペースを削除するという点で非常に賢明であり、すべてのスペースの極端なケースを考慮しています。

次に符号ビットを判定します。次に、文字列をトラバースし、数値を判断し、境界を判断します。

public class Solution {

    public int myAtoi(String str) {
        int len = str.length();
        // str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组
        char[] charArray = str.toCharArray();

        // 1、去除前导空格
        int index = 0;
        while (index < len && charArray[index] == ' ') {
            index++;
        }

        // 2、如果已经遍历完成(针对极端用例 "      ")
        if (index == len) {
            return 0;
        }

        // 3、如果出现符号字符,仅第 1 个有效,并记录正负
        int sign = 1;
        char firstChar = charArray[index];
        if (firstChar == '+') {
            index++;
        } else if (firstChar == '-') {
            index++;
            sign = -1;
        }

        // 4、将后续出现的数字字符进行转换
        // 不能使用 long 类型,这是题目说的
        int res = 0;
        while (index < len) {
            char currChar = charArray[index];
            // 4.1 先判断不合法的情况
            if (currChar > '9' || currChar < '0') {
                break;
            }

            // 题目中说:环境只能存储 32 位大小的有符号整数,因此,需要提前判:断乘以 10 以后是否越界
            if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) {
                return Integer.MAX_VALUE;
            }
            if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) {
                return Integer.MIN_VALUE;
            }

            // 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去
            res = res * 10 + sign * (currChar - '0');
            index++;
        }
        return res;
    }

}

解決策 4

私の意見では、解決策 3 ほど理解するのは簡単ではありません。効率は解決策 3 と一致しています。

public class Solution {

   public int myAtoi(String str) {
        int result = 0;
        boolean negative = false;
        int i = 0, len = str.length();
        int limit = -Integer.MAX_VALUE;
        int multmin;
        int digit;

        //skip space before string
        while (i < len) {
            if (str.charAt(i) == ' ') {
                i++;
            } else {
                break;
            }
        }

        if (i < len) {
            char firstChar = str.charAt(i);
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+') {
                    return 0;
                }

                i++;

                if (len == i) {
                    return 0;
                }
            }

            multmin = limit / 10;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                digit = Character.digit(str.charAt(i++), 10);
                if (digit < 0) {
                    return negative ? result : -result;
                }
                if (result < multmin) {
                    return negative ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                }
                result *= 10;
                if (result < limit + digit) {
                    return negative ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                }
                result -= digit;
            }
        } else {
            return 0;
        }

        return negative ? result : -result;
    }

}

おすすめ

転載: blog.csdn.net/weixin_53011574/article/details/131538870