1. 程式人生 > >字串中最長不重複子串和最長迴文子串演算法

字串中最長不重複子串和最長迴文子串演算法

一) 這裡用GOLANG實現了一個查詢最長不重複子串的演算法,在暴力查詢的基礎上作了優化,雖然速度還是比較慢,但是有助於理解後邊高階的演算法,值得一記。

暴力查詢的優化思路:

1)如果我們已經查詢到的最大子串長度比剩下沒有for到的子串還長,那最大子串不可能會在發生改變了,我們就不往下找了,返回這個最大子串長度;

2)這個是最重要的:在我們發現某一個字元在子串中重複時,比如" abcdccefgd"中, i = 0, 在j = 4的時候會發現 4位上的字元'c'和前面位置2上的'c'重複。此時,我們不是i++了,而是 把i 挪到位置2後面的3上,從那裡開始找子串。如果我們不這麼做,而是繼續i++, i = 1 和 i = 2 這兩個也會在j=4處發現重複,從這裡退出,最大不重複子串也仍然是i = 0時得到的那個數。

這個演算法還可以優化,可以考慮提前分配一個數組來記錄該把 i挪到哪裡,而不是用函式呼叫的方法,往下看。

func lengthOfLongestSubstring(s string) int {
    sLen := len(s)   
    var maxSubLen, lastPos int
    
    for i := 0; i < sLen - 1; i++ {
        //第二個for,如果剩下的字串長度都沒有已經找到的maxSubLen長,那我們就可以不去找了
        if maxSubLen < sLen-i {
            for j := i+1; (j < sLen) ; j++ {
                lastPos = findCharInSubStringPosition(s[i:], j-i)
                if lastPos < 0 { 
            //j位置上的字元沒有在s[i:j]中重複,可以測試一下是否要更新maxSubLen
                    if j-i > maxSubLen {
                        maxSubLen = j-i
                    }                
                } else { 
                // j 位置上的字元在s[i:j]中重複,重複位置為i+lastPos,i就從i+lastPos後一位開始
                // 因為若從i+1開始,那麼後面還是會在到達目前的j位置時,出現與i+lastPos重複的情況,
                   
                    i = lastPos + i 
                    //這裡我們把i挪到重複字元的第一個字元上,for結尾的i++幫我們挪到下一位
                    
                    break
                }
            }   
        
        }
        
    }
    
    if sLen != 0 {
        maxSubLen++ //計算過程中,都是用下標計算而已,真正的總數我們需要++一下
    }
    
    return maxSubLen
}

// 這個函式可以找到位置j處字元在 引數s中的位置,如果不存在,則返回-1
func findCharInSubStringPosition(s string, j int) int {
    for i := 0; i < j; i++ {
        if s[i] == s[j] {
            return i
        }           
    }
    
    return -1
}

藉助外部的一個雜湊表來儲存一些東西:

func lengthOfLongestSubstring(s string) int {
  //只支援ASCII碼,
    tmp := [128]int{}
    var start, maxSubstr, i int
    sLen := len(s)
    // nil string return 0
    if sLen == 0 {
        return 0
    }
    // initial hash tbl
    for i := 0; i < len(tmp); i++ {
        tmp[i] = -1
    }     
    
    for i = 0; i < sLen; i++ {
        if tmp[ s[i] ] >= start {
            if s[i] != s[start] && i - tmp[s[i]] > maxSubstr {
                maxSubstr = i - tmp[s[i]]
            }      
            
            start = tmp[ s[i] ] + 1            
        }
        
        if i - start > maxSubstr {
            maxSubstr = i - start 
        }
        
        tmp[ s[i] ] = i
    }
    
    //the last i++ isn't what we want
    i--
    if i - start > maxSubstr {
        maxSubstr = i - start
    }
    
    return maxSubstr + 1
}

二)最長迴文子串演算法

最長迴文子串,首先用暴力搜尋。接著進行改進,改進的是從母字串往後面掃描,同時進行了優化,LeetCode上速度還行:

//最長迴文子串 1
func longestPalindrome(s string) string {
	var subLen, maxLen, pos int
	var i, j int
	sLen := len(s)
	for i = 0; i < sLen; i++ {
		for j = i+1; j <= sLen; j++ {
			subLen = isPalindrome(s[i:j])
			if ( subLen > 0) &&  subLen > maxLen {
				maxLen = subLen
				pos = i
			}
		}
	}

	return s[pos: pos+maxLen]
}

func isPalindrome(s string) int {
	sLen := len(s)
	for i := 0; i < sLen/2; i++ {
		if s[i] != s[sLen - 1 -i] {
			return -1
		}
	}

	return sLen
}
//最長迴文子串2
func longestPalindrome(s string) string {
	var tmpLen, maxLen, pos int
	var i, j int
	sLen := len(s)
	if sLen == 0 {
		return ""
	}

	for i = 0; i < sLen; i++ {
		if (sLen-i)*2 <= maxLen {
			break
		}
		for j = i; j >= 0 && 2*i-j < sLen; j-- {
			if s[j] != s[2*i-j] {
				break
			}
		}
		j++
		tmpLen = 2*(i-j) + 1
		if tmpLen > maxLen {
			maxLen = tmpLen
			pos = j
		}
		for j = 0; j <= i && i+j+1 < sLen; j++ {
			if s[i-j] != s[i+j+1] {
				break
			}
		}
		if j != 0 {
			j--
			tmpLen = 2 * (j+1)
			if tmpLen > maxLen {
				maxLen = tmpLen
				pos = i-j
			}
		}

	}

	return s[pos: pos+maxLen]
}