10-數(shù)字序列-神奇數(shù)-兩個連續(xù)str作為子串的最短字符串-合法括號序列拆分方案-最短回文長度

年輕即出發(fā)...

簡書http://www.itdecent.cn/u/7110a2ba6f9e

知乎https://www.zhihu.com/people/zqtao23/posts

GitHub源碼https://github.com/zqtao2332

個人網(wǎng)站http://www.zqtaotao.cn/ (停止維護(hù)更新內(nèi)容)

QQ交流群:606939954

? 咆哮怪獸一枚...嗷嗷嗷...趁你現(xiàn)在還有時間,盡你自己最大的努力。努力做成你最想做的那件事,成為你最想成為的那種人,過著你最想過的那種生活。也許我們始終都只是一個小人物,但這并不妨礙我們選擇用什么樣的方式活下去,這個世界永遠(yuǎn)比你想的要更精彩。

最后:喜歡編程,對生活充滿激情



本節(jié)內(nèi)容預(yù)告

實例1:數(shù)字序列

實例2:神奇數(shù)

實例3:兩個連續(xù)str作為子串的最短字符串

實例4:合法括號序列拆分方案

實例5:最短回文長度



實例1:數(shù)字序列

東東從京京那里了解到有一個無限長的數(shù)字序列: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, ...(數(shù)字k在該序列中正好出現(xiàn)k次)。

東東想知道這個數(shù)字序列的第n項是多少,你能幫幫他么輸入描述:

輸入包括一個整數(shù)n(1 <=n<=10^18)

輸出描述:

輸出一個整數(shù),即數(shù)字序列的第n 項

示例1

輸入169

輸出18

/**
 * 1 22 333 4444 5555
 *
 * 每個連續(xù)數(shù)字的結(jié)尾
 * 1:1
 * 2:3
 * 3:6
 * 4:10
 * 5:15
 *
 * 每一個結(jié)尾,在這里可以看做是等差數(shù)列的求和
 * 例如
 * 6:是1+2+3
 * 15:是1+2+3+4+5
 * 即每個連續(xù)數(shù)的結(jié)尾數(shù),是這個連續(xù)數(shù)的單數(shù)等差數(shù)列的和
 * 等差數(shù)列求和公式 S(n)= n*(n+1)/2
 *
 * 現(xiàn)在知道了這個 S(n) --> K 來反推這個連續(xù)數(shù)
 */
public class Code_34_NNum {

    public static int getNum(long n) {
        return (int) Math.ceil((Math.sqrt(1 + 8 * ((double) n)) - 1) / 2);
    }
}

實例2:神奇數(shù)

東東在一本古籍上看到有一種神奇數(shù),如果能夠?qū)⒁粋€數(shù)的數(shù)字分成兩組,其中一組數(shù)字的和等于另一組數(shù)字的和,我們就將這個數(shù)稱為神奇數(shù)。例如242就是一個神奇數(shù),我們能夠?qū)⑦@個數(shù)的數(shù)字分成兩組,分別是12,2}以及[4),而且這兩組數(shù)的和都是4.東東現(xiàn)在需要統(tǒng)計給定區(qū)間中有多少個神奇數(shù),即給定區(qū)間[1, r],統(tǒng)計這個區(qū)間中有多少個神奇數(shù),請你來幫助他。

輸入描述:

輸入包括一行,一行中有兩個整數(shù)L和R

輸出描述:

輸出一個整數(shù),即區(qū)間內(nèi)的神奇數(shù)個數(shù)

示例1輸入1 50

輸出4

import java.util.ArrayList;

/**
 * 思路:背包問題
 * 背包問題就是行是可選擇的數(shù),列是能組合成的各種可能結(jié)果
 */
public class Code_35_SplitNumberToTwoParts {
    /**
     * 將num 分解為獨立的數(shù)
     * 求和,然后求是否能夠組合成 sum/2
     * <p>
     * dp[][]
     */
    public static boolean isMagic(int num) {
        int sum = 0;
        ArrayList<Integer> nums = new ArrayList<>();
        while (num != 0) {
            int n = num % 10;
            nums.add(n);
            sum += n;
            num /= 10;
        }

        if ((sum & 1) == 1) return false; // 和是奇數(shù)無法加出 sum / 2
        sum = sum / 2;
        boolean[][] dp = new boolean[nums.size()][sum + 1];
        dp[0][0] = true;
        if (nums.get(0) <= sum) dp[0][nums.get(0)] = true; // 初始化第一行,只有第一個數(shù)時,這個數(shù)不能超過 sum
        for (int i = 1; i < nums.size(); i++) {
            for (int j = sum; j >= 0; j--) {
                dp[i][j] = dp[i - 1][j] || (j - nums.get(i) >= 0 ? dp[i - 1][j - nums.get(i)] : false);
            }
        }
        for (int i = 0; i < nums.size(); i++) {
            if (dp[i][sum]) {
                return true;
            }
        }
        return false;
    }

    /**
     * dp[]
     * 降維處理,節(jié)約空間資源
     *
     * 從右往左進(jìn)行填充,不要從左往右進(jìn)行填充,
     * i 表示第 i 行,那么從左往右填充時,它依賴的上一行的數(shù)已經(jīng)改變
     * 例如
     *
     * 224
     *      0 1 2 3 4 5 6 7 8
     * 2    T F F T F F F F F
     * 2
     * 4
     *
     * 如上從左往右進(jìn)行遍歷時,i=1,arr[i]=2,那么j=5號位置的填充,
     * 需要依賴的是上一行i=0時的 j-arr[i]=3 和 5 兩個位置,
     * 由于從左往右進(jìn)行更新,那么 3 號位置的信息已經(jīng)被修改,不再是原來上一行的信息了
     */
    public static boolean isMagic2(int num) {
        int sum = 0;
        int tmp = num;
        while (num != 0) {
            sum += num % 10;
            num /= 10;
        }

        if ((sum & 1) == 1) return false;// 奇數(shù)
        sum = sum / 2;

        boolean[] dp = new boolean[sum + 1];
        dp[0] = true;
        num = tmp;
        int cur = 0;
        while (num != 0) {
            cur = num % 10;
            for (int i = sum; i >= 0; i--) { // 從右往左進(jìn)行填表
                dp[i] = dp[i] || (i - cur >= 0 ? dp[i - cur] : false);
            }
            if (dp[sum]) return true; // 只要加出sum 立即結(jié)束
            num /= 10;
        }
        return false;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            int num = (int) (Math.random() * 100000 + 1);
            try {
                if (num != 0 && isMagic(num) != isMagic2(num)) {
                    System.out.println(num);
                    System.out.println(isMagic(num));
                    System.out.println(isMagic2(num));
                }
            } catch (Exception e) {
                System.out.println(num + "\t\t" + e.toString());
            }
        }
    }
}

實例3:兩個連續(xù)str作為子串的最短字符串

給定一個字符串s,請計算輸出含有連續(xù)兩個s作為子串的最短字符串。注意兩個s可能有重疊部分。例如, "ababa"含有兩個"aba"輸入描述:輸入包括一個字符串s,字符串長度length (1 <=ength <= 50), s中每個字符都是小寫字母.輸出描述:輸出一個字符串,即含有連續(xù)兩個s作為子串的最短字符串。

示例1輸入abracadabra

輸出abracadabracadabra

具體意思就是給定一個字符串Str=“abcdd" 輸出含有兩個Str的字符串

KMP算法

字符串查找算法,簡稱為 “KMP算法”,常用于在一個文本串S內(nèi)查找一個模式串P 的出現(xiàn)位置

KMP算法 解決 str1 中是否包含 str2
包含返回 str2 開始位置,不包含返回 -1

1、暴力方法:以str1 的每一個字符去 匹配str2 的每一個字符,str1長N, str2長M,時間復(fù)雜度 O(M*N)
2、KMP算法 str1長N, str2長M 可以優(yōu)化到 O(N) N > M

開始了解KMP算法前,先了解最長前綴和最長后綴匹配

注意:最長前綴和最長后綴匹配不是針對 str1 的,是針對str2的

一個例子理解:最長前綴和最長后綴匹配

aaaab 求 其中 b 的最長前綴和最長后綴匹配

aaaab

長度  前綴  后綴
1   a   a
2   aa  aa
3   aaa aaa
那么字符串a(chǎn)aaab 中 b 的最長前綴和最長后綴匹配就是 aaa ,長度為3
其中前綴不能沖到 aaaa 長度為4位置,同理后綴

人為規(guī)定
前綴不能包含最后一個字符
后綴不能包含第一個字符

求一個字符串中每一個字符的最長前綴和最長后綴匹配,用next[] 記錄

str:    a   a   a   a   a
next[]  -1  0   1   2   3

時間復(fù)雜度分析

1、未斷掉,Str1直接匹配出Str2

2、斷掉

未斷掉,走的就是 Str2 的長度 ,時間復(fù)雜度 O(N)

斷掉,一旦斷掉Str2 的開始指針就前移一位,知道結(jié)束,時間復(fù)雜度也是 O(N)

next[] 怎么求?

人為規(guī)定 0 位置:-1;1位置:0

其他位置 ,當(dāng)求解 i 位置的時候,可以認(rèn)為 0~i 位置的已經(jīng)求解完畢。

import java.util.Arrays;

/**
 * @description: 連續(xù)Str子串的最短字符串
 * @version: 1.0
 */
public class Code_36_ShortestHaveTwice {

    public static String answer(String str) {
        if (str == null || str.length() == 0) return "";
        char[] chars = str.toCharArray();
        if (chars.length == 1) return str + str;
        if (chars.length == 2) // ab -->  aba是最短的
            return chars[0] == chars[1] ? (str + String.valueOf(chars[0])) : str + str;
        int endNext = endNextLength(chars);
        return str + str.substring(endNext);
    }

    // 求next[]
    private static int endNextLength(char[] chars) {

        int[] next = new int[chars.length + 1];
        next[0] = -1;
        next[1] = 0; // 人為規(guī)定0:-1 ,1:0
        int position = 2; // 指向chars[] 的第position位置的元素
        int cn = 0; // 指向next[] 最長前綴和后綴匹配位置
        while (position < next.length) {
            if (chars[position - 1] == chars[cn]){
                next[position++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[position++] = 0;
            }
        }

        System.out.println(Arrays.toString(next));
        return next[next.length - 1];
    }


    /**
     * KMP算法
     * KMP算法 解決 str1 中是否包含 str2
     * 包含返回 str2 開始位置,不包含返回 -1
     *
     * 1、暴力方法:以str1 的每一個字符去 匹配str2 的每一個字符,str1長N, str2長M,時間復(fù)雜度 O(M*N)
     * 2、KMP算法 str1長N, str2長M 可以優(yōu)化到 O(N) N > M
     *
     * 此題僅僅使用了KMP 算法中的 next[] 的應(yīng)用
     */
    public static void main(String[] args) {
        String test1 = "a";
        System.out.println(answer(test1));

        String test2 = "aa";
        System.out.println(answer(test2));

        String test3 = "ab";
        System.out.println(answer(test3));

        String test4 = "abcdabcd";
        System.out.println(answer(test4));

        String test5 = "abracadabra";
        System.out.println(answer(test5));
        System.out.println("abracadabra".substring(4)); // 保留 i+1~length-1位置
    }
}

實例4:合法括號序列拆分方案

合法的括號匹配序列被定義為:、

1.空串""是合法的括號序列

2.如果" "和"Y"是合法的序列,那么"XY"也是一個合法的括號序列

3,如果"X"是一個合法的序列,那么"(X) "也是一個合法的括號序列

4,每個合法的括號序列都可以由上面的規(guī)則生成例如"", "()", "() () ()", "(() ())", "(((0))"都是合法的。

東東現(xiàn)在有一個合法的括號序列s,一次移除操作分為兩步:

1·移除序列s中第一個左括號

2,移除序列s中任意一個右括號保證操作之后s還是一個合法的括號序列

東東現(xiàn)在想知道使用上述的移除操作有多少種方案可以把序列s變?yōu)榭?/p>

如果兩個方案中有一次移除操作移除的是不同的右括號就認(rèn)為是不同的方案。例如: s= "() () () () ()",輸出1,因為每次都只能選擇被移除的左括號所相鄰的右括號.s= "(((()))) ,輸出24,第一次有4種情況,第二次有3種情

輸入描述:

輸入包括一行,一個合法的括號序列s,序列長度 <=20

輸出描述:

輸出一個整數(shù),表示方案數(shù)。

(((())))
4*3*2*1=24
思路:
求每一個左括號,右括號比左括號多多少
( ( ( ) ) )
1 2 3

從右向左進(jìn)行遍歷,維護(hù)一個sum , 遇到右括號++,左括號--

查看一個單獨的合法括號序列移除方案

( ( ( ) ) )
第一個( 可以支配的右括號是3個
第二個( 可以支配的右括號是2個
第三個( 可以支配的右括號是1個

( ( ( ) ) )
3 2 1

所以這個合法括號序列移除方案是3*2*1個

其實統(tǒng)計出每一個左括號位置,右括號比左括號多多少,逆序就是每一個( 可以支配的括號數(shù)
public class Code_37_Parentheses {
    /**
     * 維護(hù)一個sum 變量,遇到 ) ,sum++,遇到 ( sum--
     *
     * 思路:
     * 求每一個左括號,右括號比左括號多多少
     * ( ( ( ) ) )
     * 1 2 3
     *
     * 從右向左進(jìn)行遍歷,維護(hù)一個sum , 遇到右括號++,左括號--
     *
     * 查看一個單獨的合法括號序列移除方案
     * ( ( ( ) ) )
     * 第一個( 可以支配的右括號是3個
     * 第二個( 可以支配的右括號是2個
     * 第三個( 可以支配的右括號是1個
     *
     * ( ( ( ) ) )
     * 3 2 1
     *
     * 所以這個合法括號序列移除方案是3*2*1個
     * 其實統(tǒng)計出每一個左括號位置,右括號比左括號多多少,逆序就是每一個( 可以支配的括號數(shù)
     */
    public static int possibilities(String parentheses) {
        char[] chars = parentheses.toCharArray();
        int ans = 1;
        int sum = 0;
        for (int i = chars.length - 1; i >= 0; i--) {
            if (chars[i] == ')'){
                sum++;
            } else {
                ans *= sum--;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        System.out.println(possibilities("((()))")); // 6
        System.out.println(possibilities("()(())")); // 2
        System.out.println(possibilities("()()()")); // 1
    }
}

實例5:最短回文長度

京京和東東是好朋友。東東很喜歡回文?;匚氖侵笍那巴笞x和從后往前讀是一樣的詞語。京京準(zhǔn)備給東東一個驚喜,先取定一個字符串s,然后在后面附上0個或者更多個字母形成回文,京京希望這個回文越短越好。請幫助京京計算他能夠得到的最短的回文長度。

輸入描述:輸入包括一個字符串s,字符串s長度length (1 <=length<= 50)

輸出描述:輸出一個整數(shù),表示牛牛能夠得到的最短的回文長度。

示例1輸入 abab輸出 5

import java.util.Arrays;

/**
 * @description: 最短回文長度
 */
public class Code_38_ShortestMakePalindrome {

    /**
     * 思路:必須包含最后一個字符的情況下,求最長的回文子串,其中,不是回文串的范圍字符,逆序填在后面即可
     * 如 abc1234321
     * 必須包含最后一個字符 4 的情況下,最長的回文子串是 1234321
     * 將不包含在最長回文子串內(nèi)的其他字符abc逆序cba --> 添加在后面  abc1234321cba
     *
     * 算法被分解為,怎么求一個字符串的最長回文子串,子串要求連續(xù)
     * 1、暴力解
     * 以每一個字符向兩邊進(jìn)行擴(kuò)散
     * 121
     *  null<-1->2     ---->0
     *  1<-2->1        ---->3
     *  2<-1->1        ---->0
     *  所以擴(kuò)散的最長回文長度就是 3
     *  但是問題來了,對于這種對稱擴(kuò)散尋找,121 奇數(shù)個,是以實軸進(jìn)行的擴(kuò)散,
     *  然而 1221 偶數(shù)個,是虛軸擴(kuò)散才能找到真正的值4,不然就是 0
     *
     *  處理方式:不管是奇數(shù)個還是偶數(shù)個,都進(jìn)行擴(kuò)容處理,求解完畢后除以 2 得到的就是解
     *  121 --> #1#2#1#  最長對稱是 7  7/2=3
     *  1221--> #1#2#2#1# 最長對稱是 9  9/2=4
     *
     *  注意:添加的字符是 任意的,可以是存在的字符,如 2,不影響相對的結(jié)果
     *  時間復(fù)雜度 O(N^2)
     *
     *
     * 2、Manacher 算法
     *
     * 1)、回文半徑:以每個位置的字符為回文中心求出的回文半徑長度;
     *      維護(hù)一個回文半徑數(shù)組,記錄每一個位置可以擴(kuò)散的回文半徑
     * 2)、回文最右邊界:這個位置及之前的位置的回文子串,所到達(dá)的最右邊的地方,同時記錄最右回文中心;
     *      如果有兩個位置擴(kuò)散到同一個右邊界,只記錄最早的那個。
     *
     *      如  # 1 # 2 # 2 # 1 #
     *          0 1 2 3 4 5 6 7 8
     *          4號位置# 的回文最右邊界達(dá)到了8號位置
     *          7號位置1 的回文最右邊界達(dá)到了8號位置
     *          最右回文中心只記錄 4 號位置,不記錄 7 號位置
     *
     *算法出現(xiàn)的幾種情況
     * a、當(dāng)前所求的位置,不在左右邊界里,此時和暴力方法一樣,向兩邊依次檢查
     *      如  # 1 # 2 # 2 # 1 #
     *          0 1 2 3 4 5 6 7 8
     *          0號位置,不在邊界,擴(kuò)
     *          1號位置,不在邊界,向兩邊依次檢查,擴(kuò)到了2 號位置,此時更新左右邊界為 2
     * b、在最右回文右邊界里面
     * c、在最右回文右邊界外
     * d、壓線
     *
     * 其中一點就是找到回文的最右邊界就停, 同時記錄這個最大的回文半徑
     *
     */

    // 預(yù)處理字符串,排除奇偶個字符的影響
    public static char[] getPreprocessedStr(String str){
        char[] tmp = new char[str.length() * 2 + 1];
        int j = 0;
        for (int i = 0; i < tmp.length; i++) {
            if ((i & 1) == 0) {
                tmp[i] = '#';
            } else {
                tmp[i] = str.charAt(j);
            }
            tmp[i] = (i & 1) == 0 ? '#' : str.charAt(j++);
        }
        return tmp;
    }

    public static int manacherStr(String str) {
        if (str == null || str.length() == 0) return 0;

        char[] preArr = getPreprocessedStr(str);// 預(yù)處理字符串,排除奇偶影響

        int index = -1; // 最右回文對稱中心
        int maxR = -1; // 最右回文延伸邊界
        int max = -1; // 最大延伸長度
        int[] radius = new int[preArr.length]; // 記錄每個字符可以延伸的最大回文邊界

        for (int i = 0; i < preArr.length; i++) {
            // 找到當(dāng)前點關(guān)于最右回文對稱中心的對稱點位置
//            int symmetryNode = radius[2 * index - i];
            // 當(dāng)前點的狀態(tài),1、在最右回文邊界之外,暫時假設(shè)只有自己是回文字符串,記錄為1
            // 2、在最右回文邊界之內(nèi),記錄為對稱點的回文邊界和最右邊界-i 中最小的
            radius[i] = maxR > i ? Math.min(radius[2 * index - i], maxR - i) : 1;

            // 檢查并更新當(dāng)前下標(biāo)為中心的回文串最遠(yuǎn)延伸的長度
            while (i + radius[i] < preArr.length // 當(dāng)前位置擴(kuò)出的最大右邊界不能超出數(shù)組范圍
                && (i - radius[i] + 1) > 0 // 當(dāng)前位置存在左邊點 :i-radius[i] <-i-> i+radius[i], 可以繼續(xù)向兩邊擴(kuò)
            ) {
                // 繼續(xù)向外擴(kuò)充,尋找最大的回文半徑
                if (preArr[i + radius[i]] == preArr[i - radius[i]]){
                    radius[i]++;
                } else {
                    break;
                }
            }

            if (i + radius[i] > maxR) {
                maxR = i + radius[i]; // 更新最大回文右邊界
                index = i; // 更新最大回文右邊界
            }

            if (maxR == preArr.length) { // 最大回文右邊界已經(jīng)擴(kuò)到了最后一個字符
                max = radius[i]; // 記錄最大回文半徑
                break;
            }

            System.out.println(Arrays.toString(radius));
        }

        System.out.println("max: " + max);
        return str.length() * 2 - max + 1;
    }

    public static void main(String[] args) {
        System.out.println(manacherStr("abb"));
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 近日睡得越來越早 夢可以做好多好多 舅舅家的櫻桃熟了 去了你家 見了你的母親 似乎她不太喜歡我 第一次和你以及你的...
    琦兒愛做夢閱讀 219評論 0 0
  • 我掙扎著, 在擁擠的人群中, 禁錮著安穩(wěn)執(zhí)守。 穿過指尖錯亂的紋路, 開始去尋找那暗淡的光。 我在孤寂的塵世喧囂里...
    墨落語安閱讀 106評論 0 0
  • 你沒有享受過過一次美麗的中秋節(jié)?那是什么時候呢? 老師中午提前到班級進(jìn)行查班,看看學(xué)生們是否到齊,是否有什么特殊的...
    JAMESCAMERONL閱讀 583評論 5 3
  • 隱痛 九十年代初,我在安慶碼頭商貿(mào)城做服裝批發(fā)生意。九六年結(jié)婚嫁到樅陽,兩地分居,同行們紛紛取笑,回家當(dāng)太太去吧,...
    纖纖柔荑閱讀 88評論 0 0
  • 今天主要來講關(guān)于人性認(rèn)知的故事,這幾天被身邊的一個小伙子徹底顛覆了三觀,話說小伙子(以下稱他為W吧)以前是我...
    冬季限定閱讀 2,677評論 0 2

友情鏈接更多精彩內(nèi)容