01-字符串高频面试题目

目录

字符串简介

面试总体分析

一些例题:

 例1.0-1串交换顺序

 例2.字符的替换和复制

 例3.交换星号

 例4.子串变位词

 例:leetcode 3  无重复字符的最长子串

 例5.单词(字符串)翻转

总结


字符串简介

字符串(String)

 通常把它作为字符数组

 java:String内置类型,不可更改,需要更改的话把它转化为StringBuffer,StringBuilder,char[]之类

 C++:std::string可更改,也可以考虑用char[](char *)

 C: 只有char[]

注意:C++中的‘+’运算符,复杂度未定义,但通常认为是线性的; C++std::string substr 和 java的String的subString参数不同

字符范围:c/c++[-128,127],我们通常转化为 [0,255]     java:[0.65535]

面试总体分析

和数组相关,内容广泛

   概念理解:字典序

   简单操作:插入,删除字符,旋转

   规则判断:(罗马数字转换 是否是合法的整数 浮点数)

   数字运算 (大数加法 二进制加法)

   排序,交换 (partition过程)

   字符计数(hash):变位词

   匹配 (正则表达式,全串匹配,kmp,周期判断)

  动态规划 (LCS,编辑距离,最长回文子串)

  搜索 (单词变换,排列组合)

 

一些例题:

    例1.0-1串交换顺序

int answer=0;
for(int i=0,j=len-1;i<j;i++,j--){
    for(;(i<j)&&(a[i]=='0');i++);
    for(;(i<j)&&(a[j]=='1');j--);
    if(i<j) ++answer; 
}

  例2.字符的替换和复制

删除一个字符串所有的a,并且复制所有的b。 注:字符数组足够大

分析:

  先删除a,可以利用原来字符串的空间

int n=0,numb=0;
for(int i=0;s[i];i++){
    if(s[i]!='a'){s[n++]=s[i];}
    if(s[i]=='b'){++numb;}
}
s[n]=0;

再复制b,注意字符串要加长【1.先计算字符串里有几个b,得到复制后的长度  2.然后,倒着复制--惯用技巧】

int newlen=n+numb;
s[newlen]=0;
for(int i=newlen-1,j=n-1; j>=0; j--){
    s[i--]=s[j];
    if(s[j]=='b')s[i--]='b';
}

 

    例3.交换星号

一个字符串只包含*和数字,请把它的*都放到开头

方法一 快排partition——数字相对顺序会变化

  循环不变式:[0...i-1]都是*,[i...j-1]是数字,[j,n-1]未探测

for(int i=0,j=0; j<n; j++)
    if(s[j]=='*') swap(s[i++],s[j]);

方法二 数字相对顺序不变

“”倒着”

int j=n-1;
for(int i=n-1;i>=0;--i) //拉过来
    if(isdigits(s[i])) s[j--]=s[i];
for(; j>=0; --j) s[j]='*';

 例4.子串变位词

给定两个串a和b,问b是否是a的子串的变位词,例如输入a=hello,b=lel,lle,ello,都是true;但是b=elo是false。

滑动窗口的思想

   动态维护一个‘窗口’

   比如b的长度是3,我们考查a[0...2],[1...3],[2...4]是否和b是变位词

   如何与b比较

我们用一个hash,基于字符串的特殊性,我们可以用[0...255]或者[0...65535]的数组,我们暂且认为他们都是小写英文字母,用

[0...25]来表示b中每个单词出现的次数

我们可以存一下有多少个非0次出现的,以后用

int nonZero=0;
for(int i=0;i<lenb; i++){
    if(++num[b[i]-'a']==1) ++nonZero;
}

现在,我们用子串b中的次数去减去主串a中的一个窗口内的字符种类,如果结果全是0,则找到这样的子串了。注意num[]的含义变为了字符种类差 

第一个窗口[0...lenb-1] (注意lena<lenb无解)

for(int i=0;i<lenb;i++){
    int c=a[i]-'a';
    --num[c];
    if(num[c]==0) --nonZero;
    else if(num[c]==-1) ++nonZero;
}
if(nonZero==0) return true;

窗口如何滑动? 向右移动一位

 新窗口 a[i-lenb+1...i]

 旧窗口 a[i-lenb...i-1]

  扔掉a[i-lenb] 加上a[i]

for(int i=lenb; i<lena; i++){
    //扔掉a[i-lenb]
    int c=a[i-lenb]-'a';
    ++num[c];
    if(num[c]==1) ++nonZero;
    else if (num[c]==0) --nonZero;

    //加上a[i]
    c=a[i]-'a';
    --num[c];
    if(num[c]==0) --nonZero;
    else if (num[c]==-1) ++nonZero;
    if(nonZero==0) return true;
}

例:leetcode 3  无重复字符的最长子串

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            // try to extend the range [i, j]
            if (!set.contains(s.charAt(j))){
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            }
            else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}

滑动窗口优化

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {//当前的j指向的字母和滑动窗口中某个位置的字母重了
                i = Math.max(map.get(s.charAt(j)), i); //(若重了的字母在窗口内,index>i)i直接指向重了字母的下一个位置 
            }
            ans = Math.max(ans, j - i + 1);
            map.put(s.charAt(j), j + 1);
        }
        return ans;
    }
}

Java(假设字符集为 ASCII 128)

以前的我们都没有对字符串 s 所使用的字符集进行假设。

当我们知道该字符集比较小的时侯,我们可以用一个整数数组作为直接访问表来替换 Map。

常用的表如下所示:

int [26] 用于字母 ‘a’ - ‘z’ 或 ‘A’ - ‘Z’
int [128] 用于ASCII码
int [256] 用于扩展ASCII码

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if(index[s.charAt(j)]!=0)
                i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }
}

 

 

例5.单词(字符串)翻转

翻转句子中全部的单词单词内容保持不变  例:I am a student 翻转 student a am I 

in-place翻转 

while(i<j) swap(s[i++],s[j--]);

用处:翻两次 : 翻转整个句子 tenduts a ma I   每个单词单独翻转 student a am I 

难点:如何区分单词,找空格 split

思考:字符串循环移位abcd  

移动一次:bcda 

移动两次:cdab

移动三次:dabc

结论:长度为n,移动m次,相当于移动m%n次   前m%n位翻转,后n-m%n位翻转 总体再翻一次 

 

总结

in-place(原地)

-O(1)空间  递归,堆栈空间可以不考虑 

原地相关问题:

字符串循环左移,右移  快排 partition 

滑动窗口

-时间复杂度:O(n) 空间复杂度:O(1)

规则相关——细致

匹配:kmp

Manacher——要求比较高的面试 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值