可信认证之九阴真经二


2.3.6   最低成本联通所有城市(1135_会员)

 

 

2.3.6.1   最小生成树

https://blog.csdn.net/luoshixian099/article/details/51908175

关于图的几个概念定义:

 

连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。

强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。

连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。

生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。

最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

 

 

2.3.7   以图辨树(261_会员)

 

/*

* Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.

* Description: 261. 以图判树 securec.h

* Author: w00448446

* Create: 2019-11-14

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <stdbool.h>

#define MAX_VERTEX_NUM 10000

int g_vertex[MAX_VERTEX_NUM];

int GetMin(int a, int b)

{

    if (a <= b) {

        return a;

    }

    return b;

}

int FindRoot(int a)

{

    while (g_vertex[a] != a) {

        a = g_vertex[a];

    }

 

    return a;

}

bool validTree(int n, int** edges, int edgesSize, int* edgesColSize)

{

    int i;

    int a, b;

    if (edgesSize != (n - 1)) {

        return false;

    }

 

    if (edgesSize == 0 && n == 1) {

        return true;

    }

 

    for (i = 0; i < n; i++) {

        g_vertex[i] = i;

    }

 

    for (i = 0; i < edgesSize; i++) {

        a = FindRoot(edges[i][0]);

        b = FindRoot(edges[i][1]);

 

        if (a == b) {

            return false;

        }

 

        if (edges[i][0] < edges[i][1]) {

            g_vertex[edges[i][1]] = a;

        } else {

            g_vertex[edges[i][0]] = b;

        }

    }

    return true;

}

void TestCase1()

{

    int m1[] = {0, 1};

    int m2[] = {0, 2};

    int m3[] = {0, 3};

    int m4[] = {1, 4};

    int *g[] = {m1, m2, m3, m4};

    int length = sizeof(g) / sizeof(g[0]);

    int col[] = {2,2,2,2};

    int rslt;

    int n = 5;

    rslt = validTree(n, g, length, col);

    if (rslt == 1) {

        printf("t1: pass\n");

    }

}

void main()

{

    TestCase1();

}

 

2.3.8   按字典序排列最小的等效字符串(1061_会员)

 

 

2.3.9 无向图中连通分量的数目(323_会员)

 

2.3.10   尽量减少恶意软件的传播(924_困难)

 

 

注:将initial视为临时圈长,那么所拥有最大个数的圈,就是目标解。

 

 

#include <stdlib.h>

#include <string.h>

#include "securec.h"

#define MAX_LEN 301

 

int g_preNode[MAX_LEN];

int g_hashInfo[MAX_LEN];

 

int FindRoot(int pos)

{

    int son = pos;

    int temp;

 

    if (g_preNode[pos] == -1) {

        return pos;

    }

 

    while (g_preNode[pos] != -1) {

        pos = g_preNode[pos];

    }

 

    while (g_preNode[son] != -1) {

        temp = g_preNode[son];

        g_preNode[son] = pos;

        son = temp;

    }

 

    return pos;

}

 

void UnionRoot(int x, int y)

{

    int a = FindRoot(x);

    int b = FindRoot(y);

 

    if (a != b) {

        g_preNode[a] = b;

    }

}

 

int minMalwareSpread(int **graph, int graphSize, int *graphColSize, int *initial, int initialSize)

{

    int temp;

    int max = 0;

    int result;

 

    if (graphSize <= 0 || initialSize <= 0) {

        return 0;

    }

 

    result = initial[0];

    (void)memset_s(g_preNode, MAX_LEN * sizeof(int), -1, MAX_LEN * sizeof(int));

    (void)memset_s(g_hashInfo, MAX_LEN * sizeof(int), 0, MAX_LEN * sizeof(int));

 

    // step1:建圈

    for (int i = 0; i < graphSize; i++) {

        for (int j = 0; j < graphColSize[i]; j++) {

            if (graph[i][j] == 1) {

                UnionRoot(i, j);

            }

        }

    }

 

    // step2:统计各圈长下挂节点数

    for (int i = 0; i < graphSize; i++) {

        temp = FindRoot(i);

        if (temp != -1) {

            g_hashInfo[temp]++;

        }

    }

 

    // step3:获取initial列表中节点所属圈中圈长下挂节点数最大的节点

    for (int i = 0; i < initialSize; i++) {

        temp = FindRoot(initial[i]);

        if ((temp != -1) && ((max < g_hashInfo[temp]) || ((max == g_hashInfo[temp]) && initial[i] < result))) {

            max = g_hashInfo[temp];

            result = initial[i];

        }

    }

 

    return result;

}

void main(void)

{

    return;

}

 

 

 

3          九阴真经第三式 :滑动窗口

3.1      代表题目:尽可能使字符串相等(1208)

 

 

      

3.2       滑动窗口介绍

https://zhuanlan.zhihu.com/p/61564531)滑动窗口法,也叫尺取法(可能也不一定相等,大概就是这样 =。=),可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。

3.2.1   Leetcode 209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。示例: 
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

 

3.2.2   Leetcode 3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

3.2.1   Leetcode 1004. 最大连续1的个数 III

给定一个由若干 0  1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 
返回仅包含 1 的最长(连续)子数组的长度。
 
示例 1
 
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释: 
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6
示例 2
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10

 

3.3       触类旁通

3.3.1   尽可能使字符串相等(1208)的一种解法

 

#include <stdio.h>

#include <stdbool.h>

#include <stdlib.h>

#include <string.h>

#define MAX_STRING_LEN 100001

int g_num;

char g_stringValue[MAX_STRING_LEN];

void InitStringValue(const char *s, const char *t)

{

    char *tmpS = s;

    char *tmpT = t;

 

    g_num = 0;

    while (*tmpS != 0) {

        g_stringValue[g_num] = abs(*tmpS - *tmpT);

        g_num++;

        tmpS++;

        tmpT++;

    }

    return;

}

int equalSubstring(char *s, char *t, int maxCost)

{

    int i, j;

    int sum = 0;

    int maxCount = 0;

    int begin = 0;

    InitStringValue(s, t);

    for (i = 0; i < g_num; i++) {

        sum += g_stringValue[i];

        if (sum > maxCost) {

            maxCount = (maxCount >= (i - begin)) ? maxCount : (i - begin);

            sum -= g_stringValue[begin];

            begin++;

        }

 

    }

 

    maxCount = (maxCount >= (i - begin)) ? maxCount : (i - begin);

    return maxCount;

}

3.3.2 340. 至多包含 K 个不同字符的最长子串(会员_困难)

 

 

 

/*

 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2018. All rights reserved.

 * Description: 项目 LEET_340_LongestSubstringforKDiffChar 的源文件

 * Author: f00485759

 * Create: 2019-12-16

 */

#include <stdio.h>

#include <stdbool.h>

#include "securec.h"

#define MAX_HASH_NUM 256

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int g_hash[MAX_HASH_NUM];

void Init(void)

{

    (void)memset(g_hash, 0, sizeof(g_hash));

}

void HashChar(char c)

{

    g_hash[c]++;

}

void DeHashChar(char c)

{

    if (g_hash[c] > 0) {

        g_hash[c]--;

    }

}

bool IsExtendKChar(int k)

{

    int count = 0;

    for (int i = 0; i < MAX_HASH_NUM; i++) {

        if (g_hash[i] != 0) {

            count++; // 可以通过变量来记忆,减少计算量

        }

        if (count >= k) {

            return true;

        }

    }

 

    return false;

}

 

bool IsRepeat(char c)

{

    if (g_hash[c] != 0) {

        return true;

    }

 

    return false;

}

int lengthOfLongestSubstringKDistinct(const char *s, int k)

{

    int start, end, maxlen;

    int slen = (int)strlen(s);

    Init();

    start = 0;

    maxlen = 0;

    for (end = 0; end < slen; end++) {

        if (!IsExtendKChar(k) || IsRepeat(s[end])) {

            HashChar(s[end]);

            continue;

        }

 

        if (start == end) {

            break;

        }

 

        maxlen = MAX(maxlen, end - start);

        DeHashChar(s[start]);

        start++;

 

        while (IsExtendKChar(k)) {

            DeHashChar(s[start]);

            start++;

        }

        HashChar(s[end]);

    }

    maxlen = MAX(maxlen, end - start);

    return maxlen;

}

int main()

{

    const char *s = "a";

    int k = 0;

 

    int maxlen = lengthOfLongestSubstringKDistinct(s, k);

 

    printf("%d\n", maxlen);

 

    return 0;

}

3.3.3   1151. 最少交换次数来组合所有的 1(会员)

 

3.3.4 159. 至多包含两个不同字符的最长子串(会员)

 



 

 

 

3.3.5 1100. 长度为 K 的无重复字符子串(会员)

 

 

#define MAX_LEN 512

int g_array[MAX_LEN];

 

int numKLenSubstrNoRepeats(char * s, int k){

    int right = 0;

    int left = 0;

    int result = 0;

    int count = 0;

    int len;

 

    len = strlen(s);

    memset(g_array, 0, sizeof(g_array));

 

    while (right < len) {

        if (g_array[s[right]] == 0) {

            count++;   

        }

        g_array[s[right]]++;

        while (right - left + 1 > k) {

            g_array[s[left]]--;

            if (g_array[s[left]] == 0) {

                count--;

            }

            left++;

        }

 

        printf("%d, %d, %d\n", right, left, count);

        if ((right - left + 1 == k) && (count == k)) {

            result++;

        }

 

        right++;

    }

 

    return result;

}

4          九阴真经第四式 :前缀和 & HASH

4.1      代表题目:560. 和为K的子数组

 

注:前缀和+hash,可以在O(n)复杂度来解决此问题。

        

4.2       前缀和介绍

前缀和(Prefix Sum)定义(https://www.jianshu.com/p/3021429f38d4):

给定一个数组A[1..n],前缀和数组PrefixSum[1..n]定义为:PrefixSum[i] = A[0]+A[1]+...+A[i-1];

例如:A[5,6,7,8] --> PrefixSum[5,11,18,26]

PrefixSum[0] =A[0] ;

PrefixSum[1] =A[0] + A[1] ;

PrefixSum[2] =A[0] + A[1] + A[2] ;

PrefixSum[3] =A[0] + A[1] + A[2] + A[3] = PrefixSum[2] + A[3] ;

 

4.3       触类旁通

4.3.1 523. 连续的子数组和

 

             注:《= O(n^2)复杂度解决

 

// 使用Hash O(n)复杂度,因没有进行动态内存申请,所以空间占用较大,留给大家进一步优化

 

#include <stdio.h>

#include <stdbool.h>

#include <stdlib.h>

#include <string.h>

 

#define MAX_HASH_LEN 20001

#define MAX_HASH_NODE_LEN (MAX_HASH_LEN + MAX_HASH_LEN + 1)

 

typedef struct {

    int valid;

    int value;

    int count;

} HashNode;

 

HashNode g_hashMap[MAX_HASH_NODE_LEN];

int g_max;

 

int GetHashCode(int val)

{

    int hashCode = (abs(val) * 131) % MAX_HASH_LEN;

    return hashCode;

}

 

int GetHashMapIndex(int val)

{

    int hashCode;

    hashCode = GetHashCode(val);

    for (int i = hashCode; i < MAX_HASH_NODE_LEN; i++) {

        if (g_hashMap[i].valid == 0) {

            return i;

        }

 

        if (g_hashMap[i].value == val) {

            return i;

        }

    }

    // 异常保护

    printf("GetHashMapIndex:error\n");

    return 0;

}

 

void UpdateHashMap(int index, int val)

{

    if (g_hashMap[index].valid == 0) {

        g_hashMap[index].valid = 1;

        g_hashMap[index].value = val;

        g_hashMap[index].count = 1;

        return;

    }

 

    if (g_hashMap[index].value != val) {

        printf("UpdateHashMap: error\n");

        return;

    }

 

    g_hashMap[index].count++;

    return ;

}

 

void Init()

{

    g_max = 0;

    for (int i = 0; i < MAX_HASH_NODE_LEN; i++) {

        g_hashMap[i].valid = 0;

        g_hashMap[i].count = 0;

        g_hashMap[i].value = 0;

    }

 

    // 将0初始化为有效值,但初始计数为0

    g_hashMap[0].valid = 1;

    g_hashMap[0].count = 1;

}

int subarraySum(int* nums, int numsSize, int k)

{

    int index;

    int sum = 0;

    int preSum = 0;

 

    Init();

    for (int i = 0; i < numsSize; i++) {

        sum += nums[i];

 

        // 获取preSum的个数

        preSum = sum - k;

        index = GetHashMapIndex(preSum);

        if (g_hashMap[index].valid == 1) {

            g_max += g_hashMap[index].count;

        }

 

        // 获取HashIndex

        index = GetHashMapIndex(sum);

 

        // 放入Hash表中

        UpdateHashMap(index, sum);

    }

 

    return g_max;

}

 

void Test1()

{

    int nums[] = {1,1,1};

    int numsSize = sizeof(nums) / sizeof(nums[0]);

    int k = 2;

    int rslt;

    rslt = subarraySum(nums, numsSize, k);

    if (rslt == 2) {

        printf("Test1 pass \n");

    } else {

        printf("Test1 fail \n");

    }

}

void main()

{

    Test1();

    return;

}

 

4.3.2 974. 和可被 K 整除的子数组

 

 

5          九阴真经第五式 :差分

5.1.1 代表题目:1094. 拼车

 

 

 

5.2       差分介绍

对于数组array[N]中的某一段进行增减操作,通过差分可在O(n)时间内完成。如

trips = [[2,1,5],[3,3,7]]

第一步:更新array[1] = 2, array[5] = -2;

第二步:更新array[3] = 3, array[7] = -3;

第三步:进行求和,得到结果array[] = {0,2,2,5,5,3,3,0};

 

5.3       触类旁通

5.3.1 1109. 航班预订统计

 

 

5.3.2 121. 买卖股票的最佳时机(简单)

 

 

        

5.3.3 122. 买卖股票的最佳时机 II

      

      

       int maxProfit(int *prices, int pricesSize)

       {

           int purchasePrice;

           int profit = 0;

           if (prices == NULL || pricesSize <= 0) {

               return 0;

           }

           purchasePrice = prices[0];

           for (int i = 1; i < pricesSize; i++) {

               if (purchasePrice < prices[i]) {

                   profit += (prices[i] - purchasePrice);

               }

               purchasePrice = prices[i];

           }

           return profit;

       }

5.3.4 253. 会议室 II (会员)

 

#include <stdio.h>

#include <securec.h>

#define MAX_LEN 1000000

int g_hashInfo[MAX_LEN];

int minMeetingRooms(int **intervals, int intervalsSize, int *intervalsColSize)

{

    int max = 0;

    int sum = 0;

    if (intervals == NULL || intervalsSize <= 0 || intervalsColSize == NULL) {

        return 0;

    }

    (void)memset_s(g_hashInfo, MAX_LEN * sizeof(int), 0, MAX_LEN * sizeof(int));

    for (int i = 0; i < intervalsSize; i++) {

        g_hashInfo[intervals[i][0]]++;

        g_hashInfo[intervals[i][1]]--;

    }

    for (int i = 0; i < MAX_LEN; i++) {

        sum += g_hashInfo[i];

        if (max < sum) {

            max = sum;

        }

    }

    return max;

}

 

 

6          九阴真经第六式 :拓扑排序(专业级)

6.1      代表题目:210. 课程表 II

 

 

6.2       拓扑排序介绍

https://blog.csdn.net/lisonglisonglisong/article/details/45543451

一、什么是拓扑排序

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

每个顶点出现且只出现一次。

若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

 

例如,下面这个图:

 

它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:

从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。

从图中删除该顶点和所有以它为起点的有向边。

重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

 

于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。通常,一个有向无环图可以有一个或多个拓扑排序序列。

      二、拓扑排序的应用

拓扑排序通常用来“排序”具有依赖关系的任务。比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边<A,B><A,B>表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。

6.3       触类旁通

6.3.1   课程表II(210)的一种解法

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <stdbool.h>

#include <ctype.h>

typedef struct {

    int num;

    int *array;

} Node;

int *g_dest;

int g_destLen;

int g_front;

 

Node *g_preNode;

Node *g_outNode;

int GetMax(int a, int b)

{

    if (a >= b) {

        return a;

    }

    return b;

}

bool Init(int numCourses, int **prerequisites, int prerequisitesSize, int *prerequisitesColSize)

{

    int i;

    int input;

    int output;

 

    g_preNode = (Node *)malloc(sizeof(Node) * numCourses);

    if (g_preNode == NULL) {

        return false;

    }

    g_outNode = (Node *)malloc(sizeof(Node) * numCourses);

    if (g_outNode == NULL) {

        return false;

    }

    for (i = 0; i < numCourses; i++) {

        g_preNode[i].num = 0;

        g_preNode[i].array = NULL;

        g_outNode[i].num = 0;

        g_outNode[i].array = NULL;

    }

 

    for (i = 0; i < prerequisitesSize; i++) {

        input = prerequisites[i][1];

        output = prerequisites[i][0];

        g_preNode[output].num++;

        g_outNode[input].num++;

    }

 

    // 动态分配数组

    for (i = 0; i < numCourses; i++) {

        if (g_preNode[i].num > 0) {

            g_preNode[i].array = (int *)malloc(sizeof(int) * g_preNode[i].num);

            if (g_preNode[i].array == NULL) {

                return false;

            }

            g_preNode[i].num = 0;

        }

 

        if (g_outNode[i].num > 0) {

            g_outNode[i].array = (int *)malloc(sizeof(int) * g_outNode[i].num);

            if (g_outNode[i].array == NULL) {

                return false;

            }

            g_outNode[i].num = 0;

        }

    }

 

    for (i = 0; i < prerequisitesSize; i++) {

        input = prerequisites[i][1];

        output = prerequisites[i][0];

        g_preNode[output].array[g_preNode[output].num] = input;

        g_preNode[output].num++;

        g_outNode[input].array[g_outNode[input].num] = output;

        g_outNode[input].num++;

    }

 

    g_dest = (int *)malloc(numCourses * sizeof(int));

    if (g_dest == NULL) {

        return false;

    }

    g_destLen = 0;

    g_front = 0;

    return true;

}

 

void FreeNode(int numCourses)

{

    int i;

    if (g_preNode != NULL) {

        for (i = 0; i < numCourses; i++) {

            if (g_preNode[i].array != NULL) {

                free(g_preNode[i].array);

            }

        }

        free(g_preNode);

        g_preNode = NULL;

    }

 

    if (g_outNode != NULL) {

        for (i = 0; i < numCourses; i++) {

            if (g_outNode[i].array != NULL) {

                free(g_outNode[i].array);

                g_outNode[i].array = NULL;

            }

        }

        free(g_outNode);

        g_outNode = NULL;

    }

}

 

void Bfs(int numCourses)

{

    int i;

    int curr;

    int out;

 

    for (i = 0; i < numCourses; i++) {

        if (g_preNode[i].num == 0) {

            g_dest[g_destLen++] = i;

        }

    }

 

    while (g_front < g_destLen) {

        curr = g_dest[g_front];

        g_front++;

        for (i = 0; i < g_outNode[curr].num; i++) {

            out = g_outNode[curr].array[i];

 

            // 删除, 为0,则进入BFS队列

            g_preNode[out].num--;

            if (g_preNode[out].num == 0) {

                g_dest[g_destLen] = out;

                g_destLen++;

            }

        }

    }

}

 

/**

 * Note: The returned array must be malloced, assume caller calls free().

 */

int *findOrder(int numCourses, int **prerequisites, int prerequisitesSize, int *prerequisitesColSize,

               int *returnSize)

{

    bool rslt;

    rslt = Init(numCourses, prerequisites, prerequisitesSize, prerequisitesColSize);

    if (!rslt) {

        FreeNode(numCourses);

        (*returnSize) = 0;

        return NULL;

    }

    Bfs(numCourses);

    FreeNode(numCourses);

    (*returnSize) = g_destLen;

    if (g_destLen < numCourses) {

        (*returnSize) = 0;

    }

    return g_dest;

}

 

void TestCase1()

{

    int m1[] = { 1, 0 };

    int *g[] = { m1 };

    int prerequisitesColSize[] = { 2 };

    int numCourses = 2;

    int prerequisitesSize = 1;

    int returnSize;

    int *rslt;

    rslt = findOrder(numCourses, g, 1, prerequisitesColSize, &returnSize);

    if (returnSize == 2) {

        printf("1: pass");

    }

}

void TestCase2()

{

    int m1[] = { 1, 0 };

    int m2[] = { 2, 0 };

    int m3[] = { 3, 1 };

    int m4[] = { 3, 2 };

    int *g[] = { m1, m2, m3, m4 };

    int prerequisitesColSize[] = { 2 };

    int numCourses = 4;

    int prerequisitesSize = 4;

    int returnSize;

    int *rslt;

    rslt = findOrder(numCourses, g, 1, prerequisitesColSize, &returnSize);

    if (returnSize == 4) {

        printf("1: pass");

    }

}

int main()

{

    TestCase2();

    TestCase1();

    return 0;

}

 

6.3.2 444. 序列重建 (会员)

 

 

 

 

注:进行拓扑排序:

(5,2),(2,6),(6,3),(4,1),(1,5),(5,2)

 

6.3.3 269. 火星词典

 

 

 

 

7          九阴真经第七式 :字符串

7.1      代表题目:5. 最长回文子串

 

 

 

7.2       字符串介绍

字符串可以涉及非常多的考点:如递归、栈、hash、dfs、bfs、动态规划等,需要重点关注。

 

7.3       触类旁通

7.3.1   93. 复原IP地址(dfs)

 

7.3.2   43. 字符串相乘

 

 

7.3.3   227. 基本计算器 II

 

给出一个表达式,计算结果,就是一个计算器类型的问题,也是栈的经典应用。

栈:添加和删除元素都在队尾进行,先进后出,后进先出,类比于子***,入栈和出栈。

思路:

将表达式(中缀)转化为后缀

将后缀计算出结果

具体规则为:

1.中缀转后缀:

- 数字直接输出到后缀表达式

- 栈为空时,遇到运算符,直接入栈

- 遇到运算符,弹出所有优先级大于或等于该运算符的栈顶元素,并将该运算符入栈

- 将栈中元素依次出栈

例如:表达式:3+2 * 2

遇到3,直接输出到后缀表达式中,栈中元素为空,结果为: 栈: 空; 后缀表达式:3

遇到符号“+”,入栈,结果为: 栈:+ ; 后缀表达式:3

遇到2,直接输出,结果为: 栈:+; 后缀表达式: 3 2

遇到乘号*,入栈,结果为: 栈: * + ;后缀表达式:3 2

遇到2,直接输出,结果为: 栈: * + ;后缀表达式:3 2 2

最后,将元素出栈:结果为:后缀表达式:3 2 2 * +

 

2.计算后缀:

- 遇到数字,入栈

- 遇到运算符,弹出栈顶两个元素,做运算,并将结果入栈

- 重复上述步骤,直到表达式最右端

 

例如上述得到的后缀表达式为 3 2 2 * +

3 2 2 都是数字,入栈,结果为:栈:2 2 3

遇到* 号, 2 2 出栈,并计算,2*2 = 4, 4入栈,结果为:栈:4 3 ,表达式还剩一个加号

遇到+ 号,栈顶两个元素出栈并运算,4 3 做加法,4+3 =7

后缀表达式空了,计算完毕,输出结果:7

 

8          九阴真经第八式 :BFS

8.1      代表题目:127. 单词接龙

 

8.2       Bfs介绍

https://zhuanlan.zhihu.com/p/62884729

BFS(广度优先搜索) 常用来解决最短路径问题。第一次遍历到目的节点时,所经过的路径是最短路径。几个要点:

只能用来求解无权图的最短路径问题

队列:用来存储每一层遍历得到的节点

标记:对于遍历过的结点,应将其标记,以防重复访问。

 

注:

  1. 1.       广度搜索时候,如果曾经加入过,后续就不用再加入了;
  2. 2.       加入队列时候,需要标记当前层级,方便后续直接返回目标解;

 

 

8.3       触类旁通

8.3.1 139. 单词拆分

 

8.3.2 130. 被围绕的区域

 

 

8.3.3 317. 离建筑物最近的距离(困难)

 

 

 

8.3.4 505. 迷宫 II (会员)

 

 

 

8.3.5 529. 扫雷游戏

 



8.3.6  1263. 推箱子(困难)

 

8.3.7 1197. 进击的骑士

 

8.3.8 815. 公交路线(困难_必做)

 

8.3.9 934. 最短的桥

 

 

9          九阴真经第九式 :DFS

9.1       代表题目:934. 最短的桥

 

9.2       Dfs介绍

DFS实质就是一种枚举,不过借助递归实现;

DFS的基本模式:

void dfs(int step)

{

判断边界;

for(int i=1;i<=n;++i)//尝试每一种可能;

{

dfs(step+1);/‘/继续下一步

}

返回;

}

 

9.3       触类旁通

9.3.1 1102. 得分最高的路径

 

 

 

9.3.2   685. 冗余连接 II(困难)

 

 

9.3.3  531. 孤独像素 I

 

9.3.4 533. 孤独像素 II

 

9.3.5 332. 重新安排行程

 

 

9.3.6   337. 打家劫舍 III

 

9.3.7 113. 路径总和 II

 

 

 

10        九阴真经第十式 :动态规划

10.1   代表题目:213. 打家劫舍 II

      

        

10.2    动态规划介绍

https://blog.csdn.net/qq_37763204/article/details/79394397

https://blog.csdn.net/u013309870/article/details/75193592

 

10.3    触类旁通

10.3.1 1043. 分隔数组以得到最大和

 

10.3.2 416. 分割等和子集

 

 

10.3.3 123. 买卖股票的最佳时机 III

 

一、穷举框架

首先,还是一样的思路:如何穷举?这里的穷举思路和上篇文章递归的思想不太一样。

 

递归其实是符合我们思考的逻辑的,一步步推进,遇到无法解决的就丢给递归,一不小心就做出来了,可读性还很好。缺点就是一旦出错,你也不容易找到错误出现的原因。比如上篇文章的递归解法,肯定还有计算冗余,但确实不容易找到。

 

而这里,我们不用递归思想进行穷举,而是利用「状态」进行穷举。我们具体到每一天,看看总共有几种可能的「状态」,再找出每个「状态」对应的「选择」。我们要穷举所有「状态」,穷举的目的是根据对应的「选择」更新状态。听起来抽象,你只要记住「状态」和「选择」两个词就行,下面实操一下就很容易明白了。

 

Python

for 状态1 in 状态1的所有取值:

    for 状态2 in 状态2的所有取值:

        for ...

            dp[状态1][状态2][...] = 择优(选择1,选择2...)

比如说这个问题,每天都有三种「选择」:买入、卖出、无操作,我们用 buybuy,sellsell,restrest 表示这三种选择。但问题是,并不是每天都可以任意选择这三种选择的,因为 sellsell 必须在 buybuy 之后,buybuy 必须在 sellsell 之后。那么 restrest 操作还应该分两种状态,一种是 buybuy 之后的 restrest(持有了股票),一种是 sellsell 之后的 restrest(没有持有股票)。而且别忘了,我们还有交易次数 kk 的限制,就是说你 buybuy 还只能在 k > 0 的前提下操作。

 

很复杂对吧,不要怕,我们现在的目的只是穷举,你有再多的状态,老夫要做的就是一把梭全部列举出来。这个问题的「状态」有三个,第一个是天数,第二个是允许交易的最大次数,第三个是当前的持有状态(即之前说的 restrest 的状态,我们不妨用 11 表示持有,00 表示没有持有)。然后我们用一个三维数组就可以装下这几种状态的全部组合:

 

Python

dp[i][k][0 or 1]

0 <= i <= n-1, 1 <= k <= K

n 为天数,大 K 为最多交易数

此问题共 n × K × 2 种状态,全部穷举就能搞定。

 

for 0 <= i < n:

    for 1 <= k <= K:

        for s in {0, 1}:

            dp[i][k][s] = max(buy, sell, rest)

而且我们可以用自然语言描述出每一个状态的含义,比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 22 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 33 次交易。很容易理解,对吧?

 

我们想求的最终答案是 dp[n - 1][K][0],即最后一天,最多允许 K 次交易,最多获得多少利润。读者可能问为什么不是 dp[n - 1][K][1]?因为 [1] 代表手上还持有股票,[0] 表示手上的股票已经卖出去了,很显然后者得到的利润一定大于前者。

 

记住如何解释「状态」,一旦你觉得哪里不好理解,把它翻译成自然语言就容易理解了。

 

二、状态转移框架

现在,我们完成了「状态」的穷举,我们开始思考每种「状态」有哪些「选择」,应该如何更新「状态」。只看「持有状态」,可以画个状态转移图。

 

 

 

通过这个图可以很清楚地看到,每种状态(0 和 1)是如何转移而来的。根据这个图,我们来写一下状态转移方程:

 

Python

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])

              max(   选择 rest  ,           选择 sell      )

 

解释:今天我没有持有股票,有两种可能:

要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;

要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。

 

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

              max(   选择 rest  ,           选择 buy         )

 

解释:今天我持有着股票,有两种可能:

要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;

要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。

这个解释应该很清楚了,如果 buybuy,就要从利润中减去 prices[i]prices[i],如果 sellsell,就要给利润增加 prices[i]prices[i]。今天的最大利润就是这两种可能选择中较大的那个。而且注意 kk 的限制,我们在选择 buybuy 的时候,把 kk 减小了 11,很好理解吧,当然你也可以在 sellsell 的时候减 11,一样的。

 

现在,我们已经完成了动态规划中最困难的一步:状态转移方程。如果之前的内容你都可以理解,那么你已经可以秒杀所有问题了,只要套这个框架就行了。不过还差最后一点点,就是定义 base case,即最简单的情况。

 

Python

dp[-1][k][0] = 0

解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。

dp[-1][k][1] = -infinity

解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。

dp[i][0][0] = 0

解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。

dp[i][0][1] = -infinity

解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。

把上面的状态转移方程总结一下:

 

Python

base case:

dp[-1][k][0] = dp[i][0][0] = 0

dp[-1][k][1] = dp[i][0][1] = -infinity

 

状态转移方程:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

读者可能会问,这个数组索引是 -1 怎么编程表示出来呢,负无穷怎么表示呢?这都是细节问题,有很多方法实现。现在完整的框架已经完成,下面开始具体化。

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-wen/

10.3.4 62. 不同路径

 

10.3.5 63. 不同路径 II

 

10.3.6 651. 4键键盘(会员)

 

 

10.3.7 361. 轰炸敌人

 

10.3.8 1066. 校园自行车分配 II(会员)

 

10.3.9   750. 角矩形的数量(会员)

 

10.3.10         1230. 抛掷硬币

 

10.3.11       1055. 形成字符串的最短路径(会员)

 

 

 

11        九阴真经第十一式 :贪心算法

11.1.1 代表题目:452. 用最少数量的箭引爆气球

 

       注:排序后,第一箭能射穿所有end《= start的球。

        

11.2    贪心算法介绍

 

11.3    触类旁通

11.3.1 1231. 分享巧克力(会员)

 

 

 

11.3.2 1247. 交换字符使得字符串相同

 

11.3.3 45. 跳跃游戏 II

 

 

 

11.3.4 621. 任务调度器

 

11.3.5 376. 摆动序列

 

 

12       

 九阴真经第十二式 

:字典树

12.1   代表题目:820. 单词的压缩编码

 

       注:本题可信考了两次。

      

      

       解法二:

       直接按前序进行排序,如果第一个在第二个里面,则忽略第一个。 如:em emit,则忽略em。

        

12.2    字典树介绍

 

12.3    触类旁通

12.3.1 1231. 分享巧克力(会员)

 

 

 

12.3.2 648. 单词替换

 

 

12.3.3 208. 实现 Trie (前缀树)

 

 

 

posted @ 2022-07-19 20:58  易先讯  阅读(373)  评论(0编辑  收藏  举报