algorithm - 字符串和数组

有关自字符串和数组的一些基本算法

1.字符串匹配

  • 从长度为n的母串中匹配长度为m的子串
  • 两种算法
    1. 暴力匹配
    2. kmp

暴力匹配

  • 顺序遍历母串,将每个字符作为匹配的起始字符,判断是否匹配子串,时间复杂度为:O(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
    27
    28
    29
    30
    31
    32
    int strMatch(const char* src, const char* target)
    {
    // 字符串不为空
    if (!strlen(target) || !strlen(src))
    {
    return -1;
    }

    int index = 0;
    while (index <= (strlen(src) - strlen(target)))
    {
    int count = 0;
    bool flag = true;
    while (count < strlen(target))
    {
    if (src[count+index] != target[count])
    {
    flag = false;
    break;
    }
    count++;
    }

    if (flag)
    {
    return index;
    }
    index++;
    }

    return -1;
    }

KMP 算法

  • 参考1
  • 参考2
  • 时间复杂度:O(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
    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
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    #include <set>

    void build_next_table(int *next, char *target_str) {
    next[0] = 0;
    int target_len = strlen(target_str);

    // 遍历 target_str 的所有子串
    for (int i = 1; i < target_len; ++i) {
    // 前缀子串
    std::set<std::string> head_set;
    std::string tmp_head = "";
    tmp_head = target_str[0];
    head_set.insert(tmp_head);
    for (int j = 1; j < i; ++j) {
    tmp_head = tmp_head + target_str[j];
    head_set.insert(tmp_head);
    }

    // 后缀子串
    std::set<std::string> tail_set;
    std::string tmp_tail = "";
    tmp_tail = target_str[i];
    tail_set.insert(tmp_tail);
    for (int j = i - 1; j > 0; --j) {
    tmp_tail = target_str[j] + tmp_tail;
    tail_set.insert(tmp_tail);
    }

    // 共同子串的集合
    std::set<std::string> common_set;
    for (const auto &sub_str : head_set) {
    if (tail_set.count(sub_str) > 0) {
    common_set.insert(sub_str);
    }
    }

    // 找出最长的共同子串
    int max_len = 0;
    for (const auto &common_str : common_set) {
    if (common_str.size() > max_len) {
    max_len = common_str.size();
    }
    }

    next[i] = max_len;
    }
    }

    int kmp(char *src_str, char *target_str) {
    if (src_str == nullptr || target_str == nullptr) {
    return -1;
    }

    int src_len = strlen(src_str);
    int target_len = strlen(target_str);

    int *next = new int[src_len];
    build_next_table(next, target_str);

    for (int i = 0; i< target_len;i++)
    {
    std::cout << next[i] << " ";
    }

    int cur_find = 0;
    for (int i = 0; i < src_len;)
    {
    if (src_str[i] == target_str[cur_find])
    {
    i++;
    cur_find++;
    }
    else
    {
    if (cur_find == 0)
    {
    ++i;
    }
    else
    {
    cur_find = next[cur_find-1];
    }
    }

    if(cur_find == target_len)
    {
    delete []next;
    return i - target_len;
    }
    }

    delete []next;
    return -1;
    }

通过hash解决字符串问题

  • 有一些关于string的算法题目,例如:
    1. 判断两个string是否为各自的排列?
    2. 判断string中字符是否重复?

给一个int[]和一个int,数组中是否存在两个数之和为target?

  1. 暴力解法:先从数组中确定一个,再遍历其他的数,计算和是否为target -> O(n*n)
  2. 通过 hashtable解决,将 int[] 转化成 map[int]int,计算 target-array[i] 是否在map中 -> O(n)

给一个int[],找出最长连续的子数组?

  • 例如:给你[31,9,74,3,4,2,1,],返回[1,2,3,4]
  • 思路如下:
    • 首先使用hash table去除重复元素
    • 遍历 hash table,对于每一个元素x,如果 x-1 存在,则这个数必定不是最长的
    • 然后对于 x-1 不存在的元素,依次搜索 x+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

      // longest slice in array
      func maxSubString(src []int) int {
      m := make(map[int]bool)

      // delete repeated
      for _, v := range src {
      m[v] = true
      }

      longest := 0
      for value := range m {
      // not the longest
      if _, isExist := m[value-1]; isExist {
      continue
      }

      tempLongest := 0
      for {
      if _, isExist := m[value+tempLongest]; !isExist {
      break
      }
      tempLongest++
      }

      if tempLongest > longest {
      longest = tempLongest
      }
      }

      return longest
      }

给出两个字符串,返回最长的相同子串?

  • 动态规划 问题
  • 例如:s1=”abcde”  s2= “ace”,求两个字符串的公共子序列,答案是“ace”
    1. S{s1,s2,s3….si} T{t1,t2,t3,t4….tj}
    2. 子问题划分
      (1) 如果S的最后一位等于T的最后一位,则最大子序列就是{s1,s2,s3…si-1}和{t1,t2,t3…tj-1}的最大子序列+1
      (2) 如果S的最后一位不等于T的最后一位,那么最大子序列就是
      ① {s1,s2,s3..si}和 {t1,t2,t3...tj-1} 最大子序列
      ② {s1,s2,s3...si-1}和{t1,t2,t3....tj} 最大子序列    
      - 以上两个自序列的最大值
    3. 边界
      • 只剩下{s1}和{t1},如果相等就返回1,不等就返回0
    4. 使用一个表格来存储dp的结果
      • 如果 S[i] == T[j] 则dp[i][j] = dp[i-1][j-1] + 1
      • 否则dp[i][j] = max(dp[i][j-1],dp[i-1][j])

result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func longestCommonSubsequence(text1 string, text2 string) int {
var dp [][]int

for i := 0; i <= len(text1); i++ {
temp := make([]int, len(text2)+1)
dp = append(dp, temp)
}

for i := 1; i <= len(text1); i++ {
for j := 1; j <= len(text2); j++ {
if text1[i-1] == text2[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
if dp[i-1][j] > dp[i][j-1] {
dp[i][j] = dp[i-1][j]
} else {
dp[i][j] = dp[i][j-1]
}
}
}
}

return dp[len(text1)][len(text2)]
}