持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情
206. 反转链表
思路
(双指针,迭代) (n)
给定一个链表的头节点,让我们反转该链表并输出反转后链表的头节点。
样例:
如样例所示,原始链表为1->2->3->4->5->NULL
,我们将其翻转输出5->4->3->2->1->NULL
。下面我们来讲解双指针的做法。
将一个链表翻转,即将该链表所有节点的next
指针指向它的前驱节点。由于是单链表,我们在遍历时并不能直接找到其前驱节点,因此我们需要定义一个指针保存其前驱节点。
每次翻转时,我们都需要修改当前节点的next
指针。如果不在改变当前节点的next
指针前保存其后继节点,那么我们就失去了当前节点和后序节点的联系,因此还需要额外定义一个指针用于保存当前节点的后继节点。
具体过程如下:
1、定义一个前驱指针pre
和cur
指针,pre
指针用来指向前驱节点,cur
指针用来遍历整个链表,初始化pre = null
,cur = head
。
2、我们首先保存cur
指针指向节点的后继节点,然后让cur
指针指向节点的next
指针指向其前驱节点,即cur->next = pre
。
3、pre
指针和cur
指针分别后移一位,重复上述过程,直到cur
指向空节点。
4、最后我们返回pre
节点。
图示过程如下:
时间复杂度分析: 只遍历一次链表,时间复杂度是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
中所有顶点排成一个线性序列,使得图中任意一对顶点u
和v
,若<u,v> ∈E(G)
,则u
在线性序列中出现在v
之前。
一个合法的选课序列就是一个拓扑序,拓扑序是指一个满足有向图上,不存在一条边出节点在入节点前的线性序列,如果有向图中有环,就不存在拓扑序。可以通过拓扑排序算法来得到拓扑序,以及判断是否存在环。
拓扑排序步骤:
- 1、建图并记录所有节点的入度。
- 2、将所有入度为
0
的节点加入队列。 - 3、取出队首的元素
now
,将其加入拓扑序列。 - 4、访问所有
now
的邻接点nxt
,将nxt
的入度减1
,当减到0
后,将nxt
加入队列。 - 5、重复步骤
3
、4
,直到队列为空。 - 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)
快选是在快排的基础上只递归一半区间。
如果当前要找的数>=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);
}
};