Tarjan算法(寻找强连通分量)

铺垫:连通图的两种DFS遍历方式, 在tarjan算法中采用方式2来遍历顶点image-20240911155905523

顶点X(i,j)其中

i:DFS中x点被访问的时间点

j: x通过可以回溯到的最早时间点

image-20240911162218892

1129 颜色交替的最短路径

给定一个整数 n,即有向图中的节点数,其中节点标记为 0n - 1。图中的每条边为红色或者蓝色,并且可能存在自环或平行边。

给定两个数组 redEdgesblueEdges,其中:

  • redEdges[i] = [ai, bi] 表示图中存在一条从节点 ai 到节点 bi 的红色有向边,
  • blueEdges[j] = [uj, vj] 表示图中存在一条从节点 uj 到节点 vj 的蓝色有向边。

返回长度为 n 的数组 answer,其中 answer[X] 是从节点 0 到节点 X 的红色边和蓝色边交替出现的最短路径的长度。如果不存在这样的路径,那么 answer[x] = -1

示例 1:

1
2
输入:n = 3, red_edges = [[0,1],[1,2]], blue_edges = []
输出:[0,1,-1]

示例 2:

1
2
输入:n = 3, red_edges = [[0,1]], blue_edges = [[2,1]]
输出:[0,1,-1]

提示:

  • 1 <= n <= 100
  • 0 <= redEdges.length, blueEdges.length <= 400
  • redEdges[i].length == blueEdges[j].length == 2
  • 0 <= ai, bi, uj, vj < n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def shortestAlternatingPaths(self, n, redEdges, blueEdges):
"""
:type n: int
:type redEdges: List[List[int]]
:type blueEdges: List[List[int]]
:rtype: List[int]
"""
e=[[] for i in range(n)]
for x,y in redEdges:
e[x].append((y,0))
for x,y in blueEdges:
e[x].append((y,1))

ans=[-1]*n
vis={(0,0),(0,1)}
q=[(0,0),(0,1)]
distance=0
while q:
tmp=q
q=[]
for x,y in tmp:
if ans[x]==-1:
ans[x]=distance
for p in e[x]:
if p[1]!=y and p not in vis:
vis.add(p)
q.append(p)
distance+=1
return ans

解题思路:在路径长度都为一情况下,可以用广度优先遍历一遍便是到各个点的最短路径

前缀和

560.和为k的数组个数

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数

子数组是数组中元素的连续非空序列。

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 2 * 104

  • -1000 <= nums[i] <= 1000

  • -107 <= k <= 107

    解题思路:

    用map映射前缀和值和出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int count = 0, pre = 0;
for (auto& x:nums) {
pre += x;
if (mp.find(pre - k) != mp.end()) {
count += mp[pre - k];
}
mp[pre]++;
}
return count;
}
};

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

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

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

示例 1:

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

示例 2:

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

提示:

  • 2 <= nums.length <= 105
  • -30 <= nums[i] <= 30
  • 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int l=nums.size();
vector<int> L(l,0),R(l,0);
L[0]=1;
for(int i=1;i<l;i++){
L[i]=L[i-1]*nums[i-1];
}
R[l-1]=1;
for(int i=l-2;i>=0;i--){
R[i]=R[i+1]*nums[i+1];
}
vector<int>ans(l,0);
for (int i=0;i<l;i++){
ans[i]=L[i]*R[i];
}
return ans;
}
};

优先队列

在C++中,priority_queue是一个容器适配器,它提供了常数时间的最大元素查找。它通常实现为堆。堆是一种数据结构,其中最大(或最小)元素始终位于顶部。priority_queue是一个模板类,定义在头文件中。它有三个模板参数:元素类型、容器类型和比较函数类型(可选)。默认情况下,它使用std::vector作为其底层容器 。

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

1
2
3
4
5
6
7
8
9
10
11
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

示例 2:

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

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<pair<int, int>> q;
for (int i = 0; i < k; ++i) {
q.emplace(nums[i], i);
}
vector<int> ans = {q.top().first};
for (int i = k; i < n; ++i) {
q.emplace(nums[i], i);
while (q.top().second <= i - k) {
q.pop();
}
ans.push_back(q.top().first);
}
return ans;
}
};

动态规划

53.最大子数列和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组

是数组中的一个连续部分。

示例 1:

1
2
3
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

1
2
输入:nums = [1]
输出:1

示例 3:

1
2
输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto &x: nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
};

代码

测试用例

测试结果

测试结果

56.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

示例 1:

1
2
3
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

1
2
3
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:先对vector进行排序,再根据题意插入

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.size() == 0) {
return {};
}
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (int i = 0; i < intervals.size(); ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (!merged.size() || merged.back()[1] < L) {
merged.push_back({L, R});
}
else {
merged.back()[1] = max(merged.back()[1], R);
}
}
return merged;
}
};

快慢指针

234.回文列表

给你一个单链表的头节点 head ,请你判断该链表是否为

回文链表

。如果是,返回 true ;否则,返回 false

示例 1:

img

1
2
输入:head = [1,2,2,1]
输出:true

示例 2:

img

1
2
输入:head = [1,2]
输出:false

提示:

  • 链表中节点数目在范围[1, 105]
  • 0 <= Node.val <= 9

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

尝试用O(1)的空间复杂度进行解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr) {
return true;
}

// 找到前半部分链表的尾节点并反转后半部分链表
ListNode* firstHalfEnd = endOfFirstHalf(head);
ListNode* secondHalfStart = reverseList(firstHalfEnd->next);

// 判断是否回文
ListNode* p1 = head;
ListNode* p2 = secondHalfStart;
bool result = true;
while (result && p2 != nullptr) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}

// 还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}

ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}

ListNode* endOfFirstHalf(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next; //快慢指针,慢指针一次移动一步,快指针一次移动两步
slow = slow->next;
}
return slow;
}
};

留言

2024-11-04

⬆︎TOP