這是一道基本的題目,簡單說來就是三次翻轉 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位
將{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調用完美洗牌算法,即爲結論。
動態規劃思想
思想簡單描述:
若是兩個字符串最後一位相同,則最後一位字符確定是最長公共子序列的最後一位。
若是最後一位不一樣,則有可能第一個字符串中的最後一位是公共子序列,也多是第二個字符串中的最後一位。固然也可能都不是則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)
例如: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; } }
給定文本串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
求字符串的最小週期串,
例如
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]; } } } }
使用哈夫曼樹來解決這個問題:
哈夫曼樹是基於統計的編碼方式,機率高的字符使用較短編碼,例子以下:
結點上面的數字表示頻數。
同理,上述題目中使用哈夫曼樹獲得的結果就是:
a:1
b:01
c:001
d:000
因此須要14位。
由於哈夫曼編碼是前綴編碼,即任何一個字符的編碼都不是另一個字符編碼的前綴。因此是能夠解碼惟一的。
給定一個字符串,求它的最長迴文子串的長度。
最直接的方法就是枚舉每一個子串,看看是不是迴文子串,而後保存最長的子串。
因爲奇數和偶數子串不一樣,因此要遍歷兩次。每一個字符遍歷時都當作迴文的中心向兩邊擴展遍歷。
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
迴文劃分
系列:
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