github.com/qiuhoude/go-web@v0.0.0-20220223060959-ab545e78f20d/algorithm/datastructures/dp/char_correction_test.go (about) 1 package dp 2 3 import ( 4 "fmt" 5 "testing" 6 ) 7 8 /* 9 字符校正 10 11 12 中文字符的校正 13 中文纠错很多时候是通过拼音进行的,比如 "刘得花"->"liudehua"->"刘德华". 14 拼音检索方法也有很多,比如可以把热门词汇的拼音字母组织成Trie树,每个热词的结尾汉字 15 的最后一个拼音字母就是叶子,整体性能就是O(n)的,n为query的拼音总长度. 除了拼音外 16 也有根据字形(二维文字版的编辑距离?)甚至语义等做的纠错策略。 17 传统搜索引擎中的查询词智能提示、纠错、同义词、近义词、同好词、相关搜索、知识图谱 18 等系列功能统称为用户的意图识别模块。 19 20 */ 21 22 // 1. 莱温斯坦距离 Levenshtein distance 23 24 /* 25 增加一个字符、删除一个字符、替换一个字符 编辑距离都+1 也就是 str1[i]!=str2[i] 编辑距离就+1 26 回溯法方式 27 i 串i的指针 28 j 串j的指针 29 editDis 编辑距离 30 minDis 最小编辑距离 31 */ 32 func lvstBT(i, j, editDis int, stri, strj []rune, minDis *int) { 33 leni := len(stri) 34 lenj := len(strj) 35 if i == leni || j == lenj { // 出口 36 if i < leni { 37 editDis += leni - i 38 } 39 if j < lenj { 40 editDis += lenj - j 41 } 42 if editDis < *minDis { 43 *minDis = editDis 44 } 45 return 46 } 47 if stri[i] == strj[j] { // 相等编辑距离 +0 48 lvstBT(i+1, j+1, editDis, stri, strj, minDis) 49 } else { // 不相等的情况下 , 就进 (i+1,j) (i,j+1) (i+1,j+1) 比较尝试 50 lvstBT(i+1, j, editDis+1, stri, strj, minDis) 51 lvstBT(i, j+1, editDis+1, stri, strj, minDis) 52 lvstBT(i+1, j+1, editDis+1, stri, strj, minDis) 53 } 54 } 55 56 // 动态规划的写法 , 57 // 递推式 stri[i]==strj[j] minDis = min(dis(i-1,j) , dis(i,j-1) ,dis(i-1,j-1)) 58 // stri[i]!=strj[j] minDis = min(dis(i-1,j) , dis(i,j-1) ,dis(i-1,j-1))+1 59 func lvstDP(stri, strj []rune) int { 60 leni := len(stri) 61 lenj := len(strj) 62 dp := make([][]int, leni) 63 for j := range dp { 64 dp[j] = make([]int, lenj) 65 } 66 if stri[0] == strj[0] { 67 dp[0][0] = 0 68 } else { 69 dp[0][0] = 1 70 } 71 // 求出首行 首列的值 72 for i := 1; i < leni; i++ { //首列 73 if stri[i] == strj[0] { 74 dp[i][0] = dp[i-1][0] 75 } else { 76 dp[i][0] = dp[i-1][0] + 1 77 } 78 } 79 for j := 1; j < lenj; j++ { //首行 80 if stri[0] == strj[j] { 81 dp[0][j] = dp[0][j-1] 82 } else { 83 dp[0][j] = dp[0][j-1] + 1 84 } 85 } 86 // 依次填表 87 for i := 1; i < leni; i++ { 88 for j := 1; j < lenj; j++ { 89 if stri[i] == strj[j] { 90 dp[i][j] = min3(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) 91 } else { 92 dp[i][j] = min3(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 93 } 94 } 95 } 96 return dp[leni-1][lenj-1] 97 } 98 99 func min3(a, b, c int) int { 100 return min(a, min(b, c)) 101 } 102 103 func Test_lvst(t *testing.T) { 104 stri := []rune("mitcmu") 105 strj := []rune("mtacnu") 106 editDis1 := 0x7fffffff 107 lvstBT(0, 0, 0, stri, strj, &editDis1) 108 t.Log("最小编辑距离 editDis1:", editDis1) 109 110 editDis2 := lvstDP(stri, strj) 111 t.Log("最小编辑距离 editDis2:", editDis2) 112 } 113 114 // 2. 最长公共子串 Longest common substring length 115 // 最长公共子串长度只允许增加、删除字符这两个编辑操作 116 // 只要当 str1[i] == str2[i] 或str1[i] == str2[i+1] 或str1[i+1] == str2[i] 这三种情况都算长度+1 117 // 回溯写法 118 func lcsBT(i, j, lcs int, stri, strj []rune, maxLen *int) { 119 leni := len(stri) 120 lenj := len(strj) 121 if i == leni || j == lenj { // 出口 122 if lcs > *maxLen { 123 *maxLen = lcs 124 } 125 return 126 } 127 if stri[i] == strj[j] { 128 lcsBT(i+1, j+1, lcs+1, stri, strj, maxLen) 129 } else { 130 lcsBT(i, j+1, lcs, stri, strj, maxLen) 131 lcsBT(i+1, j, lcs, stri, strj, maxLen) 132 lcsBT(i+1, j+1, lcs, stri, strj, maxLen) 133 } 134 } 135 136 // 动态规划的方式 137 func lcsDP(stri, strj []rune) int { 138 leni := len(stri) 139 lenj := len(strj) 140 dp := make([][]int, leni) 141 for j := range dp { 142 dp[j] = make([]int, lenj) 143 } 144 if stri[0] == strj[0] { 145 dp[0][0] = 1 146 } else { 147 dp[0][0] = 0 148 } 149 150 // 求出首行 首列的值 151 for i := 1; i < leni; i++ { //首列 152 if stri[i] == strj[0] && i == 1 { 153 dp[i][0] = dp[i-1][0] + 1 154 } else { 155 dp[i][0] = dp[i-1][0] 156 } 157 } 158 for j := 1; j < lenj; j++ { //首行 159 if stri[0] == strj[j] && j == 1 { 160 dp[0][j] = dp[0][j-1] + 1 161 } else { 162 dp[0][j] = dp[0][j-1] 163 } 164 } 165 for i := 1; i < leni; i++ { 166 for j := 1; j < lenj; j++ { 167 if stri[i] == strj[j] { 168 dp[i][j] = max3(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 169 } else { 170 dp[i][j] = max3(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) 171 } 172 } 173 } 174 return dp[leni-1][lenj-1] 175 } 176 177 func max(a, b int) int { 178 if a > b { 179 return a 180 } 181 return b 182 } 183 184 func max3(a, b, c int) int { 185 return max(a, max(b, c)) 186 } 187 188 func Test_lcs(t *testing.T) { 189 stri := []rune("mitcmu") 190 strj := []rune("mtacnu") 191 lsc1 := 0 192 lcsBT(0, 0, 0, stri, strj, &lsc1) 193 t.Log(" 最长公共子串 lsc1:", lsc1) 194 195 lsc2 := lcsDP(stri, strj) 196 t.Log(" 最长公共子串 lsc2:", lsc2) 197 } 198 199 /* 200 我们有一个数字序列包含 n 个不同的数字,如何求出这个序列中的最长递增子序列长度?比如 201 2, 9, 3, 6, 5, 1, 7 这样一组数字序列,它的最长递增子序列就是 2, 3, 5, 7,所以最长递增子序列 202 的长度是 4 203 */ 204 205 func longestSubsequence(arr []int) int { 206 n := len(arr) 207 dp := make([]int, n) // dp 下标的状态当数组长度, 里面的值表示数组当前长度是对于最大的递增串的长度 208 for i := range dp { 209 dp[i] = 1 210 } 211 for i := 1; i < n; i++ { 212 for j := i - 1; j >= 0; j-- { 213 if arr[i] >= arr[j] { //倒序遍历,只要找到 后面比前面大的 就+1 214 dp[i] = dp[j] + 1 215 break 216 } else if dp[i] == 1 { 217 dp[i] = dp[i-1] 218 } 219 } 220 } 221 fmt.Println(dp) 222 return dp[n-1] 223 } 224 225 func Test_longestSubsequenceBT(t *testing.T) { 226 arr := []int{2, 9, 3, 6, 5, 1, 7} 227 ret := longestSubsequence(arr) 228 t.Log("ret:", ret) 229 }