10-数字序列-神奇数-两个连续str作为子串的最短字符串-合法括号序列拆分方案-最短回文长度

年轻即出发...

简书https://www.jianshu.com/u/7110a2ba6f9e

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

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

个人网站http://www.zqtaotao.cn/ (停止维护更新内容)

QQ交流群:606939954

​ 咆哮怪兽一枚...嗷嗷嗷...趁你现在还有时间,尽你自己最大的努力。努力做成你最想做的那件事,成为你最想成为的那种人,过着你最想过的那种生活。也许我们始终都只是一个小人物,但这并不妨碍我们选择用什么样的方式活下去,这个世界永远比你想的要更精彩。

最后:喜欢编程,对生活充满激情



本节内容预告

实例1:数字序列

实例2:神奇数

实例3:两个连续str作为子串的最短字符串

实例4:合法括号序列拆分方案

实例5:最短回文长度



实例1:数字序列

东东从京京那里了解到有一个无限长的数字序列: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, ...(数字k在该序列中正好出现k次)。

东东想知道这个数字序列的第n项是多少,你能帮帮他么輸入描述:

輸入包括一个整数n(1 <=n<=10^18)

输出描述:

输出一个整数,即数字序列的第n 项

示例1

输入169

输出18

/**
 * 1 22 333 4444 5555
 *
 * 每个连续数字的结尾
 * 1:1
 * 2:3
 * 3:6
 * 4:10
 * 5:15
 *
 * 每一个结尾,在这里可以看做是等差数列的求和
 * 例如
 * 6:是1+2+3
 * 15:是1+2+3+4+5
 * 即每个连续数的结尾数,是这个连续数的单数等差数列的和
 * 等差数列求和公式 S(n)= n*(n+1)/2
 *
 * 现在知道了这个 S(n) --> K 来反推这个连续数
 */
public class Code_34_NNum {

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

实例2:神奇数

东东在一本古籍上看到有一种神奇数,如果能够将一个数的数字分成两组,其中一组数字的和等于另一组数字的和,我们就将这个数称为神奇数。例如242就是一个神奇数,我们能够将这个数的数字分成两组,分别是12,2}以及[4),而且这两组数的和都是4.东东现在需要统计给定区间中有多少个神奇数,即给定区间[1, r],统计这个区间中有多少个神奇数,请你来帮助他。

输入描述:

输入包括一行,一行中有两个整数L和R

输出描述:

输出一个整数,即区间内的神奇数个数

示例1输入1 50

输出4

import java.util.ArrayList;

/**
 * 思路:背包问题
 * 背包问题就是行是可选择的数,列是能组合成的各种可能结果
 */
public class Code_35_SplitNumberToTwoParts {
    /**
     * 将num 分解为独立的数
     * 求和,然后求是否能够组合成 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; // 和是奇数无法加出 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; // 初始化第一行,只有第一个数时,这个数不能超过 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[]
     * 降维处理,节约空间资源
     *
     * 从右往左进行填充,不要从左往右进行填充,
     * i 表示第 i 行,那么从左往右填充时,它依赖的上一行的数已经改变
     * 例如
     *
     * 224
     *      0 1 2 3 4 5 6 7 8
     * 2    T F F T F F F F F
     * 2
     * 4
     *
     * 如上从左往右进行遍历时,i=1,arr[i]=2,那么j=5号位置的填充,
     * 需要依赖的是上一行i=0时的 j-arr[i]=3 和 5 两个位置,
     * 由于从左往右进行更新,那么 3 号位置的信息已经被修改,不再是原来上一行的信息了
     */
    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;// 奇数
        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--) { // 从右往左进行填表
                dp[i] = dp[i] || (i - cur >= 0 ? dp[i - cur] : false);
            }
            if (dp[sum]) return true; // 只要加出sum 立即结束
            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:两个连续str作为子串的最短字符串

给定一个字符串s,请计算输出含有连续两个s作为子串的最短字符串。注意两个s可能有重叠部分。例如, "ababa"含有两个"aba"输入描述:输入包括一个字符串s,字符串长度length (1 <=ength <= 50), s中每个字符都是小写字母.输出描述:输出一个字符串,即含有连续两个s作为子串的最短字符串。

示例1输入abracadabra

输出abracadabracadabra

具体意思就是给定一个字符串Str=“abcdd" 输出含有两个Str的字符串

KMP算法

字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置

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

1、暴力方法:以str1 的每一个字符去 匹配str2 的每一个字符,str1长N, str2长M,时间复杂度 O(M*N)
2、KMP算法 str1长N, str2长M 可以优化到 O(N) N > M

开始了解KMP算法前,先了解最长前缀和最长后缀匹配

注意:最长前缀和最长后缀匹配不是针对 str1 的,是针对str2的

一个例子理解:最长前缀和最长后缀匹配

aaaab 求 其中 b 的最长前缀和最长后缀匹配

aaaab

长度  前缀  后缀
1   a   a
2   aa  aa
3   aaa aaa
那么字符串aaaab 中 b 的最长前缀和最长后缀匹配就是 aaa ,长度为3
其中前缀不能冲到 aaaa 长度为4位置,同理后缀

人为规定
前缀不能包含最后一个字符
后缀不能包含第一个字符

求一个字符串中每一个字符的最长前缀和最长后缀匹配,用next[] 记录

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

时间复杂度分析

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

2、断掉

未断掉,走的就是 Str2 的长度 ,时间复杂度 O(N)

断掉,一旦断掉Str2 的开始指针就前移一位,知道结束,时间复杂度也是 O(N)

next[] 怎么求?

人为规定 0 位置:-1;1位置:0

其他位置 ,当求解 i 位置的时候,可以认为 0~i 位置的已经求解完毕。

import java.util.Arrays;

/**
 * @description: 连续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; // 人为规定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,时间复杂度 O(M*N)
     * 2、KMP算法 str1长N, str2长M 可以优化到 O(N) N > M
     *
     * 此题仅仅使用了KMP 算法中的 next[] 的应用
     */
    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,每个合法的括号序列都可以由上面的规则生成例如"", "()", "() () ()", "(() ())", "(((0))"都是合法的。

东东现在有一个合法的括号序列s,一次移除操作分为两步:

1·移除序列s中第一个左括号

2,移除序列s中任意一个右括号保证操作之后s还是一个合法的括号序列

东东现在想知道使用上述的移除操作有多少种方案可以把序列s变为空

如果两个方案中有一次移除操作移除的是不同的右括号就认为是不同的方案。例如: s= "() () () () ()",输出1,因为每次都只能选择被移除的左括号所相邻的右括号.s= "(((()))) ,输出24,第一次有4种情况,第二次有3种情

输入描述:

输入包括一行,一个合法的括号序列s,序列长度 <=20

输出描述:

输出一个整数,表示方案数。

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

从右向左进行遍历,维护一个sum , 遇到右括号++,左括号--

查看一个单独的合法括号序列移除方案

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

( ( ( ) ) )
3 2 1

所以这个合法括号序列移除方案是3*2*1个

其实统计出每一个左括号位置,右括号比左括号多多少,逆序就是每一个( 可以支配的括号数
public class Code_37_Parentheses {
    /**
     * 维护一个sum 变量,遇到 ) ,sum++,遇到 ( sum--
     *
     * 思路:
     * 求每一个左括号,右括号比左括号多多少
     * ( ( ( ) ) )
     * 1 2 3
     *
     * 从右向左进行遍历,维护一个sum , 遇到右括号++,左括号--
     *
     * 查看一个单独的合法括号序列移除方案
     * ( ( ( ) ) )
     * 第一个( 可以支配的右括号是3个
     * 第二个( 可以支配的右括号是2个
     * 第三个( 可以支配的右括号是1个
     *
     * ( ( ( ) ) )
     * 3 2 1
     *
     * 所以这个合法括号序列移除方案是3*2*1个
     * 其实统计出每一个左括号位置,右括号比左括号多多少,逆序就是每一个( 可以支配的括号数
     */
    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:最短回文长度

京京和东东是好朋友。东东很喜欢回文。回文是指从前往后读和从后往前读是一样的词语。京京准备给东东一个惊喜,先取定一个字符串s,然后在后面附上0个或者更多个字母形成回文,京京希望这个回文越短越好。请帮助京京计算他能够得到的最短的回文长度。

输入描述:输入包括一个字符串s,字符串s长度length (1 <=length<= 50)

输出描述:输出一个整数,表示牛牛能够得到的最短的回文长度。

示例1输入 abab输出 5

import java.util.Arrays;

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

    /**
     * 思路:必须包含最后一个字符的情况下,求最长的回文子串,其中,不是回文串的范围字符,逆序填在后面即可
     * 如 abc1234321
     * 必须包含最后一个字符 4 的情况下,最长的回文子串是 1234321
     * 将不包含在最长回文子串内的其他字符abc逆序cba --> 添加在后面  abc1234321cba
     *
     * 算法被分解为,怎么求一个字符串的最长回文子串,子串要求连续
     * 1、暴力解
     * 以每一个字符向两边进行扩散
     * 121
     *  null<-1->2     ---->0
     *  1<-2->1        ---->3
     *  2<-1->1        ---->0
     *  所以扩散的最长回文长度就是 3
     *  但是问题来了,对于这种对称扩散寻找,121 奇数个,是以实轴进行的扩散,
     *  然而 1221 偶数个,是虚轴扩散才能找到真正的值4,不然就是 0
     *
     *  处理方式:不管是奇数个还是偶数个,都进行扩容处理,求解完毕后除以 2 得到的就是解
     *  121 --> #1#2#1#  最长对称是 7  7/2=3
     *  1221--> #1#2#2#1# 最长对称是 9  9/2=4
     *
     *  注意:添加的字符是 任意的,可以是存在的字符,如 2,不影响相对的结果
     *  时间复杂度 O(N^2)
     *
     *
     * 2、Manacher 算法
     *
     * 1)、回文半径:以每个位置的字符为回文中心求出的回文半径长度;
     *      维护一个回文半径数组,记录每一个位置可以扩散的回文半径
     * 2)、回文最右边界:这个位置及之前的位置的回文子串,所到达的最右边的地方,同时记录最右回文中心;
     *      如果有两个位置扩散到同一个右边界,只记录最早的那个。
     *
     *      如  # 1 # 2 # 2 # 1 #
     *          0 1 2 3 4 5 6 7 8
     *          4号位置# 的回文最右边界达到了8号位置
     *          7号位置1 的回文最右边界达到了8号位置
     *          最右回文中心只记录 4 号位置,不记录 7 号位置
     *
     *算法出现的几种情况
     * a、当前所求的位置,不在左右边界里,此时和暴力方法一样,向两边依次检查
     *      如  # 1 # 2 # 2 # 1 #
     *          0 1 2 3 4 5 6 7 8
     *          0号位置,不在边界,扩
     *          1号位置,不在边界,向两边依次检查,扩到了2 号位置,此时更新左右边界为 2
     * b、在最右回文右边界里面
     * c、在最右回文右边界外
     * d、压线
     *
     * 其中一点就是找到回文的最右边界就停, 同时记录这个最大的回文半径
     *
     */

    // 预处理字符串,排除奇偶个字符的影响
    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);// 预处理字符串,排除奇偶影响

        int index = -1; // 最右回文对称中心
        int maxR = -1; // 最右回文延伸边界
        int max = -1; // 最大延伸长度
        int[] radius = new int[preArr.length]; // 记录每个字符可以延伸的最大回文边界

        for (int i = 0; i < preArr.length; i++) {
            // 找到当前点关于最右回文对称中心的对称点位置
//            int symmetryNode = radius[2 * index - i];
            // 当前点的状态,1、在最右回文边界之外,暂时假设只有自己是回文字符串,记录为1
            // 2、在最右回文边界之内,记录为对称点的回文边界和最右边界-i 中最小的
            radius[i] = maxR > i ? Math.min(radius[2 * index - i], maxR - i) : 1;

            // 检查并更新当前下标为中心的回文串最远延伸的长度
            while (i + radius[i] < preArr.length // 当前位置扩出的最大右边界不能超出数组范围
                && (i - radius[i] + 1) > 0 // 当前位置存在左边点 :i-radius[i] <-i-> i+radius[i], 可以继续向两边扩
            ) {
                // 继续向外扩充,寻找最大的回文半径
                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) { // 最大回文右边界已经扩到了最后一个字符
                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"));
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 近日睡得越来越早 梦可以做好多好多 舅舅家的樱桃熟了 去了你家 见了你的母亲 似乎她不太喜欢我 第一次和你以及你的...
    琦儿爱做梦阅读 203评论 0 0
  • 我挣扎着, 在拥挤的人群中, 禁锢着安稳执守。 穿过指尖错乱的纹路, 开始去寻找那暗淡的光。 我在孤寂的尘世喧嚣里...
    我是小倩倩阅读 76评论 0 0
  • 你没有享受过过一次美丽的中秋节?那是什么时候呢? 老师中午提前到班级进行查班,看看学生们是否到齐,是否有什么特殊的...
    JAMESCAMERONL阅读 519评论 5 3
  • 隐痛 九十年代初,我在安庆码头商贸城做服装批发生意。九六年结婚嫁到枞阳,两地分居,同行们纷纷取笑,回家当太太去吧,...
    纤纤柔荑阅读 72评论 0 0
  • 今天主要来讲关于人性认知的故事,这几天被身边的一个小伙子彻底颠覆了三观,话说小伙子(以下称他为W吧)以前是我...
    冬季限定阅读 2,561评论 0 2