细节决定成败!华为面试题库刷题整理(三)

写在前面

华为面试题库刷题第三次整理。

203. 移除链表元素

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6

输出: 1->2->3->4->5

解法:题目不难,重点是用多钟解法来解题。

递归

从尾部开始删除就不需要特别记录前一个节点,利用递归在这一点上就很讨巧。

代码:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(!head) return NULL;//递归边界
        head->next = removeElements(head->next,val);//递去:到底端再开始删除操作
        return head->val==val?head->next:head;//递归式
    }
};

迭代

遍历链表,删除节点。每次判断的都是下一位,这样方便删除,然后再最后return位置特判头。

代码:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(!head) return NULL;
        ListNode *curr, * del;
        curr = head;
        while(curr->next){
            if(curr->next->val==val){//删除下一位
                del = curr->next;
                curr->next = del->next;
                delete del;
            }
            else{
                curr = curr->next;
            }
        }
        return head->val==val?head->next:head;//头元素特判
    }
};

538. 把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 原始二叉搜索树:

        5

        /   \

      2   13

输出: 转换为累加树:

        18

        /   \

      20  13

解法:加上所有比这个节点大的,大的都在右边,所以从右往左遍历并累加就行了。

递归

代码:

/**
* Definition for a binary tree node.
* struct TreeNode {
*     int val;
*     TreeNode *left;
*     TreeNode *right;
*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
private:
    int sum = 0;
public:
    TreeNode* convertBST(TreeNode* root) {
        if(root){
            convertBST(root->right);//遍历右子树
            sum += root->val;//累加
            root->val = sum;//更新数值
            convertBST(root->left);//遍历左子树
        }
        return root;
    }
};

迭代

其实和递归大同小异,只不过把递归过程放进了循环里面。

扫描二维码关注公众号,回复: 9985619 查看本文章

代码:

class Solution {
public:
    TreeNode* convertBST(TreeNode* root) {
        int sum = 0;
        TreeNode* t = root;
        vector<TreeNode*> s;
        while(!s.empty()||t){
            while(t){
                s.push_back(t);
                t = t->right;
            }
            t = s.back();
            s.pop_back();
            sum += t->val;
            t -> val = sum;
            t = t -> left;
        }
        return root;
    }
};

820. 单词的压缩编码

给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A。

例如,如果这个列表是 [“time”, “me”, “bell”],我们就可以将其表示为 S = “time#bell#” 和 indexes = [0, 2, 5]。

对于每一个索引,我们可以通过从字符串 S 中索引的位置开始读取字符串,直到 “#” 结束,来恢复我们之前的单词列表。

那么成功对给定单词列表进行编码的最小字符串长度是多少呢?

提示:

  1. 1 <= words.length <= 2000
  2. 1 <= words[i].length <= 7
  3. 每个单词都是小写字母 。

解法:

后缀字符串

一个数组如果是另外一个字符串的后缀字符串,就会被删除,因为数据的单词长度不超过7,穷举即可,为了方便除重和删除操作,我们这里利用set数据结构。

代码:

class Solution {
public:
    int minimumLengthEncoding(vector<string>& words) {
        int l = words.size();
        set<string> s(words.begin(),words.end());
        for(auto word:words)
            for(int i=1; i<word.length(); i++)
                s.erase(word.substr(i));
        int res = 0;
        for(auto i = s.begin(); i != s.end(); i++)
            res += (*i).length()+1;
        return res;
    }
};

字典树

上面那种方法明显不具有课扩展性,穷举单词的后缀字符串很繁琐,这题可以利用字典树来进行计算。

去找到是否不同的单词具有相同的后缀,我们将其反序之后放入一棵字典树中(前缀树)。例如,我们有 “time” 和 “me”,可以将 “emit” 和 “em” 放入字典树中。

然后,字典树的叶子节点(没有孩子的节点)就代表没有后缀的词,统计 sum(word.length + 1 for word in words)。

代码:

class Solution {
public:
    struct TreeNode {
        int len;
        map<char, TreeNode*> next;
        TreeNode() : len(0) {};
    };
    TreeNode* root;
    Solution() {
        root = new TreeNode();
    }
    int minimumLengthEncoding(vector<string>& words) {
        int res = 0;
        for (auto& w : words) {
            auto node = root;
            for (int i = w.size() - 1; i >= 0; --i) {
                char c = w[i];
                if (node->next.count(c) == 0) {
                    node->next[c] = new TreeNode();
                }
                node = node->next[c];
                if (node->len > 0) {
                    res -= node->len;
                    node->len = 0;
                }
            }
            if (node->len == 0 && node->next.empty()) {
                node->len = 1 + w.size();
                res += node->len;
            }
        }
        return res;
    }
};

1109. 航班预订统计

这里有 n 个航班,它们分别从 1 到 n 进行编号。

我们这儿有一份航班预订表,表中第 i 条预订记录 bookings[i] = [i, j, k] 意味着我们在从 i 到 j 的每个航班上预订了 k 个座位。

请你返回一个长度为 n 的数组 answer,按航班编号顺序返回每个航班上预订的座位数。

提示:

  1. 1 <= bookings.length <= 20000
  2. 1 <= bookings[i][0] <= bookings[i][1] <= n <= 20000
  3. 1 <= bookings[i][2] <= 10000

解法:

暴力模拟(超时)

看范围就知道会爆了,不过还是手贱试了一下。

代码:

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int> a(n+1,0);
        for(auto x : bookings){
            for(int i=x[0]; i<=x[1]; i++)
                a[i] += x[2];
        }
        a.erase(a.begin());
        return a;
    }
};

扫描法

  1. 换一种思路理解题意,将问题转换为:某公交车共有 n 站,第 i 条记录 bookings[i] = [i, j, k] 表示在 i 站上车 k 人,乘坐到 j 站,在 j+1 站下车,需要按照车站顺序返回每一站车上的人数
  2. 根据 1 的思路,定义 counter[] 数组记录每站的人数变化,counter[i] 表示第 i+1 站。遍历 bookings[]:bookings[i] = [i, j, k] 表示在 i 站增加 k 人即 counters[i-1] += k,在 j+1 站减少 k 人即 counters[j] -= k
  3. 遍历(整理)counter[] 数组,得到每站总人数: 每站的人数为前一站人数加上当前人数变化 counters[i] += counters[i - 1]

代码:

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int> a(n,0);
        for(auto x : bookings){
            if(x[0]-1>=0)a[x[0]-1] += x[2];
            if(x[1]<n)a[x[1]] -= x[2];
        }
        for(int i=1; i<n; i++){
            a[i] += a[i-1];
        }
        return a;
    }
};

423. 从英文中重建数字

给定一个非空字符串,其中包含字母顺序打乱的英文单词表示的数字0-9。按升序输出原始的数字。

注意:

  1. 输入只包含小写英文字母。
  2. 输入保证合法并可以转换为原始的数字,这意味着像 “abc” 或 “zerone” 的输入是不允许的。
  3. 输入字符串的长度小于 50,000。

解法:我用了很笨的方法,就嗯解,写了10个函数,然后再判断顺序修改的时候发现了一些优化的思路,不过已经不想写了,从0-9顺序判断,会出错,因为是恰好全部能转换成数字,这样可能会抢占别的数字的字母,所以我们要先判断一些有唯一字母的数字,然后再判断其他数字,每次都要保证当前数字不会抢占别的数字。

代码:

class Solution {
public:
    bool checkZero(vector<int>& a){
        if(a[int('z'-'a')]>=1 && a[int('e'-'a')]>=1 && a[int('r'-'a')]>=1 && a[int('o'-'a')]>=1){
            a[int('z'-'a')]--;
            a[int('e'-'a')]--;
            a[int('r'-'a')]--;
            a[int('o'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkOne(vector<int>& a){
        if(a[int('o'-'a')]>=1 && a[int('n'-'a')]>=1 && a[int('e'-'a')]>=1){
            a[int('o'-'a')]--;
            a[int('n'-'a')]--;
            a[int('e'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkTwo(vector<int>& a){
        if(a[int('t'-'a')]>=1 && a[int('w'-'a')]>=1 && a[int('o'-'a')]>=1){
            a[int('t'-'a')]--;
            a[int('w'-'a')]--;
            a[int('o'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkThree(vector<int>& a){
        if(a[int('t'-'a')]>=1 && a[int('h'-'a')]>=1 && a[int('t'-'a')]>=1 && a[int('e'-'a')]>=2){
            a[int('t'-'a')]--;
            a[int('h'-'a')]--;
            a[int('r'-'a')]--;
            a[int('e'-'a')]-=2;
            return true;
        }
        return false;
    }
    bool checkFour(vector<int>& a){
        if(a[int('f'-'a')]>=1 && a[int('o'-'a')]>=1 && a[int('u'-'a')]>=1 && a[int('r'-'a')]>=1){
            a[int('f'-'a')]--;
            a[int('o'-'a')]--;
            a[int('u'-'a')]--;
            a[int('r'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkFive(vector<int>& a){
        if(a[int('f'-'a')]>=1 && a[int('i'-'a')]>=1 && a[int('v'-'a')]>=1 && a[int('e'-'a')]>=1){
            a[int('f'-'a')]--;
            a[int('i'-'a')]--;
            a[int('v'-'a')]--;
            a[int('e'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkSix(vector<int>& a){
        if(a[int('s'-'a')]>=1 && a[int('i'-'a')]>=1 && a[int('x'-'a')]>=1){
            a[int('s'-'a')]--;
            a[int('i'-'a')]--;
            a[int('x'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkSeven(vector<int>& a){
        if(a[int('s'-'a')]>=1 && a[int('v'-'a')]>=1 && a[int('n'-'a')]>=1 && a[int('e'-'a')]>=2){
            a[int('s'-'a')]--;
            a[int('v'-'a')]--;
            a[int('n'-'a')]--;
            a[int('e'-'a')]-=2;
            return true;
        }
        return false;
    }
    bool checkEight(vector<int>& a){
        if(a[int('e'-'a')]>=1 && a[int('i'-'a')]>=1 && a[int('g'-'a')]>=1 && a[int('h'-'a')]>=1 && a[int('t'-'a')]>=1){
            a[int('e'-'a')]--;
            a[int('i'-'a')]--;
            a[int('g'-'a')]--;
            a[int('h'-'a')]--;
            a[int('t'-'a')]--;
            return true;
        }
        return false;
    }
    bool checkNine(vector<int>& a){
        if(a[int('i'-'a')]>=1 && a[int('e'-'a')]>=1 && a[int('n'-'a')]>=2){
            a[int('i'-'a')]--;
            a[int('e'-'a')]--;
            a[int('n'-'a')]-=2;
            return true;
        }
        return false;
    }
    string originalDigits(string s) {
        if(s.empty()) return s;
        vector<int> a(30,0);
        for(char c:s)
            a[int(c-'a')]++;
        string res = "";
        while(checkZero(a))//顺序就按照上面的思路
            res += '0';
        while(checkSix(a))
            res += '6';
        while(checkEight(a))
            res += '8';
        while(checkTwo(a))
            res += '2';
        while(checkThree(a))
            res += '3';
        while(checkFour(a))
            res += '4';
        while(checkFive(a))
            res += '5';
        while(checkSeven(a))
            res += '7';
        while(checkOne(a))
            res += '1';
        while(checkNine(a))
            res += '9';
        sort(res.begin(),res.end());
        return res;
    }
};

215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

解法:这题是很经典的一题,我们用多种方法来解决。

排序

时间复杂度O(NlogN)的解法,空间复杂度O(1)。

代码:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.rbegin(),nums.rend());
        return nums[k-1];
    }
};

可以维护一个小顶堆,然后保持堆大小不超过k,这样遍历结束时,堆顶就是答案。时间:O(Nlogk) 空间:O(k)。

代码:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        if(k<=0 || nums.empty()) return 0;
        priority_queue<int,vector<int>,greater<int> > pq;//小顶堆
        for(int i=0; i<nums.size(); i++){
            pq.push(nums[i]);
            if(pq.size()>k){//保持个数不超过k
                pq.pop();
            }
        }
        return pq.top();
    }
};

快速选择法

用快徘的思维解决第k小问题。时间:O(N),空间:O(1)。

  1. 从开头讲起,随机选择一个pivot,将其移动至数组的尾端
  2. 然后初始化两个指针,一个叫store_index,其值代表当前小于pivot的个数,在数组中,其左边的元素代表小于pivot的元数,其初始值为0;
  3. 另一个指针j是用来遍历的,起点为store_index,终点为right-1。
  4. 每次j的数值小于pivot的话,把j和store_index的值互换,store_index+=1,也就是把小于pivot的值放在store_index的左边,然后j继续遍历。
  5. j从store_index到right-1的遍历结束后,把最终store_index的位置和right(也就是pivot)的位置呼唤,使得此时pivot左边的值都小于pivot,右边的值都大于pivot。
  6. 然后判断top k_smallest与当前pivot位置的大小关系,若两者相等,则找到目标位置啦,若两者不等,则由其大小关系去判断是要选择pivot的左边还是右边部分继续前面的步骤直到找到目标位置。

代码:

class Solution { //最小堆的方法
public:
    int partition(int left, int right, int k_smallest, vector<int>& nums, int pivot_index){
        //首先把pivot_index放在最后的位置
        int pivot=nums[pivot_index];
        int tmp=nums[pivot_index];
        nums[pivot_index]=nums[right];
        nums[right]=tmp;
        //选定当前小于pivot的位置,记为i
        int i=left;
        for(int j=i;j<right;j++){
            if(nums[j]<pivot){
                int tmp=nums[j];
                nums[j]=nums[i];
                nums[i]=tmp;
                i+=1;
            }
        }
        tmp=nums[i];
        nums[i]=nums[right];
        nums[right]=tmp;
        return i;
    }
    int select(int left, int right, int k_smallest, vector<int>& nums){
        if(left==right){
            return nums[left];
        }

        int pivot_index = (rand() % (right-left+1))+ left;//随机化pivot,防止数组有序使算法退化

        pivot_index = partition(left, right, k_smallest, nums, pivot_index);

        if(pivot_index==k_smallest){
            return nums[pivot_index];
        }else if(k_smallest>pivot_index){
            return select(pivot_index+1, right,k_smallest, nums);
        }
        else{
            return select(left,pivot_index-1, k_smallest, nums);
        }
    }

    int findKthLargest(vector<int>& nums, int k) {
        return select(0, nums.size()-1, nums.size()-k, nums);
    }
};

593. 有效的正方形

给定二维空间中四点的坐标,返回四点是否可以构造一个正方形。

一个点的坐标(x,y)由一个有两个整数的整数数组表示。

注意:

  1. 所有输入整数都在 [-10000,10000] 范围内。
  2. 一个有效的正方形有四个等长的正长和四个等角(90度角)。
  3. 输入点没有顺序。

解法:题目乍一看属实不难,只是情况比较多,繁琐是这题的难点,我们理清一下思路,可以发现有三种情况需要考虑:

  1. 平90°的正方形,意味着四个边都在坐标线了。
  2. 斜45°的正方形。
  3. 其他角度的正方形。

代码:

class Solution {
public:
    int sqr(int a){
        return a*a;
    }
    bool validSquare(vector<int>& p1, vector<int>& p2, vector<int>& p3, vector<int>& p4) {
        vector<vector<int>> p = {p1,p2,p3,p4};
        sort(p.begin(),p.end());
        //情况1
        if(p[0][0]==p[1][0]){
            if(p[0][0]!=p[2][0] && p[2][0]==p[3][0] && abs(p[0][1]-p[1][1])==abs(p[2][1]-p[3][1]) && abs(p[2][0]-p[0][0])==abs(p[0][1]-p[1][1]) && max(p[0][1],p[1][1])==max(p[2][1],p[3][1])){
                return true;
            }
            return false;
        }
        //情况2
        if(p[1][0]==p[2][0]){
            if(p[1][0]-p[0][0] == p[3][0]-p[2][0] && abs(p[1][1]-p[0][1])==abs(p[3][1]-p[2][1])){
                return true;
            }
            return false;
        }
        //情况3
        if(double(p[0][0]-p[1][0]) * double(p[0][0]-p[2][0])/double(p[0][1]-p[1][1])/double(p[0][1]-p[2][1])==-1 && double(p[0][0]-p[1][0]) * double(p[1][0]-p[3][0])/double(p[0][1]-p[1][1])/double(p[1][1]-p[3][1])==-1 && double(p[1][0]-p[3][0]) * double(p[3][0]-p[2][0])/double(p[1][1]-p[3][1])/double(p[3][1]-p[2][1])==-1){//三个角是90°
            if(sqr(p[0][0]-p[1][0])+sqr(p[0][1]-p[1][1])==sqr(p[0][0]-p[2][0])+sqr(p[0][1]-p[2][1]) && sqr(p[0][0]-p[1][0])+sqr(p[0][1]-p[1][1])==sqr(p[1][0]-p[3][0])+sqr(p[1][1]-p[3][1]))//三条边相等
                return true;
        }
        return false;
    }
};

题目小结:这道题让我学到了不少小知识,在这里总结一下:

  1. 注意我的自定义函数sqr,一开始我写的是int sqr(int& a),然后报错了,主要原因是下面的判断语句里面调用sqr的时候是常数,不是变量,所以不需要引用。
  2. 一开始我的斜率相乘判断的时候,没有加double,导致出错,int除法会出现0,导致结果错误。
  3. 仔细看我的斜率判断条件,你会发现我先乘再除,然后不是两个斜率分别算出来相乘,因为double的原因,先除再乘会有误差,如果斜率乘积是-1,先乘再除不会影响结果。

    这些小细节的错误反应了我C++基础的不扎实哈哈,所谓细节决定成败,还要多努力,刷的题目越多,基础就会越扎实,加油!
发布了6 篇原创文章 · 获赞 21 · 访问量 789

猜你喜欢

转载自blog.csdn.net/u011708337/article/details/104942805