【算法系列 四】 String

1. 字符串循環左移(九度OJ1362),要求時間複雜度O(N),空間複雜度O(1)

這是一道基本的題目,簡單說來就是三次翻轉 php

好比:abcdef 左移兩位 cdefab html

過程: java

ab 翻轉 ba 算法

cdef 翻轉 fedc 數組

將上面兩個翻轉後的結果拼接 bafedc app

再翻轉cdefab獲得結果 函數

代碼: 優化

import java.io.IOException;
import java.util.Scanner;

public class Main
{
	public static void main(String[] args) throws IOException
	{
		Scanner cin = new Scanner(System.in);
		int N;
		String str;
		while (cin.hasNext())
		{
			str = cin.next();
			N = cin.nextInt();
			N = N % str.length();
			String a = str.substring(0, N);
			String b = str.substring(N);
			StringBuffer abuffer = new StringBuffer(a);
			StringBuffer bbuffer = new StringBuffer(b);
			StringBuffer areverse = abuffer.reverse();
			StringBuffer breverse = bbuffer.reverse();
			StringBuffer creverse = areverse.append(breverse);
			System.out.println(creverse.reverse().toString());
		}
	}

}
另外:循環左移K位等價於循環右移n-K位

2. 完美洗牌算法,要求時間複雜度O(N),時間複雜度O(1)

將{a1,a2,a3,...,an,b1,b2,b3,...,bn}變成{a1,b1,a2,b2,a3,b3,...,an,bn} 編碼

0、樸素的想法:記錄一半的數據,依次從新插入。顯然空間複雜度是O(N)。須要作點「高級」的分析。 spa

一、題目要求空間複雜度爲O(1),顯然除了固定數目的臨時變量不能額外開闢內存。這個要求變相的告訴咱們:只能在原始數組上就地整理,不能新申請數組。

二、對原始位置的變化作以下分析:

依次kao察每一個位置的變化規律:

a1:1 -> 2
a2:2 -> 4
a3:3 -> 6
a4:4 -> 8
b1:5 -> 1
b2:6 -> 3
b3:7 -> 5
b4:8 -> 7

2.一、馬上能夠發現變化規律:
對於原數組位置i的元素,新位置是(2*i)%(2n+1),注意,這裏用2n表示原數組的長度。後面依然使用該表述方式。

2.二、有了該表達式:i' = (2*i)%(2n+1),困難的不是尋找元素在新數組中的位置,而是爲該元素「騰位置」。若是使用暫存的辦法,空間複雜度必然要達到O(N),所以,須要換個思路。

2.三、咱們這麼思kao:a1從位置1移動到位置2,那麼,位置2上的元素a2變化到了哪裏呢?繼續這個線索,咱們獲得一個「封閉」的環:
1 -> 2 -> 4 -> 8 -> 7 -> 5 -> 1
沿着這個環,能夠把a一、a二、a四、b四、b三、b1這6個元素依次移動到最終位置;顯然,由於每次只移動一個元素,代碼實現時,只使用1個臨時空間便可完成。(即:a=t;t=b;b=a)
此外,該變化的另一個環是:
3 -> 6 -> 3
沿着這個環,能夠把a三、b2這2個元素依次移動到最終位置。

2.四、上述過程能夠經過若干的「環」的方式完整元素的移動,這是巧合嗎?事實上,該問題的研究成果已經由Peiyush Jain在10nian前公開發表在A Simple In-Place Algorithm for In-Shuffle, Microsoft, July, 2004中。原始論文直接使用了一個結論,這裏再也不證實:對於2*n =(3^k-1)這種長度的數組,剛好只有k個環,且每一個換的起始位置分別是1,3,9,...3^(k-1)。
對於2.3的例子,長度爲8,是3^2-1,所以,只有2個環。環的其實位置分別是1和3。

2.五、至此,完美洗牌算法的「主體工程」已經完工,只存在一個「小」問題:若是數組長度不是(3^k-1)呢?

2.5.一、若2n!=(3^k-1),則總能夠找到最大的整數m,使得m<n,而且2m=(3^k-1)。

2.5.二、對於長度爲2m的數組,調用2.3和2.4中的方法整理元素,剩餘的(2n-2m)長度,遞歸調用2.5.1便可。

2.5.三、在2.5.2中,須要交換一部分數組元素:

(下面使用[a,b]表示從a到b的一段子數組,包括端點)
①圖中斜線陰影部分的子數組[1,m]應該和[n 1,n m]組成一個序列,調用2.3和2.4中的算法;
②所以,數組[m,n-m]循環左移n-m次便可。(注:字符串旋轉是有空間複雜度O(1)的算法的,詳情請看本文第一題)

2.六、以上,完成了該問題的所有求解過程。關於2*n =(3^k-1)知足k個環的問題,贅述很長,不妨kao察一下ψ(3)和ψ(9)。這裏,ψ(N)即歐拉函數,表示小於N的天然數中,和N互素的數目。

2.七、原始問題要輸出a1,b1,a2,b2……an,bn,而完美洗牌卻輸出的是b1,a1,b2,a2,……bn,an。解決辦法很是簡單:忽略原數組中的a1和bn,對於a2,a3,……an,b1,b2,……bn-1調用完美洗牌算法,即爲結論。

3. 最長公共子序列(九度OJ 1042

動態規劃思想

思想簡單描述:

若是兩個字符串最後一位相同,則最後一位字符確定是最長公共子序列的最後一位。

若是最後一位不一樣,則有可能第一個字符串中的最後一位是公共子序列,也多是第二個字符串中的最後一位。固然也可能都不是則LCS(Xm,Yn)=LCS(Xm-1,Yn-1),可是這種狀況包含在max{LCS(Xm-1,Yn),LCS(Xm,Yn-1)}中。

因此得出上面的式子。

Coding:

最直觀的代碼就是遞歸:

import java.io.IOException;
import java.util.Scanner;

public class Main
{
	static char[] x = new char[50];
	static char[] y = new char[50];

	public static void main(String[] args) throws IOException
	{
		Scanner cin = new Scanner(System.in);
		String a, b;
		while (cin.hasNext())
		{
			a = cin.next();
			b = cin.next();
			x = a.toCharArray();
			y = b.toCharArray();
			int res = LCS(x.length - 1, y.length - 1);
			System.out.println(res);
		}
	}

	public static int LCS(int i, int j)
	{
		if (i < 0 || j < 0)
		{
			return 0;
		}
		if (x[i] == y[j])
		{
			return LCS(i - 1, j - 1) + 1;
		}
		else
		{
			int aa = LCS(i, j - 1);
			int bb = LCS(i - 1, j);
			return aa > bb ? aa : bb;
		}
	}

}
可是遞歸的效率過低,而且有太多的重複操做。

咱們使用打表的方式來避免遞歸操做:

使用一個二維數組C[m,n]來保存LCS

C[i,j]表明Xi,Yj的最長公共子序列

當i=0或者j=0時表明有一個字符串爲空,則C[i,j] =0

import java.io.IOException;
import java.util.Scanner;

public class Main
{
	static char[] x = new char[50];
	static char[] y = new char[50];

	public static void main(String[] args) throws IOException
	{
		Scanner cin = new Scanner(System.in);
		String a, b;
		while (cin.hasNext())
		{
			a = cin.next();
			b = cin.next();
			x = a.toCharArray();
			y = b.toCharArray();
			int res = LCS(x.length, y.length);
			System.out.println(res);
		}
	}

	public static int LCS(int i, int j)
	{
		int[][] c = new int[50][50];
		for (int k = 0; k <= i; k++)
		{
			c[k][0] = 0;
		}
		for (int k = 0; k <= j; k++)
		{
			c[0][k] = 0;
		}
		for (int k = 1; k <= i; k++)
		{
			for (int k2 = 1; k2 <= j; k2++)
			{
				if (x[k - 1] == y[k2 - 1])
				{
					c[k][k2] = c[k - 1][k2 - 1] + 1;
				}
				else
				{
					c[k][k2] = (c[k][k2 - 1] > c[k - 1][k2] ? c[k][k2 - 1]
							: c[k - 1][k2]);
				}
			}
		}
		return c[i][j];
	}

}
時間複雜度O(m*n)

4. 最長遞增子序列(Leetcode 300

例如:10, 9, 2, 5, 3, 7, 101, 18

輸出:2, 3, 7, 101

很簡單的思路就是,使用最長公共子序列來解決這個問題

最長公共子序列的解法在第3題中已經解釋了。

解決最長遞增子序列只須要

將原序列:10, 9, 2, 5, 3, 7, 101, 18

將原序列排序後的序列: 2, 3, 5, 7, 9, 10, 18, 101

這兩個序列求最長公共子序列,獲得的序列就是最長遞增子序列

代碼:

public class Solution {
    public int lengthOfLIS(int[] nums) {
        Integer[] s = new Integer[nums.length];
		for (int i = 0; i < nums.length; i++)
		{
			s[i] = nums[i];
		}
		TreeSet<Integer> treeSet = new TreeSet<Integer>(Arrays.asList(s));
		s =  treeSet.toArray(new Integer[0]);
		int[][] c = new int[nums.length + 1][nums.length + 1];
		for (int i = 0; i <= nums.length; i++)
		{
			c[i][0] = 0;
		}
		for (int i = 0; i <= s.length; i++)
		{
			c[0][i] = 0;
		}
		for (int i = 1; i <= nums.length; i++)
		{
			for (int j = 1; j <= s.length; j++)
			{
				if (nums[i - 1] == s[j - 1])
				{
					c[i][j] = c[i - 1][j - 1] + 1;
				}
				else
				{
					c[i][j] = c[i - 1][j] > c[i][j - 1] ? c[i - 1][j]
							: c[i][j - 1];
				}
			}
		}
		return c[nums.length][s.length];
    }
}

因爲是遞增子序列,因此排序後的序列須要去重,這裏用TreeSet即作了排序又去了重。時間複雜度O(n*n)

固然咱們也可使用動態規劃的思想去解決這個問題

維護一個dp[]數組,dp[i]的意思是,必須以arr[i]結尾的最大遞增子序列是多少。

好比arr = {1,2,3,2}

那麼dp[3]的意思是,必須以arr[3]=2爲最後的最大遞增子序列,即{1,2}

那麼咱們知道了dp[i]後,如何獲得dp[i+1]呢。

根據最大遞增子序列的定義咱們就能知道,dp[i+1]是dp[0...i]中最大的值dp[j],而且arr[j]<arr[i+1],這樣咱們也獲得了一個O(n*n)的算法

public class Solution
{
	public int lengthOfLIS(int[] nums)
	{
		if(nums.length == 0)
			return 0;
		int[] dp = new int[nums.length];
		dp[0] = 1;
		for (int i = 1; i < nums.length; i++)
		{
			int max = 0;
			for (int j = i - 1; j >= 0; j--)
			{
				if (dp[j] > max && nums[j] < nums[i])
				{
					max = dp[j];
				}
			}
			dp[i] = max + 1;
		}
		int max = 0;
		for (int i = 0; i < dp.length; i++)
		{
			if(dp[i] > max)
			{
				max = dp[i];
			}
		}
		return max;
	}
}
最大遞增子序列就是dp數組中最大的一個值

那麼咱們發現求dp[i]時須要遍歷dp[0...i-1]的全部元素,可否優化這個操做呢?

假設存在一個序列d[1..9] ={ 2,1 ,5 ,3 ,6,4, 8 ,9, 7},能夠看出來它的LIS長度爲5。
下面一步一步試着找出它。
咱們定義一個序列B,而後令 i = 1 to 9 逐個查看這個序列。
此外,咱們用一個變量Len來記錄如今最長算到多少了

首先,把d[1]有序地放到B裏,令B[1] = 2,就是說當只有1一個數字2的時候,長度爲1的LIS的最小末尾是2。這時Len=1

而後,把d[2]有序地放到B裏,令B[1] = 1,就是說長度爲1的LIS的最小末尾是1,d[1]=2已經沒用了,很容易理解吧。這時Len=1

接着,d[3] = 5,d[3]>B[1],因此令B[1+1]=B[2]=d[3]=5,就是說長度爲2的LIS的最小末尾是5,很容易理解吧。這時候B[1..2] = 1, 5,Len=2

再來,d[4] = 3,它正好加在1,5之間,放在1的位置顯然不合適,由於1小於3,長度爲1的LIS最小末尾應該是1,這樣很容易推知,長度爲2的LIS最小末尾是3,因而能夠把5淘汰掉,這時候B[1..2] = 1, 3,Len = 2

繼續,d[5] = 6,它在3後面,由於B[2] = 3, 而6在3後面,因而很容易能夠推知B[3] = 6, 這時B[1..3] = 1, 3, 6,仍是很容易理解吧? Len = 3 了噢。

第6個, d[6] = 4,你看它在3和6之間,因而咱們就能夠把6替換掉,獲得B[3] = 4。B[1..3] = 1, 3, 4, Len繼續等於3

第7個, d[7] = 8,它很大,比4大,嗯。因而B[4] = 8。Len變成4了

第8個, d[8] = 9,獲得B[5] = 9,嗯。Len繼續增大,到5了。

最後一個, d[9] = 7,它在B[3] = 4和B[4] = 8之間,因此咱們知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。

因而咱們知道了LIS的長度爲5。

注意,這個1,3,4,7,9不是LIS,它只是存儲的對應長度LIS的最小末尾。有了這個末尾,咱們就能夠一個一個地插入數據。雖然最後一個d[9] = 7更新進去對於這組數據沒有什麼意義,可是若是後面再出現兩個數字 8 和 9,那麼就能夠把8更新到d[5], 9更新到d[6],得出LIS的長度爲6。

而後應該發現一件事情了:在B中插入數據是有序的,並且是進行替換而不須要挪動——也就是說,咱們可使用二分查找,將每個數字的插入時間優化到O(logN)~~~~~因而算法的時間複雜度就下降到了O(NlogN)~!

代碼以下(代碼中的數組B從位置0開始存數據):

public class Solution
{
	public int lengthOfLIS(int[] nums)
	{
		if (nums.length == 0)
			return 0;
		int[] dp = new int[nums.length];
		int[] B = new int[nums.length];
		dp[0] = 1;
		B[0] = nums[0];
		int begin = 0;
		int middle = 0;
		int end = 0;
		int right = 0;
		for (int i = 1; i < nums.length; i++)
		{
			begin = 0;
			end = right;
			while (begin <= end)
			{
				middle = (begin + end) / 2;
				if (nums[i] > B[middle])
				{
					begin = middle + 1;
				}
				else
				{
					end = middle - 1;
				}
			}
			right = right > begin ? right : begin;
			B[begin] = nums[i];
			dp[i] = begin + 1;
		}
		int max = 0;
		for (int i = 0; i < dp.length; i++)
		{
			if (dp[i] > max)
			{
				max = dp[i];
			}
		}
		return max;
	}
}

5. KMP算法(LeetCode 28

給定文本串text與模式串pattern。從文本串text中找到模式串pattern第一次出現的位置。

KMP是一種線性時間複雜度的字符串匹配算法,它是對暴力算法的改進。

文本串長度爲N,模式串長度爲M,KMP算法時間複雜度爲O(M+N),空間複雜度爲O(M)(next數組)


暴力算法思想很簡單,就是不斷匹配,如上圖,文本串從i位置開始,模式串從0位置開始匹配,若匹配失敗,則文本串從i+1位置開始,模式串回溯到0位置。

暴力求解的時間輔助度爲O(M*N),空間複雜度爲O(1)

而KMP算法的思想則是儘可能減小回溯的發生

如上圖兩個字符串,當發現綠色部分與黃色部分不相等時,若是是暴力算法,則模式串要從0開始從新匹配,而KMP的思想則是,若是A和B是相同的,d與黃色部分不相等,不須要從0開始比較,能夠從c開始比較。

由於A與B是相同的,能比較到綠色和黃色是否相等,即綠色前面和黃色前面是相等的,因此A與黃色前面字符串是相等的。這樣就減小了回溯。

因此KMP的問題就歸結到若是求出模式串中的最大相等的k前綴與k後綴。

那麼該如何高效地求得next[j]呢?

next數組有以下遞推關係:

當next[j]=k,且p[k]==p[j]時,則很明顯next[j+1]=next[j]+1

p[k]不等於p[j]


記next[k]=h,因此上圖中的1,3,2都是相等的,即1和2是相等的,那麼只須要比較藍色部分和p[j]是否相等,若是相等又回到了第一種狀況,若是不相同則再查看next[h]

代碼:

public class Solution
{
	public int strStr(String haystack, String needle)
	{
		if(needle.length() == 0)
		{
			return 0;
		}
		if(haystack.length() == 0)
		{
			return -1;
		}
		int ans = -1;
		int needle_n = needle.length();
		int haystack_n = haystack.length();
		int[] next = new int[needle_n];
		goNext(next, needle);
		char[] haystackchar = haystack.toCharArray();
		char[] needlechar = needle.toCharArray();
		int i = 0;
		int j = 0;
		while (i < haystack_n)
		{
			if (j == -1 || haystackchar[i] == needlechar[j])
			{
				++i;
				++j;
			}
			else
			{
				j = next[j];
			}
			if(j == needle_n)
			{
				ans = i - needle_n;
				break;
			}
			
		}

		return ans;
	}

	public void goNext(int[] next, String needle)
	{
		next[0] = -1;
		int j = 0;
		int k = -1;
		int length = needle.length();
		char[] p = needle.toCharArray();
		while (j < length - 1)
		{
			// k表示next[j - 1],p[k]是前綴,p[j]是後綴
			if (k == -1 || p[k] == p[j])
			{
				++j;
				++k;
				next[j] = k;
			}
			else
			{
				k = next[k];
			}
		}
	}
}
優化:

若是i與j不相等,按照上述描述,應該講模式串移到next[j]處,假設next[j]=k,若是k與j是相等的,那麼k與i必然不相等,因此還要繼續移到next[k]處。

那麼何不直接將next[j]=next[k]呢,少了一步比較,效率更高。

public void goNext(int[] next, String needle)
	{
		next[0] = -1;
		int j = 0;
		int k = -1;
		int length = needle.length();
		char[] p = needle.toCharArray();
		while (j < length - 1)
		{
			// k表示next[j - 1],p[k]是前綴,p[j]是後綴
			if (k == -1 || p[k] == p[j])
			{
				++j;
				++k;
				if(p[k] == p[j])
				{
					next[j] = next[k];
				}else
				{
					next[j] = k;
				}
			}
			else
			{
				k = next[k];
			}
		}
	}
KMP(沒有優化)的最好狀況是,模式串中不存在相等的k前綴和k後綴,則next數組都是-1。一旦不匹配就跳過,比較次數是N

最差狀況是,模式串中全部字符都是相等的,next數組是遞增序列-1,0,1,2……

最差狀況:

比較次數<2N

當優化後的KMP,最差狀況也變成了最好狀況

比較次數爲N

6. Power Strings(POJ 2406

求字符串的最小週期串,

例如

ababab的最小週期串是ab,重複了3次

aaaa的最小週期串是a,重複了4次

很直觀的想法就是,暴力求解,

假設字符串是ababab

先拿一個字符a試,沒法遍歷babab

再拿兩個字符試ab,能夠遍歷abab。

總之拿字符串長度可以整除的數去嘗試。

import java.io.IOException;
import java.util.Scanner;

public class Main
{
	public static void main(String[] args) throws IOException
	{
		Scanner cin = new Scanner(System.in);
		String str;
		while (cin.hasNext())
		{
			str = cin.next();
			if(str.equals("."))
			{
				break;
			}
			for (int i = 1; i <= str.length(); i++)
			{
				if(str.length() % i != 0)
				{
					continue;
				}
				String pattern = str.substring(0, i);
				String temp = str;
				while(temp.length() > 0)
				{
					if(temp.startsWith(pattern))
					{
						temp = temp.substring(i);
					}else {
						break;
					}
				}
				if(temp.length() == 0)
				{
					System.out.println(str.length()/i);
					break;
				}
			}
		}
		
	}

}
時間輔助度爲O(n^2),有沒有更好的方法呢?

咱們想到了KMP

求KMP中的next數組(非優化求法),記p =len - next[len],若是len%p==0,則p就是最小週期長度。

證實:

如上圖,黃色部分就是next中的最長相等先後綴,兩個綠色部分相等,即上圖中下面部分的1=1,又1=2,並且2=2……如此迭代,,若是整個字符串的長度整除1,恰好可以遍歷完整個字符串,則1就是最小週期長度。

代碼:

import java.io.IOException;
import java.util.Scanner;

public class Main
{
	public static void main(String[] args) throws IOException
	{
		Scanner cin = new Scanner(System.in);
		String str;
		int[] next = new int[1000001];
		while (cin.hasNext())
		{
			str = cin.next();
			if(str.equals("."))
			{
				break;
			}
			getNext(next , str);
			int minlength = str.length() - next[str.length()] ;
			if(str.length() % minlength == 0)
			{
				System.out.println(str.length() / minlength);
			}else {
				System.out.println(1);
			}
		}
		
	}
	public static void getNext(int[] next ,String str)
	{
		int length = str.length();
		char[] p = str.toCharArray();
		next[0] = -1;
		int k = -1;
		int j = 0;
		while(j < length)
		{
			if(k == -1 || p[k] == p[j])
			{
				++j;
				++k;
				next[j] = k;
			}else {
				k = next[k];
			}
		}
	}

}

7. 用二進制來編碼字符串「abcdabaa",須要可以根據編碼,解碼回原來的字符串,最少須要__位的二進制字符串?

使用哈夫曼樹來解決這個問題:

哈夫曼樹是基於統計的編碼方式,機率高的字符使用較短編碼,例子以下:

結點上面的數字表示頻數。

同理,上述題目中使用哈夫曼樹獲得的結果就是:

a:1

b:01

c:001

d:000

因此須要14位。

由於哈夫曼編碼是前綴編碼,即任何一個字符的編碼都不是另一個字符編碼的前綴。因此是能夠解碼惟一的。

8. 最長迴文子串(Leetcode 5

給定一個字符串,求它的最長迴文子串的長度。

最直接的方法就是枚舉每一個子串,看看是不是迴文子串,而後保存最長的子串。

因爲奇數和偶數子串不一樣,因此要遍歷兩次。每一個字符遍歷時都當作迴文的中心向兩邊擴展遍歷。

public class Solution
{
	public String longestPalindrome(String s)
	{
		char[] c = s.toCharArray();
		int max = 1;
		int maxBegin = 0;
		int maxEnd = 0;
		int temp = 0;
		int tempBegin = 0;
		int tempEnd = 0;
		for (int i = 0; i < c.length; i++)
		{
			//奇數
			for (int j = 0; i - j >= 0 && i + j < c.length; j++)
			{
				if (c[i - j] != c[i + j])
				{
					break;
				}
				temp = j * 2 + 1;
				tempBegin = i - j;
				tempEnd = i + j;
			}
			if (temp > max)
			{
				max = temp;
				maxBegin = tempBegin;
				maxEnd = tempEnd;
			}
			//偶數
			for (int j = 0; i - j >= 0 && i + j + 1 < c.length; j++)
			{
				if (c[i - j] != c[i + j + 1])
				{
					break;
				}
				temp = j * 2 + 2;
				tempBegin = i - j;
				tempEnd = i + j + 1;
			}
			if (temp > max)
			{
				max = temp;
				maxBegin = tempBegin;
				maxEnd = tempEnd;
			}
		}
		return s.substring(maxBegin, maxEnd + 1);
	}
}
時間複雜度爲O(n^2)

時間複雜度那麼高的緣由是每次都要從新擴展,i爲中心的擴展並無影響到i+1,致使不少重複擴展。

有沒有什麼好的方法可以下降時間複雜度呢?

這裏就提到了著名的Manacher算法。

首先Manacher算法再也不須要區分奇數迴文和偶數迴文,它使用一種技巧迴避了這個問題。

它將子串中都加入特殊字符

好比aba -> #a#b#a#    abba -> #a#b#b#a#

這樣都只用kao慮奇數狀況往外擴展就能夠了。

那麼如何使擴展更高效呢?

這裏使用了3個變量來輔助擴展

pArr[] 這個是迴文半徑,pArr[i]表示以i爲中心的迴文半徑

pR表示已遍歷過的迴文半徑的最大邊界的下一個

index表示pR的迴文中心

那麼當遍歷到i時,分爲如下幾種狀況,取j和i於index對稱

由於此時j的迴文已經遍歷過了,咱們但願經過j來直接獲得i的迴文,而不須要進行擴展

1. j的迴文包含在index迴文中,那麼由圖能夠知道,必然i的迴文就等於j的迴文

2. j的迴文不包含在index內,出了邊界,能夠得知a不等於d(由於若是相等,index的迴文會擴展到d),a==b==c,因此c不等於d。那麼必然i的迴文會比j的範圍要小一點,i的迴文的右邊界必定是pR-1處

3.j的迴文與左邊界重合,此時很明顯a==b==c==d,可是i的迴文不必定只到d,它能夠繼續嘗試擴展

4.i在最大回文邊界以外,此時沒有優化手段,直接由i進行擴展

從以上的狀況中咱們發現,只有3,4須要進行擴展,而且3的只須要擴展一部分。這樣大大優化了擴展的次數,下降了時間複雜度,Manacher的時間複雜度爲O(n),由於pR最大包括整個字符串。

代碼:

public class Solution
{
	public String longestPalindrome(String s)
	{
		char[] d = s.toCharArray();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.length(); i++)
		{
			sb.append("#");
			sb.append(d[i]);
		}
		sb.append("#");
		char[] c = sb.toString().toCharArray();
		int[] pArr = new int[c.length];
		int pR = 0;
		int index = 0;
		int begin = 0;
		int end = 0;
		int max = 0;
		for (int i = 0; i < c.length; i++)
		{
			if (pR > i)
			{
				if (pArr[2 * index - i] < pR - i)//狀況1
				{
					pArr[i] = pArr[2 * index - i];
				}
				else//狀況2,3
				{
					pArr[i] = pR - i;
				}
			}
			else//狀況4
			{
				pArr[i] = 1;
			}
			while (i + pArr[i] < c.length && i - pArr[i] >= 0
					&& c[i + pArr[i]] == c[i - pArr[i]])
			{
				pArr[i]++;
			}
			if (pArr[i] + i > pR)
			{
				pR = i + pArr[i];
				index = i;
			}
			if(pArr[i] > max)
			{
				max = pArr[i];
				begin = i - pArr[i] + 1;
				end = i + pArr[i] - 1;
			}
		}
		StringBuffer res = new StringBuffer();
		for (int i = begin + 1; i < end; i++)
		{
			if (c[i] != '#')
			{
				res.append(c[i]);
			}
		}
		return res.toString();
	}
}

字符串總結:

字符串查找:CRUD

KMP/BM

map/set;RBtree

hash

trie樹

對字符串自己操做

全排列

Manacher

迴文劃分




系列:

【算法系列 一】 Linked List

【算法系列 二】 Stack

【算法系列 三】 Quene

【算法系列 四】 String


Reference:

1. 七月算法十月算法在線班

2. http://ask.julyedu.com/question/33

3. http://qiemengdao.iteye.com/blog/1660229

4. http://v.qq.com/page/s/j/g/s0157v08yjg.html?start=3

相關文章
相關標籤/搜索