「アルゴリズムシリーズ」の数学

導入

  数学系の問題では、長方形の面積を求める、傾きを求めるなど、義務教育9年間の数学の簡単な知識が出題されます。また、Bash ゲーム、グレイ コーディング、その他の質問など、より高度な数学的知識ポイントもあり、そのような高度な数学的知識ポイントを伴う質問については、私の個人的な提案は、理解してさらに多くのことを行うことですが、巻き込まれないようにすることですこの種の問題は、このような知識ポイントを知っていればコードは非常に簡単ですが、やり方が分からないとできません。実際、このテストはもはやコードのアルゴリズムの問​​題ではなく、数学の問題になっており、面接官であれば、このような答えを暗記するような質問は思いつかないでしょうし、選考の意味もありません。もちろん、学ぶための十分なエネルギーがあれば、より多くのことを学び、より理解して、徹底的に武装することができます。

理論的根拠

  数学は、記号言語を用いて量、構造、変化、空間などの概念を研究する学問であり、ある観点からは形式科学の一種に属します数学は、抽象化と論理的推論を使用して、物体の形状と運動を数えたり、計算したり、測定したり観察したりすることから生まれます。数学者は、新しい予想を定式化し、選択した公理と定義から厳密に導出された定理を確立するために、これらの概念を拡張します。
  基礎的な数学の知識と応用は、個人およびグループの生活に不可欠な部分です。数学の基本概念の完成は、古代エジプト、メソポタミア、古代インドの古代数学書に早くも見られ、古代ギリシャではより厳密な扱いが行われています。それ以来、数学の発展は 16 世紀のルネサンスまで少しずつ続き、新しい科学的発見と数学的革新の相互作用により、今日に至るまで数学の発展が加速されました。数学は多くの国や地域で教育の一部となっています。今日、数学は科学工学医学経済学金融
など   のさまざまな分野で使用されていますこれらの分野への数学の応用は、応用数学と呼ばれることが多く、時には新しい数学的発見を刺激し、まったく新しい分野の発展につながります。たとえば、物理学の実質的な発展において確立された特定の理論は、数学者に特定の問題を解決するよう促すものです。さまざまな角度から。数学者もまた、実用化を目的とせずに純粋数学、つまり数学の本質そのものを研究します。多くの研究は純粋な数学から始まりますが、その過程で多くの応用が見つかります。コンピュータの終わりは数学であり、数学の終わりは哲学であり、哲学の終わりは神学です
  

問題解決の経験

  • 数学とコンピューターは密接な関係にあり、数式の使用は時間計算量を改善するのに非常に役立ちます。
  • 数学の分野では、高度な数学の知識が含まれる質問に対する答えを暗記することは、あまり複雑にする必要はなく、理解するだけで十分であり、面接の焦点にはなりません。
  • コンピュータ言語で数式を表現する方法を学びます。
  • 学習するエネルギーが十分にあれば、高度な数学の知識を含む問題を暗記することができます。

アルゴリズムのトピック

7. 整数反転

ここに画像の説明を挿入
問題分析: より広い範囲で Long に直接変換し、反転後に出力を強制します。
コードは以下のように表示されます。

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

9. 回文番号

ここに画像の説明を挿入
トピック分析: 整数が否定された後、2 つの値が等しいかどうかを比較します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public boolean isPalindrome(int x) {
        // 如果为负数,刚一定不是回文,直接返回false
        if (x < 0) {
            return false;
        }
        int reverseVal = 0;
        int val = x;
        // 对值进行反转
        while (val != 0) {
            int pop = val % 10;
            val /= 10;
            reverseVal = reverseVal * 10 + pop;
        }
        if (reverseVal == x) {
            return true;
        } else {
            return false;
        }
    }
}

50. パウ(x, n)

ここに画像の説明を挿入
トピック分析: 半分の計算 x^n = (x 2) (n/2) により、計算量が大幅に削減されます。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public double myPow(double x, int n) {
        double res = 1.0D;
        // i每次操作都除2,不用一直相乘
        // i = 1 时,i /= 2 就等于0了
        for (int i = n; i != 0; i /= 2) {
            if (i % 2 != 0) {
                res *= x;
            }
            x *= x;
        }
        // 根据n的正负性,返回正数,还是倒数
        return n < 0 ? 1 / res : res;
    }
}

60. 順列シーケンス

ここに画像の説明を挿入
トピック分析: バックトラッキングと再帰はタイムアウトになりますが、これは主に数学的手法によって最適化されており、数値は 1 から始まる連続した自然数であり、ソート結果をプッシュすることができます。式は、index = k / (n-1)! このとき、index は残りの候補番号のインデックスです。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {

    public String getPermutation(int n, int k) {

        // 用StringBuilder,比直接用String快1ms,多击败50%选手
        StringBuilder res = new StringBuilder();

        // n为0,直接返回
        if (n == 0) {
            return res.toString();
        }

        // 声明候选数字组
        LinkedList<Integer> nums = new LinkedList();
        for (int i = 1; i <= n; i++) {
            nums.add(i);
        }

        k = k - 1;
        // 查找候选数字
        while (n != 0) {
            // 计算需要第几个侯选值
            int index = k / helper(n - 1);
            res.append(nums.remove(index));
            // 计算k剩余值
            k %= helper(n - 1);
            n--;
        }
        return res.toString();
    }

    // 实现阶乘
    private int helper(int n) {
        int sum = 1;
        for (int i = n; i > 0; i--) {
            sum *= i;
        }
        return sum;
    }
}

66. プラスワン

ここに画像の説明を挿入
トピック分析: 順番に 1 つを再帰的に追加します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int[] plusOne(int[] digits) {
        int[] res = helper(digits, digits.length - 1);
        return res;
    }

    // 递归加一
    public int[] helper(int[] digits, int index) {
        // 如果进位到首位,直接新建数组返回
        if (index == -1) {
            int[] temp = new int[digits.length + 1];
            temp[0] = 1;
            return temp;
        }
        // 不等于9就加1,等于9就进一位
        if (digits[index] != 9) {
            digits[index] = digits[index] + 1;
            return digits;
        } else {
            digits[index] = 0;
            digits = helper(digits, index - 1);
            return digits;
        }
    }
}

69. xの平方根

ここに画像の説明を挿入
トピック分析: 数学の外套を着て、二分法を使用します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int mySqrt(int x) {
        // 特殊值处理
        if (x == 0 || x == 1) {
            return x;
        }
        int left = 0;
        int right = x;
        int mid = 0;

        // 二分法
        while (left <= right) {
            mid = left + (right - left) / 2;
            if (mid > x / mid) {
                right = mid - 1;
            } else if (mid < x / mid) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        // 否则返回r
        return right;
    }
}

149. ほとんどの点は直線上にある

ここに画像の説明を挿入
問題分析: 点を固定し、この点と直線を形成する他の点を見つけて、その傾きを数えます。最も回数が多い傾きが答えになります。
コードは以下のように表示されます。

/**
 * 数学 + 哈希表
 */
class Solution {
    public int maxPoints(int[][] points) {
        int n = points.length;
        if (n <= 2) {
            return n;
        }
        int ret = 0;
        for (int i = 0; i < n; i++) {
            if (ret >= n - i || ret > n / 2) {
                break;
            }
            Map<Integer, Integer> map = new HashMap<Integer, Integer>();
            for (int j = i + 1; j < n; j++) {
                int x = points[i][0] - points[j][0];
                int y = points[i][1] - points[j][1];
                if (x == 0) {
                    y = 1;
                } else if (y == 0) {
                    x = 1;
                } else {
                    if (y < 0) {
                        x = -x;
                        y = -y;
                    }
                    int gcdXY = gcd(Math.abs(x), Math.abs(y));
                    x /= gcdXY;
                    y /= gcdXY;
                }
                int key = y + x * 20001;
                map.put(key, map.getOrDefault(key, 0) + 1);
            }
            int maxn = 0;
            for (Map.Entry<Integer, Integer> entry: map.entrySet()) {
                int num = entry.getValue();
                maxn = Math.max(maxn, num + 1);
            }
            ret = Math.max(ret, maxn);
        }
        return ret;
    }

    public int gcd(int a, int b) {
        return b != 0 ? gcd(b, a % b) : a;
    }
}

168. Excel テーブルの列名

ここに画像の説明を挿入
トピック分析: 16 進数は 0 から始まるため、整数は 1 減算され、16 進数に従って処理されます。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public String convertToTitle(int columnNumber) {
        StringBuffer sb = new StringBuffer();
        while (columnNumber != 0) {
            columnNumber--;
            sb.append((char)(columnNumber % 26 + 'A'));
            columnNumber /= 26;
        }
        return sb.reverse().toString();
    }
}

171. Excelの列番号

ここに画像の説明を挿入
トピック分析: 16 進数を 10 進数に変換します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int titleToNumber(String columnTitle) {
        int number = 0;
        int multiple = 1;
        for (int i = columnTitle.length() - 1; i >= 0; i--) {
            int k = columnTitle.charAt(i) - 'A' + 1;
            number += k * multiple;
            multiple *= 26;
        }
        return number;
    }
}

172. 階乗後のゼロ

ここに画像の説明を挿入
トピック分析: 3 つの結論が導き出されます。 1. 0 の数を決定するには、因子 2 と 5 の数を見つけるだけで済みます。2 つ目は、2 の数は 5 より大きいので、因数 5 の数を見つけるだけです。3. 5 つの数字の間隔ごとに 5 で割り切れる数があり、これらの 5 で割り切れる数の中に、5 つの数字の間隔ごとに 25 で割り切れる別の数があるため、結果が 0 になるまで再度割る必要があります。つまり、5 で割り切れ続ける数は存在しないということです。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int trailingZeroes(int n) {
        int count = 0;
        while(n >= 5) {
            count += n / 5;
            n /= 5;
        }
        return count;
    }
}

204. 素数を数える

ここに画像の説明を挿入
トピック分析: スクリーニング方法: x が素数の場合、x 2x、3x ... より大きい x の倍数は素数であってはいけないので、それらをマークします。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int countPrimes(int n) {
        int[] isPrime = new int[n];
        // 初始化
        Arrays.fill(isPrime, 1);
        int res = 0;
        for (int i = 2; i < n; ++i) {
            if (isPrime[i] == 1) {
                res += 1;
                if ((long) i * i < n) {
                    for (int j = i * i; j < n; j += i) {
                        // 合数标记为 0 
                        isPrime[j] = 0;
                    }
                }
            }
        }
        return res;
    }
}

223. 長方形領域

ここに画像の説明を挿入
トピック分析: ジオメトリ: 総面積 = 単一エリア 1 + 単一エリア 2 - 重複エリア。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int computeArea(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) {
        int area1 = (ax2 - ax1) * (ay2 - ay1), area2 = (bx2 - bx1) * (by2 - by1);
        int overlapWidth = Math.min(ax2, bx2) - Math.max(ax1, bx1), overlapHeight = Math.min(ay2, by2) - Math.max(ay1, by1);
        int overlapArea = Math.max(overlapWidth, 0) * Math.max(overlapHeight, 0);
        return area1 + area2 - overlapArea;
    }
}

233. 1の数

ここに画像の説明を挿入
トピック分析: 各桁の 11 の数を列挙します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int countDigitOne(int n) {
        // mulk 表示 10^k
        // 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k)
        // 但为了让代码看起来更加直观,这里保留了 k
        long mulk = 1;
        int ans = 0;
        for (int k = 0; n >= mulk; ++k) {
            ans += (n / (mulk * 10)) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk);
            mulk *= 10;
        }
        return ans;
    }
}

241. 算術式の設計の優先順位

ここに画像の説明を挿入
トピック分析: まず、式に対して前処理を実行し、すべてのオペランド (数値と演算子を含む) を ops 配列に入れ、-1、-2、-3 を使用して演算子 +、-、* をそれぞれ表します。なぜなら、式内の演算子 op の場合、その左側の部分で考えられる計算結果を左側のセットで表し、右側の部分で考えられる計算結果を右側のセットで表すからです。この場合、演算子が式の最後の演算である場合に考えられるすべての結果は、演算子の演算に対応する対応するセット left と set right 内の要素の組み合わせの数になります。次に、式内のすべての演算子を左右の区切り文字として列挙して、対応するセットを取得します。式の最終的な結果は、これらのセットの和集合になります。
コードは以下のように表示されます。

/**
  * 记忆化搜索
  */
class Solution {
    static final int ADDITION = -1;
    static final int SUBTRACTION = -2;
    static final int MULTIPLICATION = -3;

    public List<Integer> diffWaysToCompute(String expression) {
        List<Integer> ops = new ArrayList<Integer>();
        for (int i = 0; i < expression.length();) {
            if (!Character.isDigit(expression.charAt(i))) {
                if (expression.charAt(i) == '+') {
                    ops.add(ADDITION);
                } else if (expression.charAt(i) == '-') {
                    ops.add(SUBTRACTION);
                } else {
                    ops.add(MULTIPLICATION);
                }
                i++;
            } else {
                int t = 0;
                while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
                    t = t * 10 + expression.charAt(i) - '0';
                    i++;
                }
                ops.add(t);
            }
        }
        List<Integer>[][] dp = new List[ops.size()][ops.size()];
        for (int i = 0; i < ops.size(); i++) {
            for (int j = 0; j < ops.size(); j++) {
                dp[i][j] = new ArrayList<Integer>();
            }
        }
        return dfs(dp, 0, ops.size() - 1, ops);
    }

    public List<Integer> dfs(List<Integer>[][] dp, int l, int r, List<Integer> ops) {
        if (dp[l][r].isEmpty()) {
            if (l == r) {
                dp[l][r].add(ops.get(l));
            } else {
                for (int i = l; i < r; i += 2) {
                    List<Integer> left = dfs(dp, l, i, ops);
                    List<Integer> right = dfs(dp, i + 2, r, ops);
                    for (int lv : left) {
                        for (int rv : right) {
                            if (ops.get(i + 1) == ADDITION) {
                                dp[l][r].add(lv + rv);
                            } else if (ops.get(i + 1) == SUBTRACTION) {
                                dp[l][r].add(lv - rv);
                            } else {
                                dp[l][r].add(lv * rv);
                            }
                        }
                    }
                }
            }
        }
        return dp[l][r];
    }
}

258. ビットの追加

ここに画像の説明を挿入
トピック分析: 通常の質問を見つけます。3 桁の数字 'abc' の値が s1 = 100 * a + 10 * b + 1 * c の場合、数字を 1 回足すと、s2 = a + b + c になります。 、減少する差は (s1 -s2) = 99 * a + 9 * b です。各サイクル後の減少は 9 の倍数であるため、最後の値が 1 桁の場合、合計の減少は 9*n になります。このことから、9 の余りを直接取得していることがわかります。最後の num 値がゼロでない場合は、直接 num を返し、ゼロの場合は、直接 9 を返します。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public int addDigits(int num) {
        // 为零直接返回
        if (num == 0) {
            return 0;
        }
        // 对9取余,相当于减去 9*n
        num %= 9;
        if (num == 0) {
            return 9;
        }
        return num;
    }
}

263. 醜い数字

ここに画像の説明を挿入
トピック分析: ループ内で 2、3、5 で除算し、余りが 1 になると、それは醜い数になります。
コードは以下のように表示されます。

/**
 * 数学
 */
class Solution {
    public boolean isUgly(int n) {
        if (n < 1) {
            return false;
        }
        while (n % 2 == 0) {
            n /= 2;
        }
        while (n % 3 == 0) {
            n /= 3;
        }
        while (n % 5 == 0) {
            n /= 5;
        }
        return n == 1;
    }
}

273. 整数の英語表現への変換

ここに画像の説明を挿入
トピック分析: 非負の整数 num の最大値が決定されているため、最大 10 桁になります。整数を英語表現に変換するには、数値を 3 桁のグループに分割し、各グループの英語表現を連結して、整数 num の英語表現を取得します。
コードは以下のように表示されます。

/**
 * 递归
 */
class Solution {
    String[] singles = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
    String[] teens = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    String[] tens = {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    String[] thousands = {"", "Thousand", "Million", "Billion"};

    public String numberToWords(int num) {
        if (num == 0) {
            return "Zero";
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 3, unit = 1000000000; i >= 0; i--, unit /= 1000) {
            int curNum = num / unit;
            if (curNum != 0) {
                num -= curNum * unit;
                StringBuffer curr = new StringBuffer();
                recursion(curr, curNum);
                curr.append(thousands[i]).append(" ");
                sb.append(curr);
            }
        }
        return sb.toString().trim();
    }

    public void recursion(StringBuffer curr, int num) {
        if (num == 0) {
            return;
        } else if (num < 10) {
            curr.append(singles[num]).append(" ");
        } else if (num < 20) {
            curr.append(teens[num - 10]).append(" ");
        } else if (num < 100) {
            curr.append(tens[num / 10]).append(" ");
            recursion(curr, num % 10);
        } else {
            curr.append(singles[num / 100]).append(" Hundred ");
            recursion(curr, num % 100);
        }
    }
}

278. 最初の間違ったバージョン

ここに画像の説明を挿入
トピック分析: 二分法検索。値が大きすぎる場合、(left + right)/2 は使用できないことに注意してください。オーバーフローを防ぐために、left + (right -left)/2 を使用する必要があります。
コードは以下のように表示されます。

/**
 * 二分法查找
 */
public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        while(left <= right){
            int mid = left + (right -left)/2;
            if (isBadVersion(mid)){
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

292. ニムゲーム

ここに画像の説明を挿入
トピック分析: バッシュ ゲーム、n % (m+1) != 0 の場合、最初のプレイヤーが常に勝ちます。両方やる場合は、整数 4 の最初の手が必ず負けます。a が先に n を取り、b が 4-n を取る限り、b が必ず勝つことが保証されます。整数が 4 の場合、残りを取り除くと勝ちになります。したがって、最初に開始するには、数字が 4 の倍数であるかどうかを判断するだけで勝敗を判断できます。
コードは次のとおりです。

/**
 * 巴什博奕
 */
class Solution {
    public boolean canWinNim(int n) {
        if(n%4 == 0) return false;
        return true;
    }
}

ホームページに戻る

Leetcode 500 以上の質問を磨くことについての感想

「アルゴリズムシリーズ」ツリー

おすすめ

転載: blog.csdn.net/qq_22136439/article/details/126322883