LeetCode 1884, 75, 201

546 阅读1分钟

LeetCode 1884 Egg Drop With 2 Eggs and N Floors

链接:leetcode.com/problems/eg…

方法:DP

时间复杂度:O(n2)

空间复杂度:O(n)

想法:这题不属于我第二轮复习的题目,就是前几天随便点开了个题写一写的时候做到的。思考过程还挺欢乐的,所以打算稍微写一下。题目说有这么一个楼层f,在<=f的楼层往下扔鸡蛋,鸡蛋不会出问题,但再往上的话鸡蛋就会碎。然后手里有两个鸡蛋,最少扔多少次鸡蛋能完全确定这个楼层f是多少。

我们考虑n=100的情况,即总共有100层楼,这是题目里面给的一个样例,说是在最优解下,第一次要在第9楼扔鸡蛋。假设鸡蛋碎了,那就拿另一个鸡蛋从1楼试到8楼(闭区间);如果没碎,就拿着俩鸡蛋继续网上研究上面10-100这91层楼。所以假如说使用dp[i]代表拿着两个鸡蛋确定i层高的楼所需要的最少步数,那么如果说第一次我们从i-j-1这个地方扔鸡蛋,要么鸡蛋碎了,然后问题变为从1到i-j-1每一层扔第二个鸡蛋,这种情况需要的步数是i-j-1-1+1+1(扔鸡蛋)=i-j;要么鸡蛋没碎,拿着俩鸡蛋研究i-j+1~i这些楼层,总共是j层楼,所以是dp[j]。因为我们在扔之前是不知道扔这一下鸡蛋会不会碎的,题目问的是"minimum number of moves that you need to determine with certainty",with certainty一方面是说唯一确定这个楼层,另一方面是说,对于一个给定高度的楼,不管这个楼的f层在哪,我用这套算法都能在这些步数之内确定。因此对于i层楼考虑的它的第j层楼层,循环j的时候取的值应该是Math.max(dp[j] + 1, i - j)。然后对于确定dp[i]的时候,dp[i]会在所有的j中,取最小的Math.max(dp[j] + 1, i - j)

代码:

class Solution {
    public int twoEggDrop(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        
        for (int i = 2; i <= n; i++) {
            int minv = Integer.MAX_VALUE;
            for (int j = 1; j < i; j++) {
                minv = Math.min(minv, Math.max(dp[j] + 1, i - j));
            }
            dp[i] = minv;
        }
        
        return dp[n];
    }
}

LeetCode 75 Sort Colors

链接:leetcode.com/problems/so…

方法:三指针

时间复杂度:O(n)

空间复杂度:O(1)

想法:题目Follow Up就是问我们"Could you come up with a one-pass algorithm using only constant extra space?",因此哪怕有很多种能通过OJ的写法,如果不能满足这一条的话也没多大意思。这道题是很少见的三指针算法,一个指针起名叫p0,指向下一个想要放0的位置,那么在它左边所有值都应为0。一个指针起名叫p2,指向下一个想要放2的位置(从后往前挪),那么在它右边的所有值都应该为2。所以再搞一个指针我叫它p1,一开始为0,放在一个while里面循环。

第一个if:如果p1指向的当前值是0,那就扔给p0指向的那个地方,即两值交换位置,这样p0指向的就会是0,p1指向的会是一个别的什么元素(待会继续讨论)。这时候p0往前挪1,即p0++。

第二个if:如果p1指向的当前值是1,啥也不干直接p1++找下一个元素。

第三个if:如果p1指向的当前值是2,那么就扔给p2指向的那个地方,两个值交换位置,这样p2指向的就会是2。p2--。

这里有一个问题,因为p1是从左往右扫的,根据上面说的if,如果找到一个为2的元素,那么2就会被扔到数组后面去,因为p2是不管它自己指向的是啥元素的,这时候p2有可能扔一个2回来,也有可能扔一个0回来,所以这个地方p1不能++,扔回来的元素还需要再处理。如果扫到为0的元素,交换的时候,因为2都已经被扔到数组后边去了,所以这时候p0指向的元素要么是0,要么是1,但,p1是从左往后扫描的,并且p1应该在p0右边,如果p0这时候指向的是0,之前p1扫描的时候早就应该把它扔到数组前面去了,因此这时候p0一定指向1。那么换完之后,p1会指向1,这时p1需要++,不然的话可能会出现p0超过p1的情况导致出错。

代码:

class Solution {
    public void sortColors(int[] nums) {
        int n = nums.length;
        int p0 = 0, p2 = n - 1;
        int p1 = 0;
        
        while (p1 <= p2) {
            if (nums[p1] == 0) {
                swap(nums, p0, p1);
                p0++;
                p1++;
            }
            else if (nums[p1] == 1) {
                p1++;
            }
            else {
                swap(nums, p1, p2);
                p2--;
            }
        }
    }
    
    private void swap(int[] nums, int l, int r) {
        int tmp = nums[l];
        nums[l] = nums[r];
        nums[r] = tmp;
    }
}

LeetCode 201 Bitwise AND of Numbers Range

链接:leetcode.com/problems/bi…

方法1:位运算

时间复杂度:O(1)

空间复杂度:O(1)

想法:这个题是要算从left到right闭区间内所有整数的按位与,但数据规模非常大,肯定不能直接上去循环与。想清楚了其实非常简单,问题就是left和right这俩数,按照32位二进制整数表示法,从左往右看,左边一模一样的位数有多少。比方说5和7,0101和0111,一样的前缀就是01xx。xx最后用0补全。那么第一种写法就是俩数直接往后挪,挪到什么时候一样,记下挪了多少步,然后再把left挪回去,返回。

代码:

class Solution {
    public int rangeBitwiseAnd(int left, int right) {
        int i = 0;
        
        while (left != right) {
            i++;
            left >>= 1;
            right >>= 1;
        }
        
        return (left << i);
    }
}

方法2:位运算

时间复杂度:O(1)

空间复杂度:O(1)

想法:我当时只想出了上面那种做法,这一种做法参考的www.cnblogs.com/grandyang/p… 。第二种做法是lowbit,就是right一直减它最后的那个1,减到什么时候小于等于了left,就说明它只剩下跟left的共同前缀了。单写一个lowbit也是很常见的函数,然后在while里面,从while里出来就直接返回right就行了。

代码:

class Solution {
    public int rangeBitwiseAnd(int left, int right) {
        while (right > left) {
            right -= lowbit(right);
        }
        
        return right;
    }
    
    private int lowbit(int x) {
        return x & (-x);
    }
}