C++刷题总结

新年新气象!大家新年快乐!

23年计划多学习算法,多刷题,因此建立次专栏,记录自己刷题的过程,总结经验。

计划:

  • 有时间更新刷过的题目,按照专题来进行刷题,为期半年;
  • 每周总结一次,周内只刷题;
  • 必要时候采用画图、列表等方法进行总结,加深印象;

CS-Notes

排序算法

冒泡排序

方法说明

冒泡排序:将被排序的数组A[1…n]垂直排列,每个A[i]看作重量为A[i]的气泡,根据轻气泡不能在重气泡之下的原则,从上往下扫描数组A,违反原则的轻气泡“上浮”,如此反复进行得到最终有序气泡数组。·

算法的复杂度

冒泡排序
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

代码

#include <iostream>>
using namespace std;

void SwapInt(int *a1, int *a2)
{
    int temp = *a1;
    *a1 = *a2;
    *a2 = temp;
}

void BubbleSort(int a[], int len)
{
	int temp = 0;
	for (int i = 0; i < len - 1; i++) {
		for (int j = i; j < len; j++) {
			if (a[j] < a[i]) {
				SwapInt(&a[i], &a[j]);
			}
		}
	}
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1};
    BubbleSort(a, 10);

    cout << "sorted array:" << endl;
    for (int i = 0; i < 10; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    system("pause");
}

插入排序

定义

初始时,数组R[1]自成一个有序区、无序区R[2…n]。从i=2到i=n为止,依次将R[i]插入当前有序区R[1…i-1]中,生成有序数组R。(打牌时抓牌)

代码

#include <iostream>>
using namespace std;

void InsertSort(int a[], int len)
{
	int temp;
    int i, j;
	for (i = 1; i < len; i++) {
        temp = a[i];
		for (j = i - 1; j >= 0 && temp < a[j]; j--) {
			a[j + 1] = a[j];
		}
        a[j + 1] = temp;
	}
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1};
    InsertSort(a, 10);

    PrintArray(a, 10);

    system

(“pause”);
}

希尔排序

定义

插入排序相隔较远的数插入,会使得数要前移多位,多次交换,耗时。
分组排序算法:一组数按照d个分成若干组,每组进行排序,再用小的增量d1进行排序,直到增量dn=1,整个要排的数分成一组,排序完成。

代码

#include <iostream>
using namespace std;

void ShellSort(int a[], int len)
{
	int temp;
    int i, j;
    for (int increment = len / 2; increment > 0; increment = increment / 2) {
        for (i = increment; i < len; i++) {
            temp = a[i];
            for (j = i - increment; j >= 0 && temp < a[j]; j -= increment) {
                a[j + increment] = a[j];
            }
            a[j + increment] = temp;
	    }
    }	
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1};
    ShellSort(a, 10);

    PrintArray(a, 10);

    system("pause");
}

快速排序

定义

划分交换排序:分治策略:原问题分解成若干个规模更小的但结构与原问题相似的子问题,递归解决这些子问题,然后将这些子问题的解组合为原问题的解。
A[low,…,high]

  1. 分解:任选一个记录为基准pivot,划分左右两个区间a[low,…,pivot - 1]和A[pivot +1, …,
    high],使得左边区间数都小于等于A[pivot],右边都大于等于A[pivot]。

  2. 求解:递归调用快速排序对左右子区间进行类似操作

  3. 组合:当“求解”步骤中的两个递归调用结束时,其左右两个子区间已有序。(快排此步骤为空操作)

算法的复杂度

快速排序
时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),最差情况: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( l o g ( n ) ) O(log(n)) O(log(n))

代码

 #include <iostream>
using namespace std;

void QuickSort(int a[], int low, int high)
{
	int pivot;
    int i, j;
    if (low < high) {
        pivot = a[low];
        i = low;
        j = high;
        while (i < j) {
            while (i < j && a[j] >= pivot) {
                j--; // 过滤掉在右区间中比pivot大的元素
            }
            if (i < j) {
                a[i] = a[j];
                i++; // 右区间中小于pivot元素移到i索引位置,使得小于索引i的元素都比pivot小,i++
            }

            while (i < j && a[i] <= pivot) {
                i++; // 过滤掉在左区间中比pivot小的元素
            }
            if (i < j) {
                a[j] = a[i];
                j--; // 左区间中大于pivot元素移到j索引位置,使得大于索引j的元素都比pivot大,j--
            }
        }

        a[i] = pivot; // 出循环,i >= j,将i的就是pivot应该放置的地方,已排序一个位置
        QuickSort(a, low, i - 1);
        QuickSort(a, i + 1, high);
    }
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1};
    QuickSort(a, 0, 9);

    PrintArray(a, 10);

    system("pause");
}

选择排序

定义

初始有序空,无序A[0,…n];每次选出最小值与有序中排序,顺序插入最后。(开始时可以有序为第一个元素,比较之后与最小值交换)

算法的复杂度

选择排序
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

代码

#include <iostream>
using namespace std;

void SelectSort(int a[], int len)
{
	for (int i= 0; i < len - 1; i++) {
        int temp = a[i];
        int tempIndex = i;
        for (int j = i; j < len; j++) {
            if (a[j] < temp) {
                temp = a[j];
                tempIndex = j;
            }
        }

        a[tempIndex] = a[i]; // 顺序不能反,先将a[i]值给最小值原先的位置,再将最小值给a[i]
        a[i] = temp;
    }
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1};
    SelectSort(a, 10);

    PrintArray(a, 10);

    system("pause");
}

堆排序

定义

小根堆:所有子节点都大于父节点,Ai<=A2i且Ai<=A2i+1
大根堆:所有子节点都小于父节点,Ai>=A2i且Ai>=A2i+1
选择关键字最大或最小的记录。
筛选法进行对的调整:大根堆中,A[low]的左右子树已是堆,A[2low]和A[2low+1]分别是各自子树的关键字最大值。若A[low]不小于两个孩子节点,A[low]未违反堆性质,以A[low]为根的树已是大根堆;反之,A[low]必须和两个孩子节点较大者进行交换。交换后A[large]还是违反堆性质,要继续调整,直到large位置数值排到合适的地方,把较大的关键子逐层选上来,把较小的关键子组逐层筛选下去。

算法的复杂度

基数排序
时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
空间复杂度: O ( 1 ) O(1) O(1) 原地排序

代码

#include <iostream>
using namespace std;

int g_HeapSize = 0;
int LeftIndex(int index)
{
    return ((index << 1) + 1);
}

int RightIndex(int index)
{
    return ((index << 1) + 2);
}

void Swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 将当前元素与左右子树比较取最大值,如果当前元素是最大值,结束
// 如果不是最大值,交换当前元素和最大值元素,并且递归把交换后的元素继续进行左右子树比较,放到合适位置:叶子节点或者符合堆性质(当前为最大元素)
void MaxHeapify(int a[], int index)
{
    int largeIndex = 0;
    int left = LeftIndex(index);
    int right = RightIndex(index);

    if ((left <= g_HeapSize) && (a[left] > a[index])) {
        largeIndex = left;
    } else {
        largeIndex = index;
    }

    if ((right <= g_HeapSize) && (a[right] > a[largeIndex])) {
        largeIndex = right;
    }

    if (largeIndex != index) {
        Swap(&a[largeIndex], &a[index]);
        MaxHeapify(a, largeIndex); // 交换结束后的这个小元素比不一定符合堆性质,需要递归遍历,直到符合堆性质
    }

}

// 初始化一个堆,原先的数组上
void BuildMaxHeap(int a[], int maxSize)
{
    g_HeapSize = maxSize;
    for (int i = (maxSize >> 1); i >= 0; i--) {  // 只需要从后往前的一半的位置开始建立大根堆就行,堆顶元素必是最大值
        MaxHeapify(a, i);
    }
}

void HeapSort(int a[], int len)
{
    BuildMaxHeap(a, len - 1);
    for (int i = len - 1; i >= 1; i--) { // 直到最后一个元素,即最小元素
        Swap(&a[0], &a[i]);
        g_HeapSize--;
        MaxHeapify(a, 0); // 为啥是0,因为此时只有a[0]不满足大根堆要求,a[0]元素继续进行左右子树比较,放到合适位置:叶子节点或者符合堆性质(当前为最大元素)
    }
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,777,19,791,8,717};
    HeapSort(a, 10);

    PrintArray(a, 10);

    system("pause");

    return 0;
}

归并排序

定义

将若干个已排序的子文件合并成一个有序的文件

自定向下:
分解:一分为二,求分裂点
求解:递归对子序列进行归并排序a[low,…,mid]和a[mid + 1,…,high]
组合:已排序的两个子区间归并成一个有序的区间
递归的总结条件:子区间个数为1.

自底向上:二路归并排序:第一趟:a[1,…n]看成n个长度为1的数组,两两归并,n为偶数,得到n/2个长度为2的有序数组,n为奇数,最后一个文件不参与归并。直到最后的得到一个长度为n的数组。

算法的复杂度

归并排序
时间复杂度: O ( n l o g ( n ) ) O(n log(n)) O(nlog(n))
空间复杂度: O ( n ) O(n) O(n)

代码

#include <iostream>
using namespace std;

void Merge(int a[], int temp[], int low, int highStart, int high)
{
    int lowEnd = highStart - 1;
    int nums = high - low + 1;
    int tempIndex = low;

    while (low <= lowEnd && highStart <= high) {
        if (a[low] <= a[highStart]) {
            temp[tempIndex++] = a[low++];
        } else {
            temp[tempIndex++] = a[highStart++];
        }
    }

    while (low <= lowEnd) {
        temp[tempIndex++] = a[low++];
    }

    while (highStart <= high) {
        temp[tempIndex++] = a[highStart++];
    }

    for (int i = 0; i < nums; i++, high--) { // 这时候需要反向填充,low值变化,只有high值是入参固定的,借助high值填充
        a[high] = temp[high];
    }
}

void MSort(int a[], int temp[], int low, int high)
{
    if (low >= high) {  // 结束条件,原子节点return
        return;
    }

    int mid = (low + high) / 2;  // 分解
    MSort(a, temp, low, mid);  // 子区间归并排序
    MSort(a, temp, mid + 1, high);  // 子区间归并排序
    Merge(a, temp, low, mid + 1, high);  // 组合,有序子区间合并成一个有序区间
}

void MergeSort(int a[], int len)
{
    int *temp = NULL;
    temp = new int[len];
    if (temp != NULL) {
        MSort(a, temp, 0, len - 1);
        delete []temp;
    }
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,77,19,-1,8,17};
    MergeSort(a, 10);

    PrintArray(a, 10);

    system("pause");

    return 0;
}

基数排序

定义

基数排序是箱排序的改进。
箱排序:若干个箱子,一次扫描待排序的记录R[0],R[1],…R[n-1],把关键字等于K的记录全都装进第K个箱子里(分配),然后按照序号一次将非空的箱子首位连接起来(收集)。
基数排序基于多关键字,比如扑克牌有2个关键字:点数和花色。实现多关键字排序的方法:最高位优先级和最低位优先级,基数排序基于最低为优先级:从低位到高位依次对数据进行箱排序。

举例

10-99整数排序:45,13,58,64,29,61
先0-9箱子收集原序列的个位数字,进行排序:61,13,64,45,58,29
在0-9箱子收集上述序列的十位数字,进行排序:13,29,45,58,61,64
十位比个位更关键,采用最低位优先方法

算法的复杂度

基数排序
时间复杂度: O ( k n ) O(kn) O(kn) 关键字的个数K(K种类关键字)* 数组元素个数n
时间复杂度: O ( r n + r ) O(rn + r) O(rn+r) 关键字取值个数r * 数组元素个数n(所有箱子来存储数组元素) + 关键字取值个数(存储每种关键字取值的元素个数r)

代码

基数排序

#include <iostream>
#include <math.h>
#include <cstring>
using namespace std;

int FindMax(int a[], int len)
{
    int max = a[0];
    for (int i = 1; i < len ; i++) {
        if (max < a[i]) {
            max = a[i];
        }
    }
    return max;
}

int DigitNumber(int number)
{
    int digitNum = 0;
    do {
        number /= 10;
        digitNum++;
    } while (number != 0);
    return digitNum;
}

int KthNumber(int number, int k)
{
    number /= pow(10, k);
    return number % 10;
}

void RadixSort(int a[], int len)
{
    int *temp[10]; // 定义指针数组,每个元素指向一个箱子
    int count[10] = {0}; // 定义每个箱子的装的元素个数
    int max = FindMax(a, len);
    int maxDigit = DigitNumber(max);
    int i, j, k;
    for (i = 0; i < 10; i++) {
        temp[i] = new int[len];
        memset(temp[i], 0, sizeof(int) * len); // 每一个箱子装载前清空
    }

    for (i = 0; i <maxDigit; i++) {
        memset(count, 0, sizeof(int) * 10); // 装载前Count清空
        for (j = 0; j < len; j++) {
            int x = KthNumber(a[j], i); // 求出i位的数字
            temp[x][count[x]] = a[j]; // 将元素按照i位数字存储到对应的箱子中,并记录箱子中元素的个数
            count[x]++;
        }

        int index = 0;
        // 元素放回到数组中,为了下次遍历i + 1位排序
        for (j = 0; j < 10; j++) {
            for (k = 0; k < count[j]; k++) {
                a[index++] = temp[j][k];
            }
        }

    }
    for (i = 0; i < 10; i++) {
        delete []temp[i];
        temp[i] = NULL;
    }
}

void PrintArray(int a[], int len)
{
    cout << "sorted array:" << endl;
    for (int i = 0; i < len; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

int main()
{
    int a[10] = {4,1,2,3,5,777,19,791,8,717};
    RadixSort(a, 10);

    PrintArray(a, 10);

    system("pause");

    return 0;
}

排序算法总结

常见排序算法

递归与动态规划

递归与迭代

定义

递归的解是基于子问题的解构建,有f(n-1)推导出f(n)。
递归方法:自底向上、自顶向下、数据分割
递归算法极耗空间,所有递归都可以有用迭代实现。
动态规划就是使用递归算法发现的重叠子问题(重复调用),缓存成中间结果来使用。

斐波那契数列

递归实现
#include <iostream>

using namespace std;
int Fibonacci(int i)
{
    if (i == 0) {
        return 0;
    }
    if (i == 1) {
        return 1;
    }

    return Fibonacci(i - 1) + Fibonacci(i -2);
}


int main()
{
    int i = 18;
    int ret = Fibonacci(i);
    cout << i << "th Fibonacci number = " << ret << endl;

    system("pause");
    return 0;
}

递归树
运行时间:子树节点数*节点运行时间 = O ( 2 n ) O(2^n) O(2n),实际 = O ( 1. 6 n ) O(1.6^n) O(1.6n)

自顶向下(记忆法)

计算Fib(i),使用可能取值Fib(0)~Fib(i-1),将之前的所有数值都存储起来吗,以备后续使用,称为记忆法。

#include <iostream>

using namespace std;

int Fibonacci(int i, int memo[])
{
    // 记忆法
    if (i == 0 || i == 1) {
        return i;
    }

    if (memo[i] == 0) {
        memo[i] = Fibonacci(i - 1, memo) + Fibonacci(i - 2, memo);
    }

    return memo[i];
}

int FibonacciAlgo(int n)
{
    int a[n + 1] = {0};
    return Fibonacci(n, a);
}

int main()
{
    int i = 18;
    int ret = FibonacciAlgo(i);
    cout << i << "th Fibonacci number = " << ret << endl;

    system("pause");
    return 0;
}

记忆法

自底向上的动态规划方法
#include <iostream>

using namespace std;

int FibonacciAlgo(int n)
{
    if (n == 0 || n == 1) {
        return n;
    }
    int memo[n] = {0};
    memo[0] = 0;
    memo[1] = 1;

    int i;
    for (i = 2; i <= n; ++i) {
        memo[i] = memo[i - 1] + memo[i - 2];
    }

    return memo[n];
}

int main()
{
    int i = 18;
    int ret = FibonacciAlgo(i);
    cout << i << "th Fibonacci number = " << ret << endl;

    system("pause");
    return 0;
}

方法优化:

#include <cstring>
#include <iostream>

using namespace std;

// 自底向上方法优化,斐波那契数值的最后两次存储的数值有关,用两个变量来替换数组,O(1)复杂度
int FibonacciAlgo(int n)
{
    if (n == 0 || n == 1) {
        return n;
    }
    int a = 0;
    int b = 1;
    int c = 1;

    int i;
    for (i = 2; i <= n; ++i) {
        c = b + a;
        a = b;
        b = c;
    }

    return c;
}

int main()
{
    int i = 18;
    int ret = FibonacciAlgo(i);
    cout << i << "th Fibonacci number = " << ret << endl;

    system("pause");
    return 0;
}

典型动态规划题目总结

台阶问题

70. 爬楼梯

class Solution {
public:
    // // 递归方法超出时间限制
    // // f(n) = f(n-1) + f(n-2);f(1)=1;f(2)=2;
    // int climbStairs(int n) {
    //     if (n == 1 || n == 2) {
    //         return n;
    //     }

    //     return climbStairs(n - 1) + climbStairs(n - 2);
    // }

    // // 自底向上的动态规划方法——O(n) O(n)
    // int climbStairs(int n) {
    //     vector<int> dp(n+1);
    //     dp[0]=1;dp[1]=1;
    //     for(int i=2;i<=n;i++)
    //     dp[i]=dp[i-1]+dp[i-2];  //动态转移方程
    //     return dp[n];
    // }

    // 自底向上的动态规划方法——O(n),O(1)
    // a=1;b=2;c=a+b;
    int climbStairs(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        int status1 = 1;
        int status2 = 2;
        int ret;
        for (int i = 3; i <= n; ++i) {
            ret = status1 + status2;
            status1 = status2;
            status2 = ret;
        }

        return ret;
    }
};

1936. 新增的最少台阶数

// #define INT_MAX   2147483647
// #define INT_MIN    (-INT_MAX - 1)
class Solution {
public:
    // 自顶向下的动态规划方法
    int addRungs(vector<int>& rungs, int dist) {
        int len = rungs.size();
        int ret = 0;
        int curStep = 0;

        for (int i = 0; i < len; ++i) {
            // 差值,不包括当前台阶数,【5, 10】,2:到达9就可以了,所以10-5-1=4,4/2=2;【3】,1:3-0-1/1=2
            int diff = rungs[i] - 1 - curStep; 
            if (diff >= dist) { // 是否能到达第i台阶
                ret = ret + (diff / dist);
            }
            curStep = rungs[i];
        }
        return ret;
    }
};

746. 使用最小花费爬楼梯
说明:典型的动态规划问题:每个台阶当前的花费体力dp[n]
下一个台阶花费dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])

class Solution {
public:
    // int const MAX_LEN = 1000;
    //  dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
    int minCostClimbingStairs(vector<int>& cost) {
        // int len = cost.size();
        // int dp[len + 1];

        // dp[0] = dp[1] = 0; // 当前台阶需要的体力值
        // for (int i = 2; i <= len; ++i) {
        //     dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        // }

        // return dp[len];

        int len = cost.size();
        int cur = 0;
        int pre = 0; // 当前台阶需要的体力值
        int ret = 0;
        for (int i = 2; i <= len; ++i) {
            ret = min(cur + cost[i - 1], pre + cost[i - 2]);
            pre = cur;
            cur = ret;
        }

        return ret;
    }
};

面试题 08.01. 三步问题
注意:放置整型溢出风险,定义long long类型,并结果取余1000000007
ACM技巧 - 为何要对 1000000007 取模?

class Solution {
public:
    int waysToStep(int n) {
        if (n == 1 || n == 2) {
            return n;
        }
        if (n == 3) {
            return 4;
        }
        int a = 1;
        int b = 2;
        int c = 4;
        long long ret = 0;

        for (int i = 4; i <= n; ++i) {
            ret = (a + b) % 1000000007;
            ret = (ret + c) % 1000000007;
            a = b;
            b = c;
            c = ret;
        }

        return ret % 1000000007;
    }
};
机器人路径

剑指 Offer 13. 机器人的运动范围
注意:求数位之和、二维数组初始化、动态规划思想——二维元素的传染性(空间)。

class Solution {
public:
    // 怎么计算数位和
    int DigitBitSum(int num)
    {
        int ret = 0;
        for (; num != 0; num /= 10) {
            ret += num % 10;
        }

        return ret;
    }

    // 第(m, n)位置 = 第(m-1, n)位置 + 第(m, n-1)位置移动下来,一旦超出K,停止格子计数
    int movingCount(int m, int n, int k) {
        if (k == 0) {
            return 1;
        }
        // 怎么定义一个m*n初始化为0的二维数组
        vector<vector<int>> vis(m, vector<int>(n, 0));
        int ans = 1;
        // 初始化二位数组的0,0位置被传染
        vis[0][0] = 1;

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 拦截条件满足才往下面走
                if ((i == 0 && j == 0) || DigitBitSum(i) + DigitBitSum(j) > k) {
                    continue;
                }

                if (i - 1 >= 0) {
                    vis[i][j] |= vis[i - 1][j];
                }
                if (j - 1 >= 0) {
                    vis[i][j] |= vis[i][j - 1];
                }

                ans += vis[i][j]; // 如果vis【i】【j】被传染了,置1,加到结果中
            }
        }
        return ans;
    }
};

还可以采用DFS、BFS方法,BFS方法如下:注意:遍历向右和向下的元素,采用队列存储其位置(技巧)。

class Solution {
    // 计算 x 的数位之和
    int get(int x) {
        int res=0;
        for (; x; x /= 10) {
            res += x % 10;
        }
        return res;
    }
public:
    int movingCount(int m, int n, int k) {
        if (!k) return 1;
        queue<pair<int,int> > Q;
        // 向右和向下的方向数组
        int dx[2] = {0, 1};
        int dy[2] = {1, 0};
        vector<vector<int> > vis(m, vector<int>(n, 0));
        Q.push(make_pair(0, 0));
        vis[0][0] = 1;
        int ans = 1;
        while (!Q.empty()) {
            auto [x, y] = Q.front();
            Q.pop();
            for (int i = 0; i < 2; ++i) {
                int tx = dx[i] + x;
                int ty = dy[i] + y;
                if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) continue;
                Q.push(make_pair(tx, ty));
                vis[tx][ty] = 1;
                ans++;
            }
        }
        return ans;
    }
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solutions/191527/ji-qi-ren-de-yun-dong-fan-wei-by-leetcode-solution/

LCR 098. 不同路径

class Solution {
public:
    // f(i,j)=f(i−1,j)+f(i,j−1)
    // 对于边界条件要考虑仔细
    int uniquePaths(int m, int n) {
        vector<vector<int>> vis(m, vector<int>(n, 0));
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) {
                    vis[0][0] = 1;
                } else if (i == 0 && j > 0) {
                    vis[i][j] = vis[i][j - 1];
                } else if (j == 0 && i > 0) {
                    vis[i][j] = vis[i - 1][j];
                } else {
                    vis[i][j] = vis[i - 1][j] + vis[i][j - 1];
                }
            }
        }

        return vis[m - 1][n - 1];
    }
};

数学方法
组合方法

class Solution {
public:
    int uniquePaths(int m, int n) {
        long long ans = 1;
        for (int x = n, y = 1; y < m; ++x, ++y) {
            ans = ans * x / y;
        }
        return ans;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/2AoeFn/solutions/1398902/lu-jing-de-shu-mu-by-leetcode-solution-ozcc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
汉诺塔

注意:状态变更,递归的方法,直到A中个数为1停止移动。
状态变更很难发现,需要读者精心话题寻找其规律。

class Solution {
public:
    // n=1时,直接把盘子从A移动到C
    // n>1时 
    // 2.1 先把上面n-1个盘子从A移动到B(子问题,递归) 
    // 2.2 再将最大的盘子从A移动到C 
    // 2.3 再将B上n-1个盘子从B移动到C(子问题,递归)
    void move(int n, vector<int> &A, vector<int> &B, vector<int> &C) {
        if (n == 1) {
            C.push_back(A.back());
            A.pop_back();
            return;
        }

        move(n - 1, A, C, B);
        C.push_back(A.back());
        A.pop_back();

        move(n - 1, B, A, C);
    }

    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
        int n = A.size();
        move(n, A, B, C);
    }
};
无重复字符串的排列组合
重复字符串的排列组合
颜色填充
硬币找零
八皇后
布尔运算
堆箱子
括号匹配
幂集

剑指 Offer 68 - II. 二叉树的最近公共祖先

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
存在q、p的最近公共祖先

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* ans;
    bool dfs(TreeNode* root, TreeNode* p, TreeNode* q)
    {
        // 递归的终止条件
        if (root == nullptr) {
            return false;
        }

        // 树的左和右子树遍历
        bool lson = dfs(root->left, p, q);
        bool rson = dfs(root->right, p, q);

        // 遍历到底,再核查是否存在满足要求的节点
        // 当前节点要么左和右子树存在p或q节点,要么为q或p的值且左或右子树存在另一个数值,返回当前节点符合题目要求
        if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson)))
        {
            ans = root;
        }

        // 当前节点要么为q或者p的值,要么它的左或者右子树存在p或者q的值
        return lson || rson || root->val == p->val || root->val == q->val;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 前提:1】所有节点的值都是唯一的。2】p、q 为不同节点且均存在于给定的二叉树中
        static_cast<void>(dfs(root, p, q));

        return ans; 
    }
};

回溯

定义

案例

剑指 Offer 12. 矩阵中的路径

class Solution {
public:
    bool check(vector<vector<char>>& board, vector<vector<int>>& vis,
        int i, int j, string word, int k)
    {
        if (board[i][j] != word[k]) {
            return false;
        } else if (k == word.size() - 1) {
            return true;
        }

        vector<pair<int, int>> dir{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        vis[i][j] = 1; // 先标记,没找在回退,其他方向找,回溯方法精髓
        bool ret = false;

        for (const auto& d : dir) {
            int newi = d.first + i;
            int newj = d.second + j;
            if (newi >= 0 && newi < board.size() && newj >= 0 && newj < board[0].size()) {
                if (!vis[newi][newj]) { // 没被访问过才处理
                    if (check(board, vis, newi, newj, word, k + 1)) {
                        return true;
                    }
                }
            }
        }

        vis[i][j] = false; // 回溯,从其他路径(非【i, j】)经过i, j就可能成功找到单词
        return ret;
    }


    bool exist(vector<vector<char>>& board, string word) {
        int row = board.size();
        int column = board[0].size();
        vector<vector<int>> vis(row, vector<int>(column, 0));
        for (int i = 0; i < row; i++) {
            for (int j = 0;j < column; j++) {
                if (check(board, vis, i ,j, word, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
};

有限状态机

定义

确定有限状态自动机(以下简称「自动机」)是一类计算模型。它包含一系列状态,这些状态中:

有一种特殊的状态,被称作「初始状态」。
还有一系列状态被称为「接受状态」,它们组成了一个特殊的集合。其中,一个状态可能既是「初始状态」,也是「接受状态」。
起初,这个自动机处于「初始状态」。随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;当状态转移完成后,它就读取下一个字符。当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」。

注意:如果输入的过程中某一步转移失败了,即不存在对应的「转移规则」,此时计算将提前中止。在这种情况下我们也判定该字符串「被拒绝」。

一个自动机,总能够回答某种形式的「对于给定的输入字符串 S,判断其是否满足条件 P」的问题。在本题中,条件 P 即为「构成合法的表示有效数字的字符串」。

自动机驱动的编程,可以被看做一种暴力枚举方法的延伸:它穷尽了在任何一种情况下,对应任何的输入,需要做的事情。

自动机在计算机科学领域有着广泛的应用。在算法领域,它与大名鼎鼎的字符串查找算法「KMP」算法有着密切的关联;在工程领域,它是实现「正则表达式」的基础。

LCR 138. 有效数字

有效数字状态转移图

class Solution {
public:
    // 定义所以的状态
    enum State {
        STATE_INITIAL,
        STATE_INT_SIGN,
        STATE_INTEGER,
        STATE_POINT,
        STATE_POINT_WITHOUT_INT,
        STATE_FRACTION,
        STATE_EXP,
        STATE_EXP_SIGN,
        STATE_EXP_NUMBER,
        STATE_END
    };

    // 行为类型,存在非法的行为
    enum ActType {
        CHAR_NUMBER,
        CHAR_EXP,
        CHAR_POINT,
        CHAR_SIGN,
        CHAR_SPACE,
        CHAR_ILLEGAL
    };

    ActType InputToActType(char ch)
    {
       if (ch >= '0' && ch <= '9') {
            return CHAR_NUMBER;
        } else if (ch == 'e' || ch == 'E') {
            return CHAR_EXP;
        } else if (ch == '.') {
            return CHAR_POINT;
        } else if (ch == '+' || ch == '-') {
            return CHAR_SIGN;
        } else if (ch == ' ') {
            return CHAR_SPACE;
        } else {
            return CHAR_ILLEGAL;
        }
    }

    bool validNumber(string s)
    {
        unordered_map<State, unordered_map<ActType, State>>transfer {
            // 初始化状态下,ACT为加整数数字,那么下一个状态就是整数状态
            {
                STATE_INITIAL, {
                    {CHAR_SPACE, STATE_INITIAL},
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_SIGN, STATE_INT_SIGN},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT}
                }
            }, {
                STATE_INT_SIGN, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT}
                }
            }, {
                STATE_INTEGER, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_POINT, STATE_POINT},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_POINT, {
                    {CHAR_NUMBER, STATE_FRACTION},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_POINT_WITHOUT_INT, {
                    {CHAR_NUMBER, STATE_FRACTION}
                }
            }, {
                STATE_FRACTION,
                {
                    {CHAR_NUMBER, STATE_FRACTION},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_EXP,
                {
                    {CHAR_NUMBER, STATE_EXP_NUMBER},
                    {CHAR_SIGN, STATE_EXP_SIGN}
                }
            }, {
                STATE_EXP_SIGN, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER}
                }
            }, {
                STATE_EXP_NUMBER, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_END, {
                    {CHAR_SPACE, STATE_END}
                }
            }
        };

        int len = s.length();
        State st = STATE_INITIAL;

        for (int i = 0; i < len; i++) {
            ActType act = InputToActType(s[i]);
            if (transfer[st].find(act) == transfer[st].end()) {
                return false; // 如果当前状态在act下没用下一个状态,返回失败
            } else {
                st = transfer[st][act]; // 当前的状态和行为找到下一个状态赋值给状态变量
            }
        }

        // 遍历结束之后的状态要为符合条件的5中状态之一
        return st == STATE_END || st == STATE_POINT || st == STATE_INTEGER || st == STATE_FRACTION || st == STATE_EXP_NUMBER;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值