牛客刷题记录

牛客刷题记录

目录

一、语法篇

1. 浮点型数字四舍五入

先判断浮点型数字的正负,正数则+0.5,负数则-0.5,之后再强制类型转换即可实现四舍五入。

2. 两数交换

值交换:t = a; a = b; b = temp;

3. 三数求最大

先设max = a, 将max与b比较,若max>b(即a>b),令max=max,否则max=b,再比较max与c即可。

max = (a > b ? a : b) > c ? (a > b ? a : b) : c;

maxNum = max(c, max(a, b));

4. C++浮点型输出

cout << fixed << setprecision(1) << a << endl;
// fixed指定以带小数点的形式表示浮点数;setprecision(1)表示小数点后面保留1位。    

5. switch语句

//输入月份,判断季节
#include <iostream>
using namespace std;

int main()
{
    int month;
    cin >> month;
    if(month>=1 && month <=12)
    {
        switch(month)
        {
            case 3:
            case 4:
            case 5:    //或者case 3...5:
                cout << "春季" << endl;
                break;
            case 6:
            case 7:
            case 8:    //或者case 6...8:
                cout << "夏季" << endl;
                break;
            case 9:
            case 10:
            case 11:    //或者case 9...11:
                cout << "秋季" << endl;
                break;
            default:
                cout << "冬季" << endl;
                break;
        }
    }
}

6. 水仙花数

三位数字其各位数字的立方之和等于该三位数字的数叫做水仙花数

//输出所有水仙花数
bool isNarcissus(int num)
{
    int temp = num;
    int a = num % 10; //个位
    num /= 10;
    int b = num % 10; //十位
    num /= 10;
    int c = num % 10; //百位
    if (temp == a * a * a + b * b * b + c * c * c) //判断个位、十位、百位数各自三次方后相加是否等于该数
        return true;
    else
        return false;
}

7. C++幂运算

C语言中,可以用pow(x,y)来求x的y次幂,注意需要#include <math.h>

在C++11中,同样可以用pow()来求幂,只是注意是#include <cmath>

8. 判断一个数是否是质数

法一:如果一个数除了被1和它本身整除,不能被其它任何数整除,则这个数是质数。所以只需要验证输入的数字n能否被2到n-1之间的任何数整除。

bool is_Prime(int a)
{
  for(int i = 2; i <= a-1 ; i++)
  {
     if(a % i == 0)
         return false;
  }
  return true;
}

这样需要遍历1到n,时间复杂度是O(n)

法二:如果n能被一个大于√n的数整除,我们不妨设这个大于√n的数为t,则n也一定能被n/t整除,而n/t是小于√n的,所以只需验证输入的数字n能否被2到√n之间的任何数整除。

//判断是否是质数
bool isPrime(int x)
{
   for ( int i = 2 ; i*i <=x ; i++)
   {
     //如果一个数能被2到根号x之间的任意一个数整除,则不是质数
     if ( x % i == 0 )
         return false ;
   }
   return true ;
}

这样只需要遍历到√n,因此时间复杂度是O(√n)

9. 输出数组的最大元素和最小元素

//输入一个数组,输出这个数组的最大和最小元素
#include <iostream>
using namespace std;

int main() 
{
    int arr[6] = { 0 };
    int len = sizeof(arr) / sizeof(int);    //C++内置数组,维护其长度
    
    for (int i = 0; i < len; i++)
        cin >> arr[i];
    
    int max = arr[0];
    int min = arr[0];
    for(int i = 0; i < len ; i++)
    {
        max = arr[i] > max ? arr[i] : max;
        min = arr[i] > min ? min : arr[i];
    }
  cout << min << " " << max << endl;
    
  return 0;
}

10. 数组反转

数组反转之后就是首到尾,尾到首,因此可以使用双指针分别指向数组首尾1,然后交换双指针指向的元素,之后指针再不断向中间靠近。

#include <iostream>
using namespace std;

int main() 
{
    int arr[6] = { 0 };
    int len = sizeof(arr) / sizeof(int);

    for (int i = 0; i < len; i++) 
        cin >> arr[i];
    
    cout << "[";
    for (int i = 0; i < len; i++) 
    {
        if (i == len - 1) 
        {
            cout << arr[i] << "]" << endl;
            break;
        }
        cout << arr[i] << ", ";
    }
    
    //双指针法
    int left = 0, right = len - 1;  //双指针,分别指向首尾
    while(left < right)
    {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = arr[left];  //交换双指针指向的元素
        // swap(arr[left], arr[right]);
        left++;
        right--;  //指针不断向中间靠近
    }

    cout << "[";
    for (int i = 0; i < len; i++) 
    {
        if (i == len - 1) {
            cout << arr[i] << "]" << endl;
            break;
        }
        cout << arr[i] << ", ";
    }
    return 0;
}

11. 数组排序

(1)冒泡排序

冒泡排序算法步骤:

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

3.针对所有的元素重复以上的步骤,除了最后一个。

4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

void Bubble_Sort(int arr[], int len) {
    int i, j;
    for (i = 0; i < len - 1; i++)  //外循环控制排序的趟数,共需要len-1趟
    {
        for (j = 0; j < len - 1 - i; j++)  //内循环不断进行比较,每一趟之后最后一个元素总是最大的,因此最后一个不需要比较
        {
            if (arr[j] > arr[j + 1])
                swap(arr[j], arr[j + 1]);
        }      
    }          
}

冒泡排序的算法时间复杂度为O(n^2)

(2)选择排序

选择排序算法步骤:

1.首先在未排序序列中找到最小元素,存放到排序序列的起始位置。

2.再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。

3.重复第2步,直到所有元素均排序完毕。

void Select_Sort(int arr[], int len) 
{
    for (int i = 0; i < len; i++) 
    {
        int min = i;
        for (int j = i + 1; j < len; j++)
        {
            if (arr[j] < arr[min])
                min = j;
        }           
        swap(arr[i], arr[min]);
    }
}

选择排序的算法复杂度为O(n^2)

(3)插入排序

插入排序算法步骤:

1.从第一个元素开始,该元素可以认为已被排序;

2.取出下一个元素,在已经排序的元素序列中从后向前扫描;

3.如果该元素(已排序)大于新元素,将该元素移到下一个位置;

4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

5.将新元素插入到该位置后,重复2~5。

void insert_sort(int arr[],int len)
{
    for(int i = 1; i < len; i++)
    {
        int key = arr[i];  //key就是取出的要插入元素
        int j = i-1;  // 0~j都是已经排好的序列
        while((j >= 0) && (key < arr[j]))
        {
            arr[j+1] = arr[j];  //把要插入位置后面的元素都后移一个位置
            j--;
        }
        arr[j+1] = key;  //将要插入的元素插入应该插入的位置
    }
}

插入排序的算法的时间复杂度为O(n^2)

12. 动态数组

(1)动态一维数组

//创建一个长度为n的动态一维数组
#include <iostream>
using namespace std;

int main() 
{
    int n;
    cin >> n;
    
    int* a = new int[n];
    for(int i = 0; i < n; i++)
    {
        a[i] = n + i;
        cout << a[i] <<" ";
    }
    
    return 0;
}

(2)动态二维数组

//创建一个n*n的动态二维数组
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin >> n;
    
    int** a = new int* [n];  //创建一个双指针
    for(int i = 0; i < n; i ++)
    {
        a[i] = new int [n];  //创建二维数组
    }
    
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            a[i][j] = i + j;
            cout << a[i][j] << " ";
        }
    cout << endl;
    }
    
    return 0;
}

13.数组元素处理

将数组中的0元素放入数组末尾并且其它元素原先顺序不变

(1) 双指针法 时间复杂度为O(n)

//双指针将数组中的0元素放入数组末尾并且其它元素原先顺序不变
int main()
{
    int arr[6] = {0};
    int len = sizeof(arr) / sizeof(int);
    
    for(int i = 0; i < len; i++)
    {
        cin >> arr[i];
    }
    
    int id = 0;  //指针id永远指向第一个0的位置
    for(int i = 0; i < len; i++)  //指针i则遍历数组
    {
        if(arr[i] != 0)  // 若指针i指向的不为0,则将id的和i的指向数据交换
        {
            swap(arr[id], arr[i]);
            id++;  //交换之后指针id再自增,此时指针id仍然指向第一个0
        }
    }

    for(int i = 0; i < len; i++)
    {
        if(i == len - 1)
            cout << arr[i] << endl;
        else
            cout << arr[i] << " ";
    }
    
    return 0;
}

(2) 冒泡法 时间复杂度为O(n^2)

类似冒泡排序,数组从左到右开始遍历,遇到第一个0元素时,则将数组自该位置到数组末尾的所有元素向前移动一个位置,下一个循环时同样重复上述步骤除了末尾的0;

int main()
{
    int arr[6] = {0};
    int len = sizeof(arr) / sizeof(int);
    
    for(int i = 0; i < len; i++)
    {
        cin >> arr[i];
    }

    int k = n - 1;
    for(int i = 0; i < len; i++)
    {
        if(p[i] == 0)
        {
            for(int j = i; j < k; j++)
            {
                swap(arr[j], arr[j + 1]);
            }
            arr[k] = 0;
            k--;
        }
    }
    
    for(int i = 0; i < len; i++)
    {
        if(i == len - 1)
            cout << arr[i] << endl;
        else
            cout << arr[i] << " ";
    }
    return 0;
}

14. 字符串比较

//字符串比较函数,src < dst返回-1,src > dst返回1,src = dst则返回0.
int mystrcmp(const char* src, const char* dst) 
{
    const char* p = src;
    const char* q = dst;
    while(*p != '\0' && *q != '\0')
    {
        if(*p > *q)
          return 1;
        else if(*p < *q)
          return -1;
        else
        {
            p++;
            q++;
        }
    }
    //两字符串前缀都相同
    if(*p == '\0' && *q == '\0')  //字符串长度相同才相等
        return 0;
    else if(*p == '\0')  //若p比q短,则p<q
        return -1;
    else if(*q == '\0')  //若p比q长,则p>q
        return 1;
    
    return 0;
}

15. 字符串子串查找

//在一个字符串中查找一个子串出现的次数
int Substr_Count(char* str, char* substr)
{
    int len1 = strlen(str);
    int len2 = strlen(substr);
    int count = 0;
    
    //双指针
    for(int i = 0; i < len1; i++)  //i遍历str
    {
        bool flag = true;  //表示是否是子串
        for(int j = 0; j < len2; j++)  // j遍历substr
        {
            if(str[i + j] != substr[j])  //同时对比str子串与substr上的字符,只有全部相等才是子串,若有不同的字符,则不是子串
                flag = false;
        }
        
        if(flag == true)
            count ++;
    }
 
    return count;
}

16. 字符判断函数

字符判断函数 功能 备注
isalnum() 判断字符是否为字母和数字字符 包括0-9、A-Z、a-z
isalpha() 判断字符是否为字母字符 包括A-Z、a-z
islower() 判断字符是否为小写字母字符 包括a-z
issupper() 判断字符是否为大写字母字符 包括A-Z
isdigit() 判断字符是否为数字字符 包括0-9
isspace() 判断字符是否为空白字符 包括空格、换页、换行、回车、制表符
isblank() 判断字符是否为空格字符 包括空格、水平制表符
ispunct() 判断字符是否为标点字符 包括!"#$%&’()*+,-./:;<=>?@[]^_`{

上面这些函数如果是相应字符则返回非零值,否则返回0。

字符转换函数 说明
tolower() 将给定字符转换为小写
tosupper() 将给定字符转换为大写

这两个字符转换函数如果转换成功则返回一个可被隐式转换为char类型的int值()(大写字母6590,小写97122)

17. 字符串去重

利用set容器关键字不可重复这一特性,去除字符串中的重复字符:

char str[100] = { 0 };
cin.getline(str, sizeof(str));

set<char> iset;
for(int i = 0; str[i] !='\0'; i++)
{
    iset.insert(str[i]);
}

set<char>::iterator it;
for(it = iset.begin(); it != iset.end(); it++)
    cout << *it;

18. 统计字符串中的各字母出现个数

利用map容器的特性可以统计字符串中各字母出现个数:

char str[100] = { 0 };
cin.getline(str, sizeof(str));

map<char, int> char_count;
for(int i = 0; str[i] != '\0' ; i++)
{
    if(isalpha(str[i]))
        char_count[str[i]]++;
}

map<char, int>::iterator it;
for(it = char_count.begin(); it != char_count.end(); it++)
    cout << it->first << ":" << it->second << endl;

二、入门题

1. HJ.5进制转换

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

int main() 
{
    string s;
    while(cin >> s)
    {
        s = s.substr(2);  //除去字符串前两位无效的0x
        int res = 0;  //记录结果
        int bits = 0;  //记录位数
        for(int i = s.length() - 1; i >= 0; i--)  //逆向遍历字符串
        {
            if(s[i] >= '0' && s[i] <= '9')
            {
                res += (s[i] - '0') * pow(16,bits);
                bits++;
            }
            else if(s[i] >= 'A' && s[i] <='F')
            {
                res += (s[i] - 'A' + 10) * pow(16, bits);
                bits++;
            }
        }
        cout << res << endl;
    }  
}

2. NC.61两数之和

class Solution
{
public:
    /*
     * @param numbers int整型vector
     * @param target int整型
     * @return int整型vector
     */
    vector<int> twoSum(vector<int> &numbers, int target)
    {
        vector<int> result;
        unordered_map<int, int> hash;
        for (int i = 0; i < numbers.size(); ++i)
        {
            int temp = target - numbers[i];
            // target与当前元素的差值,寻找该差值是否在哈希表中出现过
            if (hash.find(temp) == hash.end())
            {
                // 若未出现,将当前元素及其下标存入map
                hash[numbers[i]] = i;
            }
            else
            {
                // 若已出现,返回该差值的下标+1以及当前元素的下标+1
                result.push_back(hash[temp] + 1);
                result.push_back(i + 1);
                break; // 找到一对就停止
            }
        }
        return result;
    }
};

3. HJ3.明明的随机数

#include <iostream>
#include <set>
using namespace std;

int main() {
    int n;
    while( cin >> n)
    {      
        set<int> iset;
        for(int i = 0; i < n; i++)
        {
            int a;
            cin >> a;
            iset.insert(a);
        }
        for(auto &s: iset)
            cout << s << endl;
    }
}

4. HJ10.字符串统计

#include <iostream>
#include <set>
using namespace std;

int main()
{
    string s;
    while (cin >> s)
    {
        set<char> m;
        for (int i = 0; i < s.length(); i++)
        {
            m.insert(s[i]);
        }
        cout << m.size() << endl;
    }
}

5. NC.68 跳台阶

/* NC.68 跳台阶 */
// 法一、递归法
class Solution
{
public:
    int jumpFloor(int number)
    {
        if (number == 1)
        {
            return 1;
        }
        else if (number == 2)
        {
            return 2;
        }
        else
        {
            return jumpFloor(number - 1) + jumpFloor(number - 2);
        }
    }
};

// 法二、动态规划
class Solution
{
public:
    int jumpFloor(int number)
    {
        dp[number + 1];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i < number + 1; i++)
            dp[i] = dp[i - 1] + dp[i - 2];
        
        return dp[number];   
    }
};

三、字符串操作

1. HJ17.坐标移动

#include<iostream>
#include<vector>
#include<string>
using namespace std;

//坐标移动的函数接口
int Coordinate_Movement(string str)
{
    int x = 0, y = 0; //初始化横纵坐标
    int len = str.size(); //获取字符串的总长度
    vector <string> vec; //用一个向量来存储分割后的多个子字符串
    //将字符串 str 按 ';' 分割成多个子字符串 substr,然后依次写入向量 vec 中
    int sublen = 0; //记录每个子字符串的长度
    for(int i = 0; i < len; i++)
    {
        if(str[i] != ';')
        {
            sublen++;
            continue;
        }
        vec.push_back(str.substr(i - sublen, sublen));
        sublen = 0;
    }
    //坐标移动,方向(左右上下) + 大小(-99~99)
    for(int i = 0; i < vec.size(); i++)
    {
        //确定坐标移动的大小,[-99,99]
        int num = 0; //(横或纵)坐标移动的大小
        //若字符串为三位有效位,则第二和第三位是坐标移动的大小
        if((vec[i].size() == 3) && (vec[i][1] >='0') && (vec[i][1] <= '9') && (vec[i][2] >='0') && (vec[i][2] <= '9'))
        {
            num = (vec[i][1] - '0') * 10 + (vec[i][2] - '0');
        }
        //若字符串为两位有效位,则第二位是坐标移动的大小
        if((vec[i].size() == 2) && (vec[i][1] <= '9') && (vec[i][1] >= '0'))
        {
            num = (vec[i][1] - '0');
        }
        //若字符串为一位有效位,则无坐标移动
        if(vec[i].size() == 1)
        {
            num = 0;
        }
        //确定坐标移动的方向,左右上下
        switch(vec[i][0])
        {
            case 'A': x -= num;
                break;
            case 'D': x += num;
                break;
            case 'S': y -= num;
                break;
            case 'W': y += num;
                break;
            default:
                break;
        }
    }
    cout << x << "," << y << endl;
    return 0;
}
//主函数
int main()
{
    string str;
    while (cin >> str)
    {
        Coordinate_Movement(str);
    }
    return 0;
}

2. HJ20.密码验证程序

#include<iostream>
#include<string>
using namespace std;

int main() {
    string s;
    while (cin >> s) {
        if (s.length() <= 8) { //长度不超过8不可行
            cout << "NG" << endl;
            continue;
        }
        int flag[4] = {0};
        for (int i  = 0; i < s.length(); i++) 
        {
            if (s[i] >= 'A' && s[i] <= 'Z') //大写字母
                flag[0] = 1;
            else if (s[i] >= 'a' && s[i] <= 'z') //小写字母
                flag[1] = 1;
            else if (s[i] >= '0' && s[i] <= '9') //数字
                flag[2] = 1;
            else  //其他符号
                flag[3] = 1;
        }
        if (flag[0] + flag[1] + flag[2] + flag[3] < 3) //符号少于三种
        { 
            cout << "NG" << endl;
            continue;
        }
        bool repeated = false; //记录重复子串
        for (int i = 0; i <= s.length() - 6; i++) //遍历检查是否有长度为3的相同的字串
            for (int j = i + 3; j < s.length(); j++)
                if (s.substr(i, 3) == s.substr(j, 3)) {
                    repeated = true;
                    break;
                }
        if (repeated) //有重复
            cout << "NG" << endl;
        else
            cout << "OK" << endl;
    }
    return 0;
}

3. HJ23.删除字符串中出现次数最少的字符

/* HJ.23 删除字符串中出现次数最少的字符*/
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    while (cin >> s)
    {
        int a[26] = {0}; // 存储每个小写字母出现的次数
        int min = 20;    // 表示字符串中字符最少出现的次数
        for (int i = 0; i < s.length(); i++)
        {
            a[s[i] - 'a']++; // 遍历字符串,把字符串中每个字母的出现次数写入数组中
        }

        for (int i = 0; i < 26; i++)
        {
            if (a[i] != 0 && a[i] < min)
                min = a[i]; // 寻找字符串中字母最少出现的次数
        }

        for (int i = 0; i < s.length(); i++)
        {
            if (a[s[i] - 'a'] != min) // 查询数组中该字母出现次数是否为最少,若不是则打印该字母
                cout << s[i];
        }
        cout << endl;
    }
}

4. HJ33.整数和IP地址间的转换

#include <iostream>
#include <string>
using namespace std;

int main()
{
    long long int a, b, c, d;  //注意是long long,否则int进行位移会超范围
    long long int num;
    while(scanf("%lld.%lld.%lld.%lld", &a, &b, &c, &d) !=EOF)  //格式化输入
    {
        cin >> num;
        cout << (a << 24) + (b << 16) + (c << 8) + d << endl;  //将每个数字左移相应位数再组装起来即为IP地址对应的整数

        a = num >> 24;  
        num = num - (a << 24);
        b = num >> 16;
        num = num - (b << 16);
        c = num >> 8;
        d = num - (c << 8);

        cout << a << "." << b << "." << c << "." << d << endl;
    }
}

5. HJ101.输入整型数组和排序标识,对其元素进行升序或者降序排序

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() 
{
    int n;
    cin >> n;
    vector<int> v;
    for(int i = 0; i < n; i++)
    {
        int temp;
        cin >> temp;
        v.push_back(temp);
    }
    int order;
    cin >> order;

    if(order == 0)
        sort(v.begin(), v.end());
    else if(order == 1)
        sort(v.begin(), v.end(), greater<int>());
    
    for(auto &x: v)
        cout << x <<" ";           
}

6. leetcode 1839 最长美丽子字符串

//用滑动窗口解决:
class Solution
{
public:
    int longestBeautifulSubstring(string word)
    {
        vector<char> window;  //滑动窗口
        unordered_set<char> cnt;  //存储窗口内的字符种类

        int res = 0;
        int left = 0, right = 0;
        while(right < word.length())  //滑动窗口右扩
        {
            if(window.empty() || word[right] >= window.back())  //取字符串的下一个字符,如果大于等于滑动窗口的最后一个字符的话(即字典序)则加入窗口中,并且进行字符种类统计
            {
                window.push_back(word[right]);
                cnt.insert(word[right]);

                if(cnt.size() == 5)
                    res = max(res, (int)window.size());
            }
            else  //窗口左侧收缩
            {
                window.clear();
                cnt.clear();
                left = right;
                window.push_back(word[left]);  //注意要把left的字符加入window
                cnt.insert(word[left]);
            }
            right++;
        }
        return res;
    }
};

滑动窗口类题型的解题思想

(1)窗口由两个指针构成,一个左指针left,一个右指针right,然后[left,right]表示的索引范围就是一个窗口;

(2)右指针right的功能是用来扩展窗口:当窗口内的条件没有达到题目要求时,需要不断移动右指针right直到窗口内的条件第一次满足题目要求为止;

(3)左指针left的功能是用来缩小窗口的:当窗口内的条件已满足题目条件或多于题目条件时(窗口溢出),我们缩小窗口,也就是左指针left需要右移直到窗口条件不满足为止。这时,我们需要记录当前窗口的大小,并更新目前为止满足条件的最小窗口记录。之后,再次扩展右指针right,使得窗口满足题目的条件。

滑动窗口类的题目一般是在数组、链表、字符串等线性结构上进行操作,常用来找最长的子字符串、最短的子字符串等。

其他例题:

leetcode.3 无重复最长子串

题目:输入一个字符串s,求字符串s的最长无重复字符的子串

题解:

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        set<char> window;  //窗口
        int left = 0, right = 0;
        int res = 0;
        
        while(right < s.length())
        {
            if(window.find(s[right]) == window.end())  //尝试取下一个字符,如果与窗口中的元素不重复,则加入窗口中,窗口右扩
            {
                window.insert(s[right]);
                right++;
                res = max(res, (int)window.size());
            }
            else
            {
                window.erase(s[left]);  //若字符重复,则删除窗口中原来的重复字符,窗口收缩
                left++;
            }
        }
        return res;
    }
};

leetcode.239 滑动窗口最大值

题目:输入一个数组nums和滑动窗口大小k,求滑动窗口每次在数组中滑动的过程中,滑动窗口中的最大元素

题解:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) 
    {
        vector<int> result;  //存储每个窗口的最大元素
        int left = 0, right = 0;

        while(right < nums.size())
        {
            if(right - left + 1 == k)  //若窗口长度达到k
            {
                auto it = max_element(nums.begin() + left, nums.begin() + right + 1);  //寻找窗口内的最大元素,并插入result动态数组中
                result.push_back(*it);
                left++;  //窗口收缩
            }
            right++;  //窗口右扩
        }
        return result;
    }
};

leetcode.424 替换后的最长重复字符子串

题目:输入一个字符串s和次数k,字符串s只由大写字母组成,最多k次可以将任意字符替换为另一个字符,求替换后的最长的全是重复字符的子串

题解:

/*leetcode.424 替换后的最长重复字符*/
class Solution
{
public:
    int characterReplacement(string s, int k)
    {
        int count[26] = {0}; // 记录窗口中每个字符出现的个数
        int left = 0, right = 0;
        int res = 0, maxCount = 0;
        while (right < s.size())
        {
            count[s[right] - 'A']++; 
            maxCount = max(maxCount, count[s[right] - 'A']);  // 维护窗口中出现最多的字符的个数
            while (right - left + 1 > maxCount + k)  //如果窗口的大小减去最多字符的个数后,仍然大于k,即没有足够的替换次数来把窗口全部填为重复字符
            {
                count[s[left] - 'A']--;  //窗口收缩
                left++;
            }
            res = max(res, right - left + 1);
            right++;
        }
        return res;
    }
};

7. NC.149 KMP算法

暴力算法的思想就是,若将主串从开始处与子串逐位比较,若主串与子串在某一位置不匹配,则主串回退到第二个字符,而子串则回退到子串的第一个字符,往复循环,直至子串与主串匹配或主串到最后一个匹配位置。暴力算法的时间复杂度为O(mn)。

int BruteForce(string s, string p)
{
    int i = 0, j = 0;
    while(i < s.length() && j <p.length())
    {
        if(s[i] == p[j])  //子串字符与主串匹配,则主串和子串都指针都后移一个位置
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1;  //若主串字符与子串不匹配,则主串指针回到第二个字符的位置,子串回退到第一个字符的位置
            j = 0;
        }
    }

    if(j == p.length())  //若子串成功遍历了一次,则主串成功匹配了一次子串,返回该子串第一次出现的位置
        return i - j;
    return -1;  //匹配失败,返回-1
}

KMP算法是一种字符串匹配算法,可以在 O(n+m) 的时间复杂度内实现两个字符串的匹配。KMP算法的关键是利用匹配后失败的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现上表现为维护一个next数组,该数组存储每次匹配失败后,子串应该回退的位置。

next数组:next数组存储当主串与子串匹配失败时,子串应该回退到的位置。当匹配失败时,j要移动的下一个位置为k(即next[j]= k),此时,对应模式串(子串)P来说,此时模式串的前k个字符与后k个字符相同,即P[0~k] == P[j-k ~ j-1];当P[k] == p[j]时,有P[0 ~ k-1] + P[k] == p[j-k ~ j-1] + P[j],即:P[0 ~ k] == P[j-k ~ j],因此可得next[j + 1] = k + 1 = next[j] + 1当P[k] != P[j]时,此时只能在前k个字符串中找最长前缀字符串了,即k = next[k]

void getNext(string p,int next[])
{
    int len = (int)p.size();
    next[0] = -1;  //规定next[0] = -1
    int j = 0;
    int k = -1;
    
    while(j < len - 1)
    {
        if(k == -1 || p[j] == p[k])
        {
            ++j;
            ++k;
            next[j] = k;  // next[1] = 0;
        }
        else
        {
            k = next[k];
        }
    }
}

int kmp(string s, string p)
{
    int i = 0;
    int j = 0;
    int ans = -1;  //结果,返回主串匹配到的第一个子串的位置
    // int count  = 0;  统计子串在主串中出现的次数
    
    int pLen = (int)p.size();
    int next[pLen] = {0};
    getNext(p,next);

    while(i < s.length())
    {
        if(j == -1 || s[i] == p[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j = next[j];
        }

        if(j == pLen)
        {
            ans = i - j;  
            break;  //第一次匹配成功就退出循环,返回第一个匹配的子串的位置
        }
        
        /*
        if(j == pLen)
        {
            count++;
            j = next[j];  //返回子串在主串中出现的次数
        }
        */
    }
    return ans;
}

8. NC.100 把字符串转换成整数(atoi)

题解:

int StrToInt(string s)
{
    int len = s.length();
    long long int res = 0;
    int index = 0;
    while (index < s.length())
    {
        if (s[index] == ' ') // 跳过空格
            index++;
        else
            break;
    }
    if (index == len) // 字符串全是空格
        return 0;

    int isPositive = 1; // 是否为正数,默认是
    if (s[index] == '+')
        index++;
    else if (s[index] == '-')
    {
        isPositive = -1; // 负数
        index++;
    }
    if (index == len) // 字符串只有空格和+或-符号
        return 0;

    while (index < len)
    {
        if (s[index] >= '0' && s[index] <= '9')
        {
            res = res * 10 + isPositive * (s[index] - '0');
            if (res > INT_MAX)  //越界检查
                res = INT_MAX;
            else if (res < INT_MIN)
                res = INT_MIN;

            index++;
        }
        else
            break;
    }
    return res;
}

四、数组

1. HJ70. 矩阵乘法计算量估计

#include <iostream>
#include <string>
#include <stack>
using namespace std;

int main()
{
    int n;
    int a[26][2];
    string s;
    while(cin >> n)
    {
        for(int i = 0; i < n; i++)
            cin >> a[i][0] >> a[i][1];
        cin >> s;
        
        int len = s.length();
        stack<pair<int, int>> stk;  //栈,pair分别存储矩阵的行数和列数
        int res = 0;
        for(int i = 0; i < len; i++)
        {
            if(isupper(s[i]))  //如果是大写字母,则入栈
            {
                pair<int, int> temp = {a[s[i] - 'A'][0], a[s[i] - 'A'][1]};
                stk.push(temp);
            }
            else if(s[i] == ')')  //遇到右括号,则出栈两个
            {
                auto x = stk.top();
                stk.pop();
                auto y = stk.top();
                stk.pop();

                res += y.first * y.second * x.second;  //x先出栈,y后出栈,因此是矩阵相乘顺序是yx
                stk.push({y.first, x.second});  //相乘之后的新矩阵入栈
            }
        }
        cout << res << endl;
    }
}

五、查找排序

1. HJ.8 合并表记录

#include <iostream>
#include <map>
using namespace std;

int main()
{
    int n;
    map<int, int> m;
    while(cin >> n)
    {
        int index, value;
        for(int i = 0; i < n; i++)
        {
            cin >> index >> value;
            m[index] += value;
        }

        for(auto it = m.begin(); it != m.end(); ++it)
        {
            cout << it->first << " " << it->second << endl;
        }
    }

}

2. HJ.14 字符串排序

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    int n;
    multiset<string> mset;  //字符串可能重复,因此使用multiset
    string s;
    while(cin >> n)
    {
        for(int i = 0; i < n; i++)
        {
            cin >> s;
            mset.insert(s);
        }

        for(auto &w: mset)
            cout << w << endl;
    }
}

3. HJ.27 查找兄弟单词

bool isBrothers(string s1, string s2)
{
    if(s1.length() != s2.length())
        return false;
    else
    {
        if(s1 == s2)
            return false;  //两个字符串相等,不是兄弟单词
        sort(s1.begin(), s1.end());
        sort(s2.begin(), s2.end());  

        if(s1 == s2)
            return true;  //若两个字符串原本不相同,但都经过排序之后相同了,则两者互为兄弟单词
    }
    return false;
}

int main()
{
    int n;
    while(cin >> n)
    {
        vector<string> vec;  //存储字典单词
        string s;  
        for(int i = 0; i < n; i++)
        {
            cin >> s;
            vec.push_back(s);
        }

        string x;
        cin >> x;
        int k;
        cin >> k;

        multiset<string> mset;
        for(int i = 0; i < vec.size(); i++)
        {
            if(isBrothers(vec[i], x))
            {
                mset.insert(vec[i]);  //将x的兄弟单词都插入set中,自动字典升序
            }
        }
        cout << mset.size() << endl;

        if(mset.size() < k)  //兄弟单词总数若小于k即没有字典序的第k个兄弟单词
            return 0;
        else
        {
            auto it = mset.begin();
            advance(it, k - 1); 
            cout << *(it) << endl;
        };  

    }
}

4. NC37. 合并区间

struct Interval  //区间
{
    int start;
    int end;
    Interval() : start(0), end(0) {}
    Interval(int s, int e) : start(s), end(e) {}
};

class Solution
{
public:
    static bool Interval_Sort(Interval &a, Interval &b)  //排序谓词,按区间的start升序排序,注意是static否则不能在sort()里调用
    {
        return a.start < b.start;
    }

    vector<Interval> merge(vector<Interval> &intervals)
    {
        vector<Interval> res; // 合并后的区间
        if (intervals.size() == 0)  //若为空区间,则返回空的向量
            return res;
        sort(intervals.begin(), intervals.end(), Interval_Sort);
        res.push_back(intervals[0]);  //先将排序后的第一个区间插入res中

        for (int i = 1; i < intervals.size(); i++)  //遍历所有区间
        {
            if (intervals[i].start <= res.back().end)    //能合并的一定是取出的新区间的start小于等于res的最后一个区间的end
            {
                res.back().end = max(intervals[i].end, res.back().end);  // 注意这种[1, 4] [2, 3] -> [1, 4]情况
            }
            else
            {
                res.push_back(intervals[i]);  //否则不能合并,直接插入res中
            }
        }
        return res;
    }
};

5. HJ68. 成绩排序

struct stu
{
    string name;
    int score;
    int pos; // 存储输入的序号
};

bool cmp_0(stu &a, stu &b) // 成绩从高到低排序
{
    if (a.score != b.score)
    {
        return a.score > b.score;
    }
    else
    {
        return a.pos < b.pos; // 若成绩相同,则按原来的输入顺序排序
    }
}

bool cmp_1(stu &a, stu &b) // 成绩从低到高排序
{
    if (a.score != b.score)
    {
        return a.score < b.score;
    }
    else
    {
        return a.pos < b.pos;
    }
}
int main()
{
    int n, flag;
    cin >> n >> flag;
    vector<stu> vec;
    for (int i = 0; i < n; i++)
    {
        stu a;
        cin >> a.name >> a.score;
        a.pos = i;
        vec.push_back(a);
    }

    if (flag == 0)
    { // flag == 0 从高到低排序
        sort(vec.begin(), vec.end(), cmp_0);
    }
    else if (flag == 1) // flag == 1 从低到高排序
    {
        sort(vec.begin(), vec.end(), cmp_1);
    }

    for (auto &x : vec)
    {
        cout << x.name << " " << x.score << endl;
    }
    return 0;
}

6. HJ.65 查找两个字符串a,b中的最长公共子串

#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string a, b;
    string res;  //c保存最长的公共子串
    string temp;
    while( cin >> a >> b)
    {
        int len1 = a.length(), len2 = b.length();
        /*
        直接交换a和b,保证字符串a是较小的字符串,使代码更加简洁
        if(len1 < len2)
        {
            swap(a, b);
        }
        */
        if(len1 > len2)  //a串更长,穷举b串的子串
        {
            for(int i = 0; i < len2; i++)  
            {
                for(int j = i; j < len2; j++)
                {
                    temp = b.substr(i, j - i + 1);  //穷举b串的子串
                    if(a.find(temp) != a.npos)  //在a中查找每个b的子串,若找得到并且其长度还大于目前保存的最长子串,则更新最长子串
                    {
                        if(temp.length() > res.length())
                            res = temp;
                    }
                }
            }
        }
        else  //b串更长,穷举a串的子串
        {
            for(int i = 0; i < len1; i++)  
            {
                for(int j = i; j < len1; j++)
                {
                    temp = a.substr(i, j - i + 1);  //穷举a串的子串
                    if(b.find(temp) != b.npos)  //在b中查找每个a的子串,若找得到并且其长度还大于目前保存的最长子串,则更新最长子串
                    {
                        if(temp.length() > res.length())
                            res = temp;
                    }
                }
            }
        
        }
        cout << res << endl;
    }
}

7. JZ4. 二维数组的查找

//暴力法,时间复杂度为O(nm),n和m分别为矩阵的行数和列数
    bool Find(int target, vector<vector<int> > array) 
    {
        for(int i = 0; i < array.size(); i++)
        {
            for(int j = 0; j < array[i].size(); j++)
            {
                if(array[i][j] == target)
                    return true;
            }
        }
        return false;
    }

//二分查找,时间复杂度为O(nlog(m))
    bool binary_search(int target, vector<int>& arr)
    {
        int left = 0, right = arr.size() - 1;
        while(left <= right)
        {
            int mid = (left + right) / 2;
            if(arr[mid] > target)  //target在数组的左半区
                right = mid - 1;  
            else if(arr[mid] < target)  // target在数组的右半区
                left = mid + 1;
            else
                return true;  // target == arr[mid]
        }
        return false;
    }

    bool Find1(int target, vector<vector<int> > array)
    {
        for(auto i: array)  // c++11语法,逐行遍历  for(auto j: i)在行i中逐个遍历
        {
            if(binary_search(target, i))
                return true;
        }
    }

六、栈

1. NC.52 有效括号序列

bool isValid(string s) 
{
        stack<char> stk;
        for(int i = 0; i < s.length(); i++)
        {
            switch(s[i])
            {
                case('('):
                case('['):
                case('{'):
                    stk.push(s[i]);  //遇到左括号就入栈
                    break;
                case(')'):
                    if(stk.empty() || stk.top() != '(')  //遇到右括号时,若栈是空的或者栈顶的括号不匹配,则括号序列非法
                        return false;
                    stk.pop();  // 遇到右括号就出栈
                    break;
                case(']'):
                    if(stk.empty() || stk.top() != '[')
                        return false;
                    stk.pop();   
                    break;
                case('}'):
                    if(stk.empty() || stk.top() != '{')
                        return false;
                    stk.pop();   
                    break;
                default:
                    break;             
            }
        }
        return stk.empty() ? true : false;  //最后若栈为空,则括号序列合法     
}

2. Leetcode 1614 括号的最大嵌套深度

class Solution {
public:
    int maxDepth(string s) 
    {
        int depth = 0;
        stack<char> stk;

        for(int i = 0; i < s.length(); i++)
        {
            if(s[i] == '(')
            {
                stk.push(s[i]);
                depth = max(depth, (int)stk.size());
            }
            else if(s[i] == ')')
            {
                stk.pop();
            }   
        }
        return depth;
    }
};

3. NC.175 合法的括号字符串

bool isValidString(string s)
{
    int len = s.length();
    if (len == 0)
        return true;

    stack<char> stk1, stk2; // 分别存储左括号和星号
    for (int i = 0; i < s.length(); i++)
    {
        if (s[i] == '(')
        {
            stk1.push(i); // 遍历字符串,遇到左括号则将其下标压入栈中
        }
        else if (s[i] == '*')
        {
            stk2.push(i); // 遍历字符串,同理遇到星号也将其下标压入栈中
        }
        else if (s[i] == ')')
        {
            if (stk1.empty() && stk2.empty()) // 遇到右括号,若两个栈都为空,则无法匹配右括号,字符串必然非法
                return false;
            else if (!stk1.empty()) // 若左括号栈不空,则优先左括号栈弹出一个
            {
                stk1.pop();
            }
            else if (stk1.empty() && !stk1.empty()) // 若左括号栈为空,则星号栈弹出一个
            {
                stk2.pop();
            }
        }
    }
    while (!stk1.empty()) // 若左括号栈非空且左括号栈的栈顶元素小于星号栈顶元素,则左括号能匹配,两栈各弹出一个,继续匹配,直至左括号栈为空,字符串才合法
    {
        if (!stk2.empty() && stk1.top() < stk2.top())
        {
            stk1.pop();
            stk2.pop();
        }
        else
            return false;
    }

    return stk1.empty() ? true : false;
}

七、排列组合

1. Leetcode 面试题08.08 有重复字符串的排列组合

// 深度优先搜索
class Solution
{
public:
    //递归,返回类型不要定义成vector<string>
    void dfs(vector<string> &res, string &S, int i)
    {
        if (i == S.length())
        {
            res.push_back(S);
            return;
        }

        set<char> hash; // 使用set对重复字符去重
        for (int j = i; j < S.length(); ++j)
        {
            if (hash.find(S[j]) == hash.end()) // 对set内不存在的字符才进行递归
            {
                hash.insert(S[j]); // 当前字符插入set中

                swap(S[i], S[j]);
                dfs(res, S, i + 1);  //求全排列时,遍历字符串,将每个字符与其后面的每个字符交换,再对交换后的字符串递归
                swap(S[i], S[j]);  //注意交换之后要复原
            }
        }
    }

    vector<string> permutation(string S)
    {
        vector<string> res;
        dfs(res, S, 0);
        return res;
    }
};

2. Leetcode 77 组合

class Solution
{
public:
    vector<int> temp;
    vector<vector<int>> ans;

    //从cur~n的数字范围内选取k个数的所有组合
    void dfs(int cur, int n, int k)  // cur当前位置,n要组合的元素个数,k从n个元素中选择k个进行组合
    {
        if (temp.size() + (n - cur + 1) < k)  // 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
        {
            return;
        }
        
        if (temp.size() == k)  // 记录合法的组合
        {
            ans.push_back(temp);
            return;
        }

        if (cur == n + 1)  //cur = n + 1是递归结束的条件
        {
            return;
        }
        // 考虑选择当前位置进行组合
        temp.push_back(cur);
        dfs(cur + 1, n, k);
        temp.pop_back();
        // 考虑不选择当前位置进行组合
        dfs(cur + 1, n, k);
    }

    vector<vector<int>> combine(int n, int k)
    {
        dfs(1, n, k);
        return ans;
    }
};

八、双指针

1. 最长连续递增序列

//滑动窗口
class Solution
{
public:
    int findLengthOfLCIS(vector<int> &nums)
    {
        vector<int> window;
        int left = 0, right = 0;
        int maxlen = 0;
        while (right < nums.size())
        {
            if (window.empty() || window.back() < nums[right])
            {
                window.push_back(nums[right]);
                maxlen = max(maxlen, (int)window.size());
            }
            else
            {
                window.clear();
                left = right;
                window.push_back(nums[left]); 
            }
            right++;
        }
        return maxlen;
    }
};

2. NC.17 最长回文子串

// 贪心法加双指针,因为回文字符串是从首和从尾访问都是相同的并且关于中间对称,因此可以从中央向两边来求最长回文字符串
// 具体算法是:遍历字符串,以每个字符为中心,向两边扩展,如果左右两边字符都相等则是回文,保存回文字符串的长度
// 问题在于,回文可以是偶数长度如"abba"也可以是奇数长度如"aba",因此需要分以一个字符为中心和两个字符为中心讨论
class Solution
{
public:
    int fun(string &s, int begin, int end)
    {
        // 每个中心点开始扩展
        while (begin >= 0 && end < s.length() && s[begin] == s[end])
        {
            begin--;
            end++;
        }
        // 返回长度
        return end - begin - 1;
    }
    int getLongestPalindrome(string A)
    {
        int maxlen = 1;
        // 以每个点为中心
        for (int i = 0; i < A.length() - 1; i++)
            // 分奇数长度和偶数长度向两边扩展
            //fun(A,i,i)是以一个字符为中心向两边扩展,fun(A, i, i + 1)是以两个字符为中心向两边扩展
            maxlen = max(maxlen, max(fun(A, i, i), fun(A, i, i + 1)));
        return maxlen;
    }
};

3. NC.28 最小覆盖子串

class Solution
{
public:
    unordered_map<char, int> window, cnt;

    bool check()  //检测动态窗口中是否已经包含了字符串T的所有字符
    {
        for(auto &x: cnt)
        {
            if(window[x.first] < x.second)  //若窗口中有字符串T的字符但是字符数量不满足,则仍然没有完全覆盖子串
                return false;
        }
        return true;
    }

    string minWindow(string S, string T)
    {
        int left = 0, right = 0;
        int minlen = INT_MAX, ansL = -1;  // ansL用于记录最小覆盖子串的左下标, minlen用于记录最小覆盖子串的长度
        
        for(auto &x: T)  // 统计字符串T中的所有字符及其个数(T中可能有重复字符)
            cnt[x]++;
        
        while(right < S.length())
        {
            if(cnt.find(S[right]) != cnt.end())  //遍历字符串S,若right指针指向的字符是字符串T的字符,则将动态窗口哈希表中对应字符数量加一
            {
                window[S[right]]++;    
            }
            while(check() && left <= right)  //若此时滑动窗口中的字符串已经覆盖了字符串T的所有字符
            {
                if(right - left + 1 < minlen)  
                {
                    minlen = right - left + 1;  //记录此时覆盖子串的左下标和长度
                    ansL = left;
                }

                if(cnt.find(S[left]) != cnt.end())  //开始收缩窗口,此时要注意S[left]的字符是否是字符串T的,若是,则需要在动态窗口哈希表中减一对应的字符的数量
                {
                    window[S[left]]--;  
                }
                left++;  
            }
            right++;
        }
        return ansL == -1 ? string() : S.substr(ansL, minlen);  //若没有遍历完都没有找到,则返回空串
    }

};

九、深度优先搜索

1. HJ.41 称砝码

法一、集合法:

#include<iostream>
#include<vector>
#include<unordered_set>
#include<algorithm>
using namespace std;
 
int main(){
    int n;
    while(cin >> n){
        vector<int> weight(n);
        vector<int> num(n);
        for(int i = 0; i < n; i++) //输入n种砝码重量
            cin >> weight[i];
        for(int i = 0; i < n; i++) //输入n种砝码的数量
            cin >> num[i];
        unordered_set<int> s; //集合用于去重
        s.insert(0); //0也是一种
        for(int i = 0; i < n; i++){ //对于每一种砝码
            for(int j = 1; j <= num[i]; j++){ //用完之前数量之前
                unordered_set<int> temp(s);
                for(auto iter = temp.begin(); iter != temp.end(); iter++) //当前集合每个都可以加一次
                    s.insert(*iter + weight[i]);
            }
        }
        cout << s.size() << endl; //最后集合的大小就是种类
    }
    return 0;
}

//方法二、动态规划
//可以先计算出所有砝码总重量,然后用长为总重量为sum+1的bool型的dp数组计算每种重量是否可能出现,最后遍历这个dp数组统计每种重量出现的次数。
//首先dp[0]=1,然后我们遍历每个砝码(即每种重量的每一个),每次从sum开始往下递减,如果该重量k减去当前遍历到的这个砝码的重量得到的那个重量已经出现了即已经被别的组装好了,那我们就认为这个重量可以出现,置为1.
//动态规划递推方程为:dp[k] = dp[k - weight[i]]
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
 
int main(){
    int n;
    while(cin >> n){
        vector<int> weight(n);
        vector<int> num(n);
        int sum = 0;
        for(int i = 0; i < n; i++) //输入n种砝码重量
            cin >> weight[i];
        for(int i = 0; i < n; i++){//输入n种砝码的数量
            cin >> num[i];
            sum += num[i] * weight[i]; //砝码总重量
        }
        vector<bool> dp(sum + 1, false); //记录0到sum是否可行
        dp[0] = true;
        for(int i = 0; i < n; i++){ //遍历每一种砝码
            for(int j = 0; j < num[i]; j++){ //遍历砝码每一个数量
                for(int k = sum; k >= weight[i]; k--) //每次剩余的每个都可以添加
                    if(dp[k - weight[i]])
                        dp[k] = true;
            }
        }
        int count = 0;
        for(int i = 0; i <= sum; i++) //找到为1的就是可以组成的质量,计数
            if(dp[i])
                count++;
        cout << count << endl;
    }
    return 0;
}

十、二叉树

1. Leetcode 剑指offer 32 II从上到下打印二叉树II

class Solution
{
public:
    vector<vector<int> > LevelOrder(Treenode* root)  //广度优先搜索BFS
    {
        vector<vector<int> > res;  //返回结果
        queue<Treenode*> que;  //队列,存储每层的节点
        if(root != NULL)
            que.push(root);

        while(!que.empty())  //队列为空时,结束遍历
        {
            int size = que.size();
            vector<int> vec;  //存储每层节点的val
            Treenode* temp;
            for(int i = 0; i < size; i++)
            {
                temp = que.front();  //队列出队列一个,分别判断其左右子节点是否为空,若不为空则加入队列
                que.pop();
                if(temp->left != NULL)
                    que.push(temp->left);
                if(temp->right != NULL)
                    que.push(temp->right);
                vec.push_back(temp->val);  //将节点的val加入vec
            }
            res.push_back(vec);
        }
        return res;
    }
};

2. Leetcode 剑指offer 32 III从上到下打印二叉树III

class Solution
{
public:
    vector<vector<int> > LevelOrder(Treenode* root)  //广度优先搜索BFS
    {
        vector<vector<int> > res;  //返回结果
        queue<Treenode*> que;  //队列,存储每层的节点
        int count = 0;  //记录层数,奇数层反转之后再插入res中
        if(root != NULL)
            que.push(root);

        while(!que.empty())  //队列为空时,结束遍历
        {
            int size = que.size();
            vector<int> vec;  //存储每层节点的val
            Treenode* temp;
            for(int i = 0; i < size; i++)
            {
                temp = que.front();  //队列出队列一个,分别判断其左右子节点是否为空,若不为空则加入队列
                que.pop();
                if(temp->left != NULL)
                    que.push(temp->left);
                if(temp->right != NULL)
                    que.push(temp->right);
                vec.push_back(temp->val);  //将节点的val加入vec
            }
            if(count % 2 !=0)  //奇数层则将vec反转之后再插入res中
                reverse(vec.begin(), vec.end());
            res.push_back(vec);
            count++;
        }
        return res;
    }
};

十一、链表

1. HJ.48 从单向链表中删除指定值的节点

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

struct node { //链表结点
    int val;
    struct node* next = NULL;
};

int main() {
    int n, val;
    while (cin >> n >> val) {
        node* head = new node; //头结点
        head->val = val;
        for (int i = 1; i < n; i++) {
            int pre, cur;
            cin >> cur >> pre;
            node* p = new node; //添加这个结点
            p->val = cur;
            node* q = head;
            while (q->val != pre) //找到前序结点
                q = q->next;
            p->next = q->next; //断开
            q->next = p; //插入
        }
        int remove;
        cin >> remove;
        node* p = head;
        while (p != NULL) {
            if (p->val != remove) //不输出remove,其他都输出
                cout << p->val << " ";
            p = p->next;
        }
        cout << endl;
    }
    return 0;
}

2. Leetcode 160 相交链表

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode *> visited;  //将链表A的所有节点存入哈希集合中
        ListNode *temp = headA;
        while (temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {  //遍历链表B,若节点不在哈希集合中,则继续遍历;若节点在哈希集合中,则返回该节点
            if (visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
    }
};

3. JZ25. 合并两个排序的链表

//双指针法
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        //一个已经为空了,直接返回另一个
        if(pHead1 == NULL)
            return pHead2;
        if(pHead2 == NULL)
            return pHead1;
        //加一个表头
        ListNode* head = new ListNode(0);
        ListNode* cur = head;
        //两个链表都要不为空
        while(pHead1 && pHead2){
            //取较小值的节点
            if(pHead1->val <= pHead2->val){
                cur->next = pHead1;
                //只移动取值的指针
                pHead1 = pHead1->next;
            }else{
                cur->next = pHead2;
                //只移动取值的指针
                pHead2 = pHead2->next;
            }
            //指针后移
            cur = cur->next;
        }
        //遍历到最后肯定有一个链表还有剩余的节点,它们的值将大于前面所有的,直接连在新的链表后面即可,哪个链表还有剩,直接连在后面
        if(pHead1)
            cur->next = pHead1;
        else
            cur->next = pHead2;
        //返回值去掉表头
        return head->next;
    }
};

//递归法 + 双指针
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) 
    {
        //一个已经为空了,返回另一个
        if(pHead1 == NULL)
            return pHead2;
        if(pHead2 == NULL)
            return pHead1;
        //先用较小的值的节点
        if(pHead1->val <= pHead2->val){
            //递归往下
            pHead1->next = Merge(pHead1->next, pHead2);
            return pHead1;
        }else{
            //递归往下
            pHead2->next = Merge(pHead1, pHead2->next);
            return pHead2;
        }
    }
};

十二、 其他

1. Leetcode 994 腐烂的橘子

//将所有腐烂的橘子看成树的同一层的节点
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        queue<pair<int, int>> q;  // 用队列存储待搜索的腐烂橘子,pair分别表示腐烂橘子的坐标位置
        int fresh = 0;  // 记录当前新鲜橘子的数量
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(grid[i][j] == 2) {
                    q.push({i, j});  // 将腐烂橘子加入队列
                }
                else if(grid[i][j] == 1) {
                    fresh++;  // 统计新鲜橘子的数量
                }
            }
        }
        int time = 0;  // 经过的分钟数
        while(!q.empty() && fresh > 0) {  // 只要队列不为空且还有新鲜橘子
            int size = q.size();
            for(int i = 0; i < size; i++) {
                auto cur = q.front();
                q.pop();
                int x = cur.first;
                int y = cur.second;
                if(x > 0 && grid[x - 1][y] == 1) {  // 上方橘子变腐烂
                    grid[x - 1][y] = 2;
                    q.push({x - 1, y});
                    fresh--;  // 更新新鲜橘子数量
                }
                if(x < m - 1 && grid[x + 1][y] == 1) {  // 下方橘子变腐烂
                    grid[x + 1][y] = 2;
                    q.push({x + 1, y});
                    fresh--;
                }
                if(y > 0 && grid[x][y - 1] == 1) {  // 左边橘子变腐烂
                    grid[x][y - 1] = 2;
                    q.push({x, y - 1});
                    fresh--;
                }
                if(y < n - 1 && grid[x][y + 1] == 1) {  // 右边橘子变腐烂
                    grid[x][y + 1] = 2;
                    q.push({x, y + 1});
                    fresh--;
                }
            }
            time++;  // 更新时间
        }
        if(fresh > 0) {  // 如果还有新鲜橘子,返回-1
            return -1;
        }
        return time;  // 否则返回经过的分钟数
    }
};

2. leetcode 204 计数质数

//方法一、枚举法 时间复杂度为O(n^(3/2))
class Solution {
public:
    bool isPrime(int &x)
    {
       for ( int i = 2 ; i * i <= x ; i++)
       {
           if ( x % i == 0 )
               return false ;
       }
         return true ;
    }

    int countPrimes(int n) 
    {
        int res = 0;
        if( n < 2)
            return 0;
        for(int i = 2; i < n; i++)
        {
            if(isPrime(i))
                res++;
        }

        return res;
    }
};

// 方法二、埃氏筛  时间复杂度为O(nlog(n))
//主要思想:如果一个数x是质数,则其整数倍一定不是质数
class Solution {
public:
    int countPrimes(int n) 
    {
        if (n < 2) return 0;

        vector<bool> isPrime(n, true);  //n范围内的数全部标记为true,true表示是质数
        int count = n - 2;  // 不考虑 0 和 1

        for (int i = 2; i * i < n; i++) 
        {
            if (!isPrime[i]) continue;  // i不是质数

            for (int j = i * i; j < n; j += i) 
            {
                if (isPrime[j]) 
                {  // i 是质数时,将 i*i 开始全部标记为false
                    count--;
                    isPrime[j] = false;
                }
            }
        }
        return count;
    }
};

3. HJ.25 数据分类处理

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n, m;
    cin >> n;
    vector<int> I(n);
    for (int i = 0; i < n; i++) {
        cin >> I[i];
    }
    cin >> m;
    vector<int> R(m);
    for (int i = 0; i < m; i++) {
        cin >> R[i];
    }
    sort(R.begin(), R.end());  //先对R序列排序
    vector<int> result;
    int count = 0;  //记录I中的数能连续包含R<i>的个数
    for (int i = 0; i < m; i++) {
        int ri = R[i];
        int icount = 0;
        vector<int> index;  //存储满足条件的I中的数的下标
        for (int j = 0; j < n; j++) {
            int ij = I[j];
            string rs = to_string(ri);
            string is = to_string(ij);
            if (is.find(rs) != string::npos) {  //将数字转化为字符串来判断
                icount++;
                index.push_back(j);
            }
        }
        if (icount > 0) {
            result.push_back(ri);
            result.push_back(icount);
            for (int j = 0; j < icount; j++) {
                result.push_back(index[j]);
            }
            for (int j = 0; j < icount; j++) {
                result.push_back(I[index[j]]);
            }
            count += 2 + icount;
        }
    }
    if (count > 0) {
        cout << count << " ";
        for (int i = 0; i < result.size(); i++) {
            cout << result[i] << " ";
        }
        cout << endl;
    }
    return 0;
}

4. HJ.29 字符串加解密

//方法一、直接法
#include<iostream>
#include<string>
 
using namespace std;
 
void encoder(string str)
{
    for(int i=0;i<str.size();i++)
    {
        if(isalpha(str[i])){//英文字母
            if(str[i]>='a'&&str[i]<='z'){//小写字母
                if(str[i]=='z'){
                    str[i]='A';
                }else{
                    str[i]=str[i]-'a'+'A'+1;//变换大小写同时用后一个字母替换
                }
            }else{//大写字母
                if(str[i]=='Z'){
                    str[i]='a';
                }else{
                    str[i]=str[i]-'A'+'a'+1;//变换大小写同时用后一个字母替换
                }
            }
        }else{//数字
            if(str[i]<'9') str[i]++;
            else str[i]='0';//9变为0
        }
    }
    cout<<str<<endl;
}
void decoder(string str)
{
    for(int i=0;i<str.size();i++)
    {
        if(isalpha(str[i])){//英文字母
            if(str[i]>='a'&&str[i]<='z'){//小写字母
                if(str[i]=='a'){
                    str[i]='Z';
                }else{
                    str[i]=str[i]-'a'+'A'-1;//变换大小写同时用前一个字母替换
                }
            }else{//大写字母
                if(str[i]=='A'){
                    str[i]='z';
                }else{
                    str[i]=str[i]-'A'+'a'-1;//变换大小写同时用前一个字母替换
                }
            }
        }else{//数字
            if(str[i]>'0') str[i]--;
            else str[i]='9';//0变为9
        }
    }
    cout<<str<<endl;
}
int main()
{
    string str1, str2;
    while(cin>>str1>>str2)
    {
        encoder(str1);
        decoder(str2);
    }
    return 0;
}

//方法二 查表法
#include <iostream>
#include <vector>
#include <string>
using namespace std;
 
string strlist1 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";//加密前的字符list
 
string strlist2 = "BCDEFGHIJKLMNOPQRSTUVWXYZAbcdefghijklmnopqrstuvwxyza1234567890";//加密后的字符list,和list1一一对应
void encoder(string str)
{
    for(int i=0; i<str.size(); i++)//遍历一遍字符串
    {
        for(int j=0; j<strlist1.size(); j++)//找到在list1中的位置
        {
            if(str[i] == strlist1[j])
            {
                str[i] = strlist2[j];//加密
                break;
            }
        }
    }
    cout<<str<<endl;
}
 
void decoder(string str)
{
    for(int i=0; i<str.size(); i++)//遍历一遍字符串
    {
        for(int j=0; j<strlist2.size(); j++)//找到在list2中的位置
        {
            if(str[i] == strlist2[j])
            {
                str[i] = strlist1[j];//解密
                break;
            }
        }
    }
    cout<<str<<endl;
}
int main()
{
    string str1, str2;
    while(cin >> str1 >> str2)
    {
        encoder(str1);//加密
        decoder(str2);//解密
    }
    return 0;
}

5. JZ.61 扑克牌中的顺子

// 是顺子的充要条件:顺子中无重复牌,并且顺子中的最大牌减顺子中的最小牌点数之差小于5
class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        set<int> se;
        int _min=14,_max=0;//一副牌最大K点,所以_min初始化14;
        for(auto i:numbers){//C++11语法
            if(i==0)continue;//大小王
            _min=min(_min,i);//最小牌
            _max=max(_max,i);//最大牌
            if(se.count(i))return false;//出现重复牌
            se.insert(i);
        }
        return _max-_min<5; // 顺子中最大的牌点数max - 顺子中最小的牌点数min < 5
    }
};

6. HJ.43 迷宫问题

// 深度优先搜索 DFS
int map[N + 1][N + 1];
int visit[N + 1][N + 1]; // 标记地图中每个点的状态
int final_x, final_y;  // 目标坐标
int _min = 99999999; // 记录最后的最小步数
int m = 0, n = 0;
int next[5][2] = {{0, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 方向数组
void dfs(int x, int y, int sum)
{
    // 如果已经到达目标地点
    int xr, yr;
    if (x == final_x && y == final_y)
    {
        if (sum < _min)
            _min = sum; // 如果这条路线的最小步数小于当前记录的最小步数就更新
        return;
    }
    for (int i = 1; i <= 4; i++)
    {
        xr = x + next[i][0];
        yr = y + next[i][1];
        if (xr < 1 || xr > m || yr < 1 || yr > n)  //边界检查
            continue;
        if (visit[xr][yr] == 0 && map[xr][yr] == 0)  // 这个点没有经过并且地图上可行
        {
            visit[xr][yr] = 1;
            dfs(xr, yr, sum + 1);
            visit[xr][yr] = 0; // 另一条路可能会经过(xr,yr)这个点,所以需要将(xr,yr)这个点置0
        }
    }
    return;
}
int main(){
	int beginx,beginy;
	int sum=0;
	book[beginx][beginy]=1;  //先将起点标记为1
	dfs(beginx,beginy,sum);
	if(_min == 99999999) 
        // do nothing  //如果没有可行解
	else 
        cout<<_min; 
	 return 0;
}

// 广度优先搜索 BFS
int x, y, a, b;
struct  point
{
	int x, y, dis;//x坐标 y坐标 步数
};
int fx[4] = { -1,1,0,0 }, fy[4] = { 0,0,-1,1 };
int bfs(int x, int y,int maze[][9])
{
	queue<point> myque;
	point tp;
	tp.x = x;tp.y = y;tp.dis = 0;//初始化开始,节点dis设为0
	myque.push(tp);  //起点入队列
	while (!myque.empty())
	{
		tp = myque.front();
        myque.pop(); //从队头取节点
		if (tp.x == a && tp.y == b) { return tp.dis; }  //到达目的地,则退出
		for (int i = 0;i < 4;i++)
		{
			if (tp.x + fx[i] < 9 && tp.x + fx[i] >= 0 && tp.y + fy[i] < 9 &&
				 tp.y + fy[i] >= 0 && maze[tp.x + fx[i]][tp.y + fy[i]] == 0)
			{
				point tmp;
				tmp.x = tp.x + fx[i];
				tmp.y = tp.y + fy[i];
				tmp.dis = tp.dis + 1;
				maze[tmp.x][tmp.y] = 1; //添加进队列就将该位置设为1
				myque.push(tmp);
			}
		}
	}
}

7. leetcode 322 零钱兑换

//动态规划
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int Max = amount + 1;
        vector<int> dp(amount + 1, Max);
        dp[0] = 0;
        for (int i = 1; i <= amount; ++i) {
            for (int j = 0; j < (int)coins.size(); ++j) {
                if (coins[j] <= i) {
                    dp[i] = min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

8. 火锅问题

9. leetcode 200 岛屿问题

posted @ 2023-07-14 00:05  N1rv2na  阅读(46)  评论(0)    收藏  举报