高频笔试题5

127 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

今日题解

丑数Ⅲ

思路:二分搜索加集合求交并补。小于给定mid值的丑数个数是可以用公式计算出来的:n == mid/a + mid/b + mid/c - mid/ab - mid/bc - mid/ac + mid/abc 明明大致思路是一样的,但是第一种方法怎么都出不来结果,卡在[7 7 7 7]

class Solution {
public:
    long long big(long long a, long long b) {
        return a/gcd(a, b)*b;
    }
    int nthUglyNumber(int n, int a, int b, int c) {
        long long ab = big(a, b);
        long long bc = big(b, c);
        long long ac = big(a, c);
        long long abc = big(ac, b);

        long long left = 1, right = LONG_LONG_MAX;
        while(left < right) {//左闭右开区间
            long long mid = left + (right-left)/2;
            if(mid/a + mid/b + mid/c - mid/ab - mid/bc - mid/ac + mid/abc == n) {
                return int(max(mid/a*a, max(mid/b*b, mid/c*c)));
            }
            if(mid/a + mid/b + mid/c - mid/ab - mid/bc - mid/ac - mid/abc < n) {
                left = mid + 1;
            }else right = mid;
        }
        return 0;
    }
};

最后忍无可忍的我只能找一个新方法 运用c++17的新特性,求最小公倍数,用lcm内置函数就行,但公式还是那个公式,只是变了个形式而已

class Solution {
public:
    int nthUglyNumber(int n, int a, int b, int c) {
        long lcm_ab  = lcm<long>(a, b);//求最小公倍数
        long lcm_ac  = lcm<long>(a, c);
        long lcm_bc  = lcm<long>(b, c);
        long lcm_abc = lcm<long>(lcm_ab, c);
        int left = min({a, b, c}), right = min(static_cast<long>(left) * n, 2000000000l);
        while (left <= right) {//左闭右闭区间
            int mid = left + (right - left) / 2;
            if (mid / a + mid / b + mid / c - mid / lcm_ab -
                mid / lcm_ac - mid / lcm_bc + mid / lcm_abc < n) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
};

播放列表的数量

思路:(动态规划)都在代码的注释里了,当然也可以去看上一期的动态规划讲解,比这个要清楚些

class Solution {
public:
    typedef long long LL;
    int numMusicPlaylists(int n, int goal, int k) {
        LL m = pow(10, 9) + 7;
        LL base = 1;
        for(LL i=1; i<= n; i++) {
            base = base*i%m;
        }
        //题目意思是说两首相同歌之间必须至少间隔K首歌
        //dp[i][0]表示重复次数
        //dp[0][j]表示k个间距
        //k=n-1个间距的下标为0,也就是间距从最大值开始
        vector<vector<LL>> dp(goal-n+1, vector<LL> (n-k, 1));
        for(LL i=1; i<dp.size(); i++) {
            for(LL j=1; j<dp[0].size(); j++) {
                dp[i][j] = (dp[i][j-1] + (j+1)*dp[i-1][j]%m)%m;
            }
        }
        LL res = dp[goal-n][n-k-1]*base%m;
        return res;
    }
};

统计点对的数目

思路:emmm,这一题直到晚上十点也没整出来,还是超时,卡在20000,实在搞不出来了,万能的C友啊,请教教我吧。。==下面的代码是超时的==

class Solution {
public:
    struct BIT{
        static const int maxn = 1e5+10;
        int zu[maxn], n;
        void init(int _n)
        {
            memset(zu, 0, sizeof(zu));
            n = _n;
        }
        int lowbit(int x) { return x & -x; }
        int sum(int x)
        {
            int ret = 0;
            while(x)
            {
                ret += zu[x];
                x -= lowbit(x);
            }
            return ret;
        }
        void add(int x, int val)
        {
            while(x <= n)
            {
                zu[x] += val;
                x += lowbit(x);        
            }
        }
    }bit;

    vector<int> countPairs(int n, vector<vector<int>>& edges, vector<int>& queries) {
        vector<int>cnts(n+1, 0);
        vector<vector<int>>common(n+1, vector<int>(n+1, 0));
        for(auto edge : edges)
        {
            cnts[edge[0]]++;
            cnts[edge[1]]++;
            common[edge[0]][edge[1]]++;
            common[edge[1]][edge[0]]++;
        }

        int edge_num = edges.size();
        bit.init(edge_num);

        unordered_map<int, int>mp;
        for(int i = 1;i <= n;i++)
            for(int j = i+1;j <= n;j++)
            {
                int tmp = cnts[i] + cnts[j] - common[i][j];
                if(tmp)  mp[tmp]++;
            }
            
        for(auto it = mp.begin();it != mp.end();it++)  // 不会超过边数
            if(it->second > 0)  bit.add(it->first, it->second);

        vector<int>res;
        int all = bit.sum(edge_num);
        for(int num : queries)
            res.push_back(all - bit.sum(num));
        
        return res;
    }
};

二叉树的坡度今日打卡题

思路:深度优先搜索遍历+递归

class Solution {
    int res = 0;
private:
    int travel(TreeNode* root) {
        //返回root包含当前节点的所有子节点的值
        //同时计算出左右节点的绝对值
        if(root == nullptr) {
            return 0;
        }else {
            int left = travel(root->left);//左子树之和
            int right = travel(root->right);//右子树之和
            res += abs(left-right);//累加每个节点的坡度
            return root->val + left + right;
        }
    }
public:
    int findTilt(TreeNode* root) {
        travel(root);
        return res;
    }
};

今天干脆就把丑数给大家讲个透彻

丑数

思路:判断一个数是不是丑数,先把有关五的全部去除,再把有关三的全部去除,最后去除所有有关二的,最后剩下的数若为1,证明它能整除2、3、5这三个丑数,也就是说他自己是丑数

class Solution {
public:
    bool isUgly(int n) {
        if(n <= 0) return false;
        while(n%5 == 0) {
            n /= 5;
        }
        while(n%3 == 0) {
            n /= 3;
        }
        while(n%2 == 0) {
            n /= 2;
        }
        if(n == 1) {
            return true;
        }else return false;
    }
};

丑数Ⅱ

思路:三指针法,所以先设置一个dp数组表示当前已经找出并排列好的丑数数组 再3个指针:num2, num3, num5 表示 将当前已经排好的数组元素 *2,*3,*5 所得的数组 这里面最关键的在于dp[i] = min(min(num2, num3), num5); 这样可以让丑数按照大小顺序来排列,也不会产生重复的丑数

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n+1);
        dp[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0;
        for(int i=1; i<n; i++) {
            int num2 = dp[p2]*2, num3 = dp[p3]*3, num5 = dp[p5]*5;
            dp[i] = min(min(num2, num3), num5);
            if(dp[i] == num2) {
                p2++;
            }
            if(dp[i] == num3) {
                p3++;
            }
            if(dp[i] == num5) {
                p5++;
            }
        }
        return dp[n-1];
    }
};

最长公共前缀

思路:先取第一个字符串当作他们的公共前缀,再找出它和第二个字符串的公共前缀,依此类推

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if(strs.size() == 0) return "";
        int size = strs.size();
        string res = strs[0];
        int cnt = 1;
        while(cnt < size) {
            while(strs[cnt].find(res) != 0) {
                res = res.substr(0, res.size()-1);
            }
            cnt++;
        }
        return res;
    }
};

最接近的三数之和

思路:先用sort函数排好序,再在内部使用双指针 给大家一个小建议,能用 ++i 就不要用 i++ ,用 ++i 效率要高些

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());//排序
        int left = 0, right = 0;
        int closetNum = nums[0] + nums[1] + nums[2];
        for(int i=0; i<nums.size()-2; ++i) {
            //因为i是内层循环的开始,为了保证最少有三个数,否则会出界
            int left = i+1, right = nums.size()-1;//内部使用双指针
            while(left < right) {
                int threeSum = nums[left] + nums[right] + nums[i];
                if(abs(threeSum-target) < abs(closetNum-target)) {
                    closetNum = threeSum;
                }
                if(threeSum > target) {
                    --right;
                }else if(threeSum < target) {
                    ++left;
                }else return target;
            }
        }
        return closetNum;
    }
};

除自身以外数组的乘积

思路:当前乘积==当前数左边的乘积*当前数右边的乘积 方法一:两次遍历

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int size = nums.size();
        vector<int> res(size, 1);
        int cnt = 1;
        for(int i=0; i<size; ++i) {
            res[i] *= cnt;
            cnt *= nums[i];
        }
        cnt = 1;
        for(int i=size-1; i>=0; --i) {
            res[i] *= cnt;
            cnt *= nums[i];
        }
        return res;
    }
};

优化:一次遍历

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int size = nums.size();
        vector<int> res(size, 1);
        int left = 1, right = 1;
        for(int i=0; i<size; ++i) {
            res[i] *= left;
            left *= nums[i];

            res[size-i-1] *= right;
            right *= nums[size-i-1];
        }
        return res;
    }
};

螺旋矩阵

思路:主要是确定四条边的位置,然后 从左到右,从上到下,从右到左,从下到上不断遍历,并将所有的元素值都加入到res里

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.empty() || matrix[0].empty()) return {};
        vector<int> res;
        int m = matrix.size(), n = matrix[0].size();

        //确定上下左右四条边的位置
        int up = 0, down = m-1, left = 0, right = n-1;
        while(1) {
            for(int i=left; i<=right; i++) {//左到右
                res.push_back(matrix[up][i]);
            }
            if(++up > down) break;
            
            for(int i=up; i<=down; i++) {//上到下
                res.push_back(matrix[i][right]);
            }
            if(--right < left) break;

            for(int i=right; i>=left; i--) {//右到左
                res.push_back(matrix[down][i]);
            }
            if(--down < up) break;

            for(int i=down; i>=up; i--) {//下到上
                res.push_back(matrix[i][left]);
            }
            if(++left > right) break;
        }
        return res;
    }
};

螺旋矩阵Ⅱ

思路:和上一题思路是差不多的,稍微改部分代码就能实现

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
         vector<vector<int>> res(n, vector<int>(n));
        //确定上下左右四条边的位置
        int up = 0, down = n-1, left = 0, right = n-1, cnt = 1;
        while(1) {
            for(int i=left; i<=right; i++) {//左到右
                res[up][i] = cnt++;
            }
            if(++up > down) break;
            
            for(int i=up; i<=down; i++) {//上到下
                res[i][right] = cnt++;
            }
            if(--right < left) break;

            for(int i=right; i>=left; i--) {//右到左
                res[down][i] = cnt++;
            }
            if(--down < up) break;

            for(int i=down; i>=up; i--) {//下到上
                res[i][left] = cnt++;
            }
            if(++left > right) break;
        }
        return res;
    }
};