代码随想录

先贴个大佬链接
代码随想录网站链接

经典算法

BFS 广度优先搜索

一般过程

  • 可以分为四个步骤:

    • 初始化(初始化队列和所求的值) ->

    • 判空取队头(判断是否为空并取出队头) ->

    • 拓展(利用队头去扩展) ->

    • 判断入队(如果符合,将该点入队)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      void bfs(){
      queue<int>q;
      q.push(初始位置);
      //初始化

      while(q.size()){
      int t = q.front();
      q.pop();//取出队头的点,用该点向周围扩散。
      if(check(j)){ //如果该点可行就将它加入队列中
      q.psuh(j);
      //实施相应的操作
      }
      }
      }
  • 如果是对图进行广度搜索,则可以建立一个邻接表,以优化枚举时间。

  • 如果需要记录路径,则可以设定一个路径的队列;或是用father指针记录。

并查集

并查集常用来解决连通性问题。当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。

  • 主要有两个功能:
    • 将两个元素添加到一个集合中。
    • 判断两个元素在不在同一个集合
  • 复杂度
    • 空间复杂度:O(n),申请一个 father 数组
    • 时间复杂度:O(logn)~O(1),随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。

原理讲解

  • 只需要用一个一维数组来表示。即:father[A] = B, father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。

  • 寻根:在同一个根下就是同一个集合

  • 初始化:father数组初始化的时候要 father[i] = i,默认自己指向自己。

  • 注意!join方法一定是先寻根,再关联。不能简单调用isSame方法。

    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
    int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
    vector<int> father = vector<int> (n, 0); // 数组结构

    // 并查集里寻根的过程
    int find(int u) {
    if (u == father[u]) return u; // 如果根就是自己,直接返回
    else return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
    }
    // 并查集初始化
    void init() {
    for (int i = 0; i < n; ++i) {
    father[i] = i;
    }
    }
    // 将v,u 这条边加入并查集
    void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
    }

    // 判断 u 和 v是否找到同一个根
    bool isSame(int u, int v) {
    u = find(u);
    v = find(v);
    return u == v;
    }
    // 计算集合的个数
    int count_sets(int n){
    int cnt = 0;
    for (int i = 0; i < n; i++)
    if (find(i) == i)
    cnt++;
    return cnt;
    }

路径压缩

在实现 find 函数的过程中,我们知道,通过递归的方式,不断获取father数组下标对应的数值,最终找到这个集合的根。
搜索过程像是一个多叉树中从叶子到根节点的过程。
如果这棵多叉树高度很深的话,每次 find 函数去寻找根的过程就要递归很多次

  • 我们的目的只需要知道这些节点在同一个根下就可以。
  • 除了根节点其他所有节点都挂载根节点下,这样我们在寻根的时候就很快,只需要一步。
  • 路径压缩,将非根节点的所有节点直接指向根节点。
  • 只需要在递归的过程中,让 father[u] 接住 递归函数 find(father[u]) 的返回结果。

代码实现

1
2
3
4
5
// 并查集里寻根的过程
int find(int u) {
if (u == father[u]) return u;
else return father[u] = find(father[u]); // 路径压缩
}

排序算法

冒泡排序

  • 时间复杂度:O(n²)

  • 从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。这样操作后数组最右端的元素即为该数组中所有元素的最大值。

  • 重复 n 次如上步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* 冒泡排序 */
    void BubbleSort(int arr[], int length){
    for (int i = 0; i < length; i++){
    for (int j = 0; j < length - i - 1; j++){
    if (arr[j] > arr[j + 1]){
    int temp;
    temp = arr[j + 1];
    arr[j + 1] = arr[j];
    arr[j] = temp; // swap
    }
    }
    }
    }

选择排序

  • 时间复杂度:O(n²)

  • 每一趟在 n-i+1 (i=1,2,…,n-1) 个记录中选取关键字最小的记录作为有序序列中第 i 个记录。【蚂蚁上课演示过的排序方式】

  • 假设长度为n的数组arr,要按照从小到大排序,那么先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /* 选择排序 */
    void SelectionSort(int arr[], int length){
    int min_index;
    for (int i = 0; i < length; i++){
    min_index = i;
    for (int j = i + 1; j < length; j++){
    if (arr[j] < arr[min_index])
    min_index = j;
    }
    if (min_index != i){
    int temp;
    temp = arr[i];
    arr[i] = arr[min_index];
    arr[min_index] = temp;
    }
    }
    }

插入排序

  • 时间复杂度:O(n²)

  • 插入排序的基本思想就是将无序序列插入到有序序列中。例如要将数组 arr = [4,2,8,0,5,1] 排序,可以将4看做是一个有序序列(图中用蓝色标出),将 [2,8,0,5,1] 看做一个无序序列。无序序列中2比4小,于是将2插入到4的左边,此时有序序列变成了 [2,4] ,无序序列变成了 [8,0,5,1] 。以此类推,最终数组按照从小到大排序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 插入排序
    void InsertSort(int arr[], int length){
    for (int i = 1; i < length; i++){
    int j;
    if (arr[i] < arr[i - 1]){
    int temp = arr[i];
    for (j = i - 1; j >= 0 && temp < arr[j]; j--){
    arr[j + 1] = arr[j];
    }
    arr[j + 1] = temp;
    }
    }
    }

快速排序

  • 时间复杂度:O(n log n)

  • 通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。

  • 具体做法为:设置两个指针 low 和 high 分别指向待排序列的开始和结尾,记录下基准值 baseval (待排序列的第一个记录),然后先从 high 所指的位置向前搜索直到找到一个小于 baseval 的记录并互相交换,接着从low所指向的位置向后搜索直到找到一个大于 baseval 的记录并互相交换,重复这两个步骤直到 low=high 为止。

    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
    // 快速排序
    void QuickSort(int arr[], int start, int end){
    if (start >= end)
    return;
    int i = start;
    int j = end;
    // 基准数
    int baseval = arr[start];
    while (i < j){ // 先右后左
    // 从右向左找比基准数小的数
    while (i < j && arr[j] >= baseval){
    j--;
    }
    if (i < j){
    arr[i] = arr[j];
    i++;
    }
    // 从左向右找比基准数大的数
    while (i < j && arr[i] < baseval){
    i++;
    }
    if (i < j){
    arr[j] = arr[i];
    j--;
    }
    }

    // 把基准数放到i的位置
    arr[i] = baseval;
    // 递归
    QuickSort(arr, start, i - 1);
    QuickSort(arr, i + 1, end);
    }

归并排序

  • 时间复杂度:O(n log n)

  • “归并”的含义是将两个或两个以上的有序序列组合成一个新的有序表。假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 ⌈n / 2⌉( ⌈x⌉ 表示不小于x的最小整数)个长度为2(或者是1)的有序子序列,再两两归并。如此重复,直到得到一个长度为n的有序序列为止。这种排序方法称为 2-路归并排序。

  • img

    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
    // 归并排序
    void MergeSort(int arr[], int start, int end, int * temp){
    if (start >= end)
    return;
    int mid = (start + end) / 2;
    MergeSort(arr, start, mid, temp);
    MergeSort(arr, mid + 1, end, temp);

    // 合并两个有序序列
    int length = 0; // 表示辅助空间有多少个元素
    int i_start = start;
    int i_end = mid;
    int j_start = mid + 1;
    int j_end = end;
    while (i_start <= i_end && j_start <= j_end){
    if (arr[i_start] < arr[j_start]){
    temp[length] = arr[i_start];
    length++;
    i_start++;
    }
    else{
    temp[length] = arr[j_start];
    length++;
    j_start++;
    }
    }

    while (i_start <= i_end){
    temp[length] = arr[i_start];
    i_start++;
    length++;
    }
    while (j_start <= j_end){
    temp[length] = arr[j_start];
    length++;
    j_start++;
    }

    // 把辅助空间的数据放到原空间
    for (int i = 0; i < length; i++){
    arr[start + i] = temp[i];
    }
    }

KMP算法

  • KMP算法的作用是在一个已知字符串中查找子串的位置,也叫做串的模式匹配。
  • 字符串 abcdab
    • 前缀的集合:{a,ab,abc,abcd,abcda}
    • 后缀的集合:{b,ab,dab,cdab,bcdab}
    • 最长相等前后缀:ab
  • 流程就是一个循环过程了。事实上,每一个字符前的字符串都有最长相等前后缀,而且最长相等前后缀的长度是我们移位的关键,所以我们单独用一个next数组存储子串的最长相等前后缀的长度。而且next数组的数值只与子串本身有关。
  • next[i]=j,含义是:下标为i 的字符前的字符串最长相等前后缀的长度为j
    • 可以算出,子串t= “ababc”的next数组为
    • next[0]=0;next[1]=0;next[2]=1;next[3]=2;next[4]=0;
    • 可以通过递推快速求解
    • 假设已知若干项的前后缀,
      • 若接下来 字符仍然相同,则+1
      • 若不同,
        alt text
        alt text
        alt text
        alt text
  • 字串回溯:去看最后一个匹配字符它所对应的 next 数值
  • alt text
  • alt text
  • alt text

Leecode 算法题

135. 分发糖果【***】

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
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> candy(n, 0);

// 两次遍历,左->右,保证右边的一定不小于左边的(即使分数比左边低)。
candy[0] = 1;
for(int i = 1; i < n; i++){
if(ratings[i] > ratings[i - 1])
candy[i] = candy[i - 1] + 1;
else
candy[i] = 1;
}

// 右->左,如果左边比右边分高,则(candy[i + 1] + 1, candy[i])取较大值
for(int i = n - 2; i >= 0; i--){
if(ratings[i] > ratings[i + 1]){
candy[i] = max(candy[i + 1] + 1, candy[i]);
}
}

int sum = 0;
for(int i : candy)
sum += i;
return sum;
}
};

42. 接雨水【***】

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
class Solution{
public:
int trap(vector<int> &height){
int res = 0;
int n = height.size();
int waterFlag = false; // 当前状态是否可以装水【即是否在桶内】
int maxHigh = 0; // 当前左边最高柱子
int waterHigh = 0; // 当前最高水位

vector<int> rightMax(n, 0);
for (int i = n - 2; i >= 0; i--) // 记录右边最高的柱子,限制最高水位 waterHigh
rightMax[i] = max(rightMax[i + 1], height[i + 1]);

for (int i = 0; i < n; i++){
if (height[i] > maxHigh) // 更新左边最高柱子
maxHigh = height[i];

waterHigh = min(maxHigh, rightMax[i]); // 根据左边高度和右边高度来限制水位

if (waterHigh > height[i])
res += waterHigh - height[i];
}
return res;
}
};

14. 最长公共前缀【*】

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
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.size() == 0)
return "";

//公共前缀比所有字符串都短,随便选一个先
string prefix = strs[0];
for (string s : strs) {
while(!startsWith(prefix, s)){
if(prefix.size() == 0)
return "";
//公共前缀不匹配就让它变短!
prefix = prefix.substr(0, prefix.size()-1);
}
}
return prefix;
}
bool startsWith(string & str1, string & str2){ // str1 是否为 str2 的前缀
int n1 = str1.size();
int n2 = str2.size();
if(n1 <= n2){
string tmp = str2.substr(0, n1);
if(str1 == tmp)
return true;
else
return false;
}else{
return false;
}
}
};

68. 文本左右对齐【***】

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Solution {
public:
vector<string> fullJustify(vector<string>& words, int maxWidth) {
vector<string> res;
int n = words.size();
string line;
int line_l = 0;
int idx_s = 0;
for(int i = 0; i < n; i++){
string tmp_s = words[i];
int tmp_l = tmp_s.size();
if((line_l + tmp_l) > maxWidth){
line = processLine(idx_s, i, words, maxWidth);
res.emplace_back(line);

idx_s = i;
line = tmp_s; // 删除并替换
if(i != n - 1)
line += " ";
line_l = line.size();
}else if((line_l + tmp_l) == maxWidth){
line += tmp_s;
res.emplace_back(line);

idx_s = i + 1;
line = "";
line_l = 0;
}
else{
if(i != n - 1){
line += tmp_s + " ";
line_l = line.size();
}else{
line += tmp_s;
line_l = line.size();
}
}
}
if(line_l != 0)
res.emplace_back(line.append(maxWidth - line_l, ' '));
return res;
}

string processLine(int idx_s, int idx_e, vector<string>& words, int maxWidth){
int wordsCnt = idx_e - idx_s;
int len = 0;
for(int i = idx_s; i < idx_e; i++)
len += words[i].size();

int spaceCnt = maxWidth - len;
if(wordsCnt == 1)
return words[idx_s].append(spaceCnt, ' ');


int spaceAvg = spaceCnt / (wordsCnt - 1);
int spaceLeft = spaceCnt % (wordsCnt - 1);

string res;
for(int i = idx_s; i < idx_e; i++){
if(i != idx_e - 1){
res += words[i];
res.append(spaceAvg, ' ');
if(spaceLeft-- > 0){
res.append(" ");
}
}else{
res += words[i];
}
}

return res;
}
};

11. 盛最多水的容器【**】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int maxArea(vector<int>& height) {
int max = 0;
int left = 0;
int right = height.size() - 1;
while(left < right){
int area = calArea(left, right, height);
max = std::max(max, area);
if(height[left] <= height[right]){ // 移动低的一边,这样面积才可能变大;移动高的一边的面积一定会变小。
left++;
}else{
right--;
}
}
return max;
}
int calArea(int idx1, int idx2, vector<int>& height){
return (idx2 - idx1) * std::min(height[idx1], height[idx2]);
}
};

15. 三数之和【**】

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
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end(), less<int>());
int n = nums.size();

// 枚举第一个数
for(int first = 0; first < n; first++){
if(nums[first] > 0)
return res;
// 需要和上一个不同【相当于保证第一层不会重复】
if(first > 0 && nums[first] == nums[first - 1])
continue;

// c 对应的指针初始指向数组的最右端
int third = n - 1;

// 枚举第二个数
for(int second = first + 1; second < n; second++){
// 需要和上一个不同【相当于保证第二层不会重复】
if(second > first + 1 && nums[second] == nums[second - 1])
continue;

while(second < third && (nums[first] + nums[second] + nums[third] > 0)){
third--;
}
// 如果指针重合,那么这种情况不存在,结束这个循环
if(second == third){
break;
}
if(nums[first] + nums[second] + nums[third] == 0){
res.emplace_back(vector<int>{nums[first], nums[second], nums[third]});
}
}
}
return res;
}
};

209. 长度最小的子数组【**】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int n = nums.size();
int res = n + 1; // 结果最大只能是 n, 设为n + 1
int sum = 0;
int left = 0, right = 0;
while(right < n){
sum += nums[right];
while(sum >= s){ // 说明目前right右边界最优,这时候优化左边。
res = min(right - left + 1, res);
sum -= nums[left];
left++; // 之所以左边能++,以left为边界的最优情况都考虑过了
}
right++;
}
return res == (n + 1) ? 0 : res;
}
};

3. 无重复字符的字串【**】

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
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
int res = 0;
string subStr;
int left = 0, right = 0;
int pos = 0;
while(right < n){
pos = subStr.find(s[right]); // 在原本不重复的字串里面寻找
if(pos != string::npos){ // 如果新增的重复了
subStr.erase(0, pos + 1); // 将重复的左边全部删除,裁掉 pos + 1 个元素
left += pos + 1; // 左指针相应移动 pos + 1
}
subStr += s[right];
res = max(res, right - left + 1);
right++;
}
return res;
}
/** 腾讯面试题,如若需要返回的是子串
* 唯一性:如果有唯一性,则将left和right维护好就行
* 不唯一:如果答案不唯一,可以使用栈的思想,满足一个就扔进去;出现更好的把之前的全部弹出来,再把最好的扔进去。
*/
};

30. 串联所有单词的子串【***】

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
class Solution {
public:
vector<int> findSubstring(string &s, vector<string> &words) {
vector<int> res; // 存储结果的数组
int m = words.size(); // 单词数量
int n = words[0].size(); // 单词长度
int ls = s.size(); // 字符串 s 的长度
for (int i = 0; i < n && i + m * n <= ls; i++) { // 外层循环,从每个可能的起始位置 i 开始【i 属于 [0, ls % mn]】
unordered_map<string, int> differ; // 用于统计当前窗口内单词的出现次数
// 初始化窗口(能容纳 m 个单词),分为两步
// 1. 统计 s 中从当前起始位置 i 开始的 m 个单词
for (int j = 0; j < m; j++) {
++differ[s.substr(i + j * n, n)]; // 将子串加入到 differ 中并计数
}
// 2. 遍历 words 中的每个单词,检查其在 differ 中的出现次数
for (string &word: words) {
if (--differ[word] == 0) { // 如果单词的计数减为 0,则从 differ 中删除
differ.erase(word);
}
}

// 内层循环,从起始位置 i 开始滑动窗口
for (int start = i; start < ls - m * n + 1; start += n) { // 滑动到最后所剩不到一个窗口时停止
if (start != i) {
// 添加新进入窗口的单词到 differ 中
string word = s.substr(start + (m - 1) * n, n);//窗口右边加的单词
if (++differ[word] == 0) {
differ.erase(word);
}
// 移除窗口左侧移出的单词
word = s.substr(start - n, n);
if (--differ[word] == 0) {
differ.erase(word);
}
}
// 如果 differ 为空,表示当前窗口符合要求,将起始位置加入结果数组 res
if (differ.empty()) {
res.emplace_back(start);
}
}
}
return res; // 返回所有符合要求的起始位置数组
}
};

76. 最小覆盖子串【***】

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
class Solution {
public:
unordered_map<char, int> s_map, t_map;

bool check(){
for(auto p : t_map){
if(s_map[p.first] < p.second)
return false;
}
return true;
}

string minWindow(string s, string t) {
int sl = s.size(), tl = t.size();
string res;
if(sl < tl)
return res;
// 初始化 t_map 哈希表
for(char c : t)
t_map[c]++;

int left = 0, right = - 1; // 滑动窗口边界
int resL = -1; // 最终结果左边界
int len = INT_MAX; // 最终结果长度
while(right < sl){
if(t.find(s[++right]) != string::npos){ // s[right] 当前元素在 t 当中
s_map[s[right]]++;
}

while(check() && left <= right){
if(int tmp; (tmp = right - left + 1) < len){
len = tmp;
resL = left;
}

if(t.find(s[left]) != string::npos){ // s[left] 当前元素在 t 当中
s_map[s[left]]--;
}
left++;
}
}

if(resL != -1){
res = s.substr(resL, len);
}
return res;
}
};

909. 蛇梯棋(BFS)【**】

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
class Solution {
public:
pair<int, int> id2rc(int id, int n){
int r = (id - 1) / n;
int c = (id - 1) % n;
if(r % 2 == 1) // 逆序的r
c = n - 1 - c;
return{n - 1 - r, c};
}
int snakesAndLadders(vector<vector<int>>& board) {
int n = board.size();
vector<int> vis(n * n + 1, 0); // 1 -> n 范围
queue<pair<int,int>> q; // pair<id, steps>
q.emplace(1, 0);
while(!q.empty()){
auto p = q.front();
q.pop();
// 枚举所有情况
for(int i = 1; i <= 6; i++){
// 得到下一步的格子
int next_id = p.first + i;
if(next_id > n * n){ // 超出边界
break;
}
auto next_rc = id2rc(next_id, n);

if(int tmp = board[next_rc.first][next_rc.second]; tmp != -1){ // 遇到蛇 梯
next_id = tmp;
next_rc = id2rc(next_id, n);
}

// 处理
if(next_id == n * n){ // 到达终点
return p.second + 1;
}

if(!vis[next_id]){ // 没走过
vis[next_id] = p.second + 1;
q.emplace(next_id, p.second + 1);
}else{ // 走过,比较步数
if(vis[next_id] > p.second + 1){
vis[next_id] = p.second + 1;
q.emplace(next_id, p.second + 1);
}
}
}
}
return -1;
}
};

433. 最小基因变化 && 127. 单词接龙 (BFS)【***】

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
52
53
54
55
56
57
58
59
60
61
62
class Solution {
public:
int minMutation(string startGene, string endGene, vector<string>& bank) {
if(find(bank.begin(), bank.end(), endGene) == bank.end()) // 目标基因不在变异基因库里
return -1;

unordered_map<string, int> mutTimes; // map<变异基因, 变异次数>
for(string str: bank){
mutTimes.emplace(str, INT_MAX);
}

// 建立一个邻接表,用于优化枚举时间
unordered_map<string, vector<string>> edges;
for(int n = bank.size(), i = 0; i <= n; i++){
string str;
if(i != n)
str = bank[i];
else
str = startGene; // 不要忘记初始边

vector<string> list;
for(string edge: bank){
if(edge != str){
int diff = 0;
for(int i = 0; i < str.size(); i++)
if(str[i] != edge[i])
diff++;

if(diff == 1)
list.emplace_back(edge);
}
}
edges.emplace(str, list);
}


queue<string> q;
q.emplace(startGene);

while(!q.empty()){
string gene = q.front();
q.pop();
int steps = 0; // 当前基因的变异次数

if(gene == startGene){ // 起始基因
if(mutTimes.find(gene) != mutTimes.end()) // bank中找得到,原始基因无需经过变异就是一个变异基因,直接将置为0
mutTimes[gene] = 0;
}else{ // 变异基因, 获取上一次变异次数
steps = mutTimes[gene];
}

// 枚举所有可能变异情况
for(string edge: edges[gene]){
if(mutTimes[edge] >= (steps + 1)){ // 只有一处不同,并且变异次数比原来更少,允许变异
mutTimes[edge] = steps + 1; // 更新变异次数
q.emplace(edge);
}
}
}
return mutTimes[endGene] == INT_MAX ? -1 : mutTimes[endGene];
}
};

1905. 统计岛屿(BFS && 并查集)【**】

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class UnionFind{
private:
vector<int> parent;
vector<int> rank; // 根节点的权重,岛屿的主陆地
public:
UnionFind(int n){
rank = vector<int>(n, 0);
parent = vector<int>(n, 0);
for(int i = 0; i < n; i++)
parent[i] = i;
}
int find(int x){
if(x== parent[x])
return x;
else
return parent[x] = find(parent[x]);
}
void join(int u, int v){
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u != v) {
if (rank[u] > rank[v]) {
parent[v] = u;
} else if (rank[u] < rank[v]) {
parent[u] = v;
} else {
parent[v] = u;
rank[u]++;
}
}
}
};
class Solution {
public:
int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
int m = grid1.size();
int n = grid1[0].size();
int N = m * n;
UnionFind uf(N);
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(grid2[i][j]){
if(i > 0 && grid2[i - 1][j]){ // 连接上边
uf.join(i * n + j, (i-1) * n + j);
}
if(j > 0 && grid2[i][j - 1]){ // 连接左边
uf.join(i * n + j, i * n + j - 1);
}
}
}
}

unordered_set<int> islands;
for(int i = 0; i < N; i++){
int r = i / n;
int c = i % n;
if(grid2[r][c]){
islands.emplace(uf.find(i));
}
}
for(int i = 0; i < N; i++){
int r = i / n;
int c = i % n;
if(!grid1[r][c]){ // 如果grid1是海,但是grid2是land,需要删除这片land,因为不是子land
islands.erase(uf.find(i));
}
}
return islands.size();
}
};

牛客网算法题

迷宫问题(BFS DFS)

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <algorithm>

using namespace std;

struct Point {
int x;
int y;
Point (int x_ = -1, int y_ = -1)
: x(x_), y(y_){}
};
bool isValid(int x, int y, int r, int c){
if(x < 0 || x >= r || y < 0 || y >= c)
return false;
return true;
}
string processXY(int x, int y){
return ("(" + to_string(x) + "," + to_string(y) + ")");
}

int main() {
// 分别对应,上 下 左 右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

// 处理输入
int r, c;
cin >> r >> c;
vector<vector<int>> board(r, vector<int>(c));
for(int i = 0; i < r; i++)
for(int j = 0; j < c; j++)
cin >> board[i][j];

unordered_set<string> vis;
queue<vector<string>> path; // 所有路径的记录
vector<string> finPath; // 最终路径

queue<Point> q;
// 初始化,把起点加入
q.push({0, 0});
finPath.emplace_back(processXY(0, 0));
path.emplace(finPath);
while(!q.empty()){
Point p = q.front();
q.pop();
vector<string> tmpPath = path.front(); // 当前节点路径
path.pop();

int px = p.x;
int py = p.y;

// 如果是终点,则记录
if(px == r - 1 && py == c - 1){
finPath = tmpPath;
break;
}
//如果已访问,则进行下轮循环
if(vis.find(to_string(px) + to_string(py)) != vis.end()){
continue;
}

// 枚举所有情况
for(int i = 0; i < 4; i++){
int next_x = px + dx[i];
int next_y = py + dy[i];
// 如果能走,并且未访问,则添加至队列
if(isValid(next_x, next_y, r, c) && board[next_x][next_y] == 0 && vis.find(to_string(next_x) + to_string(next_y)) == vis.end()){
q.push({next_x, next_y});
tmpPath.emplace_back(processXY(next_x, next_y)); // 路径扩增。
path.emplace(tmpPath);
tmpPath.pop_back(); // 可能还有其他可能,回溯影响
}
}
// 添加已访问
vis.emplace(to_string(px) + to_string(py));
}


for(int i = 0; i < finPath.size(); i++){
cout << finPath[i] << endl;
}

return 0;
}

其余算法题

01 背包问题

输入格式

第一行两个整数,N,V 用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi, wi 用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

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
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
int N,V;
cin >> N >> V;
vector<vector<int>> dp(N, vector<int>(V + 1, 0));
vector<vector<int>> items(N, vector<int>(2, 0));
for(int i = 0; i < N; i++)
cin >> items[i][0] >> items[i][1];

// 初始化: 背包容积为0的时候价值为0
for(int i = 0; i < N; i++){
dp[i][0] = 0;
}
// 初始化: 背包只装物品0,容量 >= 物品0的体积时,价值为物品0的价值
for (int j = items[0][0]; j < V + 1; j++) {
dp[0][j] = items[0][1];
}

// 递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - items[i][0]] + items[i][1]);
for(int i = 1; i < N; i++){
for(int j = 1; j < V + 1; j++){
if(j - items[i][0] < 0)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - items[i][0]] + items[i][1]);
}
}

cout << dp[N - 1][V] << endl;
return 0;
}