【剑指Offer】剑指offer题目----字符串

【剑指Offer】剑指offer题目


本文为《剑指Offer》刷题笔记的总结篇,计划将牛客网上《剑指Offer》的66道题刷了一遍,以博客的形式整理了一遍,这66道题属于相对基础的算法题目,对于刷题练手是很好的实践,接下来会继续回到LeetCode,争取每天拿出一个小时,刷一到两道题。  

如果有什么问题,请多多指教。


前言

  主要对这66道题做一个总结,整体来看,这66道题,涉及到了常用的数据结构:数组、字符串、链表、树、二叉树、栈、队列,还有在编程中经常用到的数据操作和算法:循环、递归、查找、排序、回溯、动态规划、位运算。以下对这66道题做一个归类。   从这些题目中,我们可以学习到以下一些解题经验: 首先要真正理解题意,对于一些不确定的情况,比如输入输出格式,可以追问。 思考要全面,对于特殊情况,比如指针为null,输入为空等等情况,要充分考虑,保证代码的完整性和鲁棒性。 当遇到一个比较复杂的问题时,可以通过画图、举例或者分解来考虑,从具体的实例中总结普遍规律。 注重效率的考量,这66道题用蛮力法解决可能都不是很难,但是我们需要考虑的是时间效率和空间效率的平衡,以空间换时间有时候是一个不错的选择。 常用数据结构和数据操作是基础,要重点掌握,如树的遍历,排序,查找,递归等操作,在题目中反复用到,要深刻理解算法思想。

一、字符串(共9道题目)

【剑指Offer】2、替换空格

在这里插入图片描述
解题思路:
  对于这个题目,我们首先想到原来的一个空格替换为三个字符,字符串长度会增加,因此,存在以下两种不同的情况:
  (1)允许创建新的字符串来完成替换。
  (2)不允许创建新的字符串,在原地完成替换。(这种看过别人通过双指针的形式展示。)
  
第一种情况比较简单。对于第二种情况,有以下两种解法:
(1)时间复杂度为O(n^2)的解法。
  从头到尾遍历字符串,当遇到空格时,后面所有的字符都后移2个。
(2)时间复杂度为O(n)的解法。
  可以先遍历一次字符串,这样可以统计出字符串中空格的总数,由此计算出替换之后字符串的长度,每替换一个空格,长度增加2,即替换之后的字符串长度为原来的长度+2*空格数目。接下来从字符串的尾部开始复制和替换,用两个指针P1和P2分别指向原始字符串和新字符串的末尾,然后向前移动P1,若指向的不是空格,则将其复制到P2位置,P2向前一步;若P1指向的是空格,则P1向前一步,P2之前插入%20,P2向前三步。这样,便可以完成替换,时间复杂度为O(n)。

看下别人的图:
在这里插入图片描述

解题一:

#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param s string字符串 
# @return string字符串
#
class Solution:
    def replaceSpace(self , s ):
        n=len(s)
        s1=list(s)
        i=0
        while(i<n):
            if s1[i]==" ":
                s1[i]="%20"
                i+=1
            else:
                i+=1
                continue
        return "".join(s1)

当然这种也很慢。
或者调包求解:

#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param s string字符串 
# @return string字符串
#
class Solution:
    def replaceSpace(self , s ):
        return s.replace(" ","%20")

解法二
不允许创建新的字符串,在原地完成替换。

 //第一种情况:创建新的字符串实现:
      public String replaceSpace(StringBuffer str) {
    	String res="";
        for(int i=0;i<str.length();i++){
            char c=str.charAt(i);
            if(c==' ')
                res += "%20";
            else
                res += c;
        }
        return res;
     }

	//第二种情况:原地替换,O(n)的解法
      public String replaceSpace(StringBuffer str) {
        if(str==null)
            return null;
        int numOfblank = 0;//空格数量
        int len=str.length();
        for(int i=0;i<len;i++){  //计算空格数量
            if(str.charAt(i)==' ')
                numOfblank++;
        }
        str.setLength(len+2*numOfblank); //设置长度
        int oldIndex=len-1;  //两个指针
        int newIndex=(len+2*numOfblank)-1;
        
        while(oldIndex>=0 && newIndex>oldIndex){
            char c=str.charAt(oldIndex);
            if(c==' '){
                oldIndex--;
                str.setCharAt(newIndex--,'0');
                str.setCharAt(newIndex--,'2');
                str.setCharAt(newIndex--,'%');
            }else{
                str.setCharAt(newIndex,c);
                oldIndex--;
                newIndex--;
            }
        }
        return str.toString();
    }

解法三:

python 实现(但是python双指针感觉就是个累赘,记得以前jieba分词用的拼接字符串使用的方法):

#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param s string字符串 
# @return string字符串
#
class Solution:
    def replaceSpace(self , s ):
        res =[]
        for c in s:
            if c==" ":
                res.append("%20")
            else:
                res.append(c)
        return "".join(res)
    

总结

本题有实际的现实意义,在网络编程中,如果URL参数中含有特殊字符,则可能导致服务器端无法获得正确的参数值,因此可以通过替换的方法将其转换为可以识别的字符。转换规则就是%后面加上该字符ASCII码的16进制表示,如这里的空格,ASCII码是0x20(十进制的32),所以可以替换为%20,其实还可以使用正则匹配进行替换。后续在补充。

【剑指Offer】27、字符串的排列

在这里插入图片描述
解法一:递归法
我尝试第一个方法是把所有的可能都写出来,然后去重。
首先:
在这里插入图片描述
但是会有重复的排序:
在这里插入图片描述
python实现逻辑:

class Solution:
    ##查找字符串的全排列
    def allstring(self, ss):
        l=[]
        for i in range(len(ss)):
            temp1 = ss[i]
            temp2 = ss[:i] + ss[i+1:]
            tempt3 = self.allstring(temp2)
            if tempt3:
                for x in tempt3:
                    l.append(temp1+x)
            else:
                l.append(temp1)
        return l

    def Permutation(self, ss):
        result=self.allstring(ss)
        result=list(set(result))
        return result


if __name__=="__main__":
    s=Solution()
    res=s.Permutation('abc')
    print(res)

解法二:java 实现:
对于这个问题,我们同样可以采用分解的办法。要求整个字符串的排列,可以看成两步:第一步:求所有可能出现在第一个位置的字符,即把第一个字符与后面的字符依次交换。第二步:固定一个字符,求后面所有字符的排列。
  很明显,求后面所有字符的排列,我们仍然可以把所有的字符分成两个部分:后面的字符的第一个字符以及这个字符之后的所有字符,然后把第一个字符逐一与其后的字符交换。因此,这是典型的递归思路。
在这里插入图片描述

import java.util.*;
public class Solution {
    ArrayList<String> res=new ArrayList<>();
    public ArrayList<String> Permutation(String str) {
       if(str==null||str.length()==0)
           return res;
       Permutation(str.toCharArray(),0);
       Collections.sort(res);
       return res;
    }
    public void Permutation(char[] strArr,int begin){
        if(begin==strArr.length-1){
            String s=String.valueOf(strArr);
            if(!res.contains(s))
                res.add(s);
        }else{
            for(int i=begin;i<strArr.length;i++){ //依次将后面的与begin交换
                swap(strArr,begin,i);
                Permutation(strArr,begin+1);
                swap(strArr,begin,i);
            }
        }
    }
    public void swap(char[] strArr,int a,int b){
        char temp=strArr[a];
        strArr[a]=strArr[b];
        strArr[b]=temp;
    }
}

解法三:看到牛客网大神写的思路:

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

首先读题我们发现,这种从固定的数组/String 里的取出元素进行排列组合,是经典的permutation题目。然后我们发现,这题的要求是 需要我们返回所有不重复的排列组合,而不是返回有多少种排列组合,所以我们需要建一个String的Arraylist来存储所有排列出的不重复的字符串。
除此之外,我们新建一个辅助method,用于不停的循环向下寻找所有可能的排列组合。在这个辅助method里,有三个arguments。第一个是str,即当前我们剩下可以取的string,第二个是cur,即我们当前所拥有的字符串,第三个是result,是我们符合条件的字符串的合集。
在这个辅助method中,我们首先需要检测,我们还有没有能取的string,如果没有,我们便可以开始确认我们当前所拥有的字符串有没有被放进最后的Arraylist result中,如果没有,我们便将他加进去。接下来,当我们还有能取的string时,我们就可以开始进行循环。对于现在所剩下的string其中的每一个字符串,我们都将他尝试与我们现有的string cur进行组合,所以循环的argument的cur 此时变成cur+Str.charAt(i)。然后因为我们已经把当前位于i的字符取过了,我们的第一个argument需要被更新为除这个的剩下的string,我们这里运用了substring来取i之前的string和i之后的string进行相加。
如此循环,直到我们所有的可能性都被取完。
这个方法同样也适用于我们需要排列组合的时一个数组而非string的情况。
具体代码如下:

import java.util.ArrayList;
public class Solution {
    public ArrayList<String> Permutation(String str) {
       ArrayList<String> result = new ArrayList<>();
       if(str.length() == 0){
           return result;
       }

       recur(str,"",result);
       return result;
    }

    public void recur(String str,String cur,ArrayList<String> result){
        if(str.length() == 0){
            if(!result.contains(cur)){
                result.add(cur);
            }
        }
        for(int i = 0; i < str.length();i++){
            recur(str.substring(0,i)+str.substring(i+1,str.length()),cur+str.charAt(i),result);
        }
    }
}

【剑指Offer】34、第一个只出现一次的字符

在这里插入图片描述
解法一:

# -*- coding:utf-8 -*-
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        s=list(s)
        d={}
        for i ,value in enumerate(s):
            if s.count(value)==1:
                return i
            else:
                continue
        return -1


if __name__ == "__main__":
    s = Solution()
    res = s.FirstNotRepeatingChar("google")
    print(res)

解法二:
参考博客大神的答案,
对于本题,这里给出以下三种解法:(1)用 HashMap 建立每个字符与其出现次数的映射,然后再依次遍历字符串时,找到第一个出现次数为1的字符,返回其位置即可。
在这里插入图片描述
(2)更进一步,因为该字符串全部是字母,所以可以用一个数组代替哈希表,数组下标就代表该字母。
(3)使用模式匹配从前(indexOf)和从后(lastIndexOf)匹配每一个字符,相等即为唯一。

//方法一:哈希表
    public int FirstNotRepeatingChar(String s) {
        Map<Character,Integer> map = new HashMap<Character,Integer>();
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            map.put(c,map.getOrDefault(c,0)+1);
        }
        for(int i=0;i<s.length();i++)
            if(map.get(s.charAt(i))==1)
                 return i;
        return -1;
    }

    //方法二:数组代替哈希表
    public int FirstNotRepeatingChar(String str) {
        if(str==null || str.length()==0)
            return -1;
        // A-Z对应的ASCII码为65-90,a-z对应的ASCII码值为97-122
        int len=str.length();
        int[] count=new int[58]; //122-65+1
        for(int i=0;i<len;i++){
            char c=str.charAt(i);
            count[c-'A']++;
        }
        for(int i=0;i<len;i++){
            char c=str.charAt(i);
            if(count[c-'A']==1)
                return i;
        }
        return -1;
    }

    //方法三:模式匹配
    public int firstUniqChar(String s) {
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            if(s.indexOf(c)==s.lastIndexOf(c))
                return i;
        }
        return -1;
    }

【剑指Offer】43、左旋转字符串

在这里插入图片描述
方法一:使用标准库

# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        s_list = list(s)
        str1 = s_list[:n]
        str2 = s_list[n:]
        t = "".join(str2)
        s1 = "".join(str1)
        return t + s1


if __name__ == "__main__":
    s = Solution()
    res = s.LeftRotateString("abcXYZdef", 3)
    print(res)
    

解法二:
对于本题,从最直观的角度我们首先可以想到暴力解法:每次移动一位,移动k次为止。对于每一次移动,其实就是将字符串第一个字符放到字符串末尾,而为了实现这一目标,需要将字符串其他位置的元素依次前移,因此,暴力解法时间复杂度为O(n^2)。
  还是那句话:最容易想到的解法往往不是最优的。
  进一步考虑,字符串左移k位,就相当于将字符串分为两部分,第一部分是前k位,另一部分是剩余的其他位,然后将这两部分交换顺序即可得到最后结果。因此,我们可以得到以下的三次反转算法:
  将字符串分为两部分,即前k个字符和剩余的其他字符,然后分别对这两部分进行反转,然后再对整个字符串进行一次反转,这样得到的结果就是我们想要的循环左移之后的字符串。事实上,这并不难理解,前后两部分各自经历了两次反转,因此每一部分的顺序并没有改变,只是将前后两部分进行了交换。对字符串进行一次反转,需要一次扫描,因此次算法时间复杂度为O(n)。
  举例:
  输入字符串"abcdefg"和数字2,该函数将返回左旋转2位得到的结果"cdefgab";
  第一步:翻转字符串“ab”,得到"ba";
  第二步:翻转字符串"cdefg",得到"gfedc";
  第三步:翻转字符串"bagfedc",得到"cdefgab";

//方法一,依次左移,每次移动一位
    public String LeftRotateString(String str,int n) {
        char[] strArr=str.toCharArray();
        int len=strArr.length;
        if(len<=0)
            return str;
        n=n%len;
        for(int i=0;i<n;i++){  //只控制循环次数
            char c=strArr[0];
            for(int j=0;j<len-1;j++) //拿出第一个,后面依次前移,复杂度O(n^2)
                strArr[j]=strArr[j+1];
            strArr[len-1]=c;
        }
        return new String(strArr);
    }

    //方法二:三次反转
    public String LeftRotateString(String str,int n) {
        char[] strArr=str.toCharArray();
        int len=strArr.length;
        if(len<=0)
            return str;
        n=n%len;
        reverseStr(strArr,0,n-1);
        reverseStr(strArr,n,len-1);
        reverseStr(strArr,0,len-1);
        return new String(strArr);
    }
    public void reverseStr(char[] array,int begin,int end){  //反转字符串,前后指针
        for(;begin<end;begin++,end--){
            char c=array[begin];
            array[begin]=array[end];
            array[end]=c;
        }
    }

参考思路,编写python程序实现:
模拟字符串左旋的写法,其实还可以用入栈出栈的想法去实现。

# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        if len(s) <= 0:
            return str
        str_list=list(s)
        # 同时
        for i in range(n):
            t=str_list[0]
            str_list.append(t)
            str_list.pop(0)
        return "".join(str_list)


if __name__ == "__main__":
    s = Solution()
    res = s.LeftRotateString("abcXYZdef", 3)
    print(res)

【剑指Offer】44、翻转单词序列

在这里插入图片描述
解法一:
将字符串反过来输出:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值