2021winter leetcode刷题(coding)

前言

开始抱一下佛脚。另外解法参考了一下慕课网还有网上一些教程。本博客仅仅作为学习使用,请勿恶意转载。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

大家可以点击 leetcode 题库 开始刷题吧。
在这里插入图片描述



数组

349. 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

思路:
使用一个set作为媒介, 存放nums1中的元素,然后遍历nums2的元素,去set中查找是否有改元素,若没有,即加入解的vector中。
注意:如果是自己IDE环境下,记得引入头文件(接下来所有题目都默认已经引入):

include <set>
include <vector>
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> record(nums1.begin(), nums1.end());

        set<int> resultSet;
        for(int i = 0; i < nums2.size(); i++)
        	// 若找得到则是公共元素
            if(record.find(nums2[i]) != record.end())
                resultSet.insert(nums2[i]);
        return vector<int>(resultSet.begin(), resultSet.end());
    }
};

时间复杂度:O(nlogn), 因为这里用到的set 在c++中是二分搜索树实现,如果用unordered_set则可以降低为O(n)
空间复杂度:O(n)



1. Two Sum

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    	// 需要引入头文件<unordered_map>, 与map区别似乎map底层是二分搜索树实现的
    	// 而unordered_map底层实现是哈希表实现的
        unordered_map<int, int> record;
        int res[2];
        for(int i = 0; i < nums.size(); i++){
            int complement = target - nums[i];
            if(record.find(complement) != record.end()){
                res[0] = i;
                res[1] = record[complement];
                break;
            }
            record[nums[i]] = i;
        }
        // cout << "the input has no solution" << endl;
        return vector<int>(res, res + 2);
    }
};

时间复杂度:O(n^2)
空间复杂度:O(n^2)



454. 4Sum II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D){
        unordered_map<int, int> record;
        for(int i = 0; i < C.size(); i++)
            for(int j = 0; j < D.size(); j++)
                // 如果找不到则默认返回 0, 故可以直接++
                record[C[i] + D[j]] ++;
        
        int res = 0;
        for(int i = 0; i < A.size(); i++)
            for(int j = 0; j < B.size(); j++)
                if(record.find(0 - A[i] - B[j]) != record.end())
                    res += record[0 - A[i] - B[j]];
        return res;
    }
};



447. 回旋镖的数量

给定平面上 n 对 互不相同 的点 points ,其中 points[i] = [xi, yi] 。回旋镖 是由点 (i, j, k) 表示的元组 ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。

返回平面上所有回旋镖的数量。

示例 1:
输入:points = [[0,0],[1,0],[2,0]]
输出:2
解释:两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

示例 2:
输入:points = [[1,1],[2,2],[3,3]]
输出:2

示例 3:
输入:points = [[1,1]]
输出:0

class Solution {
public:
    int numberOfBoomerangs(vector<vector<int>>& points) {
        int res = 0;
        for(const auto&p: points){
            unordered_map<int, int> record;
            for(const auto&q: points){
                if (p != q){
                    int dx = p[0] - q[0];
                    int dy = p[1] - q[1];
                    record[dx * dx + dy * dy]++;
                }
            }
            for(const auto&x: record){
                res += x.second * (x.second - 1);
            }
        }
        return res;
    }
};

时间复杂度:O(n^2)
空间复杂度: 这个我暂时不清楚,知道的小伙伴可以留言hhh。



219 存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

示例 1:
输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:
输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false

思路:
滑动窗口 + 哈希表

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        bool flag = false;
        unordered_set<int> record;
        for(int i = 0; i < nums.size(); i++){
            if(record.find(nums[i]) != record.end()){
                flag = true;
                break;
            }
            record.insert(nums[i]);
            // 必须保持record中最多有k个元素
            if(record.size() == k + 1)
                record.erase(nums[i - k]);
        }
        return flag;
    }
};

时间复杂度: O(n)
空间复杂度: O(k), 因为k值固定,所以相当于是O(1)



链表

206 反转链表

这道题其实很简单,学数据结构的时候会提到,由于特别经典,这里我就在提一下吧。
反转一个单链表。

示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while(cur != nullptr){
            ListNode* next = cur->next;
            // 真正操作部分
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

时间复杂度: O(n)
空间复杂度: O(1)



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() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 虚拟头节点
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;

        while(cur->next != nullptr){
            if(cur->next->val == val){
                // 删除cur->next
                ListNode* delNode = cur->next;
                cur->next = delNode->next;
                delete delNode;
            }else{
                cur = cur->next;
            }
        }
        ListNode* retNode = dummyHead->next;
        delete dummyHead;
        return retNode;
    }
};



24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
在这里插入图片描述

示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:
输入:head = []
输出:[]

示例 3:
输入:head = [1]
输出:[1]

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* p = dummyHead;
        while(p->next && p->next->next){
            ListNode* node1 = p->next;
            ListNode* node2 = node1->next;
            ListNode* next = node2->next;
            // 颠倒操作
            node2->next = node1;
            node1->next = next;
            p->next = node2;
            // 进行下一次循环, 即下一次交换的那对节点的前一个节点
            p = node1;
        }
        ListNode * retNode = dummyHead->next;
        delete dummyHead;
        return retNode;
    }
};

时间复杂度: O(n)
空间复杂度: O(1)
另外,有一道题值得思考: 148. 要求在O(nlogn)下对链表进行排序。目前我只想到了自底向上归并排序。有其他思路的小伙伴不妨留言提供一下思路呗。



237. 删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 – head = [4,5,1,9],它可以表示为:
在这里插入图片描述

示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
        if(node == nullptr)
            return;
        // 假设删除是最后一个节点
        if(node->next == nullptr){
            delete node;
            node = nullptr;
            return;
        }
            
        node->val = node->next->val;
        ListNode* delNode = node->next;
        node->next = delNode->next;
        delete delNode;
        return;
    }
};

时间复杂度: O(1)
空间复杂度:O(1)



19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:
输入:head = [1], n = 1
输出:[]

示例 3:
输入:head = [1,2], n = 1
输出:[1]
思路:
双指针技术
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        assert(n >= 0);
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* p = dummyHead;
        ListNode* q = dummyHead;
        // 让p 和 q相差n+1
        for(int i = 0; i < n + 1; i++){
            assert(q); // 保证q不为空
            p = p->next;
        }

        while(p != nullptr){
            p = p->next;
            q = q->next;
        }
        // 现在要删除的节点就是 q->next

        ListNode* delNode = q->next;
        q->next = delNode->next;
        delete delNode;

        ListNode* retNode = dummyHead->next;
        delete dummyHead;
        return retNode;
    }
};

时间复杂度: O(1)
空间复杂度:O(1)



栈和队列的使用

20. 有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合
  2. 左括号必须以正确的顺序闭合

示例 1:
输入:s = “()”
输出:true

示例 2:
输入:s = “()[]{}”
输出:true

示例 3:
输入:s = “(]”
输出:false

示例 4:
输入:s = “([)]”
输出:false

示例 5:
输入:s = “{[]}”
输出:true

// 需要加载 stack 和 unordered_map 头文件

class Solution {
public:
    bool isValid(string s) {
        stack<char> stack;
        unordered_map<char, char> bracket_map;
        bracket_map[')'] = '(';
        bracket_map[']'] = '[';
        bracket_map['}'] = '{';
        // 遍历字符串
        for(int i = 0; i < s.size(); i++){
            if(s[i] == '(' || s[i] == '[' || s[i] == '{')
                stack.push(s[i]);
            else{
                if(stack.size() == 0)
                    return false;
                char c = stack.top();
                stack.pop();

                char match = bracket_map[s[i]];
                if (c != match)
                    return false;
            }
        }
        // 栈中的元素为空时才是匹配的
        return stack.size() == 0;
    }
};

时间复杂度:O(n)
空间复杂度:O(n)



144. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
示例 1:
在这里插入图片描述

输入:root = [1,null,2,3]
输出:[1,2,3]

示例 2:
输入:root = []
输出:[]

示例 3:
输入:root = [1]
输出:[1]

示例 4:
在这里插入图片描述
输入:root = [1,2]
输出:[1,2]

示例 5:
在这里插入图片描述

输入:root = [1,null,2]
输出:[1,2]

/**
 * 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) {}
 * };
 */

struct Command{
    string s; // go, print
    TreeNode* node;
    Command(string s, TreeNode* node): s(s), node(node){}
};

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == nullptr)
            return res;
        
        stack<Command> commandStack;
        commandStack.push(Command("go", root));
        while(!commandStack.empty()){
            Command command = commandStack.top();
            commandStack.pop();

            if(command.s == "print")
                res.push_back(command.node->val);
            else{
                assert(command.s == "go");
                if(command.node->right)
                    commandStack.push(Command("go", command.node->right));
                if(command.node->left)
                    commandStack.push(Command("go", command.node->left));
                commandStack.push(Command("print", command.node));
            }
        }
        return res;
    }
};



102. 二叉树的层序遍历

其实这个在学数据结构的时候也有学到咦。
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:
二叉树:[3,9,20,null,null,15,7],

    3
    / \
   9  20
    /   \
   15   7

返回其层序遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
/**
 * 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:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root == nullptr)
            return res;
        
        queue< pair<TreeNode*, int> > q;
        q.push(make_pair(root, 0));
        while(!q.empty()){
            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();   // 队首出队

            // 如果res中还没有该层对应的vector
            if(level == res.size())
                res.push_back(vector<int>());

            res[level].push_back(node->val);
            if(node->left)
                q.push(make_pair(node->left, level + 1));
            if(node->right)
                q.push(make_pair(node->right, level + 1));
        }
        return res;
    }
};



279. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4

示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9

提示:
1 <= n <= 104

思路:
将整个问题转化为一个图论问题。
从n到0,每个数字表示一个节点。
如果两个数字x到y相差一个完全平方数,则连接一条边。
由此我们将原问题转化为求这个得到的无权图从n到0的最短路径。
采用BFS来访问。

class Solution {
public:
    int numSquares(int n) {
        assert(n > 0);

        int res;
        // 第一个表示第几个数字, 第二个经历了多长的路径走到0
        queue<pair<int, int>> q;
        q.push(make_pair(n, 0));

        vector<bool> visited(n+1, false);
        visited[n] = true;

        while(!q.empty()){
            int num = q.front().first;
            int step = q.front().second;
            q.pop();

            if(num == 0){
                res = step;
                break;
            }  
            
            for(int i = 1; ;i++){
                int a = num - i * i;
                if(a < 0)
                    break;
                if(a == 0)
                    return step + 1;
                if(!visited[a]){
                    q.push(make_pair(a, step + 1));
                    visited[num - i * i] = true;
                }
            }
        }
        return res;
    }
};



347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。你可以按任意顺序返回答案。

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        assert(k > 0);

        // 统计每个元素出现的频率
        // (元素, 频率)
        unordered_map<int, int> freq;
        for(int i = 0; i < nums.size(); i++)
            freq[nums[i]] ++;

        assert(k <= freq.size());
        
        // 扫描freq,维护当前出现频率最高的k个元素 
        // (频率,元素)
        priority_queue< pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > pq;
        for(unordered_map<int, int>::iterator iter = freq.begin();
            iter != freq.end(); iter++){
            if(pq.size() == k){
                // 优先队列已满,然后新出现的频率又高于队列中最小的
                if(iter->second > pq.top().first){
                    pq.pop();
                    pq.push(make_pair(iter->second, iter->first));
                }
            } else {
                pq.push(make_pair(iter->second, iter->first));
            }
        }
        vector<int> res;
        while(!pq.empty()){
            res.push_back(pq.top().second);
            pq.pop();
        }
        return res;
    }
};

时间复杂度: O(nlog k)



二叉树和递归

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

/**
 * 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 maxDepth(TreeNode* root) {
        // 递归终止条件
        if(root == nullptr)
            return 0;

        int leftMaxDepth = maxDepth(root->left);
        int rightMaxDepth = maxDepth(root->right);
        return max(leftMaxDepth, rightMaxDepth) + 1;
    }
};



226. 翻转二叉树

翻转一棵二叉树。
示例:
输入:

    4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

This problem was inspired by this original tweet by Max Howell:
Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off.

/**
 * 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:
    TreeNode* invertTree(TreeNode* root) {
        if(root == nullptr)
            return nullptr;

        invertTree(root->left);
        invertTree(root->right);
        swap(root->left, root->right);
        
        return root;
    }
};



112. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

叶子节点 是指没有子节点的节点。

示例1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
在这里插入图片描述

示例2:
在这里插入图片描述
输入:root = [1,2,3], targetSum = 5
输出:false

示例3:
输入:root = [1,2], targetSum = 0
输出:false

/**
 * 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:
    bool hasPathSum(TreeNode* root, int targetSum) {
        // 注意题意一定要有叶子节点
        if (root == nullptr)
            return false;
        if(root->left == nullptr && root->right == nullptr)
            return root->val == targetSum;

        if(hasPathSum(root->left, targetSum - root->val))
            return true;
        if(hasPathSum(root->right, targetSum - root->val))
            return true;
        
        return false;
    }
};



257. 二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。

示例:
输入:

   1
 /   \
2     3
 \
  5

输出: [“1->2->5”, “1->3”]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3

/**
 * 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:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;

        if(root == nullptr)
            return res;
        if(root->left == nullptr && root->right == nullptr){
            res.push_back(to_string(root->val));
            return res;
        }

        vector<string> leftS = binaryTreePaths(root->left);
        for(int i = 0; i < leftS.size(); i++)
            res.push_back(to_string(root->val) + "->" + leftS[i]);
        vector<string> rightS = binaryTreePaths(root->right);
        for(int i = 0; i < rightS.size(); i++)
            res.push_back(to_string(root->val) + "->" + rightS[i]);

        return res;
    }
};



437. 路径总和 III

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。
和等于 8 的路径有:
1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11
/**
 * 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 {
private:
    // 在以node位根节点的二叉树中,寻找包含node的路径,和为sum 
    int findPath(TreeNode* node, int num){
        if(node == nullptr)
            return 0;

        int res = 0;
        if(node -> val == num)
            res += 1;
        
        res += findPath(node->left, num - node->val);
        res += findPath(node->right, num - node->val);

        return res;
    }

public:
    //  在以root为根节点的二叉树中,寻找和为sum的路径,返回这样的路径个数
    int pathSum(TreeNode* root, int sum) {
        if (root == nullptr)
            return 0;
        
        int res = findPath(root, sum);
        res += pathSum(root->left, sum);
        res += pathSum(root->right, sum);

        return res;
    }
};



235. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

在这里插入图片描述
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。

/**
 * 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 {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        assert(p != nullptr && q != nullptr);

        if(root == nullptr)
            return nullptr;

        if(p->val < root->val && q->val < root->val)
            return lowestCommonAncestor(root->left, p, q);
        if(p->val > root->val && q->val > root->val)
            return lowestCommonAncestor(root->right, p, q);
        
        return root; // p, q位于root两侧或者p, q两者至少一个是node
    }
};



递归和回溯

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

示例 2:
输入:digits = “”
输出:[]

示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]

提示:
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

class Solution {
private:
    vector<string> res; // 存储结果
    const string letterMap[10] = {
        " ",    // 0
        "",     // 1
        "abc",  // 2
        "def",  // 3
        "ghi",  // 4
        "jkl",  // 5
        "mno",  // 6
        "pqrs", // 7
        "tuv",  // 8
        "wxyz", // 9
    };
    // s中保存了此时从digits[0...index-1]翻译得到的一个字母字符串
    // 寻找和digits[index]匹配的字母,获得digits[0..index]翻译得到的解
    void findCombination(const string &digits, int index, const string &s){
        if(index == digits.size()){
            res.push_back(s);
            return;
        }
        char c = digits[index];
        assert(c >= '2' && c <= '9');
        string letters = letterMap[c-'0'];
        for(int i = 0; i < letters.size(); i++){
            findCombination(digits, index + 1, s + letters[i]);
        }
        return;
    }

public:
    vector<string> letterCombinations(string digits) {
        res.clear();   // 其实这句不写也可以
        if(digits == "")
            return res;
        findCombination(digits, 0, "");
        return res;
    }
};

时间复杂度O(2^n), 忽略常数。



46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

class Solution {
private:
    vector<vector<int>> res;
    vector<bool> used; // 表示元素有没有别使用过

    // p中已经保存了有index个元素的排列
    void generatePermutation(const vector<int>&nums, int index, vector<int>& p) {
        if (index == nums.size()) {
            res.push_back(p);
            return;
        }

        for(int i = 0; i < nums.size(); i++) {
            if (!used[i]) {
                p.push_back(nums[i]);
                used[i] = true;
                generatePermutation(nums, index + 1, p);
                // 注意这一步
                p.pop_back();
                used[i] = false;
            }
        }
        return;
    }

public:
    vector<vector<int>> permute(vector<int>& nums) {
        res.clear();
        if(nums.size() == 0)
            return res;

        used = vector<bool>(nums.size(), false);
        vector<int> p;
        generatePermutation(nums, 0, p);

        return res;
    }
};



77. 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
class Solution {
private:
    vector<vector<int>> res;

    // 求解C(n, k), 当前已经找到的组合存储在c中,需要从start开始搜索新的额元素 
    void generateCombinations(int n, int k, int start, vector<int> &c) {
        if (c.size() == k) {
            res.push_back(c);
            return;
        }

        // 还有 k - c.size() 个空位, 这里没到n是因为做了剪枝优化
        for (int i = start; i <= n - (k - c.size()) + 1; i++) {
            c.push_back(i);
            // i 以前的数字已经别尝试过了
            generateCombinations(n, k, i + 1, c);
            // 注意这一步很重要
            c.pop_back();
        }
    }

public:
    vector<vector<int>> combine(int n, int k) {
        res.clear();
        if (n <= 0 || k <= 0 || k > n)
            return res;
        
        vector<int> c;
        generateCombinations(n, k, 1, c);
        
        return res;
    }
};



79. 单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = “ABCCED”, 返回 true
给定 word = “SEE”, 返回 true
给定 word = “ABCB”, 返回 false

class Solution {
private:
    // 上, 右, 下, 左
    int d[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
    int m, n; //行, 列
    vector<vector<bool>> visited; // 访问标识

    bool inArea(int x, int y) {
        return x >= 0 && x < m && y >= 0 && y < n;
    }

    // 从board[startx][starty] 开始,寻找word[index...word.size()] 
    bool searchWord(const vector<vector<char>> &board, const string& word, int index,
    int startx, int starty) {
        // 如果只剩下一个字符的话
        if(index == word.size() - 1)
            return board[startx][starty] == word[index];

        if(board[startx][starty] == word[index]) {
            visited[startx][starty] = true;
            // 从startx, starty出发,想四个方向寻找
            for(int i = 0; i < 4; i++) {
                int newx = startx + d[i][0];
                int newy = starty + d[i][1];
                if(inArea(newx, newy) && !visited[newx][newy])
                    if(searchWord(board, word, index + 1, newx, newy))
                        return true;
            }
            visited[startx][starty] = false;
        }

        return false; // 找不到
    }


public:
    bool exist(vector<vector<char>>& board, string word) {
        m = board.size();
        assert(m > 0);
        n = board[0].size();
        visited = vector<vector<bool>>(m, vector<bool>(n, false));

        for(int i = 0; i < board.size(); i++)
            for(int j = 0; j < board[i].size(); j++)
                if(searchWord(board, word, 0, i, j))
                    return true;
        return false;
    }
};



200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:
输入:

grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]

输出:1

示例 2:
输入:

grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]

输出:3

class Solution {
private:
    int d[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    int m, n;
    vector<vector<bool>> visited;

    bool inArea(int x, int y) {
        return x >= 0 && x < m && y >= 0 && y < n;
    }

    // 从 grid[x][y] 的位置开始,进行floodfill
    void dfs(vector<vector<char>>&grid, int x, int y) {
        visited[x][y] = true;
        for(int i = 0; i < 4; i++) {
            int newx = x + d[i][0];
            int newy = y + d[i][1];
            if (inArea(newx, newy) && !visited[newx][newy] 
            && grid[newx][newy] == '1')
                dfs(grid, newx, newy);
        }
        return;
    }

public:
    int numIslands(vector<vector<char>>& grid) {
        m = grid.size();
        if(m == 0)
            return 0;
        n = grid[0].size();

        visited = vector<vector<bool>>(m, vector<bool>(n, false));

        int res = 0;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                // 找到陆地且没有访问过
                if(grid[i][j] == '1' && !visited[i][j]) {
                    res ++;
                    dfs(grid, i, j);
                }
        return res;
    }
};



51. N 皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例1:
输入: n = 4
输出:[[".Q…","…Q",“Q…”,"…Q."],["…Q.",“Q…”,"…Q",".Q…"]]
在这里插入图片描述

示例2:
输入:n = 1
输出:[[“Q”]]

思路: 如何快速证明不合法情况

class Solution {
private:
    vector<vector<string>> res;
    vector<bool> col, dia1, dia2;

    vector<string> generateBoard(int n, vector<int> &row) {
        assert(row.size() == n);
        vector<string> board(n, string(n, '.'));
        for(int i = 0; i < n; i++)
            board[i][row[i]] = 'Q';
        return board;
    }

    // 尝试在一个n皇后问题中,摆放滴 index 行的皇后位置
    void putQueen(int n, int index, vector<int>& row) {
        if(index == n) {
            res.push_back(generateBoard(n, row));
            return;
        }

        for(int i = 0; i < n; i++) {
            // 尝试将第 index 行的皇后摆在第 i 列
            if(!col[i] && !dia1[index + i] && !dia2[index - i + n - 1]) {
                row.push_back(i);
                col[i] = true;
                dia1[index + i] = true;
                dia2[index - i + n - 1] = true;
                putQueen(n, index + 1, row);
                // 注意恢复进入函数钱的情况很重要
                row.pop_back();
                col[i] = false;
                dia1[index + i] = false;
                dia2[index - i + n - 1] = false;
            }
        }
        return;
    }

public:
    vector<vector<string>> solveNQueens(int n) {
        res.clear();
        col = vector<bool>(n, false);
        dia1 = vector<bool>(2 * n - 1, false);
        dia2 = vector<bool>(2 * n - 1, false);
        vector<int> row;
        putQueen(n, 0, row);
        return res;
    }
};

值得一提的是, 37题 数独问题。



动态规划

用空间去换时间。

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
4.  1 阶 + 1 阶 + 1 阶
5.  1 阶 + 2 阶
6.  2 阶 + 1 阶

解法一:
自顶而下

class Solution {
private:
vector<int> memo;

int clacWays(int n) {
    if (n == 0 || n == 1)
        return 1;

    if (memo[n] == -1)
        memo[n] = clacWays(n - 1) + clacWays(n - 2);

    return memo[n];
}

public:
    int climbStairs(int n) {
        memo = vector<int>(n + 1, -1);
        return clacWays(n);
    }
};

解法二:
动态规划(自底而上)

class Solution {
public:
    int climbStairs(int n) {
        vector<int> memo(n + 1, -1);

        memo[0] = memo[1] = 1;
        for(int i = 2; i <= n; i++)
            memo[i] = memo[i - 1] + memo[i - 2];
        return memo[n];
    }
};

注意一下 120. 三角形最小路径和。



343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
说明: 你可以假设 n 不小于 2 且不大于 58

记忆化搜索(自顶向下):

class Solution {
private:
    vector<int> memo;

    // 将n 进行分割(至少要分割两部分), 可以获得的最大成绩
    int breakInteger(int n) {
        if (n == 1)
            return 1;

        if(memo[n] != -1)
            return memo[n];
        
        int res = -1;
        for(int i = 1; i <= n - 1; i++) {
            // 注意这一步: i * (n - i)
            res = max3(res, i * (n - i), i * breakInteger(n - i));
        }
        memo[n] = res;
        return res;
    }

    int max3(int a, int b, int c) {
        return max(a, max(b, c));
    }

public:
    int integerBreak(int n) {
        assert(n >= 2);
        memo = vector<int>(n + 1, -1);
        return breakInteger(n);
    }
};

动态规划:

class Solution {
private:
    vector<int> memo;

    int max3(int a, int b, int c) {
        return max(a, max(b, c));
    }

public:
    int integerBreak(int n) {
        assert(n >= 2);
       
       // memo[i] 表示数字 i 分割(至少分割成两部分)后得到的最大乘积
       vector<int> memo(n + 1, -1);

       memo[1] = 1;
        // 求解memo[i]
       for(int i = 2; i <= n; i++){
           // j + (i - j)
            for(int j = 1; j <= i - 1; j++){
                memo[i] = max3(memo[i], j * (i - j), j * memo[i - j]);
            }
       }
       return memo[n];
    }
};

时间复杂度: O(n^2)



198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400

思路:
考虑状态:
考虑偷取[x…n-1] 范围里的房子 (函数的定义)

根据对状态的定义,决定状态的转移方程:
f(0) = max{v(0) + f(2), v(1) + f(3), v(2) + f(4), …,
v(n - 3) + f(n - 1), v(n - 2), v(n - 1)}

class Solution {
private:
    // memo[i] 表示考虑抢劫 nums[i...n)所能获得的最大收益
    vector<int> memo;

    // 考虑抢劫 nums[index...nums.size()) 这个范围的所有房子
    int tryRob(vector<int> &nums, int index) {
        if (index >= nums.size())
            return 0;

        if(memo[index] != -1)
            return memo[index];
        
        int res = 0;
        for(int i = index; i < nums.size(); i++)
            res = max(res, nums[i] + tryRob(nums, i + 2));

        memo[index] = res;
        return res;
    }

public:
    int rob(vector<int>& nums) {
        memo = vector<int>(nums.size(), -1);
        return tryRob(nums, 0);
    }
};

动态规划:

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 0)
            return 0;

        // memo[i] 表示抢劫 nums[i...n-1] 所能获得的最大收益
        vector<int> memo(n, -1);
        memo[n - 1] = nums[n - 1];
        for(int i = n - 2; i >= 0; i--) {
            // memo[i]
            for(int j = i; j < n; j++)
                memo[i] = max(memo[i], nums[j] +
                 (j + 2 < n ? memo[j + 2] : 0));
        }
        return memo[0];
    }
};



416. 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200。

示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

class Solution {
private:
    // memo[i][c] 表示使用索引为[0...i]的这些元素,是否可以完全填充一个容量为c的背包
    // -1 表示未计算; 0表示不可可以填充; 1表示可以填充
    vector<vector<int>> memo;

    // 使用nums[0...index], 是否可以完全填充一个容量为sum的背包
    bool tryPartition(const vector<int> &nums, int index, int sum) {
        if(sum == 0)
            return true;
        // 背包装不下 || 没物品可选了
        if(sum < 0 || index < 0)
            return false;

        if(memo[index][sum] != -1)
            return memo[index][sum];
        
        memo[index][sum] = (tryPartition(nums, index - 1, sum) || 
            tryPartition(nums, index - 1, sum - nums[index])) ? 1 : 0;
        return memo[index][sum];
    }

public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int i = 0; i < nums.size(); i++) {
            assert(nums[i] > 0);
            sum += nums[i];
        }
        if(sum % 2 != 0) 
            return false;
        
        memo = vector<vector<int>>(nums.size(), vector<int>(sum / 2 + 1, -1));
        return tryPartition(nums, nums.size() - 1, sum / 2);
    }
};

动态规划:

class Solution {

public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int i = 0; i < nums.size(); i++) {
            assert(nums[i] > 0);
            sum += nums[i];
        }
        if(sum % 2 != 0) 
            return false;
        
        int n = nums.size();
        int C = sum / 2;
        vector<bool> memo(C + 1, false);
        for(int i = 0; i <= C; i++)
            memo[i] = (nums[0] == i);

        for(int i = 1; i < n; i++)
            for(int j = C; j > nums[i]; j--)
                memo[j] = memo[j] || memo[j - nums[i]];    

        return memo[C];
    }
};



300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

1 <= nums.length <= 2500
-104 <= nums[i] <= 104

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        
        // memo[i] 表示以 nums[i] 为结尾的最长上升子序列的长度
        vector<int> memo(nums.size(), 1);
        for(int i = 1; i < nums.size(); i++) {
            for(int j = 0; j < i; j++) {
                if(nums[j] < nums[i])
                    memo[i] = max(memo[i], 1 + memo[j]);
            }
        }
        int res = 1;
        for(int i = 1; i < memo.size(); i++)
            res = max(res, memo[i]);
        return res;
    }
};

时间复杂度: O(n^2)
其实最长上升子序列有O(nlogn)的解法,但已经不是用动态规划的方法了。



贪心算法

455. 分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

示例 2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

提示:

1 <= g.length <= 3 * 104
0 <= s.length <= 3 * 104
1 <= g[i], s[j] <= 231 - 1

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        // 降序
        sort(g.begin(), g.end(), greater<int>());
        sort(s.begin(), s.end(), greater<int>());

        int si = 0, gi = 0;
        int res = 0;
        while(gi < g.size() && si < s.size()) {
            if(s[si] >= g[gi]){
                res++;
                si++;
                gi++;
            } else {
                gi++; // 去掉此贪心的小朋友
            }
        }
        return res;
    }
};



435. 无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

我们先来复习一下动态规划的解法:

class Solution {
private:
    static bool myCompare(const vector<int>& a, const vector<int>& b) {
        if (a[0] != b[0])
            return a[0] < b[0];
        return a[1] < b[1];
    }

public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(intervals.size() == 0)
            return 0;

        // 升序排序
        sort(intervals.begin(), intervals.end(), myCompare);
        // memo[i] 表示使用 intervals[0...i] 的区间能构成的最长不重叠区间序列
        vector<int> memo(intervals.size(), 1);
        for(int i = 1; i < intervals.size(); i++) {
            // 求 memo[i]
            for(int j = 0; j < i; j++) {
                if (intervals[i][0] >= intervals[j][1])
                    memo[i] = max(memo[i], 1 + memo[j]);
            }
        }

        int res = 0;
        for(int i = 0; i < memo.size(); i++)
            res = max(res, memo[i]);
        
        return intervals.size() - res;
    }
};

时间复杂度: O(n^2)

然后我们在思考的时候发现,每个区间的结尾很重要。故我们完全可以使用贪心算法,对结尾进行排序。结尾靠前排前面,结尾靠后排后面。

class Solution {
private:
    static bool myCompare(const vector<int>& a, const vector<int>& b) {
        if (a[1] != b[1])
            return a[1] < b[1];
        return a[0] < b[0];
    }

public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if(intervals.size() == 0)
            return 0;

        // 升序排序
        sort(intervals.begin(), intervals.end(), myCompare);
        
        // 当前能够保留的区间个数
        int res = 1;
        // 记录上一个使用的区间
        int pre = 0;
        for(int i = 1; i < intervals.size(); i++) {
            if(intervals[i][0] >= intervals[pre][1]) {
                res++;
                pre = i;
            }
        }
        return intervals.size() - res;
    }
};

时间复杂度: O(n)
贪心选择性质的证明(反证法):
贪心算法为A;最优算法为O;发现A完全能替代O, 且不影响求出最优解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值