面试算法知识梳理(2) - 字符串算法第一部分

面试算法代码知识梳理系列

面试算法知识梳理(1) - 排序算法
面试算法知识梳理(2) - 字符串算法第一部分
面试算法知识梳理(3) - 字符串算法第二部分
面试算法知识梳理(4) - 数组第一部分
面试算法知识梳理(5) - 数组第二部分
面试算法知识梳理(6) - 数组第三部分
面试算法知识梳理(7) - 数组第四部分
面试算法知识梳理(8) - 二分查找算法及其变型
面试算法知识梳理(9) - 链表算法第一部分
面试算法知识梳理(10) - 二叉查找树
面试算法知识梳理(11) - 二叉树算法第一部分
面试算法知识梳理(12) - 二叉树算法第二部分
面试算法知识梳理(13) - 二叉树算法第三部分


一、概要

本文介绍了有关字符串的算法第一部分的Java代码实现,所有代码均可通过 在线编译器 直接运行,算法目录:

  • 替换字符串中的空格
  • 输入一个字符串,打印出该字符串的所有排列
  • 第一个只出现一次的字符
  • 翻转句子
  • 计算字符串之间的最短距离

二、代码实现

2.1 替换字符串中的空格

问题描述

实现一个函数,将字符串p中的所有空格都替换成为指定的字符串r

解决思路

  • 遍历原始的字符串p,统计原先字符串中空格的个数spaceNum
  • 创建一个新的数组n,用于存放替换后的字符串。由于原先字符串中空格也占了一个位置,因此新数组n的长度为p.len + (r.len - 1) * spaceNum
  • 对于p 从后往前遍历,如果 遇到了空格,那么就将需要替换的字符串r中的字符 从后往前 填入n数组中;如果 遇到了非空格,那么就将p中的字符填入n数组中。

实现代码

class Untitled {
    
    static char[] replaceSpace(char p[], char r[], int pLen, int rLen){
        int spaceNum = 0;
        int i;
        for(i = 0; i < pLen; i++){
            if(p[i] == ' ')
                spaceNum += (rLen-1);
        }
        int nLen = pLen+spaceNum;
        char n[] = new char[nLen+1];
        i = nLen-1;
        int j = pLen-1;
        while(i >= 0 && j >= 0){
            if (p[j] == ' ') {
                for (int k = rLen-1; k >= 0; k--)
                    n[i--] = r[k];
            } else {
                n[i--] = p[j];
            }
            j--;
        }
        n[nLen] = 0;
        return n;
    } 
    
    public static void main(String[] args) {
        char[] source = "I am sentence with space".toCharArray();
        char[] replace = "%20".toCharArray();
        char[] result = replaceSpace(source, replace, source.length, replace.length);
        System.out.println(result);
    }
}

运行结果

>> I%20am%20sentence%20with%20space

2.2 输入一个字符串,打印出该字符串的所有排列

问题描述

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符abc所能排列出来的所有字符串abcacbbacbcacabcba

解决思路

这是一个 递归问题,求一个长度为n的字符串的全排列的方法为:

  • n[0..n.len-1]全排列的计算方法为:将n[0]位置的字符分别和n[1..n.len-1]的每一个字符串交换,n[0]和交换后的n[1..n.len - 1]的全排列进行组合。我们将字符串{s}的全排列表示为{s},那么对于abc来说,其全排列{abc},就等于是a + {bc}b + {ac}c + {ba}
  • 以此类推,n[1..n.len - 1]的全排列,则是将n[1]分别和n[2..n.len - 1]的每一个字符串交换,再求出交换后的n[2..len - 1]的全排列,递归结束的条件为n[i..n.len - 1]只有一个字符,例如,bc的全排列为b + {c}c + {b},而{c}{b}的全排列只有一种,因此递归结束,这时候就可以打印出结果。

实现代码

class Untitled {
    
    static void permutationStr(char p[], int depth, int length){
        if (depth == length) {
            System.out.println(p);
            return;
        }
        char c;
        for (int i = depth; i < length; i++){
            c = p[depth]; p[depth] = p[i]; p[i] = c;
            permutationStr(p, depth+1, length);
            c = p[depth]; p[depth] = p[i]; p[i] = c;
        }
    }
    
    public static void main(String[] args) {
        char[] source = "abc".toCharArray();
        permutationStr(source, 0, source.length);
    }
}

运行结果

>> abc
>> acb
>> bac
>> bca
>> cba
>> cab

2.3 第一个只出现一次的字符

问题描述

在字符串中找出第一个只出现一次的字符。如输入abaccdeff,则输出b,要求时间复杂度为O(n)

解决思路

这里需要采用 以空间换时间 的思想,也就是创建一个足够大的数组c,这里为256,然后对原始的数组p进行两次遍历:

  • 第一次 从头开始 遍历p,以p的值作为数组c的下标,并将c中对应位置的值加1,也就是说c[Integer.valueOf(i)]的值表示的是字符ip中出现的次数。这和HashMap的原理有些类似,只不过是将查找的key值直接简化成为了value的整型值。
  • 第二次 从头开始 遍历p,查找数组c对应位置该值是否为1,如果为1,那么就表示它是第一次只出现一次的字符。

实现代码

class Untitled {
    
    static char firstNotRepeat(char p[], int len){
        if (len == 1) 
            return p[0];
        int c[] = new int[256];
        int i;
        char r = p[0];
        for (i = 0; i < 256; i++)
            c[i] = 0;
        for (i = 0; i < len; i++)
            c[p[i]] += 1;
        for (i = 0; i < len; i++) {
            if (c[p[i]] == 1) {
                r = p[i];
                break;
            }
        }
        return r;
    } 
    
    public static void main(String[] args) {
        char[] source = "abaccdeff".toCharArray();
        char c = firstNotRepeat(source, source.length);
        System.out.println(c);
    }
}

运行结果

>> b

2.4 翻转句子

问题描述

翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以空格符隔开。例如I am a original string翻转后的结果为string original a am I

解决思路

实现过程分为两步:

  • 第一步,将整个句子中的所有字符都翻转
  • 第二步,遍历翻转后的句子,对于句子内的每一个单词,将其字符再翻转一次,就能保证单词内字符的顺序不变。翻转单词的时候,通过pStartpEnd记录每次遇到单词的起止下标,并使用子方法reverseSub对单词中的字符进行翻转。

实现代码

class Untitled {
    
    static void reverseSub(char p[], int start, int end){
        char c;
        int i = start;
        int j = end;
        while(i < j){
            c = p[i]; p[i] = p[j]; p[j] = c;
            i++; j--;
        }
    }

    static void reverseSentence(char p[], int length){
        //首先翻转整个具体的所有字符。
        reverseSub(p, 0, length-1);
        int pStart = 0;
        int pEnd = 0;
        //从头开始遍历,寻找句子中的单词,pStart和pEnd分别表示单词的起止下标。
        while(pStart < length && pEnd < length){
            if(p[pStart] == ' '){
                pStart++;
                pEnd++;
            } else if (p[pEnd] == ' ' || p[pEnd] == '\0') {
                //翻转单词中的字符。
                reverseSub(p, pStart, --pEnd);
                pStart = ++pEnd;
            } else {
                pEnd++;
            }
        }
    } 
    
    public static void main(String[] args) {
        char[] source = "I am a original string".toCharArray();
        System.out.println(source);
        reverseSentence(source, source.length);
        System.out.println(source);
    }
}

运行结果为:

>> string original a am I

2.5 计算字符串之间的最短距离

问题描述

假设我们有两个字符串AB,那么如果想要将字符串A通过以下三种操作变换成B:删除、新增和修改,操作步骤的次数就称为 字符串 A 和 B 之间的距离

现在给定两个字符串,求这两个字符串之间的最短距离。

解决思路

首先,我们需要先明确一个前提条件:如果A的长度为0,那么AB之间的距离就为B的长度,反之对于B也如此。

下面,我们在来看普通的情况,假如A[0]B[0]相同,那么AB之间的距离就为A[1..A.len-1]B[1..B.len-1]之间的距离;假如A[0]B[0]不相同,那么想要让AB相同,执行的操作有以下几种:

  • 删除A的第一个字符,然后计算A[1..A.len-1]B[0..B.len-1]的距离
  • 删除B的第一个字符,然后计算A[0..A.len-1]B[1..B.len-1]的距离
  • 修改A的第一个字符为B的第一个字符,然后计算A[1..A.len-1]B[1..B.len-1]的距离
  • 修改B的第一个字符为A的第一个字符,然后计算A[1..A.len-1]B[1..B.len-1]的距离
  • 增加A的第一个字符到B第一个字符之前,然后计算A[1..A.len-1]B[0...B.len-1]的距离
  • 增加B的第一个字符到A第一个字符之前,然后计算A[0...A,len-1]B[1..B.len-1]的距离

对于以上这六种情况,其实最终都可以归纳为 经过一次操作,再加上剩下部分的操作次数,那么我们的接下来的工作就是 求出剩下部分的操作部分的最小值。对于上面的任意一种情况,经过划分后AB的长度都会减少,那么最终必然会达到我们在一开始谈到的 前提条件:如果A的长度为0,那么AB之间的距离就为B的长度,反之对于B也如此。

实现代码

class Untitled {
    
    static int minValue(int t1, int t2, int t3){
        if (t1 < t2) {
            return t1 < t3 ? t1 : t3;
        } else {
            return t2 < t3 ? t2 : t3;
        }
    }

    static int calStringDis(char p1[], char p2[], int p1Start, int p2Start, int p1Len, int p2Len){
        if (p1Len == 0) {
            if (p2Len == 0)
                return 0;
            else
                return p2Len;
        }
        if (p2Len == 0) {
            if (p1Len == 0)
                return 0;
            else
                return p1Len;
        }
        if (p1[p1Start] == p2[p2Start])
            //A和B的第一个字符相同。
            return calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
        else {
            //(1) 删除B的第一个字符,或者将B的第一个字符放到A之前。
            int t1 = calStringDis(p1, p2, p1Start, p2Start+1, p1Len, p2Len-1);
            //(2) 删除A的第一个字符,或者将A的第一个字符放到B之前。
            int t2 = calStringDis(p1, p2, p1Start+1, p2Start, p1Len-1, p2Len);
            //(3) 修改A的第一个字符为B的第一个字符,或者修改B的第一个字符为A的第一个字符。
            int t3 = calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
            //计算以上三种情况的最小值,再加上这次操作的次数。
            return minValue(t1, t2, t3) + 1;
        }
    } 
    
    public static void main(String[] args) {
        char[] source = "abcde".toCharArray();
        char[] other = "bcd".toCharArray();
        System.out.println("" + calStringDis(source, other, 0, 0, source.length, other.length));
    }
}

运行结果

>> 2

更多文章,欢迎访问我的 Android 知识梳理系列:

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

推荐阅读更多精彩内容