LeetCode 热题 HOT 100 打卡计划 | 第二十一天 | 每日进步一点点

70 阅读4分钟

图片.png

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

206. 反转链表

思路

(双指针,迭代) (n)

给定一个链表的头节点,让我们反转该链表并输出反转后链表的头节点。

样例:

图片.png

如样例所示,原始链表为1->2->3->4->5->NULL,我们将其翻转输出5->4->3->2->1->NULL。下面我们来讲解双指针的做法。

将一个链表翻转,即将该链表所有节点的next指针指向它的前驱节点。由于是单链表,我们在遍历时并不能直接找到其前驱节点,因此我们需要定义一个指针保存其前驱节点。

每次翻转时,我们都需要修改当前节点的next指针。如果不在改变当前节点的next指针前保存其后继节点,那么我们就失去了当前节点和后序节点的联系,因此还需要额外定义一个指针用于保存当前节点的后继节点。

具体过程如下:

1、定义一个前驱指针precur指针,pre指针用来指向前驱节点,cur指针用来遍历整个链表,初始化pre = nullcur = head

图片.png

2、我们首先保存cur指针指向节点的后继节点,然后让cur指针指向节点的next指针指向其前驱节点,即cur->next = pre

3、pre指针和cur指针分别后移一位,重复上述过程,直到cur指向空节点。

4、最后我们返回pre节点。

图示过程如下:

图片.png

时间复杂度分析: 只遍历一次链表,时间复杂度是O(n)。

c++代码

 /**
  * 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){
             ListNode* t = cur->next;  //先保存后继节点
             cur->next = pre;
             pre = cur, cur = t;
         }
         return pre;
     }
 };

207. 课程表

思路

拓扑排序: O(n+m)

对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点uv,若<u,v> ∈E(G),则u在线性序列中出现在v之前。

图片.png 一个合法的选课序列就是一个拓扑序,拓扑序是指一个满足有向图上,不存在一条边出节点在入节点前的线性序列,如果有向图中有环,就不存在拓扑序。可以通过拓扑排序算法来得到拓扑序,以及判断是否存在环。

拓扑排序步骤:

  • 1、建图并记录所有节点的入度。
  • 2、将所有入度为0的节点加入队列。
  • 3、取出队首的元素now,将其加入拓扑序列。
  • 4、访问所有now的邻接点nxt,将nxt的入度减1,当减到0后,将nxt加入队列。
  • 5、重复步骤34,直到队列为空。
  • 6、如果拓扑序列个数等于节点数,代表该有向图无环,且存在拓扑序。

时间复杂度分析: 假设 n 为点数,m 为边数,拓扑排序仅遍历所有的点和边一次,故总时间复杂度为 O(n+m)。

c++代码

 class Solution {
 public:
     /**
      1、建图并记录所有节点的入度。
      2、将所有入度为`0`的节点加入队列。
      3、取出队首的元素`now`,将其加入拓扑序列。
      4、访问所有`now`的邻接点`nxt`,将`nxt`的入度减`1`,当减到`0`后,将`nxt`加入队列。
      5、重复步骤`3`、`4`,直到队列为空。 
      6、如果拓扑序列个数等于节点数,代表该有向图无环,且存在拓扑序。
     **/
     bool canFinish(int n, vector<vector<int>>& edges) {
         vector<vector<int>> g(n);
         vector<int> d(n);  // 存贮每个节点的入度
         for(auto edge : edges){
             g[edge[1]].push_back(edge[0]);  //建图
             d[edge[0]]++;  //入度加1
         }
 ​
         queue<int> q;
         for(int i = 0; i < n; i++){
             if(d[i] == 0) q.push(i);  //将所有入度为0的节点加入队列。
         }
 ​
         int cnt = 0;  //统计拓扑节点的个数
         while(q.size()){
             int t = q.front();
             q.pop();
             cnt++;
             for(int i : g[t]){  //访问t的邻接节点
                 d[i]--;
                 if(d[i] == 0) q.push(i);
             }
         }
         
         return cnt == n;
     }
 };

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

思路

快速选择 O(n)

快选是在快排的基础上只递归一半区间。

图片.png 如果当前要找的数>=x递归左区间,否则递归右区间

具体过程:

  • 1、在特定区间[l, r]中,选中某个数x,将大于x的放在左边,小于x的放在右边,其中[l, j]是大于x的区间,[j + 1,r]是小于x的区间。
  • 2、判断出第k大与j的大小关系,若k <= j,则递归到[l, j]区间,否则递归到[j + 1,r]的区间

注意: 此处求的是第k大,而里面的方法k是指第k个位置,需要变成k - 1

c++代码

 class Solution {
 public:
     int findKthLargest(vector<int>& nums, int k) {
         return quick_sort(nums, 0, nums.size() - 1, k - 1); //注意下标
     }
 ​
     int quick_sort(vector<int>& nums, int l ,int r, int k){
         if(l == r) return nums[l];
         int x = nums[l], i = l - 1, j = r + 1;
         while(i < j){
             do i++; while(nums[i] > x);
             do j--; while(nums[j] < x);
             if(i < j) swap(nums[i], nums[j]);
         }
 ​
         if(k <= j) return quick_sort(nums, l, j, k);
         else return quick_sort(nums, j + 1, r, k);
     }
 };