LeetCode hot-100 简单and中等难度,61-70.

207. 课程表

难度中等520

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

 

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

 

提示:

  1. 输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法
  2. 你可以假定输入的先决条件中没有重复的边。
  3. 1 <= numCourses <= 10^5
此题就是拓扑排序吧。

课程安排图是否是 有向无环图(DAG)。即课程间规定了前置条件,但不能构成任何
环路,否则课程前置条件将不成立。
思路是通过 拓扑排序 判断此课程安排图是否是 有向无环图(DAG) 。 拓扑排序原
理: 对 DAG 的顶点进行排序,使得对每一条有向边 (u,v),均有 u(在排序记录
中)比 v 先出现。亦可理解为对某点 v 而言,只有当 v 的所有源点均出现了,v
 才能出现。
通过课程前置条件列表 prerequisites 可以得到课程安排图的 邻接表 adjacenc
y,以降低算法时间复杂度,以下两种方法都会用到邻接表。

作者:jyd
链接:https://leetcode-cn.com/problems/course-schedule/solution/cou
rse-schedule-tuo-bu-pai-xu-bfsdfsliang-chong-fa/
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> course(numCourses);
        vector<int> degree(numCourses);
        //  for (const auto& info: prerequisites) {
        for(vector<int> v:prerequisites){
            course[v[1]].push_back(v[0]);// 0——>1、、
            degree[v[0]]++;  //degree[1]++;
        }
        queue<int> q;
        for(int i=0;i<numCourses;i++){
            if(degree[i]==0) q.push(i);
        }
        int visit=0;
        while(!q.empty()){
            // int size=q.size();
            // for(int i=0;i<size;i++){
                visit++;
                int x=q.front();    
                // cout<<x<<endl;
                q.pop();
                for(int y:course[x]){
                    // cout<<y<<endl;
                    degree[y]--;
                    if(degree[y]==0) q.push(y);
                }
            // }
        }
        return visit==numCourses;
    }
};

208. 实现 Trie (前缀树)

难度中等367

实现一个 Trie (前缀树),包含 insertsearch, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

说明:

  • 你可以假设所有的输入都是由小写字母 a-z 构成的。
  • 保证所有输入均为非空字符串。

Trie 是一颗非典型的多叉树模型,多叉好理解,即每个结点的分支数量可能为多个。
为什么说非典型呢?因为它和一般的多叉树不一样,尤其在结点的数据结构设计上,比如一般的多叉树的结点是这样的:
struct TreeNode {
    VALUETYPE value;    //结点值
    TreeNode* children[NUM];    //指向孩子结点
};

而 Trie 的结点是这样的(假设只包含'a'~'z'中的字符):
struct TrieNode {
    bool isEnd; //该结点是否是一个串的结束
    TrieNode* next[26]; //字母映射表
};

要想学会 Trie 就得先明白它的结点设计。我们可以看到TrieNode结点中并没有直接保存字符值的数据成员,那它是怎么保存字符的呢?
这时字母映射表next 的妙用就体现了,TrieNode* next[26]中保存了对当前结点而言下一个可能出现的所有字符的链接,因此我们可以通过一个父结点来预知它所有子结点的值:
for (int i = 0; i < 26; i++) {
    char ch = 'a' + i;
    if (parentNode->next[i] == NULL) {
        说明父结点的后一个字母不可为 ch
    } else {
        说明父结点的后一个字母可以是 ch
    }
}

Trie 中一般都含有大量的空链接,因此在绘制一棵单词查找树时一般会忽略空链接,同时为了方便理解我们可以画成这样:

作者:huwt
链接:https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/trie-tree-de-shi-xian-gua-he-chu-xue-zhe-by-huwt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总结出 Trie 的几点性质:

Trie 的形状和单词的插入或删除顺序无关,也就是说对于任意给定的一组单词,Trie 的形状都是唯一的。

查找或插入一个长度为 L 的单词,访问 next 数组的次数最多为 L+1,和 Trie 中包含多少个单词无关。

Trie 的每个结点中都保留着一个字母表,这是很耗费空间的。如果 Trie 的高度为 n,字母表的大小为 m,最坏的情况是 Trie 中还不存在前缀相同的单词,那空间复杂度就为 O(m^n)。


最后,关于 Trie 的应用场景,希望你能记住 8 个字:一次建树,多次查询。(慢慢领悟叭~~)

class Trie {
    private:
        bool isEnd;
        Trie* next[26];
public:
    /** Initialize your data structure here. */
    Trie() {
        isEnd=false;
        memset(next, 0, sizeof(next));
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie* node=this;
        for(char c:word){
            if(node->next[c-'a']==NULL){
                node->next[c-'a']=new Trie();
            }
            node=node->next[c-'a'];
        }
        node->isEnd=true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie* node=this;
        for(char c:word){
            node=node->next[c-'a'];
            if(node==NULL) return false;
        }
        return node->isEnd;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie* node=this;
        for(char c:prefix){
            node=node->next[c-'a'];
            if(node==NULL) return false;
        }
        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

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

难度中等648

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

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

这道题据说是面试的高频考题,同时也是基础算法的应用。

image.png

 借助 partition 操作定位到最终排定以后索引为 len - k 的那个元素(特别注意:随机化切分元素)

partition(切分)操作,使得:

对于某个索引 j,nums[j] 已经排定,即 nums[j] 经过 partition(切分)操作以后会放置在它 “最终应该放置的地方”;
nums[left] 到 nums[j - 1] 中的所有元素都不大于 nums[j];
nums[j + 1] 到 nums[right] 中的所有元素都不小于 nums[j]。

image.png

partition(切分)操作总能排定一个元素,还能够知道这个元素它最终所在的位置,这样每经过一次 partition(切分)操作就能缩小搜索的范围,这样的思想叫做 “减而治之”(是 “分而治之” 思想的特例)。

int findKthLargest(vector<int> &nums,int k){
    int len=nums.size();
    int left=0;int right=len-1;
    int target=len-k;//第K大
    while(1){
        int index=partition(nums,left,right);
        if(index==target) return nums[index];
        else if(index<target) left=index+1;
        else right=index-1;
    }
}
//&非常重要!!!
int partition(vector<int>& nums,int left,int right){
    int pivot=nums[left];
    int j=left;
    for(int i=left+1;i<=right;i++){
        if(nums[i]<pivot){
            j++;
            swap(nums,i,j);
        }
    }
    //满足了
    swap(nums,j,left);
    return j;
}

 

优先队列的思路是很朴素的。因为第 K 大元素,其实就是整个数组排序以后后半部分最小的那个元素。因此,我们可以维护一个有 K 个元素的最小堆:
1、如果当前堆不满,直接添加;
2、堆满的时候,如果新读到的数小于等于堆顶,肯定不是我们要找的元素,只有新都到的数大于堆顶的时候,才将堆顶拿出,然后放入新读到的数,进而让堆自己去调整内部结构。

假设数组有 len 个元素。
思路1:把 len 个元素都放入一个最小堆中,然后再 pop() 出 len - k 个元素,此时最小堆只剩下 k 个元素,堆顶元素就是数组中的第 k 个最大元素。
思路2:把 len 个元素都放入一个最大堆中,然后再 pop() 出 k - 1 个元素,因为前 k - 1 大的元素都被弹出了,此时最大堆的堆顶元素就是数组中的第 k 个最大元素。

搞懂「建堆」、「调整」和「删除」的过程

void maxHeapify(vector<int>& a,int i,int heapSize){
    int l=i*2+1;
    //0 1 2
    while(l<heapSize){
        if(l+1<heapSize&&a[l+1]>a[l]) l=l+1;
        if(a[l]>a[i]){
            swap(a[l],a[i]);
            i=l;l=i*2+1;
        }else break;
    }
    
}
void buildMaxHeap(vector<int>& a,int heapSize){
    for(int i=heapSize/2;i>=0;i--)
        maxHeapify(a,i,heapSize);
}
int findKthLargest(vector<int> &nums,int k){
    int heapSize=nums.size();
    buildMaxHeap(nums,heapSize);
    for(int i=nums.size()-1;i>=nums.size()-k+1;i--){
        swap(nums[0],nums[i]);
        --heapSize;
        maxHeapify(nums,0,heapSize);
    }
return nums[0];
}
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // if(k>nums.size()) return
        priority_queue<int,vector<int>,less<int>> q;
        for(int i=0;i<nums.size();i++) q.push(nums[i]);
        for(int i=0;i<k-1;i++) q.pop();
        return q.top();
    }
};

221. 最大正方形

难度中等505收藏分享切换为英文关注反馈

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:

输入: 

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

输出: 4

image.png

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.size()==0) return 0;
        // 动态规划
        int n=matrix.size();int m=matrix[0].size();
        vector<vector<int>> dp(n,vector<int>(m));
        int maxn=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(i==0||j==0) dp[i][j]=matrix[i][j]-'0';
                else if(matrix[i][j]=='1')
                dp[i][j]=min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j]))+1;
                if(dp[i][j]>maxn) maxn=dp[i][j];
            }
        }
        return maxn*maxn;
    }
};

 

226. 翻转二叉树

难度简单531

翻转一棵二叉树。

示例:

输入:

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

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1
public TreeNode invertTree(TreeNode root) {
    if (root == null) {
        return null;
    }
    TreeNode right = invertTree(root.right);
    TreeNode left = invertTree(root.left);
    root.left = right;
    root.right = left;
    return root;
}
这道题的核心在于遍历节点,只要能接触到每一个节点,就能反转它的左右孩子,至于遍历方式反而不重要了,先序后序中序层次都一样,都可以用。

 

234. 回文链表

难度简单595收藏分享切换为英文关注反馈

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

快慢指针,然后翻转一半链表即可。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head || !head->next)
            return 1;
        ListNode *fast = head, *slow = head;
        ListNode *p, *pre = NULL;
        while(fast && fast->next){
            p = slow;
            slow = slow->next;    //快慢遍历
            fast = fast->next->next;

            p->next = pre;  //翻转
            pre = p;
        }
        if(fast)  //奇数个节点时跳过中间节点
            slow = slow->next;

        while(p){       //前半部分和后半部分比较
            if(p->val != slow->val)
                return 0;
            p = p->next;
            slow = slow->next;
        }
        return 1;
    }
};

236. 二叉树的最近公共祖先

难度中等687收藏分享切换为英文关注反馈

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

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

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

 

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

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

 

说明:

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

最近公共祖先的定义: 设节点 root为节点 p,q的某公共祖先,若其左子节点 root.left和右子节点 root.right都不是 p,q 的公共祖先,则称 root 是 “最近的公共祖先” 。
根据以上定义,若 root 是 p,q 的 最近公共祖先 ,则只可能为以下情况之一:

p 和 q 在 root的子树中,且分列 root的 异侧(即分别在左、右子树中);
p=root ,且 q 在 root 的左或右子树中;
q=root,且 p 在 root 的左或右子树中;

函数:

  1. 如果 ppp 和 qqq 都存在,则返回它们的公共祖先;
  2. 如果只存在一个,则返回存在的一个;
  3. 如果 ppp 和 qqq 都不存在,则返回NULL
具体思路:
(1) 如果当前结点 root 等于 NULL,则直接返回 NULL
(2) 如果 root 等于 p or q ,那这棵树一定返回 p q
(3) 然后递归左右子树,因为是递归,使用函数后可认为左右子树已经算出结果,用 left和 right表示
(4) 此时若left为空,那最终结果只要看 right;若 right为空,那最终结果只要看 left
(5) 如果 left  right 都非空,说明一边一个,因此 root 是他们的最近公共祖先
(6) 如果 left right 都为空,则返回空(其实已经包含在前面的情况中了)
时间复杂度是 O(n):每个结点最多遍历一次,空间复杂度是 O(n):需要系统栈空间

作者:Wilson79
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/c-jing-dian-di-gui-si-lu-fei-chang-hao-li-jie-shi-/
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL)
            return NULL;
        if(root == p || root == q) 
            return root;
            
        TreeNode* left =  lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
       
        if(left == NULL)
            return right;
        if(right == NULL)
            return left;      
        if(left && right) // p和q在两侧
            return root;
        
        return NULL; // 必须有返回值
    }
};

 

class Solution {
public:
    TreeNode* ans;
    bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return false;
        bool lson = dfs(root->left, p, q);
        bool rson = dfs(root->right, p, q);
        if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
            ans = root;
        } 
        return lson || rson || (root->val == p->val || root->val == q->val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/solution/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetc-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

238. 除自身以外数组的乘积

难度中等545收藏分享切换为英文关注反馈

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

 

示例:

输入: [1,2,3,4]
输出: [24,12,8,6]

 

提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。

说明: 不要使用除法,且在 O(n) 时间复杂度内完成此题。

进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

result[i] = left_i*right_i
利用动态规划分别求出left_i和right_i即可,O(1)空间实现也非常简单,只需要在结果数组中计算出left_i,布置一个right_i的临时变量,遍历两次即可求解。

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

作者:OrangeMan
链接:https://leetcode-cn.com/problems/product-of-array-except-self/solution/cjian-ji-dai-ma-shuang-zhi-zhen-6xing-dai-ma-by-or/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

240. 搜索二维矩阵 II

难度中等395收藏分享切换为英文关注反馈

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

class Solution {
public:
    
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
    int i=matrix.size()-1,j=0;
    while(i>=0&&j<matrix[0].size())
        if(matrix[i][j]==target) return true;
        else if(matrix[i][j]>target) i--;
        else j++;
    return false;
}
};

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值