[LeetCode]-比較的ユニークな質問-1

序文

LeetCode が質問を解決するときに遭遇するユニークな質問のいくつかを記録します。その多くは、ルール、質問情報、またはいくつかの賢いアルゴリズムに基づいて実行できます。初めて

400. N番目の桁(パターンを見つける)

public int findNthDigit(int n) {
    
    
    // [1, 9]        共 9 * 1 位数字      9 * 1 * 1      9 * pow(10,0) * 1
    // [10, 99]      共 90 * 2 位数字     9 * 10 * 2     9 * pow(10,1) * 2
    // [100, 999]    共 900 * 3 位数字    9 * 100 * 3    9 * pow(10,2) * 3
    // [1000, 9999]  共 9000 * 4 位数字   9 * 1000 * 4   9 * pow(10,3) * 4
    // ......                                           9 * pow(10,len - 1) * len(len为对应区间中数的位数)
    //由以上规律,根据n的值,可以算出它属于哪个区间 (一位数的区间还是二位数的区间还是...
    int len = 1;
    long tmp;
    while((tmp = 9 * (long)Math.pow(10,len - 1) * len) < (long)n){
    
    
        n -= tmp;
        len++;
    }
    
    //经过上面的计算后,len的值就表示第n位数是处于len位数的区间中,那么这个区间中每个数都是len位,
    //通过剩下的n除以len向上取整就可以算出第n位数所在的是这个len位数区间中的第几个len位数
    //例如n=13,那么经过上面的计算后,len=2,n=4,4/2向上取整即为2,说明第n位数所在的是二位数区间中的第2个数,即11

	//此时numCount就表示是 len位数区间中的第几个len位数
    int numCount = (int)Math.ceil((double)n / len); 
    //begin为这个len位数区间中的第一个数,len为2,那么begin为10,即Math.pow(10,len - 1)
    int begin = (int)Math.pow(10,len - 1);
    //根据begin跟numCount就可以算出第n位数所在的具体是哪个len位数,10 + 2 - 1 = 11
    begin += numCount - 1;
    //最后算出第n位数在其所在的len位数中属于第几位(从左往右数)。4-4/2*2刚好得到0,说明为len位数,即begin中的最低位begin%10
    //如果不是0,就可以通过begin / (int)Math.pow(10,len - n) % 10取出第n位数
    n -= n / len * len;
    return n == 0 ? begin % 10 : begin / (int)Math.pow(10,len - n) % 10;
}

977. 順序配列の二乗(質問情報)

 /**
 * 给你一个===按 非递减顺序 排序===的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
 * 有正有负,按非递减顺序,说明平方后赎罪内从左到右数值的大小应该是一个U形,两端数值大,中间数值小,
 * 所以可以用双指针从两端开始遍历,找到较大的就放入结果数组,要从右边开始放
**/
public int[] sortedSquares(int[] nums) {
    
    
    int length = nums.length;
    if(length == 0){
    
    
        return null;
    }
    if(length == 1){
    
    
        return new int[]{
    
    nums[0] * nums[0]};
    }
    int left = 0,right = length - 1,temp = length - 1,leftSqrt = 0,rightSqrt = 0;
    int[] resultArray = new int[length];
    while (left < right){
    
    
        leftSqrt = nums[left] * nums[left];
        rightSqrt = nums[right] * nums[right];
        if(leftSqrt >= rightSqrt){
    
    
            resultArray[temp--] = leftSqrt;
            left++;
        }else{
    
    
            resultArray[temp--] = rightSqrt;
            right--;
        }
    }
    resultArray[temp] = nums[left] * nums[left];
    return resultArray;
}

189. 配列を回転する (パターンを見つける)

まず、逆方向に回転される実際のビット数は k % n です。回転後の結果は次のとおりであることがわかります: まず、元の配列の最後の k % n 数値が前に配置されます。次に、残りの最初の n - (k % n) 数値が後ろに k % n ビット移動されます。はい、
まず配列全体を反転し、次に最初の k % n 数値と残りの n - ( k % n) 数値をそれぞれ反転します。

class Solution {
    
    
    public void rotate(int[] nums, int k) {
    
    
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

    public void reverse(int[] nums, int start, int end) {
    
    
        while (start < end) {
    
    
        	//交换数字用三步异或进一步提高效率
            nums[start] ^= nums[end];
            nums[end] ^= nums[start];
            nums[start] ^= nums[end];
            start++;
            end--;
        }
    }
}

57. インターバルを挿入する

元の各間隔には重複がありません。挿入音程の挿入後、元の音程との関係は、挿入音程の左側、挿入音程の右側、挿入音程と重なる関係になります。

挿入された間隔と重複する間隔については、状況を細分化する必要はありません。これは、間隔が挿入された間隔とどのように重複しても、これらの間隔は最終的に挿入された間隔との大きな交差にマージされるはずであるためですこの大きな交差点の左境界は、挿入区間を含むすべての重複区間の左境界の最小値であり、大きな交差点の右境界は、すべての重複区間の右境界の最大値です。

大きな交差の導入後、すべての区間は 3 つのカテゴリに分類できます。つまり、元の挿入された区間の左側の区間、元の挿入された区間の右側の区間、および大きな交差です。

したがって、問題を解決するアイデアは、間隔配列を走査し、元の挿入間隔の左側にあるすべての間隔を見つけて、それらを結果リストに直接入れることです。同時に、大きな交差点の左右の境界を維持および更新します。 , 元の挿入間隔の右側にある最初の間隔を見つけます。間隔が右側にある場合は、大きな交差部分をリストに追加し、元の挿入間隔の右側にある残りの間隔をすべてリストに追加します。もう 1 つ注意すべき点は、
挿入間隔がすべての元の間隔の左側、またはすべての元の間隔のすぐ右側にあることです。

public int[][] insert(int[][] intervals, int[] newInterval) {
    
    
    int left = newInterval[0];  //left维护大交集的左边界
    int right = newInterval[1]; //right维护大交集的右边界
    boolean flag = false; //标记大交集是否已放入 list
    List<int[]> list = new ArrayList<>();
    for (int[] interval : intervals) {
    
    
        if (interval[0] > right) {
    
     //在大交集的右侧的区间
            //此时 大交集如果还未放入,就要先把大交集放入,即遍历到第一个在大交集右侧的区间。以后再遍历到大交集右侧的区间时直接放入即可
            if (!flag) {
    
    
                list.add(new int[]{
    
    left, right});
                flag = true;                    
            }
            list.add(interval);
        } else if (interval[1] < left) {
    
    //在大交集的左侧的区间
            list.add(interval);
        } else {
    
    
            // 与插入区间有交集,维护更新左右边界来计算它们的并集
            left = Math.min(left, interval[0]);
            right = Math.max(right, interval[1]);
        }
    }
    if (!flag) {
    
     //如果从头到尾大交集都没有被放入,说明插入区间在所有原来区间的右侧,此时的大交集只有插入区间本身。直接放入即可
        list.add(new int[]{
    
    left, right});
    }
    return list.toArray(new int[0][]);
}

164. 最大間隔

この問題の 2 番目の解決策を参照して、次のように自分で整理してください。
まず、配列全体を並べ替えてから、配列を走査して最大距離を見つけます。ソート アルゴリズムとしては、通常 O(nlogn) アルゴリズムが使用されますが、基数ソート (この記事を参照) を選択して、O(n) の複雑さを実現することもできます。

次に、別のアプローチを検討します。
間隔に従って配列を複数の部分に分割します (各間隔はバケットまたはボックスと呼ばれます)。たとえば、[2,3,6,7,22,29,30,38] のようになります。間隔として 10 個の値ごとに、次の 4 つのバケットが取得されます。

サンプルグラフ

次に、各バケットの最大値と最小値を取得すると、2 つのバケットそれぞれの要素間の「間隔」を計算できます。これは、現在のバケットの最小値から左のバケットを引いた値になります(バケット内に要素がない場合)。左バケット、無視します) 最大値(元の配列がソートされた後、現在のバケットの最小値と左バケットの最大値が配列内で連続している必要があります) を比較して、これらの間の最大距離を取得します。間隔」。残念ながら、これまでは 2 つのバケット間の間隔のみを考慮しており、各バケット内の連続する (連続とはソートされた配列で連続することを指します) 要素間の差異は考慮していないため、この最大間隔は最終的な答えではありません。

しかし、各バケット内の 2 つの連続する要素間の差が 2 つのバケット間の距離を超えないことが保証できる場合は、各バケット内の要素を考慮するのではなく、バケット間の距離のみを考慮することができます。どうやってするの?

変数intervalを使用して、各バケットに含めることができる要素の最大数 (つまり、各間隔の長さ、間隔内に正の整数がいくつあるか) を表します。上の図に示すように、interval の値はが 10 の場合、バケット内で可能な最大間隔は間隔 - 1 であることがわかります。上の図の最初のバケットに示されているように、0 と 9 のみが格納されている場合、間隔は 9 になります。 「各バケット内の任意の 2 つの連続する要素間の差が 2 つのバケット間の距離よりも大きくないことを保証します。」つまり、少なくとも 2 つの連続するバケットがあり、それらの間の距離は間隔以上である必要があります。 - 1. このようにして、これらすべてのバケットの距離のうち最大の距離は、2 つのバケット間の距離以上である必要があります。つまり、間隔 - 1 以上である必要があります。

上の図からわかるように、少なくとも 1 つのバケットが空であれば、これは満たされます。2 番目のバケットが空の場合、3 番目のバケットと最初のバケットの間の距離は少なくとも 20 - 9. = 11 になります。間隔+1

では、少なくとも 1 つのバケットが空であることを確認する方法:鳩の穴の原則、いくつかのボックスに入れる要素が n 個ある場合、ボックスの数が n より大きい場合、ボックスの 1 つは空でなければなりません。この質問では、バケットの数をできるだけ減らすために、配列内の最小値と最大値を別々に処理できるため、合計で n - 2 個の要素があり、n - 1 個のバケットが設定されます

最後の質問は各バケットの間隔、つまり各バケットの範囲の選択です。まず配列の最大値と最小値の max と min を求めます。その後、各バケットの範囲は (max - min) / n - となります。 1. 計算します。整数ではない場合があります。切り上げてください。

プロセス全体の大まかな概要:

  1. 最大値と最小値を求める
  2. n - 1 個のバケットを設定し、各バケットの範囲間隔を計算します。
  3. 配列を走査し、要素をバケットに入れてから、現在のバケットの最大値と最小値を計算して更新します(もちろん、実際にバケットに入れる必要はありません)
  4. バケットを走査し、連続する 2 つのバケット間の距離を計算し、最大距離を維持します。
public int maximumGap(int[] nums) {
    
    
   int n = nums.length;
    //根据题意,数组元素个数小于2返回0
    if (n <= 1) {
    
    
        return 0;
    }
    int min = nums[0];
    int max = nums[0];
    for (int i = 1; i < n; i++) {
    
    
    	//求最大最小值
        min = Math.min(nums[i], min);
        max = Math.max(nums[i], max);
    }
    //最大值最小值相等,说明数组中的元素都是相等的,自然最大间距为0
    if(max - min == 0) {
    
    
        return 0;
    }
    //箱子的个数为n - 1
    int bucketCount = n - 1;
    //算出每个箱子的范围,向上取整
    int interval = (int) Math.ceil((double)(max - min) / (bucketCount));
    //记录每个箱子里数字的最小值和最大值
    int[] bucketMin = new int[bucketCount];
    int[] bucketMax = new int[bucketCount];
    //最小值初始为Integer.MAX_VALUE
    Arrays.fill(bucketMin, Integer.MAX_VALUE);
    //最大值初始化为 -1
    Arrays.fill(bucketMax, -1);
    for (int i = 0; i < nums.length; i++) {
    
    
        //当前数字要放入的目标箱子的下标
        int index = (nums[i] - min) / interval;
        //最大数和最小数不需要考虑
        if(nums[i] == min || nums[i] == max) {
    
    
            continue;
        }
        //更新当前数字所在箱子的最小值和最大值
        bucketMin[index] = Math.min(nums[i], bucketMin[index]);
        bucketMax[index] = Math.max(nums[i], bucketMax[index]);
    }
    int maxGap = 0;
    //min 看做第-1个箱子的最大值
    int previousMax = min;
    //从第0个箱子开始计算,计算当前箱子的最小值与前一个箱子的最大值的差值
    for (int i = 0; i < bucketCount; i++) {
    
    
        //最大值是-1说明箱子中没有数字,直接跳过
        if (bucketMax[i] == -1) {
    
    
            continue;
        }
        //计算当前箱子的最小值减去前一个箱子的最大值的差值,然后与前面得到的最大差值比较,得到较大差值
        maxGap = Math.max(bucketMin[i] - previousMax, maxGap);
        previousMax = bucketMax[i];
    }
    //一开始数组的最大值没有放入箱子,所以额外计算它与最后一个桶的最大值的差值
    maxGap = Math.max(max - previousMax, maxGap);
    return maxGap;
}

1013. 配列を 3 つの等しい部分に分割する

配列の要素の合計が sum であるとします。質問の意味によれば、配列が等しい合計を持つ 3 つの部分に分割できる場合、各部分の合計は sum / 3 であることを意味します。

public boolean canThreePartsEqualSum(int[] arr) {
    
    
    int sum = 0;
    for(int i : arr){
    
    
        sum += i;
    }
    //如果 sum 不能被3整除就肯定不能满足题意
    if(sum % 3 != 0) return false;
    int oneThird = sum / 3;
    int len = arr.length,i = 0;
    int tmp = 0; //记录计算得到的数组部分和
    while(i < len){
    
    
        tmp += arr[i++];
        //计算得到第一个等于 sum / 3 的部分就作为第一部分
        if(tmp == oneThird) break;
    }
    //由于上面的循环中是i++,所以此时i指向的应该是第二部分的第一个元素,
    //为了数组能拆分成三部分,i此时至少应该指向数组中倒数第二个元素
    if(tmp != oneThird || i >= len - 1) return false;
    tmp = 0;
    while(i < len){
    
    
        tmp += arr[i];
        if(tmp == oneThird) break;
        i++;
    }
    //上面的循环把取值arr[i]跟i+1拆开,所以当循环跳出的时候i指向的应该是
    //第二部分的最后一个元素,那么为了能拆成三部分,i此时一样至少应该指向数组中倒数第二个元素
    if(tmp != oneThird || i >= len - 1) return false;
    return true;
}

48. 画像を回転する

解決策には 3 つの解決策がありますが、次の解決策が私のお気に入りだと思います。画像を時計回りに回転すると、実際には 0 番目の行を n 番目の列に、1 行目を n-1 番目の列に、...、n 番目の列に変更することを意味します。 . 行 1 が列 0 になります。画像全体を水平方向に 1 回、主対角線 (左上から右下への対角線) に沿って 1 回反転しても、同じ効果が得られます。

public void rotate(int[][] matrix) {
    
    
    int n = matrix.length;
    //水平翻转
    for(int i = 0;i < n / 2;i++){
    
    
        for(int j = 0;j < n;j++){
    
    
            matrix[i][j] ^= matrix[n - 1 - i][j];
            matrix[n - 1 - i][j] ^= matrix[i][j];
            matrix[i][j] ^= matrix[n - 1 - i][j];
        }
    }
    //沿主对角线翻转
    for(int i = 0;i < n;i++){
    
    
        for(int j = i + 1;j < n;j++){
    
    
            matrix[i][j] ^= matrix[j][i];
            matrix[j][i] ^= matrix[i][j];
            matrix[i][j] ^= matrix[j][i];
        }
    }
}

856. 括弧内の分数

公式ソリューションを参照してください。()文字列 S に実質的なスコアのみを提供し、他の括弧はスコアを 2 で乗算するか、スコアを累積するだけです。したがって、各 に対応する深さ x を見つけることができ()、それに対応するスコアは 2 xとなるため、最終的な答えはそれぞれの()スコアの合計になります。

public int scoreOfParentheses(String S) {
    
    
        int ans = 0, dep = 0;
        int len = S.length();
        char[] c = S.toCharArray();
        for (int i = 0; i < len; ++i) {
    
    
            if (c[i] == '(') {
    
    
                dep++; //维护深度
            } else {
    
    
                dep--;
                if (c[i - 1] == '(') //遇到')',判断前面是不是'(',即判断是否是 "()",是则累加一份得分
                    ans += 1 << dep;
            }
        }
        return ans;
    }

おすすめ

転載: blog.csdn.net/Pacifica_/article/details/125135230