• 408王道数据结构强化——算法题


    目录

    1.注释以及简写 

    1.1.最大值——INT_MAX,最小值——INT_MIN

    1.2.比大小函数max(a,b) min(a,b)

    1.3.使用CIN、COUT代替PRINTF、SCANF

    1.4.i++和++i

    1.5.交换函数swap(a,b)

    2.复杂度

    3.数组

    3.1.暴力求解

    3.2.优化

    4.单链表

    5.树

    6.真题(只考虑次优解和暴力解)

    6.1.数组

    6.1.1.(2010)

    6.1.2.(2011)

    6.1.3.(2013) 

    6.1.4.(2016)

    ​编辑

    6.1.5.(2018)

    6.1.6.(2020)

    6.2.链表

    6.2.1.(2009)

    6.2.2.(2012)

    6.2.3.(2015)

    6.2.4.(2019) 

    6.3.树

    6.3.1.(2014)

    6.3.2.(2017)

    6.4.图(2021)


    1.注释以及简写 

    1.1.最大值——INT_MAX,最小值——INT_MIN

    ①找最小值初始化为MAX_INT(任何值都比它小);找最大值设置为MIN_INT(任何值都比它大)

    1. int D_min = MAX_INT; //将D_min初始化为int类型的最大值
    2. for (int i = 0; i < n; i++){ //遍历数组找到数组中的最小值
    3. if (arr[i] < D_min) D_min = arr[i];
    4. }
    5. int D_max = MIN_INT; //将D_max设置为int类型的最小值

    ②还有一种思路:初始化时,将arr[0]赋值给D_min

    1.2.比大小函数max(a,b) min(a,b)

    加注释直接用(与1.1中代码相比较)

    1. int D_min = MAX_INT; //将D_min初始化为INT类型的最大值
    2. for (int i = 0; i < n; i++) {
    3. D_min = min(D_min, arr[i]); //D_min = D_min和arr[i]中的较小值
    4. }

    1.3.使用CIN、COUT代替PRINTF、SCANF

    1. cin >> A[0];
    2. cout << A[0];

    1.4.i++和++i

    i++先对i进行处理,再自增;++i先自增再对i处理

    1.5.交换函数swap(a,b)

    ①直接用,加注释

    ②原定义中是采用的指针方式,因此交换的是两个地址空间内的内容

    1. if (arr[i] < arr[j]) swap(arr[i],arr[i+1]); //arr[i] < arr[j]时,交换A[i]和A[i+1]的值
    2. //等价于
    3. if (arr[i] < arr[j]) {
    4. int temp = arr[i];
    5. arr[i] = arr[j];
    6. arr[j] = temp;
    7. }

    2.复杂度

    1.时间上尽可能高效→不管空间

    2.时间和空间两方面都尽可能高效(等价于尽可能高效)→时间优先,空间次要

    3.空间复杂度为O(1)且时间上尽可能高效→时间优先,限定空间O(1)

    4.什么都没说→能做出来就行

    5.只考虑递归和循环

    ①循环:

    ②递归:时间复杂度为递归的层数;时间复杂度为递归的次数

    3.数组

    3.1.暴力求解

    1.枚举:穷尽所有可能→for循环

    2.对无序数组排序→快速排序

    3.2.优化

    1.考虑没有使用到的条件

    2.过度使用条件:例如并没有要求排序的条件下却使用排序的方法做

    3.考虑别的思路:

    ①折半算法:

    (1)要求:有序(根据当前查找的结果方便下次查找);数组;一个

    (2)代码实现:时间复杂度O(logn),空间复杂度O(1)

    1. int Binary_Search(int arr[], int key, int n) {
    2. int low = 0, high = n - 1, mid;
    3. while (low <= high) {
    4. mid = (low + high) / 2;
    5. if (arr[mid == key) return mid; //mid指向的元素和key相等,返回mid,即数组下标
    6. else if (arr[mid] < key) low = mid + 1; //mid指向的元素比key小,去右半部分
    7. else high = mid - 1; //mid指向的元素比key大,去左半部分
    8. }
    9. //如果数组中有和key相等的元素,则一定会在while循环中return
    10. //while循环结束后还没有执行return,则说明数组中没有和key相等的元素,return-1
    11. return -1;
    12. }

    ②指针后移:要求:有序(利用有序性,当前指向的元素是各自表中的最小值,在它们中取最小值即是当前表中所有元素的最小值);多个;线性表(可以是数组,也可以是链表)

    ③贪心算法:局部最优

    ④动态规划:以空间换时间,空间保存状态

    4.单链表

    1.不能快排,不能折半查找(没有数组随机存取的特性)

    2.注意条件:空间复杂度O(1),不可以改变链表结构

    3.数据结构定义:

    1. typedef struct LNode{ //单链表
    2. struct LNode *next;
    3. int data;
    4. }LNode, *LinkList;
    5. typedef struct LNode{ //双链表
    6. struct LNode *prior, *next;
    7. int data;
    8. }LNode, *LinkList;

    4.注意是否有头结点

    5.暴力解法:①枚举 ②利用数组保存链表元素(注意使用条件)

    6.优化方向:

    ①前后指针:前后指针相差n个距离,同时向后移动,距离不变(查找倒数第k个)

    ②快慢指针:链表存在回路,走得快的指针会追上走得慢的指针

    ③头插法:逆置链表(从头结点后的第一个元素取,然后将该元素用头插法插入头结点后)

    ④数组指针后移

    ⑤以空间换时间(长度为n的数组)

    5.树

    1.递归方式实现,不用记忆非递归

    2.定义:左右孩子表示法最常用

    3.树的遍历

    6.真题(只考虑次优解和暴力解)

    6.1.数组

    6.1.1.(2010)

     (1)新建一个与arr数组等长的数组arr,先将arr的后p个元素依次存放到res数组的前p个元素中,然后再将arr的剩余元素依次存放到res的剩余元素中

    1. int* Reverse(int arr[], int n, int p) {
    2. int *res = (int*)malloc(sizeof(n));
    3. memset(a, 0, sizeof(int) * n);
    4. int i, j;
    5. for (i = n - p + 1, j = 0; i < n; i++, j++) res[j] = arr[i];
    6. for (i = 0; i < n - p + 1; i++, j++) res[j] = arr[i];
    7. }

    (3)O(n),O(n)

    6.1.2.(2011)

    (1)都放入新数组中快速排序

    1. int Partation(int arr[], int low, int high) {
    2. 随机将数组中某一元素和arr[low]交换 //快排优化
    3. int pivot = arr[low]; //选择arr[low]为基准元素
    4. while (low < high) {
    5. while (low < high && arr[high] >= pivot) high--;
    6. arr[low] = arr[high];
    7. while (low < high && arr[low] < pivot) low++;
    8. arr[high] = arr[low];
    9. }
    10. arr[low] = pivot;
    11. return low;
    12. }
    13. void QuickSort(int arr[], int low, int high) {
    14. if (low < high) {
    15. int pivotPos = Partation(arr, low, high);
    16. QuickSort(arr, low, pivotPos - 1);
    17. QuickSort(arr, pivotPos + 1, high);
    18. }
    19. }
    20. int GetMidNum(int A[], int B[], int len){
    21. int* C = (int*)malloc((sizeof(int) * (2 * len)) //新建长度为2len的数组C
    22. int i,j;
    23. for (i = 0; i < len; i++) C[i] = A[i]; //将数组A复制到数组C的前半段中
    24. for (j = 0; j < len; i++, j++) C[i] = B[j]; //将数组B复制到数组C的后半段中
    25. QuickSort(C, 0, 2 * len - 1); //对数组C进行快速排序
    26. cout << C[len - 1]; //输出中位数
    27. }

    (2)数组指针后移把数存入第三个数组(用count记录进行次数的方法并不能降低时间复杂度,省略系数后还是n的时间复杂度)

    1. int GetMidNum(int A[], int B[], int len) {
    2. int *C = (int*)malloc(sizeof(int) * 2 * len);
    3. int i, j, k;
    4. for(i = 0, j = 0, k = 0; i < len && j < len; k++){
    5. if (A[i] < B[j]) { //比较A[i]和B[j],更小的存入C,并且该数组的指针向后移
    6. C[k] = A[i];
    7. i++;
    8. }
    9. else {
    10. C[k] = B[j];
    11. j++;
    12. }
    13. }
    14. cout << C[len - 1];
    15. }

    (3)数组指针后移(本质上和2一样,都是控制指针后移)

    1. int GetMidNum(int A[], int B[], int len) {
    2. int count = 0, i, j, res;
    3. while (i < len && j < len && count < len) {
    4. if (A[i] < B[j]) {
    5. i++;
    6. res = A[i];
    7. }
    8. else {
    9. j++;
    10. res = B[j];
    11. }
    12. count++;
    13. }
    14. cout << res;
    15. }
    1. int GetMidNum(int A[], int B[], int len) {
    2. int i, j, k;
    3. for (k = 1, i = 0, j = 0; k < n; k++) { //一共移动n - 1次
    4. if (A[i] < B[j]) i++;
    5. else j++;
    6. }
    7. cout << min(A[i], B[j]); //输出A和B数组中当前元素的较小值
    8. }

    6.1.3.(2013) 

    (1)枚举:逐个元素判断(两层循环) 

    1. void MainNum(int A[], int n) {
    2. int i, j, count;
    3. for (i = 0; i < n; i++) {
    4. for (j = 0, count = 0; j < n; j++) {
    5. if (A[j] == A[i]) count ++; //当前A[j]等于A[i],出现次数+1
    6. }
    7. if (count > n / 2) {
    8. cout << A[i]; //A[i]出现次数满足主元素要求,输出A[i]
    9. return; //结束
    10. }
    11. }
    12. cout << -1; //没有主元素,输出-1
    13. return;
    14. }

    (2)快速排序:

    1. int Partation(int arr[], int low, int high) {
    2. 随机选定数组下标low - high之间的元素和arr[low]对调 //快排优化
    3. int pivot = arr[low];
    4. while (low < high) {
    5. while (low < high && arr[high] >= pivot) high--;
    6. arr[low] = arr[high];
    7. while (low < high && arr[low]
    8. arr[high] = arr[low;
    9. }
    10. arr[low] = pivot;
    11. return low;
    12. }
    13. void QuickSort(int arr[], int low, int high) { //快速排序
    14. if (low < high) {
    15. int pivotPos = Partation(arr, low, high);
    16. QuickSort(arr, low, pivotPos - 1);
    17. QuickSort(arr, pivotPos + 1, high);
    18. }
    19. }
    20. int MainNum(int arr[], int n) {
    21. //count标记当前元素的出现次数,current记录当前元素
    22. int i, current = arr[0], count = 1;
    23. for (int i = 1; i < n; i++) { //遍历数组
    24. if (arr[i] == current) count ++; //当前元素和current相同,出现次数 + 1
    25. else { //当前元素和current不同,用当前元素替换current,并且出现次数归1
    26. current = A[i];
    27. count = 1;
    28. }
    29. if (count > n / 2) return current; //出现次数大于>n/2,则current为主元素
    30. }
    31. return -1; //没有主元素,返回-1
    32. }

    (3)动态规划:空间换时间,A出现的元素范围是正整数且

    第一遍遍历A数组记录每个元素其值出现的次数:设A中出现i,则count[i]++

    第二遍遍历count数组,找是否有元素的值 > n/2,即A中出现次数 > n/2

    1. int GetMainNum(int A[], int n) {
    2. int *count = (int*)malloc(sizeof(int) * n);
    3. memset(count, 0 , sizeof(int) * n);
    4. //遍历数组,将当前元素的值作为下标在count中对应元素+1
    5. for (int i = 0; i < n; i++) count[A[i]]++;
    6. //count[i]元素的值即i在A中出现的次数
    7. for (int i = 0; i < n; i++) {
    8. if (count[i] > n / 2) return i; //i出现次数>n/2,i为主元素
    9. }
    10. return -1;
    11. }

    6.1.4.(2016)

    快速排序:先对数组进行排序即满足需求(向下取整,即数组个数为奇数时,右大左小)

    1. int Partation(int arr[], int low, int high) {
    2. 随机选择一个数组下标为low - high的元素和arr[low]交换 //快排优化
    3. int pivot = arr[low];
    4. while (low < high) {
    5. while (low < high && arr[high] >= pivot) high--;
    6. arr[low] = arr[high];
    7. while (low < high && arr[low] < pivot) low++;
    8. arr[high] = arr[low];
    9. }
    10. arr[low] = pivot;
    11. return low;
    12. }
    13. void QuickSort(int arr[], int low, int high){
    14. if (low < high) {
    15. int pivotPos = Partation(arr, low , high);
    16. QuickSort(arr, low, pivotPos - 1);
    17. QuickSort(arr, pivotPos + 1, high);
    18. }
    19. }
    20. void Divid(int arr[], int n){
    21. QuickSort(arr, 0, n - 1); //快速排序
    22. for (int i = 0; i < n / 2; i++) cout << arr[i] << endl; //输出子集A1
    23. for (int i = n / 2; i < n; i++) cout << arr[i] << endl; //输出子集A2
    24. }

    6.1.5.(2018)

    (1)快速排序 

    1. int Partation(int arr[], int low,int high) {
    2. 随机选择一个数组下标为low - high之间的元素和arr[low]交换 //快排优化
    3. int pivot = arr[low];
    4. while (low < high) {
    5. while (low < high && arr[high] >= pivot) high--;
    6. arr[low] = arr[high];
    7. while (low < high && arr[low] < pivot) low--;
    8. arr[high] = arr[low];
    9. }
    10. arr[low] = pivot;
    11. return low;
    12. }
    13. void QuickSort(int arr[], int low, int high) {
    14. if (low < high) {
    15. int pivotPos = Partation(arr, low, high);
    16. QuickSort(arr, low, pivotPos - 1);
    17. QuickSort(arr, pivotPos + 1, high);
    18. }
    19. }
    20. int GetNum(int arr[], int n) {
    21. QuickSort(arr, 0, n - 1); //将数组排序成有序
    22. int i, j;
    23. while (arr[i] <= 0 && i < n) i++;
    24. if (i == n) return 1; //数组中的没有正数
    25. if (arr[i] != 1) return 1; //此时arr[i]为数组中最小正整数
    26. //如果不是1,则1是未出现的最小正整数
    27. else { //arr[i]为1,找到第一个正数间断点
    28. j = i + 1;
    29. while (j < n && arr[j] != arr[j - 1] && arr[j] != arr[j - 1] + 1);
    30. return arr[j - 1] + 1; //返回上一个元素+ 1的值
    31. }
    32. }

    (2)空间换时间

    1. int MinNum(int arr[], int n) {
    2. int *count = (int*)malloc(sizeof(int) * (n + 1));
    3. memset(count, 0, sizeof(int) * (n + 1));
    4. int i;
    5. //遍历数组,当前元素大于0,则在count数组以该元素为数组下标的元素自增1
    6. for (i = 0; i < n; i++) if (arr[i] > 0) count[arr[i]]++;
    7. //count数组元素的值即该下标在arr数组中出现的次数,返回第一个为0的下标
    8. for (i = 1; i < n + 1; i++) if (count[i] == 0) return i;
    9. }

    6.1.6.(2020)

    1. int GetDis(int a, int b){
    2. int c = a - b;
    3. if (c >= 0) return c;
    4. else return -c;
    5. }
    6. int MinDis(int A[], int B[], int C[], int lenA, int lenB, int lenC){
    7. int i, j, k, temp;
    8. int min = MAX_INT; //将min初始化为INT类型的最大值
    9. for (i = 0; i < lenA; i++){
    10. for (j = 0; j < lenB; j++){
    11. for (k = 0; k < lenC; k++){
    12. //计算三个数组当前元素的距离
    13. temp = GetDis(A[i], B[j]) + GetDis(A[i], C[k]) + GetDis(B[j], C[k]);
    14. //更新min
    15. if (temp < min) min = temp;
    16. }
    17. }
    18. }
    19. return min;
    20. }

    6.2.链表

    6.2.1.(2009)

    (1)前后指针 

    1. int GetK(LNode *L, int k){
    2. LNode *p = L, *q = L;
    3. for (int i = 1; i < k; i++) p = p->link; //p向前移动k - 1个结点
    4. while(p->link) { //p和q同步移动,p移动至链表最后一个结点时,q就是倒数第k个结点
    5. p = p->link;
    6. q = q->link;
    7. }
    8. return q->data; //返回q的data
    9. }

    (2)用数组保存每个结点的值

    1. int GetK(LNode *L, int k){
    2. int len = 0;
    3. LNode *p = L;
    4. while (p) { //遍历链表得到链表长度
    5. p = p->link;
    6. len++;
    7. }
    8. int *arr = (int*)malloc(sizeof(int) * len); //申请和链表长度相同的数组
    9. int i = 0;
    10. p = L;
    11. while (p) { //遍历链表,保存每个结点的值到数组中
    12. arr[i++] = p->data;
    13. p = p->link;
    14. }
    15. cout << arr[n - k]; //输出倒数第k个结点
    16. }

    (3)遍历数组两次,第一次得到数组的长度,第二次移动到倒数第k个

    1. int ans(LNode *L, int k){
    2. int len = 0;
    3. LNode *p = L;
    4. while (p) { //第一遍循环得到链表长度
    5. p = p->next;
    6. len++;
    7. }
    8. p = L; //p重新指向头结点
    9. count = n - k + 1;
    10. while (count > 0) p = p->next;
    11. cout << p->data;
    12. }

    6.2.2.(2012)

    (1)枚举

    1. LNode* ans(LNode *str1, LNode *str2){
    2. LNode *p = str1->next, *q = str2->next;
    3. while (p) {
    4. q = str2->next;
    5. while (q) {
    6. if (p == q) return p;
    7. q = q->next;
    8. }
    9. p = p->next;
    10. }
    11. }

    (2)用数组保存每个结点的地址

    1. void ans(LNode *str1, LNode *str2){
    2. LNode *p = str1->next, *q = str2->next; //pq分别指向str1和str2的头结点
    3. LNode A[maxn], B[maxn]; //申明两个分别足够容纳下str1和str2结点数组
    4. int lenA = 0, lenB = 0;
    5. while (p) {
    6. A[lenA++] = p;
    7. p = p->next;
    8. }
    9. while (q) {
    10. B[lenB++] = q;
    11. q = q->next;
    12. }
    13. int i;
    14. for (i = 1; i < min(lenA, lenB); i++) { //从后往前找
    15. if (A[lenA - i] != B[lenB - i]) { //第一个不相同的后缀结点
    16. cout << A[lenA - i + 1]; //返回该结点的上一个结点
    17. return;
    18. }
    19. }
    20. cout << A[lenA - i]; //短的链表的元素都是长链表的公共部分
    21. return;
    22. }

    (3)双指针:较长表的指针移动两表长度的差值,使得两表剩余长度一致,然后一一进行对比

    1. void ans(LNode *str1, LNode *str2){
    2. int len1 = len2 = 0;
    3. LNode *p = str1->next, *q = str2->next;
    4. while (p) { //分别遍历链表得到长度
    5. len1++;
    6. p = p->next;
    7. }
    8. while (q) {
    9. len2++;
    10. q = q->next;
    11. }
    12. if (len1 <= len2) { //移动较长表的指针,并将较短表指针指向其第一个结点
    13. q = str2;
    14. for (int i = 0; i < len2 - len1; i++) q = q->next;
    15. p = str1->next;
    16. }
    17. else {
    18. p = str1;
    19. for (int i = 0; i < len1 - len2; i++) p = p->next;
    20. q = str2->next;
    21. }
    22. int len = min(len1, len2); //len取len1和len2的较小值
    23. for (int i = 0; i < len; i++) { //输出第一个相同结点
    24. cout << p;
    25. return;
    26. }
    27. }

    6.2.3.(2015)

    (1)暴力解:每个结点都和剩余所有结点进行一次比较

    1. void ans (LNode *L) {
    2. LNode *p = L, *qpre = L, *q = L->link;
    3. while (p) {
    4. qpre = p; //重置q和qpre结点,q指针指向p的下一个结点,qpre指向q的上一个结点
    5. q = qpre->link;
    6. while (q) {
    7. if (abs(p->data) == abs(q->data)) { //q和p相等,则删除q结点
    8. qpre->link = q->link;
    9. free(q);
    10. q = qpre->link;
    11. }
    12. else { //q和p不相等,q和qpre指针后移
    13. q = q->link;
    14. qpre = qpre->link;
    15. }
    16. }
    17. p = p->link; //p指针后移
    18. }
    19. return;
    20. }

    (2) 数组保存出现过的元素

    1. void ans (LNode &L)
    2. LNode *p = L->link, *q = L; //p指向头结点,q指向p的上一个结点
    3. bool mark[n + 1] = { false };
    4. while (p) {
    5. if (mark[abs(p->data)] == false) { //该值第一次出现
    6. mark[abs(p->data)] = true; //mark中对应下标改为true
    7. p = p->link; //pq各自后移
    8. q = q->link;
    9. }
    10. else { //该值已经在之前的结点中出现过,将该结点删除
    11. q->link = p->link;
    12. free(p);
    13. p = q->link;
    14. }
    15. }
    16. }

    6.2.4.(2019) 

    (1) 暴力解

    1. void ans(node &L, int n){
    2. node *p = L->next, *q = L;
    3. int i;
    4. for (i = 0; i < n / 2; i++) { //p指向后半链的第一个结点
    5. p = p->next;
    6. q = q->next;
    7. }
    8. q->next = NULL; //将前后半链分开
    9. node *k = L->next, *kpre = L; //k指向第一个结点,kpre指向k的上一个结点
    10. while (k->next) {
    11. k = k->next;
    12. pre = pre->next;
    13. q = p;
    14. while (q->next) q = q->next; //q指向后半链的最后一个结点
    15. q->next = k; //将q插入到kpre后
    16. kpre->next = q;
    17. kpre = q;
    18. }
    19. }

    (2)逆置

    1. void ans(node &L) {
    2. node *p = L->next, *pre = L, *q = L->next; //q指向前半链第一个结点
    3. for (int i = 0; i < n / 2; i++) { //p指向后半链第一个结点,pre指向前半链最后一个结点
    4. p = p->next;
    5. pre = pre->next;
    6. }
    7. q = pre; //q指向前半链最后一个结点
    8. q->next = NULL; //前后半链分开
    9. while (p) { //采用头插法将后半链逆置
    10. pre = p;
    11. p = p->next;
    12. pre->next = q->next;
    13. q->next = pre;
    14. }
    15. p = q->next; //p指向后半链第一个结点(逆置后)
    16. q = L->next; //q指向第一个结点
    17. pre = L; //pre指向L
    18. L->next = NULL; //将L的next指针置空
    19. while (q) { //尾插法循环插入L
    20. node* temp = q; //选择前半链的第一个结点插入
    21. q = q->next;
    22. pre->next = temp;
    23. pre = pre->next;
    24. if (p) { //选择后半链的第一个结点插入
    25. temp = p;
    26. p = p->next;
    27. pre->next = temp;
    28. pre = pre->next;
    29. }
    30. }
    31. pre->next = NULL; //将pre的NEXT指针置空
    32. return;
    33. }

    6.3.树

    6.3.1.(2014)

    1. typedef struct BiTNode {
    2. struct BiTNode *lchild, *rchild;
    3. int weight;
    4. }BiTNode;
    5. int WPL = 0; //记录整棵树的WPL
    6. void PreOrder (BiTNode *T, int d) { //先序遍历
    7. if (T) { //当前结点非空
    8. if (!T->lchild && !T->child) { //叶子结点
    9. WPL = WPL + T->weight * d; //计算当前结点的WPL
    10. }
    11. PreOrder (T->lchild, d + 1); //进入其左子树
    12. PreOrder (T->rchild, d + 1); //进入其右子树
    13. }
    14. }
    15. void ans(BiTNode *T) {
    16. PreOrder (T, 0); //根节点层高为0
    17. }

    6.3.2.(2017)

    void ans (BiTNode *T) {
        

    6.4.图(2021)

  • 相关阅读:
    Linux pthread
    springboot2.7.x 集成log4j2配置写入日志到mysql自定义表格
    社交创新的先锋:探秘Facebook背后的故事与智慧
    [Linux] 数据链路层-----以太网帧协议、ARP协议
    读书笔记:python+vue实战派
    C语言典型例题31
    Docker Compose学习笔记
    C++ Tutorials: Boolean Operations
    2327. 知道秘密的人数;1722. 执行交换操作后的最小汉明距离;2537. 统计好子数组的数目
    C++——类型转换
  • 原文地址:https://blog.csdn.net/JiangNan_1002/article/details/125888205