这是一道基本的题目,简单说来就是三次翻转 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