LeetCode解题报告—— 2 Keys Keyboard & Longest Palindromic Substring & ZigZag Conversion

1. Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example:

Input: "babad"

Output: "bab"

Note: "aba" is also a valid answer.

Example:

Input: "cbbd"

Output: "bb"

https://leetcode.com/problems/longest-palindromic-substring/solution/

解题思路:其实就是求给定字符串中的最长回文。方法一是将原字符串逆置,然后找最长公共子串,但是这里有一个很容易犯的错误:

原因在于回文应该是原字符串中和逆字符串相对应的同一组,但是按照找最长公共子串匹配到的可能是两组数字。比如 abacdfgdcaba 中 按照逆置找公共子串找到的其实是 前面的abacd 和后面的dcaba,虽然倒着读的确一样但其实是两个字字串。所以在用这个方法时还要额外判断,如果我们找到的公共最长字串它们的索引在原字符串中是一致的则判定为回文,否则skip掉。

当然也可以Brute Force和dp来解决此问题,现在使用一种较简单的 Expand Around Center 方法:

import java.util.*;

public class LeetCode{
    private static int lo,maxLen;
    
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        String input=sc.nextLine();
        System.out.println(longestPalindrome(input));
    }
    
    public static String longestPalindrome(String s){
    // 求长度的话,数组是直接调用length变量,String是调用 length() 方法
int len=s.length(); if(len<2) return s; for(int i=0;i<len-1;i++){ expandAroungCenter(s,i,i); expandAroungCenter(s,i,i+1); } return s.substring(lo,lo+maxLen); } public static void expandAroungCenter(String s, int left, int right){
    // 直接用String的charAt()方法,不需要再转为字符数组了
while(left>=0&&right<s.length()&&s.charAt(left)==s.charAt(right)){ left--; right++; } if(maxLen<right-left-1){ lo=left+1; maxLen=right-left-1; } } }

尝试使用dp去解题,本来以为和求公共最长字串的dp相似,结果并非如此,耗了一番功夫,踩了几个坑才勉强解出。先是dp思想:  

 

再上错误代码:

class Solution {
    public String longestPalindrome(String s) {
        int max_long=0;
        int start_index=0;
        boolean[][] dp=new boolean[s.length()][s.length()];
        for(int i=0;i<s.length();i++){
            dp[i][i]=true;
        dp[i][i+1]=s.charAt(i)==s.charAt(i+1); }
for(int i=0;i<s.length()-1;i++){ for(int j=i+1;j<s.length();j++){ if(j==i+1){ dp[i][j] = s.charAt(i)==s.charAt(j); }else{ if(s.charAt(i)==s.charAt(j)){ dp[i][j]=dp[i+1][j-1]; }else{ dp[i][j]=false; } } if(dp[i][j]==true&&(j-i+1>max_long)){ max_long=j-i+1; start_index=i; } } } return s.substring(start_index, max_long); } }

上面的dp在于没有搞清楚dp的计算是自低向上的,即先求出最子问题的最优解再向上一层层计算,每层的子问题要利用到下层的计算结果,换而言之计算上层时,底层的计算结果应该全部计算出来了,不能一边计算上层的值的同时计算下层。以这题为例子,dp[i][j]表示从索引处 i 到 j 的字符串是不是回文,那么最子问题是 i=j 即一个字符的情形,然后根据这个再去计算 2 个字符的情形,全部计算完毕之后再去计算3个字符的情形,以此类推。上面的代码的计算是从i=0开始以此计算dp[0][1],dp[0][2],dp[0,3],dp[0,4].....,而此时以求出的只有字符为1个和2个时所有情形,其它是不知道的,如果在计算dp[0][4]时走到 dp[i][j]=dp[i+1][j-1] 这步的话就必须要计算 dp[1][3],然而此时的dp[1][3]由于没有计算过所以是数组的初始值,根本不是子问题i=1,j=3时的最优解,所以会导致整个dp的计算结果不对。

然后还有一个坑是上面代码最后取字串时直接用了max_long,忘记加上开始索引了,还把substring方法的参数意义记错了,两个参数分别时开始索引和结束索引,不是开始索引和偏移量。最后是正确的dp代码:

class Solution {
    public String longestPalindrome(String s) {
        int max_long=-1;
        int start_index=-1;
        if(s.length()<=1) return s;
        boolean[][] dp=new boolean[s.length()][s.length()];
        for(int i=0;i<s.length()-1;i++){
            dp[i][i]=true;
            dp[i][i+1]=s.charAt(i)==s.charAt(i+1);
        }
        
        for(int interval=2;interval<s.length();interval++){
            for(int i=0;i<s.length()-interval;i++){
                if(s.charAt(i)==s.charAt(i+interval)){
                    dp[i][i+interval]=dp[i+1][i+interval-1];
                }else{
                    dp[i][i+interval]=false;
                }
            }
        }
        
        for(int i=0;i<dp.length;i++){
            for(int j=i;j<dp.length;j++){
                if(dp[i][j]==true&&(j-i+1>max_long)){
                    max_long=j-i+1;
                    start_index=i;
                }
            }
        }
        
        return s.substring(start_index, start_index+max_long);
    }
}

2. ZigZag Conversion

The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

P   A   H   N
A P L S I I G 
Y   I   R

And then read line by line: "PAHNAPLSIIGYIR"

Write the code that will take a string and make this conversion given a number of rows:

string convert(string text, int nRows);

convert("PAYPALISHIRING", 3) should return "PAHNAPLSIIGYIR".

解题思路:这题乍看一下有点不明白它说的是什么意思,实际上就是给定n行,原字符串先按竖直一个一个字符放置,到行低之后再斜对角线往上放置,到顶之后再按竖直放置,这样形成一个左旋90度的 “Z” 形摆列。以这样的形式放置完所有字符后在从左往右一行行的读字符,输出摆列后的字符顺序。

一个可行的解题方法是利用 n(这里的n是行数)个StringBuffer作为n行,从原字符数组一个个取字符分别放入各个StringBuffer,到n为止再倒着往从第n个到第一个Stringbuffer里放置字符,到第一个Stringbuffer时再重复此步骤即可。注意这里不需要再考虑斜对角线上字符的位置的,因为最后输出的字符串是按行从左到右输出的,所以只需要知道原字符串排列后每行中有哪些字符即可,根本不需要细纠结排列后每个字符的具体索引。

import java.util.*;

public class LeetCode {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
        int n = sc.nextInt();

        System.out.println(convert(input, n));
    }

    public static String convert(String s, int nRows) {
        char[] c = s.toCharArray();
        int len = c.length;
        StringBuffer[] sb = new StringBuffer[nRows];
        for (int i = 0; i < sb.length; i++) sb[i] = new StringBuffer();

        int i = 0;
        while (i < len) {
            // need to judge whether i<len
            for (int idx = 0; idx < nRows && i < len; idx++) // vertically down
                sb[idx].append(c[i++]);
            for (int idx = nRows-2; idx >= 1 && i < len; idx--) // obliquely up
                sb[idx].append(c[i++]);
        }
        for (int idx = 1; idx < sb.length; idx++)
            sb[0].append(sb[idx]);
        return sb[0].toString();
    }
}

3. 2 Keys Keyboard

Initially on a notepad only one character 'A' is present. You can perform two operations on this notepad for each step:

  1. Copy All: You can copy all the characters present on the notepad (partial copy is not allowed).
  2. Paste: You can paste the characters which are copied last time.

Given a number n. You have to get exactly n 'A' on the notepad by performing the minimum number of steps permitted. Output the minimum number of steps to get n 'A'.

Example 1:

Input: 3
Output: 3
Explanation:
Intitally, we have one character 'A'.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get 'AA'.
In step 3, we use Paste operation to get 'AAA'.

这题的意思就是一开始记事本上只有1个A,要得到n个A的话至少需要几个步骤,每一步要么copy all,就是复制所有,不允许部分复制。要么paste,即将上一次复制的内容黏贴。

解题思路:思路一是利用质因数分解,因为A的增长一定是倍数增长,所以如 n 是质数的话,那么再一开始的copy all后只能一个一个paste,要不然不可能达到n。如果n不是质数,对其进行质因数分解,每个质因数即是要paste的次数,从一个质因数到另一个质因数要copyAll一次。一开始有个A,但是一开始要CopyAll以此,所以求所有质因数之和便可以了。

import java.util.*;

public class LeetCode {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        
        System.out.println(n);
    }
    
    public static int solution(int n){
        //质因数分解,从2开始判断
        int d=2,ans=0;
        while(n>1){
            while(n%d==0){
                ans+=d;
                n/=d;
            }
            d++;
        }
        return ans;
    }

}

关于上面的代码中的质因数分解,因为是从2开始判断的,所以一开始找到的肯定是最小质因数,比如对于16,你可能会觉难道不会找到4这种不是质数但是因数的情况么?但实际上因为是从2开始递增判断,所以一开始找到的是2不是4,然后16除以2剩8,8取余2为0,那么得质因数2*2,继续8除2得4。这样下来质因数分解得到的结果是2*2*2*2。 

方法二是利用动态规划dp,对于dp来说,首先还是构造数组 dp[i]。数组的值dp[i]是最优解,索引i是要得到几个A,也就是对应于给定的n。动态规划的本质还是有点类似与遍历计算,只不过它能够利用计算好的子问题的值,而不需要重复计算,从而大大缩减了计算量。

对于dp,先是初始化数组。然后采用自低向上或者自顶向下的思路来更新数组。首先用一个例子来说明,对于n为16的情况,最优解应该是对n为8时的最优解CopyAll一次再Paste;n是9时的最优解是对n为3时的最优解CopyAll一次再paste两次。如果n是质数,不能分解成几个数相乘的时候,最优解就是n。

import java.util.*;

public class LeetCode {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        
        System.out.println(n);
    }
    
    public static int solution(int n){
        int[] dp=new int[n+1];
        
        for(int i=2;i<=n;i++){
         // dp 要素之一,初始化整个dp数组
            dp[i]=i;
            
         // 更新 dp 数组    
            for(int j=i-1;j>1;j--){
                if(i%j==0){
                    dp[i]=dp[j]+i/j;
                    break;
                }
            }
        }
        return dp[n];
    }

}
原文地址:https://www.cnblogs.com/f91og/p/8250600.html