算法与基础数据结构Leetcode部分题目解析

算法与基础数据结构Leetcode部分题目解析

1. 剑指Offer11: 旋转数组的最小元素

题目链接
题目解析:升序数组旋转后,分为前后两部分,前面一部分数据偏大,后面一部分数据一定偏小,可以以数组的最后一个数字 nums[r] 作为基点:
当数组中的某个数 nums[mid]nums[r] 大时,mid 一定落在前半部分,最小值一定出现在mid的后边;
nums[mid]nums[r] 小时,mid一定落在后半部分,最小值一定出现在mid的前面;
以上正是 二分算法 的解题思路。
比较难处理的是 nums[mid] == nums[r],若出现这种情况,可能是 [2,2,2,2,2,1,2] 这种情况,mid 落在前半部分,也可能是 [2,1,2,2,2,2,2,2,2] 这种情况,mid 落在后半部分。
针对这种情况,可以在遍历数组时首先做一个前处理,比较数组最左边和最右边的元素,若二者相等,则不断移动最左边的位置,直到二者不相等。此时nums[mid] == nums[r] 一定说明mid 是出现在后半部分,最小值在mid 的前面。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int l = 0, r = numbers.size() - 1;
        //最左边的值只要等于最右边的值则不断将最左边的位置向右移动
        while (l < numbers.size() && numbers[l] == numbers[r]) l++;
        //移动到最后左边的位置索引越界,则说明数组的元素全相等,
        //直接返回最右边的值
        if (l == numbers.size()) return numbers[r];
        //二分法查找最小值
        while (l < r) {
            int mid = (l + r) / 2;
            if (numbers[mid] <= numbers[r]) {
                r = mid;
            }else if (numbers[mid] > numbers[r]) l = mid + 1;
        }
        return numbers[l];
    }
};

2. Leetcode 658: 找到最接近的k个元素

题目链接
解题思路:可以将找到k个最接近的元素转化为要 删掉n-k个元素 (n为数组元素的数量)。
从而可以从数组的两端开始,比较左右两数和x的距离,删去较大的那个数(即该数对应的索引向内移动一位)。 直到删去n - k个数位置。

class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        int i = 0, j = arr.size() - 1;
        int rN = arr.size() - k;
        while (rN > 0) {
            if (abs(arr[i] - x) > abs(arr[j] - x)) {
                //x离左边更远,所以将最左边的索引右移
                i++;
            }else {
            	//否则将最右边的索引左移
                j--;
            }
            rN--;
        }
        //删除完数字,获得新的索引后给结果数组赋值
        vector<int> res;
        for (int l = i; l <= j; l++) {
            res.push_back(arr[l]);
        }
        return res;
    }
};

3. Leetcode 690: 员工的重要性

题目链接

题目解析:深搜和广搜的模板题。用深搜时,根据输入员工,依次将其重要性加上其下属的重要性函数返回值。用广搜时,对其下属依次入队,每次出队时计算重要性。
注意每个员工可转化为hash_map存储,这样可以根据id快速获取到员工信息。
深搜代码演示:

/*
// Definition for Employee.
class Employee {
public:
    int id;
    int importance;
    vector<int> subordinates;
};
*/

class Solution {
public:
    unordered_map<int, Employee> m;
    int getImportance(vector<Employee*> employees, int id) {
    	//为根据id快速找到对应的属性,所以用一个哈希表存储。
        for (auto x: employees) m[x->id] = *x;
        return getValue(id);
    }
    int getValue(int id) {
    	//给定id,递归寻找id和其下属的重要性
        int ans = m[id].importance;
        for (auto x: m[id].subordinates) {
            ans += getValue(x);
        }
        return ans;
    }
};

广搜代码演示:

/*
// Definition for Employee.
class Employee {
public:
    int id;
    int importance;
    vector<int> subordinates;
};
*/

class Solution {
public:
    unordered_map<int, Employee> m;
    int getImportance(vector<Employee*> employees, int id) {
    	//广搜队列,存储员工信息
        queue<Employee> que;
        //为根据id快速找到对应的属性,所以用一个哈希表存储。
        for (auto x: employees) m[x->id] = *x;
        //初始id先入队
        que.push(m[id]);
        int ans = 0;
        while (!que.empty()) {
        	//不断寻找队首元素的下属,将其入队
            Employee temp = que.front();
            que.pop();
            ans += temp.importance;
            for (auto x: temp.subordinates) que.push(m[x]);
        }
        return ans;
    }
};

4. Leetcode 17: 电话号码的字母组合

题目链接
解题思路:每一个输入数字,都相当于把对应字母都尝试加到答案中,相当于N叉树的前序遍历。所以用深搜回溯的方法。
代码演示:

class Solution {
public:
    vector<string> res;
    //前两个随意赋值,重点是2-9的赋值
    vector<string> str_map = {" ", "*", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) return res;
        string buff = "";
        dfs(digits, buff, 0);
        return res;
    }
    void dfs(string digits, string buff, int ind) {
        if (ind == digits.size()) {
        	//遍历到了最后一个数字,将buff追加到结果中
            res.push_back(buff);
            return;
        }
        //digit表示ind索引对应的数字。
        int digit = digits[ind] - '0';
        //将digit对应的所有字母依次加到结果中,然后回溯重新递归
        for (int i = 0; i < str_map[digit].size(); i++) {
            buff += str_map[digit][i];
            dfs(digits, buff, ind + 1);
            buff.pop_back();
        }
    }
};

5. Leetcode 279: 完全平方数

题目链接
题目解析:可以转化为一个N叉树的层级遍历问题,第一层遍历1,4,9,…, 第二层对第一层遍历的每个数字基础上再增加1,4,9,…, 看哪一层最先遍历到给定数字N,则输出其层数即可。
所以可以用队列实现的广搜方法。队列存储数字和与对应的层数。

class Solution {
public:
    struct Node {
    	//存储当前完全平方数的和x,与层数step
        int x, step;
    };
    int numSquares(int n) {
        unordered_set<int> s; //去重
        queue<Node> que;
        que.push((Node){0, 0});
        int level = 0, size = 0;
        //以下为队列的一般写法
        while (!que.empty()) {
            Node temp = que.front();
            que.pop();
            for (int i = 1; i <= n; i++) {
                int newt = temp.x + i * i;
                if (newt > n) break;
                if (s.find(newt) != s.end()) continue;
                if (newt == n) {
                    return temp.step + 1;
                }
                que.push((Node){newt, temp.step + 1});
                s.insert(newt);
            }
        }
        return 0;
    }
};

6. Leetcode 111: 二叉树的最小深度

题目链接
题目分析:可以用深搜,也可用广搜。用深搜时,首先判断该节点是NULL节点或者叶子节点的情况,然后分别求左右节点的深度(前提条件是左右节点不能为NULL),对左右节点的深度取最小值然后加一记为当前节点的最小深度。
用广搜时,用队列存储节点和对应的深度(struct),然后层序遍历二叉树,遍历到第一个叶子节点即可输出深度并返回即可。
深搜的代码演示:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if (!root) return 0;
        if ((!root->left) && (!root->right)) return 1;
        int l = 100000, r = 100000;
        if (root->left)
            l = minDepth(root->left);
        if (root->right)
            r = minDepth(root->right);
        return min(l, r) + 1;
    }
};

7. Leetcode 1306: 跳跃游戏|||

题目链接
题目解析:状态转化问题,故抽象为搜索问题,可以用深搜,也可以用广搜。
用深搜时,递归输入当前数组arr,visited去重数组和待输入下标index, 每次递归index-arr[index]和index+arr[index]。
用广搜时,队列存储当前遍历到的位置index, 同样需visited 去重。
深搜的代码演示:

class Solution {
public:
    bool canReach(vector<int>& arr, int start) {
        int n = arr.size();
        vector<int> visited(n, 0);
        visited[start] = 1;
        return dfs(arr, visited, start);
    }
    bool dfs(vector<int> &arr, vector<int> &visited, int index) {
        if (index < 0 || index >= arr.size()) return false;
        if (arr[index] == 0) return true;
        int left = index - arr[index];
        int right = index + arr[index];
        bool l = false, r = false;
        if (left >= 0 && left < arr.size() && visited[left] == 0) {
            visited[left] = 1;
            l = dfs(arr, visited, left);
        }
        if (right >= 0 && right < arr.size() && visited[right] == 0) {
            visited[right] = 1;
            r = dfs(arr, visited, right);
        }
        return l || r;
    }
};

8. Leetcode 310: 最小高度树

题目链接
题目解析:可以把题目中的树看成一个放射状的图,最外层的节点度为1,向里依次增加。从而可以由外向内 层序遍历这个图,遍历完一圈时向内遍历下一圈,直到遍历到的最后一圈即可组成所有要求的根节点。

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if (n == 1) return vector<int>(1,0);
        vector<int> degree(n, 0);
        vector<vector<int>> map(n);
        for (auto x: edges) {
        	//每个节点对应的度
            degree[x[0]]++;
            degree[x[1]]++;
            //每个节点都和哪些节点相连
            map[x[0]].push_back(x[1]);
            map[x[1]].push_back(x[0]);
        }
        queue<int> que;
        //先将度为1的节点都放入队列中
        for (int i = 0; i < n; i++){
            if (degree[i] == 1) {
                que.push(i);
            }
        } 
        //结果数组
        vector<int> res;
        while (!que.empty()){
        	//该层有size 个节点,要将其一次性都pop出来
            int size = que.size();
            //pop前先清空结果数组,相当于每遍历一层刷新一次结果
            res.resize(0);
            for (int i = 0; i < size; i++) {
                int x = que.front();
                // printf("size: %d, x : %d\n", size, x);
                que.pop();
                //该层每一个pop出来的元素都可以组成根节点
                res.push_back(x);
                for (auto neighber : map[x]) {
                	//要在途中删除x这个节点,其相邻节点度要随之变化
                    degree[neighber]--;
                    //一旦出现了新的叶子节点则将其加到队列中
                    if (degree[neighber] == 1) {
                        que.push(neighber);
                    }
                }
            }
        }
        return res;
     }
};

9. Leetcode 575: 分糖果

题目链接
解题思路:可以用一个集合存储糖果类型,当集合的数量不小于n/2时,最多能吃到n/2个糖果类型,否则只能吃到集合数量的糖果类型。

class Solution {
public:
    int distributeCandies(vector<int>& candyType) {
        unordered_set <int> set;
        int n = candyType.size();
        //所有类型都存到集合中
        for (auto x: candyType) set.insert(x);
        //集合数量不小于n/2
        if (set.size() >= n / 2) return n/2;
        return set.size();
    }
};

10. Leetcode 1487: 保证文件名唯一

题目链接
题目解析:从"唯一"可以想到哈希表,文件名可能出现多次,故哈希表的key可以为文件名,value设为该文件名出现的次数。
遍历输入文件名name时,若其在哈希表中不存在,则直接加到结果中,同时更新哈希表的name值;
若其在哈希表中存在,获得其在哈希表中对应的value值count, 此时循环判断name + “(” + count + ")"是否在哈希表中存在,只要存在则count++继续判断。
走出上述循环判断后,name + “(” + count + ")在哈希表中必不存在,将其加到结果数组中,同时更新哈希表key为name 的值和key为name + “(” + count + ")的值。

class Solution {
public:
    string get_int_name(string name, int count) {
    	//字符串增加后缀数字函数
        return name + "(" + to_string(count) +  ")";
    }
    vector<string> getFolderNames(vector<string>& names) {
        int n = names.size();
        unordered_map<string, int> map; //哈希表,存储name和对应的count
        vector<string> res(n); //结果数组

        for (int i = 0; i < n; i++) {
            string name = names[i];
            if (map.find(name) == map.end()) { //name在哈希表中不存在
                map[name] = 1;
                res[i] = name;
            }else {
            	//那么在哈希表中存在,首先获取出现次数count
                int count = map[name];
                //循环判断name + "(" + count + ")"是否出现过
                while (map.find(get_int_name(name, count)) != map.end()) {
                    count++;
                }
                //循环判断完后既要更新name的count, 
                //还要更新name + "(" + count + ")"对应的count
                map[name] = count + 1;
                map[get_int_name(name, count)] = 1;
                //更新结果数组
                res[i] = get_int_name(name, count);
            }
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值