sword50

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));
    }
}
  1. 最长不含重复字符的子字符串
题目要求:  
输入一个字符串(只包含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;
    }
}
  1. 丑数
题目要求:  
我们把只包含因子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
  1. 第一个只出现一次的字符
题目要求:  
字符串中第一个只出现一次的字符。在字符串中找出第一个只出现一次的字符。  
如输入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]

你可能感兴趣的:(sword50)