leetcode 刷题笔记- string.

  1. cout and say
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
#include<string>
using namespace  std;
string cout_and_say(int n)
{
    string res ="1"; // 这个初始化意味着需要少循环一次,
    while ( --n )
    {
        string tmp;
        for(int i =0; i< res.size() ; i++)
        {
            int j =i; // 常用的一种遍历手段
            while(j < res.size() && res[j] ==res[i]) j +=1;
            tmp =to_string(j -i) +res[i];
            i =j -1;
        }
        res =tmp;
    }
    return res;
}

int main()
{
    int n ;
    cin >>n;
    string res =cout_and_say(n);
    for(auto  u: res)
        cout << u;
    cout << endl;
    return 0;
}
  1. group anagrams

sort 函数是 in-place() 的操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    // 思路很简单, 先是放到一个 hash,然后再遍历一遍dictionary
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> hash;
        for(auto str : strs)
        {
            string key =str;// sort 函数是 in-place() 的操作
            sort(key.begin(), key.end());
            hash[key].push_back(str);
        }
        vector<vector<string>> res ;
        for (auto item : hash)
        {
            res.push_back(item.second);
        }
        return res;
    }
};
  1. reverse words in a sting

LeetCode题目链接

c++ 中绝大部分都是左闭右开, 比如说reverse() .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>
#include<string>
using namespace std;
string reverse_string(string str)
{
    string res;
    int n =str.size() ;
    int k =0;
    for(int i =0; i< n; i++)
    {
        while (i < n && str[i] ==' ') i ++;
        if (i > n) break;
        int j =i ;
        while(j < n && str[j] != ' ') j ++;
        reverse(str.begin() +i, str.begin() +j);
        if(k ) str[k ++] =' ';
        // 赋值回来, 这个语句是非常秒的, 把 i 的index 补充了回来
        while(i < j) str[k ++] =str[i ++];
    }
    str.erase(str.begin() +k , str.end());
    reverse(str.begin(), str.end());
    return str;
}
int main()
{
    string input =" the sky is blue";
    string res =reverse_string(input);
    for(auto u : res)
        cout<< u;
    cout << endl;
    return 0;
}
  1. compare-version-numbers
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
#include<string>
using namespace std;
int cmpVersion(string str1, string str2)
{
    int n =str1.size() , m = str2.size();
    int i =0, j =0;
    while(i < n || j < m)
    {
        int x =i, y =j;
        while( x< n && str1[x] != '.')  x ++;
        while( y< m && str2[y ] != '.') y ++;
        int sum1 = x ==i ? 0 : atoi(str1.substr(i, x -i).c_str());
        int sum2 = x ==j ? 0: atoi(str2.substr(j, y -j).c_str());
        i =x +1, j = y +1;
        if(sum1< sum2) return -1;
        if (sum1 > sum2) return 1;
    }
    return 0;
}

int main()
{
    string str1; // 0.1
    string str2; //1.1
    getline(cin, str1);
    getline(cin, str2);
    cout<< cmpVersion(str1, str2)<< endl;
    return 0;
}

  1. Unique Email Addresses

这里给出了一种如何去读入 vector,最简单的方法就是多定义一个循环,这样的话就可以完整的读入所有的string,可能再最后的结果中有 +1 或者 -1 的操作。最后看效果,稍微猜测推一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
#include<unordered_set>
#include<string>
#include<vector>
using namespace std;
// 比较简单 ,分成 name 和 domain 两个方面进行处理
int numUniqueEmails(vector<string> emails)
{
    unordered_set<string> hash;
    for(auto email: emails)
    {
        int at =email.find("@");
        string name =email.substr(0, at); // 写成 email.begin() 吧
        string domain =email.substr(at +1);// 默认是到最后的,如果只是有一个参数的话
        string tmp;
        for(auto ch : name)
        {
            if (ch =='+') break;
            else if(ch != '.') tmp += ch;
        }
        string res =tmp+'@'+ domain;
        hash.insert(res);
    }
    return hash.size()-1;
}

int main()
{
    //vector<string> emails={"a@a.com", "b@b.com"};
    vector<string> emails;
    // 对于字符串 数组的输入是不不会的
    int n ;
    cin >> n;
    n ++;
    while(n --)
    {
        string tmp;
        getline(cin, tmp);
        emails.push_back(tmp);
    }
    cout << numUniqueEmails(emails)<< endl;
    return 0;
}

  1. longest palindromic substring

返回结果是子串的问题,一般使用双指针(可以处理连续的问题)。针对本题,先是枚举中心点,然后向着左右两边扩展,算法时间复杂度是$O(n^2)$。使用马拉车算法可以在$O(n)$的时间复杂度内解决,但是只能处理回文串问题,比较局限,不像kmp 算法。

在实现的时候,尽量使用 i+1 而不要使用 i-1,因为对于两个数字的相邻关系,使用 i-1 , iii+1 都是可以表示的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    string longestPalindrome(string s) {
        //时间复杂度是 On^2(), 先枚举中心点,然后向着左右两边进行扩展,最优的解法是 马拉车,但是太局限了,所以没有学习
        int n = s.size();
        string res ;      
        for(int i =0; i< n; i++)
        {
            for(int j =i, k =i ; j >= 0 && k < n&& s[j] ==s[k]; j --, k ++)
            {
                if(k -j +1 > res.size())
                    res =s.substr(j, k-j +1);
            }
            for(int j =i, k =i +1; j >=0 && k < n && s[j] ==s[k]; j --, k ++)
                if( k -j +1 > res.size())
                    res =s.substr(j, k-j +1);
        }
        return res;
    }
};
  1. zigzag conversion

好好理解一下,当for 循环的时候, 第三个参数就是等差数列中公差。for 循环是可以模拟 等差数列和等比数列的,初始化,判别条件和步伐是能够和公差公比相对应的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
    // 找规律的题目
    // 如果是首行和尾行,公差是 2(n -1),
    // 如果是中间行, 那么是两个交错的等差数列
    string convert(string s, int n) {
        if (n ==1) return s;
        string res;
        for(int i =0; i< n; i++)
        {
            if(! i ||  i ==n-1 )
            {
                for(int j =i; j< s.size(); j += 2*(n -1))
                    res += s[j];
            }
            else
            {
                // 两个交叉的数列, 只是初始化的值需要注意一下
                for(int j =i, k =2*(n-1) -i ; j < s.size() || k < s.size() ; j += 2*(n -1) , k += 2*(n -1) )
                {
                    if(j < s.size()) res += s[j];
                    if(k < s.size() ) res += s[k];
                }
            }
        }
        return res;
    }
};
  1. longest substring without repeating characters

~~一开始还以为使用dp,但是使用双指针+ dictionary 就可以解决。~~求解的是子串(不是子序列),所以一定是一个连续的区间 [i, j],有点贪心的思想,通用的“滑动窗口 + 哈希表” 的做法。由于 $i$, $j$ 最多增加 $n$ 次,且hash 表的插入和更新时间复杂度是$O(1)$, 所以总的时间复杂度是$O(n)$。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Solution(object):
    def lengthOfLongestSubstring(self, s):
        """
        :type s: str
        :rtype: int
        """    
        i, res =0, 0
        dic ={}
        for (j, ch ) in enumerate(s):
            if ch in dic and i <= dic[ch]:
                i =dic[ch] +1
            else:
                res =max(res, j -i+1)
            dic[ch] =j  # dic 是一个记录 char 到index 的映射关系的dictionary
        return res
  1. implement Trie(Predix Tree)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class Trie {
public:
    // 使用一个结构体存储一个结点
    struct Node{
        bool is_end;
        Node * son[26];
        // 这个类似构造函数,是对结构体进行初始化
        Node()
        {
            is_end =false;
            for(int i=0; i< 26; i++)
                son[i] =NULL;
        }
        
    }*root;
    // 这个是结构体变量
    
    /** Initialize your data structure here. */
    Trie() {
        root =new Node();
        
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        auto p = root;
        for(auto c: word)
        {
            auto u =c -'a';
            if(p->son[u] ==NULL) p ->son[u] =new Node();
            p =p->son[u];
        }
        p->is_end =true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {        
        auto p =root;
        for(auto c : word)
        {
            auto u =c -'a';
            if(p ->son[u] ==NULL) return false;
            p =p->son[u];
        }
        return p->is_end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        auto p =root;
        for(auto c :prefix)
        {
            auto u =c -'a';
            if(p ->son[u] ==NULL) return false;
            p =p->son[u];
        }
        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

关于trie 的理论讲解可以参考这篇博客, 这里主要讲解实现。

  1. integer to English Words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public:
    
    string small[20] ={"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
                      "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
    
    string mid[10] ={"", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
    string big[4] ={"Billion", "Million", "Thousand", ""};
    string numberToWords(int num) {
        if(! num) return small[0];
        string res;
        for(int i =1000000000,j =0; i>0; i /=1000, j++)
        {
            if(num >=i)
            {
                res += get_part(num /i) +big[j] +" ";
                num %= i;
            }    
        }
        while(res.back() ==' ') res.pop_back();
        return res;
    }
    
    string get_part(int n)
    {
        string res;
        if(n >=100)
        {
            res += small[n /100] +" Hundred ";
            n %= 100;
        }
        if(!n) return res;
        if(n >=20)
        {
            res += mid[n/10] +' ';
            n %=10;
        }
        if(!n) return res;
        res += small[n]+' ';
        return res;
    }
};

KMP 专题

首先最简单的是字符串的匹配问题, 如果暴力枚举,那么时间复杂度是 $O(mn) $,其中 $m$ 和$n$ 分别表示两个字符串的长度。如果使用kmp 算法,那么时间复杂度是 $O(m +n)$

这个是模板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Next数组
// s[]是模式串,p[]是模板串, n是s的长度,m是p的长度
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的逻辑
    }
}

这个是投机取巧的方式, 但是可以掌握关于 string:: npos 这种判断条件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
#include<string>
using namespace std;

int main()
{
    int n, m;
    string p, s;
    cin >> n>> p>> m>> s;
    auto found =s.find(p);
    while(found != string::npos)
    {
        cout << found << " ";
        found =s.find(p, found+1);
    }
    return 0;
}


使用 kmp 算法的正解。(KMP字符串)(https://www.acwing.com/problem/content/description/833/)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;

const int N =1e4+11;
const int N1 =1e5+11;
int nex[N];
char p[N];
char s[N1];
int n, m;

void get_next()
{
    for(int i =2,j =0; i<=n; i++)
    {
        while(j && p[i] != p[j+1]) j =nex[j];
        if(p[i ] ==p[j+1]) j ++;
        nex[i] =j;
    }
}

int main()
{
    // 这种写法对于一次性的读入,还是非常有效率的,学习
    cin >> n >> p+1>> m >> s+1;
    get_next();
    for(int i =1, j =0; i<=m; i++)
    {
        while(j && s[i] != p[j+1]) j =nex[j];
        if( s[i] ==p[j+1]) j +=1;
        if(j ==n)
        {
            printf("%d ", i-n);
            j =nex[j];
        }
    }
    return 0;
}

周期

next[n] 的含义: 表示以 n 为结尾的后缀和以 1位起点的前缀,相同的string , 最大长度。 n-next[n] 表示最小的循环节,那么 n/(n -next[n]) 就是最多的个数。

以i 为结点的最大的后缀和前缀相等,这个后缀的长度就是next[i]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
using namespace std;
const int N =1e6+11;
int nex[N];
char str[N];
int n;

//在所有的前缀中 最小循环节的最大重复个数
// 考察 n-next[n] 是表示最小的循环节
void get_next()
{
    for(int i =2, j =0; i<=n ; i++)
    {
        // while 和 nex[i] 是容易出粗
        while(j && str[i] != str[j+1]) j =nex[j];
        if(str[i] ==str[j+1]) j++;
        nex[i] =j;
    }
}
int main()
{
    int T =1;
    // 如果n ==0,那么就跳出了
    while(scanf("%d", &n), n)
    {
        scanf("%s\n", str+1); // 这种 \n 在读入字符串中还是非常有用,对于cin 函数,你就知道多有用了
        get_next();
        printf("Test case #%d\n", T++); 
        for(int i =1; i<=n; i++)
        {
            int t =i -nex[i];
            if(t != i && i%t ==0) printf("%d %d\n", i, i/t);
        }
        puts("");
    }
    return 0;
}

对于string 的题目,一般可以hash 或者 kmp 算法求解。

160. 匹配统计

视频讲解

我觉得可以理解的地方是,kmp 算法求解next 数组,kmp 算法进行字符串匹配,n-next[n] 表示最大的循环节的个数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// f[i] 表示匹配长度至少是 i的情况下, 这样的后缀有多少个
// 最少是 x -最少是x+1 的,那么得到的就是 x
#include<iostream>
using namespace std;
const int N =2e5+11;
int n, m, q;
char a[N], b[N];
int ne[N];
int f[N];
// next 数组本质上就是前缀和后缀的匹配的最大长度
// next 数组不止可以操作同一个string,还是可以操作另外的数组
int main()
{
    cin >>n >>m>> q;
    scanf("%s%s", a+1, b+1); // 读入了两个字符串数组
    //对于 b 求解kmp 数组
    for(int i =2, j =0;i <=m; i++)
    {
        while(j && b[i] != b[j+1]) j =ne[j];
        if(b[i] ==b[j+1]) j++;
        ne[i] =j;
    }
    // 求解a 的后缀和b 的前缀的匹配, 
    for(int i =1, j =0; i<=n; i++)
    {
        while(j && a[i] != b[j+1]) j =ne[j];
        if(a[i] ==b[j+1]) j ++;
        // 这里是不理解的
        f[j] ++;
    }
    // 这里也是不懂的
    for(int  i=m; i; i--) f[ne[i]] += f[i];
    while( q--)
    {
        int x ;
        cin >>x;
        cout << f[x]- f[x+1] <<endl;
    }
    return 0;
}

奶牛矩阵

视频讲解

太难了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

const int N =10010, M =80;

int n, m;
char str[N][M];
int ne[N];
bool st[M];
int main()
{
    cin >>n>>m;
    
    memset(st, true, sizeof st);
    
    for(int i =1 ;i<=n; i++)
    {
        scanf("%s", str[i]);
        
        for(int j =1; j<=m; j++)
        {
            if(st[j])
            {
                for(int k =j; k<m; k+=j)
                {
                    for(int u =0; u<j && k+ u< m; u++)
                    {
                        if(str[i][u] != str[i][k+u])
                        {
                            st[j] =false;
                            break;
                        }
                    }
                    if(!st[j]) break;
                }
            }
        }
    }
    
    
    int width;
    for(int i =1; i<=m; i++) 
    {
        if(st[i])
        {
            width =i;
            break;
        }
    }
    
    for(int i =1; i<=n; i++)  str[i][width] =0;
    
    // strcmp(str1, str2) 如果相等返回 0, 如果str1< str2 返回-1, 如果大于 返回+1
    for(int i =2, j =0; i<=n; i++)
    {
        while(j && strcmp(str[i], str[j+1])) j =ne[j];
        if(!strcmp(str[i], str[j+1])) j++;
        ne[i] =j;
    }
    int height =n -ne[n];
    //cout << width <<" "<< height<< endl;
    cout << width *height <<endl;
    
    
    return 0;
}

超市

主要是练习 小根堆和pair 的组合使用。是一个贪心算法,很经典的、时间复杂度是 $nlogn$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef pair<int, int> PAIR;
// 使用pair 这种结构是可以按照第一关键字先排序,然后按照第二关键字排序
// 按照过期时间排序,维护一个小根堆,每次把利润小的给 pop出去
int main()
{
    int n;
    while(cin >>n)
    {
    vector<PAIR> products(n);
    // 这种写法,真是非常的简洁,使用vector<pair<int, int>> 的格式,list 中嵌套了 pair
    // 先是按照过期时间进行排序,如果相同,那么按照金额进行排序,都是从小到大进行排序,因为sort() 默认的按照增序进行排序的
    for(int i =0; i<n; i++) cin >> products[i].second >> products[i].first;
    sort(products.begin(), products.end());
    // 定义小根堆
    // 使用greater<int> ,less<int> ,头文件不写也行的
    priority_queue<int , vector<int> , greater<int>> heap;
    for(auto p: products)
    {
        heap.push(p.second);
        if(heap.size() > p.first) heap.pop();
    }
    int res =0;
    while(heap.size() ) res += heap.top(), heap.pop();
    cout << res<< endl;
    }
    return 0;
}

第二次写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef pair<int, int> PAIR;
int main()
{
    int n ;
    while(cin >>n)
    {
        vector<PAIR> products(n);
        // 下面是index 访问,那么就需要初始化,否则是段错误;或者使用 push_back() 进行访问
        for(int i =0; i< n; i++)
        {
            cin >> products[i].second >> products[i].first;
        }
        sort(products.begin(), products.end());
        priority_queue<int, vector<int>, greater<int>> heap;
        for(auto product: products)
        {
            // 先是无脑放,如果发现不满足条件,那么弹出
            heap.push(product.second);
            if(heap.size() > product.first) heap.pop();
        }
        int res =0;
        while(heap.size())
        {
            res +=heap.top();
            heap.pop();
        }
        cout << res<< endl;
    }
    return 0;
}

Implement strStr()

注意这个不是从 1 开始计数的,这个使用边界条件是需要记录一下的。kmp 算法的应用,如果暴力求解,那么算法复杂度是 $O(mn)$,但使用KMP 算法,时间复杂度是$O(m +n)$,其中$m$, $n$ 分别是两个字符串的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
    int strStr(string haystack, string needle) {
        int n =haystack.size() , m =needle.size();
        if(m ==0) return 0;
        vector<int> nex(m);
        
        nex[0] =-1;
        
        for(int i =1, j =-1; i< m; i++)
        {
            while( j > -1 && needle[i] != needle[j +1] ) j =nex[j];
            if(needle[i] ==needle[j +1]) j ++;
            nex[i] =j;
        }
        
        for(int i =0, j =-1; i<n; i++)
        {
            while( j> -1 && haystack[i] != needle[j +1] ) j =nex[j];
            if(haystack[i] ==needle[j +1]) j ++;
            if( j ==m -1)
                return i -m +1;
        }
        return -1;
    }
};

459. Repeated Substring Pattern

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    // 考察kmp 中 i -nex[i] 是循环节的长度。更加具体的是 在这个题目中 n -nex[n] 是最小循环节的长度,
    // 这个是可以用来判断是否完美切分
    bool repeatedSubstringPattern(string s) {
        int n = s.size();
        vector<int> nex(n);
        nex[0]  =-1;
        for(int i =1, j =-1; i< n; i++)
        {
            while(j > -1 && s[i] != s[j+1]) j =nex[j];
            if(s[i] ==s[j+1]) j ++;
            nex[i] =j;
            
        }
        int t =n-1 -nex[n-1];
        return t !=n&& n%t ==0; // 字符串本身不能作为一个循环节
        
    }
};

214. Shortest Palindrome

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
    // 考察kmp 的前后缀的理解
    string shortestPalindrome(string s) {
        int n =s.size();
        // 这种情况专门就是指的是 s =="" 没有其他的情况了
        if (n ==0) return s;
        string t(s.rbegin(), s.rend());        
        vector<int> nex(n);
        nex[0] =-1;
        for(int i =1, j =-1; i< n; i++)
        {
            while(j > -1 && s[i] != s[j+1]) j =nex[j];
            if(s[i] ==s[j+1]) j ++;
            nex[i] =j;
        }
        // 做的kmp 的匹配
        int j =-1;
        // 比较的是 t 的前缀和 s 的后缀的相同的最大的长度
        for(int i =0; i<n; i++)
        {
            while(j > -1 && t[i] != s[j+1]) j =nex[j];
            if(t[i] ==s[j+1]) j ++;   
        }
        return t +s.substr(j +1, n -j -1);
    }
};

LeetCode 179. Largest Number

这个主要是考虑到排序算法。如果字符串表示的比较大, 那么最后是数字表示也是比较大。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
#include<string>
#include<vector>
using namespace std;
static bool cmp(string& a, string& b)
{
    return a+b > b+a;
}
string largestNumber(vector<string> & nums)
{
    string res="";
    sort(nums.begin(), nums.end(), cmp);
    for(string t: nums)
        res += t;
    return res;
}
int main()
{
    int n ;
    cin >>n;
    vector<string> nums;
    for(int i =0; i< n; i++)
    {
        string tmp;
        cin >> tmp;
        nums.emplace_back(tmp);
    }
    string res =largestNumber(nums);
    cout << res<< endl;
    return 0;
}

LeetCode 242. Valid Anagram

这个时间复杂度是$O(n)$,空间复杂度是$O(1)$。空间的优化是有技巧的,因为这里只有26个字母,所以空间大小是固定的,比较nice的东西。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    // 这种应该是最少的时间复杂度了
    bool isAnagram(string s, string t) {
        
        vector<int> hash(26, 0);
        for(char ch: s)
            hash[c- 'a'] ++;
        for(char ch: t)
            hash[c -'a'] --;
        
        for(int i =0; i< 26; i++)
            if(hash[i] != 0)
                return false;
        
        return true;
        
    }
};

415. Add Strings

c++ 中注意不同数据类型,int 和int 之间使用 += , string 和string 之间使用 += 才有意义。

···c++ class Solution { public: string addStrings(string num1, string num2) { string res; int carry =0; int m =num1.size() -1, n =num2.size() -1; while ( m >=0 && n >= 0) { carry = (num1[m–] -‘0’ ) + (num2[n –] -‘0’) + carry; res += (carry %10 +‘0’); carry =carry/ 10;
} while (m >=0) { carry = (num1[m–] - ‘0’) + carry; res += (carry %10 +‘0’); carry /= 10; } while( n >=0) { carry = (num2[n –] -‘0’) + carry; res += ( carry %10 + ‘0’); carry /= 10; } if( carry) res += (carry %10 +‘0’); reverse(res.begin(), res.end()); return res; } }; ···

python 中 ordchr 函数 chr()函数用整数作参数,返回一个对应的字符

ord()函数是chr()函数的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的ASCII数值,或者Unicode数值

python 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Solution:
    def addStrings(self, num1: str, num2: str) -> str:
        i, j =len(num1) -1, len(num2) -1
        ans =""
        carry =0
        
        while i >=0 or j >=0 or carry:
            a = ord(num1[i]) - ord('0') if i >= 0 else 0
            b = ord(num2[j]) - ord('0') if j >= 0 else 0
            n =a  +b + carry
            ans = chr( n%10 + ord('0')) + ans #  注意顺序,  + ord('0') 处理bad case: 都为0
            carry = carry //10
            i, j = i -1, j -1
        return ans