46:把数字翻译成字符串
题目要求:
给定一个数字,按照如下规则翻译成字符串:0翻译成“a”,1翻译成“b”...25翻译成“z”。
一个数字有多种翻译可能,例如12258一共有5种,分别是bccfi,bwfi,bczi,mcfi,mzi。
实现一个函数,用来计算一个数字有多少种不同的翻译方法。
解题思路:
下面我们从自上而下和自下而上两种角度分析这道题目,以12258为例:
自上而下,从最大的问题开始,递归 :
12258
/ \
b+2258 m+258
/ \ / \
bc+258 bw+58 mc+58 mz+8
/ \ \ \ \
bcc+58 bcz+8 bwf+8 mcf+8 mzi
/ \ \ \
bccf+8 bczi bwfi mcfi
/
bccfi
有很多子问题被多次计算,比如258被翻译成几种这个子问题就被计算了两次。
自下而上,动态规划,从最小的问题开始 :
f(r)表示以r为开始(r最小取0)到最右端所组成的数字能够翻译成字符串的种数。
对于长度为n的数字,f(n)=0,f(n-1)=1,求f(0)。
递推公式为 f(r-2) = f(r-1)+g(r-2,r-1)*f(r);
其中,如果r-2,r-1能够翻译成字符,则g(r-2,r-1)=1,否则为0。
因此,对于12258:
f(5) = 0
f(4) = 1
f(3) = f(4)+0 = 1
f(2) = f(3)+f(4) = 2
f(1) = f(2)+f(3) = 3
f(0) = f(1)+f(2) = 5
package chapter5;
public class P231_TranslateNumbersToStrings {
public static int getTranslationCount(int number){
if(number<0)
return 0;
if(number==1)
return 1;
return getTranslationCount(Integer.toString(number));
}
//动态规划,从右到左计算。
//f(r-2) = f(r-1)+g(r-2,r-1)*f(r);
//如果r-2,r-1能够翻译成字符,则g(r-2,r-1)=1,否则为0
public static int getTranslationCount(String number) {
int f1 = 0,f2 = 1,g = 0;
int temp;
for(int i=number.length()-2;i>=0;i--){
if(Integer.parseInt(number.charAt(i)+""+number.charAt(i+1))<26)
g = 1;
else
g = 0;
temp = f2;
f2 = f2+g*f1;
f1 = temp;
}
return f2;
}
public static void main(String[] args){
System.out.println(getTranslationCount(-10)); //0
System.out.println(getTranslationCount(1234)); //3
System.out.println(getTranslationCount(12258)); //5
}
}
47:礼物的最大值
题目要求:
在一个m*n的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于0)。
从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,
求拿到礼物的最大价值。例如,对于如下棋盘
1 10 3 8
12 2 9 6
5 7 4 11
3 7 16 5
礼物的最大价值为1+12+5+7+7+16+5=53。
解题思路:
思路1:动态规划
申请一个与原矩阵行列数一样的二维数组dp[][],初始化dp[0][i] = data[0][i]
;然后依次更新dp的每一行即可。由于第m行的值与第m-1行和第m行有关,因此可以
对dp进行简化,仅用两行的dp,即dp[2][]即可完成状态的记录与更新。
思路2:广度优先遍历
这个棋盘其实可以看成一个有向图,起点为左上角,终点为右下角,每一点仅仅指向
右侧和下侧。因此我们可以从左上角开始进行广度优先遍历。此外,遍历过程中可以
进行剪枝,最终移动到右下角时会仅剩下一个枝,该路径所经的点的数值之和即为所求。
package chapter5;
import java.util.LinkedList;
import java.util.Queue;
public class P233_MaxValueOfGifts {
//方法一:动态规划
public static int getMaxVaule(int[][] data){
if(data.length==0||data[0].length==0)
return 0;
int[][] dp = new int[2][data[0].length];
int dpCurRowIndex = 0,dpPreRowIndex = 0;
for(int row=0;row=dp[dpCurRowIndex][col-1])
dp[dpCurRowIndex][col] = dp[dpPreRowIndex][col]+data[row][col];
else
dp[dpCurRowIndex][col] = dp[dpCurRowIndex][col-1]+data[row][col];
}
}
}
return dp[(data.length-1)&1][data[0].length-1];
}
//方法二:有向图的遍历(广度优先,可再剪枝进行优化)
public static int getMaxVaule2(int[][] data){
if(data.length==0||data[0].length==0)
return 0;
int maxRowIndex = data.length-1;
int maxColIndex = data[0].length-1;
Queue queue = new LinkedList<>();
queue.offer(new Node(0,0,data[0][0]));
Node nodeCur = null;
while (!(queue.peek().row==maxRowIndex && queue.peek().col==maxColIndex)){
nodeCur = queue.poll();
if(nodeCur.row!=maxRowIndex)
queue.offer(new Node(nodeCur.row+1,nodeCur.col,nodeCur.sum+data[nodeCur.row+1][nodeCur.col]));
if(nodeCur.col!=maxColIndex)
queue.offer(new Node(nodeCur.row,nodeCur.col+1,nodeCur.sum+data[nodeCur.row][nodeCur.col+1]));
}
int maxSum = 0,temp = 0;
while (!queue.isEmpty()){
temp = queue.poll().sum;
if(temp>maxSum)
maxSum = temp;
}
return maxSum;
}
public static class Node{
int row;
int col;
int sum;
public Node(int r,int c,int s){
row = r;col = c;sum = s;
}
}
public static void main(String[] args){
int[][] data = {
{1,10,3,8},
{12,2,9,6},
{5,7,4,11},
{3,7,16,5}};
System.out.println(getMaxVaule(data));
System.out.println(getMaxVaule2(data));
}
}
- 最长不含重复字符的子字符串
题目要求:
输入一个字符串(只包含a~z的字符),求其最长不含重复字符的子字符串的长度。
例如对于arabcacfr,最长不含重复字符的子字符串为acfr,长度为4。
解题思路:
动态规划。用dp[]记录状态,dp[i]表示以下标为i的字符结尾不包含重复字符的
最长子字符串长度。初始化dp[0] = 1,求maxdp。每次可以根据dp的前一个状态
推导出后一个状态,因此可以省略dp数组,使用一个变量记录dp值,使用maxdp
记录最大的dp值。
package chapter5;
public class P236_LongestSubstringWithoutDup {
//动态规划
//dp[i]表示以下标为i的字符结尾不包含重复字符的最长子字符串长度
public static int longestSubstringWithoutDup(String str){
if(str==null || str.length()==0)
return 0;
//dp数组可以省略,因为只需记录前一位置的dp值即可
int[] dp = new int[str.length()];
dp[0] = 1;
int maxdp = 1;
for(int dpIndex = 1;dpIndex=dpIndex-dp[dpIndex-1];i--){
if(str.charAt(dpIndex)==str.charAt(i))
break;
}
dp[dpIndex] = dpIndex - i;
if(dp[dpIndex]>maxdp)
maxdp = dp[dpIndex];
}
return maxdp;
}
public static void main(String[] args){
System.out.println(longestSubstringWithoutDup("arabcacfr"));
System.out.println(longestSubstringWithoutDup("abcdefaaa"));
}
}
public class P236_LongestSubstringWithoutDup {
public static void main(String[] args) {
System.out.println(findLongestSubstringLength("arabcacfr")); //4
System.out.println(findLongestSubstringLength("bbb")); //1
System.out.println(findLongestSubstringLength("")); //0
}
private static int findLongestSubstringLength(String string) {
if (string == null || string.equals("")) return 0;
int maxLength = 0;
int curLength = 0;
int[] positions = new int[26];
for (int i = 0; i < positions.length; i++) {
positions[i] = -1; //初始化为-1,负数表示没出现过
}
for (int i = 0; i < string.length(); i++) {
int curChar = string.charAt(i) - 'a';
int prePosition = positions[curChar];
//当前字符与它上次出现位置之间的距离
int distance = i - prePosition;
//当前字符第一次出现,或者前一个非重复子字符串中没有包含当前字符
if (prePosition < 0 || distance > curLength) {
curLength++;
} else {
curLength = distance;
}
positions[curChar] = i; //更新字符出现的位置
//更新最长非重复子字符串的长度
if (curLength > maxLength) {
maxLength = curLength;
}
}
return maxLength;
}
}
- 丑数
题目要求:
我们把只包含因子2,3,5的数成为丑数。求按照从小到大的顺序第1500个丑数。1作为第一个丑数。
解题思路:
思路1:从1开始递增,依次判断每个数是否是丑数,不够高效;
思路2:思路1之所以效率低,比较关键的一点是遍历的每一个数字都进行丑数判断。思路2
不是去判断丑数,而是计算出丑数:因为每个丑数都可以看成是由1去乘以2、3、5,再乘以
2、3、5而衍生出来的。可以用三个指针指向第一个丑数1,三个指针分别表示乘2,乘3,乘5,
将三个指针计算出来的最小的丑数放在数组中,并将该指针向后移动一个位置。为了得到第
1500个丑数,需要一个长度1500的数组来记录已经计算出来的丑数。因此这个思路也可以说
是用空间换时间。
package chapter5;
public class P240_GetUglyNumber {
public static int getUglyNumber(int num){
if(num<=0)
return 0;
int number = 0,uglyFound = 0;
while (uglyFound
- 第一个只出现一次的字符
题目要求:
字符串中第一个只出现一次的字符。在字符串中找出第一个只出现一次的字符。
如输入abaccdeff,则输出b。
解题思路:
思路1:暴力求解,从前到后依次判断每个字符是否只出现一次,时间复杂度
o(n^2),空间复杂度o(1);
思路2:用空间换时间。这个思路可行的前提是题目中所说的“字符”指的是ascii编码的字符。
0-127是7位ASCII码的范围,是国际标准。128-255称为扩展ASCII码,不是国际标准。在C++
中,char是1字节(8bit),能表示256个不同的字符。而java中,char是unicode编码,2字
节(16bit)。但本题中,假设所有字符都可用ascii表示(0-255)。
在上述假设下,可以申请一个长度为256的int数组作为哈希表,占用空间1kB,用它来记录字
符出现的次数。第一扫描字符串,修改对应字符的次数;第二遍扫描,当遇到在数组中对应值
为1的字符,即得到所求,时间复杂度o(n)。
package chapter5;
public class P243_FirstNotRepeatingChar {
//暴力求解,时间复杂度o(n^2),空间复杂度o(1)
public static char firstNotRepeatingChar(String str){
//此处,\77表示ascii为77的字符(即?),用于表征没有只出现一次的字符
if(str==null||str.length()==0)
return '\77';
for(int i=0;i
50.2:字符流中第一个只出现一次的字符
题目要求:
找出字符流中第一个只出现一次的字符。例如,当从字符流google中只读出前两个字符go时,
第一个只出现一次的字符是g;当读完google时,第一个只出现一次的字符是l。
解题思路:
此题的关键在于“字符流”。因此最好能够记住在哪个位置出现了哪个字符,从而可以完成每读
到一个字符,就能动态更新到目前为止第一个出现一次的字符。此题同样使用了长度为256的
int数组作为哈希表,用字符的ascii码值作为表的键值,当字符仅出现了一次,就把字符的
位置作为哈希表的值,如果没有出现则值为-1,如果出现的次数大于1则哈希表对应的值为-2。
当想要知道到某一位置时第一个出现一次的字符,可以通过扫描该哈希表,找到大于等于0的
值中的最小值,该值所对应的字符就是当前状态第一个出现一次的字符。
public class P247_FirstNotRepeatingCharInStream {
public static class CharStatistics{
private int[] times;
private int index;
public CharStatistics(){
index = 0;
times = new int[256];
//-1表示未出现,>=0表示出现的位置且仅出现一次,-2表示出现两次及以上
for(int i=0;i<256;i++)
times[i] = -1;
}
public void insert(char ch){
if(times[ch]==-1)
times[ch] = index;
else
times[ch] = -2;
index++;
}
public char find(){
int minIndex = 256;
char ret = '\77'; //若没有只出现一次的字符,显示\77,即?
for(int i=0;i<256;i++){
if(times[i]>=0 && times[i]