文章目录
面试题 01.05. 一次编辑
https://leetcode.cn/problems/one-away-lcci/
思路:先求出fisrt的字符串长度n, second的字符串长度m, 分类讨论
- abs(n-m)>1: 如果两个字符串的长度差大于1,说明不可能一次编辑,返回false
- m==n: 此时只有一个对应的位置不同,其他位置都相同,当前位置i和j指向的字符不同时,i+1和j+1指向的字符一定相同,如果还不相同,返回false, 具体可以使用一个flag表示,遇到一个位置不同时,flag为false,下一次如果还出现不等,直接返回false;
- m!=n: 当前位置i和j指向的字符不同时,要么有
first[i+1]=second[j]
,要么有first[i]=second[j+1]
, 如果是前者,i++, 如果是后者,j++, 设置flag=false , 后面如果还遇到不相等的位置,根据flag=false直接判断 - 循环结束后,
i==n&&j==m||flag
是判断条件,前者对应不相等字符没有出现在最后一个位置,后者对应不相等字符出现在最后一个位置(a ab 循环结束时没有遇到不等的字符)
class Solution {
public boolean oneEditAway(String first, String second) {
int n=first.length(),m=second.length();
if(Math.abs(n-m)>1)
return false;
int i=0,j=0;
boolean flag=true;
while(i<n&&j<m){
if(first.charAt(i)==second.charAt(j)){
i++;
j++;
}else if(m!=n){
if(!flag)
return false;
if(i+1<n&&first.charAt(i+1)==second.charAt(j))
i++;
if(j+1<m&&second.charAt(j+1)==first.charAt(i))
j++;
flag=false;
}else if(m==n){
if(!flag)
return false;
i++;
j++;
flag=false;
}
}
return i==n&&j==m||flag;
}
}
//O(min(m,n))
//O(1)
125. 验证回文串
https://leetcode.cn/problems/valid-palindrome/
思路:使用两个指针,初始时分别指向字符串的开始位置和结束位置,某个指针遇到的是非数字或者非字母字母,跳过该字符,开始下一轮的比较,比较的时候可以使用Java中的Character类中的转化大小写的函数进行比较(忽略大小写)
class Solution {
public boolean isPalindrome(String s) {
int left=0,right=s.length()-1;
while(left<right){
char c1=s.charAt(left),c2=s.charAt(right);
if(!(Character.isLetterOrDigit(c1))){
left++;
continue;
}
if(!(Character.isLetterOrDigit(c2))){
right--;
continue;
}
if(Character.toLowerCase(c1)!=Character.toLowerCase(c2))
return false;
left++;
right--;
}
return true;
}
}
//O(n)
//O(1)
680. 验证回文字符串 Ⅱ
https://leetcode.cn/problems/valid-palindrome-ii/
思路:先不考虑删除,单纯地去判断一个字符串是否为回文串和上一题的解法相同,依然使用两个指针。初始时指向字符串的开始和结束位置,当两个指针指向的字符相同时,left++,right–,如果不同,则区间[left+1,right]
或[left,right-1]
中应该是回文串,因为已经出现过一次不等了,如果在这两个区间内还出现左右指针指向的字符不同的情况,则说明删除一次不可能形成回文串
class Solution {
public boolean validPalindrome(String s) {
int left=0,right=s.length()-1;
while(left<right){
char c1=s.charAt(left),c2=s.charAt(right);
if(c1==c2){
left++;
right--;
}else{
return validPalindrome(s,left+1,right)||validPalindrome(s,left,right-1);
}
}
return true;
}
public boolean validPalindrome(String s,int left,int right){
while(left<right){
char c1=s.charAt(left),c2=s.charAt(right);
if(c1!=c2)
return false;
left++;
right--;
}
return true;
}
}
//O(n)
//O(1)
468. 验证IP地址
https://leetcode.cn/problems/validate-ip-address/
思路:单纯的字符串模拟题,注意边界条件,详细步骤见代码注释
class Solution {
public String validIPAddress(String queryIP) {
if("".equals(queryIP))//处理空串
return "Neither";
boolean isIPv4=queryIP.indexOf(".")>=0;//判断是IPv4还是IPv6格式
if(isIPv4){//IPv4
//先判断首尾是否有"." 因为后面需要以"."分割 首尾存在"." 会导致分割结果不一定准确
if(queryIP.charAt(0)=='.'||queryIP.charAt(queryIP.length()-1)=='.')
return "Neither";
String []segs=queryIP.split("\\.");//以"."进行分割得到一个字符串数组
if(segs.length!=4)//数组长度必须等于4
return "Neither";
for(int i=0;i<4;i++){//遍历数组
String seg=segs[i];
if(seg.length()<1||seg.length()>3)//每个字符串的长度必须在1-3
return "Neither";
for(int j=0;j<seg.length();j++){
if(seg.charAt(0)=='0'&&seg.length()>1)//存在前导零
return "Neither";
if(!Character.isDigit(seg.charAt(j)))//遇到非数字
return "Neither";
}
if(Integer.parseInt(seg)>255)//数字值大于255
return "Neither";
}
return "IPv4";
}else{
//先判断首尾是否有":" 因为后面需要以"."分割 首尾存在"." 会导致分割结果不一定准确
if(queryIP.charAt(0)==':'||queryIP.charAt(queryIP.length()-1)==':')
return "Neither";
String []segs=queryIP.split(":");
if(segs.length!=8)//数组长度必须为8
return "Neither";
for(int i=0;i<8;i++){
String seg=segs[i];
if(seg.length()<1||seg.length()>4)//每个字符串长度范围1-3
return "Neither";
for(int j=0;j<seg.length();j++){
if(Character.isDigit(seg.charAt(j)))//是数字直接跳过
continue;
if(Character.isLetter(seg.charAt(j))){//字母
//字母范围不属于a-f 也不属于A-F
if(!(seg.charAt(j)>='A'&&seg.charAt(j)<='F')&&!(seg.charAt(j)>='a'&&seg.charAt(j)<='f'))
return "Neither";
}
}
}
return "IPv6";
}
}
}
//O(n)
//O(n)
131. 分割回文串
https://leetcode.cn/problems/palindrome-partitioning/
思路1:递归回溯,判断任意子串s[i:j]
是否为回文串,如果是则加入到临时集合中,如果最后能够遍历到最后一个字符,说明该次递归过程中的遇到的子串都是回文串,将临时集合加入最终答案
class Solution {
List<String> list=new ArrayList<>();
List<List<String>> ans=new ArrayList<>();
public List<List<String>> partition(String s) {
dfs(s,0,s.length());
return ans;
}
public void dfs(String s,int i,int n){
if(i==n){
ans.add(new ArrayList<>(list));
return;
}
for(int j=i;j<n;j++){
if(isPalindrome(s,i,j)){//s[i:j]是回文串则加入
list.add(s.substring(i,j+1));
dfs(s,j+1,n);
list.remove(list.size()-1);
}
}
}
//判断子串s[i:j] 是否为回文串
public boolean isPalindrome(String s,int i,int j){
while(i<=j){
if(s.charAt(i)!=s.charAt(j)){
return false;
}
i++;
j--;
}
return true;
}
}
//O(n*2^n) n(判断子串是否为回文串) 2^n:枚举子串
//O(n)
思路1可以进行优化,在判断回文串的过程中,会存在重复判断,考虑以下情况:任意一个字符是回文串,即s[2:2]
已经判断过了,下一次可能的子串组合是s[0:1] s[2:2]....
这时对于s[2:2]
就不需要进行判断了,因此在回溯的基础上,再加上记忆化操作,避免重复判断
class Solution {
List<String> list=new ArrayList<>();
List<List<String>> ans=new ArrayList<>();
int[][] memo;
public List<List<String>> partition(String s) {
int n=s.length();
memo=new int[n][n];
dfs(s,0,n);
return ans;
}
public void dfs(String s,int i,int n){
if(i==n){
ans.add(new ArrayList<>(list));
return;
}
for(int j=i;j<n;j++){
if(memo[i][j]==-1){//s[i:j]不可能是回文串
continue;
}
//memo[i][j]==1: 已经判断过s[i:j]是回文串了
//isPalindrome(s,i,j): 没判断过 开始判断
if(memo[i][j]==1||isPalindrome(s,i,j)){//s[i:j]是回文串则加入
list.add(s.substring(i,j+1));
dfs(s,j+1,n);
list.remove(list.size()-1);
}
}
}
//判断子串s[i:j] 是否为回文串
public boolean isPalindrome(String s,int i,int j){
int ii=i,jj=j;
while(i<=j){
if(s.charAt(i)!=s.charAt(j)){
memo[i][j]=-1;
memo[ii][jj]=-1;
return false;
}
i++;
j--;
}
memo[ii][jj]=1;
return true;
}
}
//O(2^n) n^2(判断所有的子串是否为回文串) 2^n:枚举子串
//O(n^2)
451. 根据字符出现频率排序
https://leetcode.cn/problems/sort-characters-by-frequency/
思路1:先统计出各个字符出现的次数,然后按出现的次数进行排序
class Solution {
public String frequencySort(String s) {
HashMap<Character,Integer> map=new HashMap<>();
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
map.put(ch,map.getOrDefault(ch,0)+1);
}
List<Character> list=new ArrayList<>(map.keySet());
Collections.sort(list,(o1,o2)->map.get(o2)-map.get(o1));
StringBuilder sb=new StringBuilder();
for(Character c:list){
for(int i=0;i<map.get(c);i++){
sb.append(c);
}
}
return sb.toString();
}
}
//O(n+kkogk): n:拼接字符串时间 k:字符串的种类 klogk:排序时间
//O(n+k): n: 存储拼接后的字符串的空间 k:map空间
思路2:桶排序,统计出各个字符串出现的次数,并记录最大次数为max, 创建桶编号1-max, 将相应的字符放到相应的桶中,然后从最大的桶到最小的桶开始拼接字符串
class Solution {
public String frequencySort(String s) {
HashMap<Character,Integer> map=new HashMap<>();
int max=0;
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
map.put(ch,map.getOrDefault(ch,0)+1);
max=Math.max(max,map.get(ch));
}
StringBuilder[] buckets=new StringBuilder[max+1];
for(int i=1;i<=max;i++){
buckets[i]=new StringBuilder();
}
for(Map.Entry<Character,Integer> entry:map.entrySet()){
char ch=entry.getKey();
int fre=entry.getValue();
for(int i=0;i<fre;i++){
buckets[fre].append(ch);
}
}
StringBuilder sb=new StringBuilder();
for(int i=max;i>=1;i--){
sb.append(buckets[i]);
}
return sb.toString();
}
}
//O(n+k) n拼接字符串的时间 k: 遍历桶的时间
//O(n+k)
415. 字符串相加
https://leetcode.cn/problems/add-strings/
思路:简单模拟,注意两点,短的字符串左边补0对齐,需要考虑进位,尤其是最高位有进位时需要补1
class Solution {
public String addStrings(String num1, String num2) {
StringBuilder sb=new StringBuilder();
int m=num1.length()-1,n=num2.length()-1;
int carry=0;//进位
while(m>=0||n>=0){
char ch1=m>=0?num1.charAt(m):'0';//短的字符串左边补0
char ch2=n>=0?num2.charAt(n):'0';
int num=ch1-'0'+ch2-'0'+carry;//计算
carry=num/10;//获取进位
num%=10;
sb.append(num);//添加
m--;
n--;
}
if(carry==1){//最高位有进位
sb.append('1');
}
return sb.reverse().toString();
}
}
//O(max(m,n))
//O(max(m,n))
43. 字符串相乘
https://leetcode.cn/problems/multiply-strings/
思路:根据竖式乘法的概念,被乘数中的某一位与乘数中的某一位相乘之后的结果会在对应的位置保存,对于长度为m的乘数,长度为n的被乘数,二者之积的位数最多为m+n位,也可能是m+n-1位,因此创建一个长度为m+n的数组来保存对应位的乘积
class Solution {
public String multiply(String num1, String num2) {
if(num1.equals("0")||num2.equals("0")){
return "0";
}
int m=num1.length(),n=num2.length();
int[] arr=new int[m+n];
for(int i=m-1;i>=0;i--){
int x=num1.charAt(i)-'0';//x作为被称数的某一位
for(int j=n-1;j>=0;j--){
int y=num2.charAt(j)-'0';//y作为乘数的某一位
arr[i+j+1]+=x*y;//注意这里是+= 是累加操作 不同的i j可能会对应一个累加位置
}
}
//计算进位 从低位往高位累加
for(int i=m+n-1;i>=1;i--){
arr[i-1]+=arr[i]/10;
arr[i]%=10;
}
StringBuilder sb=new StringBuilder();
if(arr[0]!=0){//可能乘法完成之后只有m+n位 此时最高位为0
sb.append(arr[0]);
}
for(int i=1;i<m+n;i++){
sb.append(arr[i]);//追加字符串
}
return sb.toString();
}
}
//O(mn)
//O(m+n)
394. 字符串解码
https://leetcode.cn/problems/decode-string/
思路:使用两个栈,一个栈用来保存上一层[前的字符串,一个栈用来保存[前面的数字,以s=3[a2[c]]
为例,当遍历到c时,此时:
- numSt: 3 2
- strSt: “” a
计算完2[c]之后,ans=strSt.pop()+2*“c”=“a”+“cc”=“acc”, 然后又遇到], 再次计算, 然后更新ans=strSt.pop()+3*“acc”=“aacaacaac”
class Solution {
public String decodeString(String s) {
LinkedList<String> strSt=new LinkedList<>();//保存结果
LinkedList<Integer> numSt=new LinkedList<>();//保存数字
StringBuilder ans=new StringBuilder();
int n=s.length();
int num=0;
for(int i=0;i<n;i++){
char ch=s.charAt(i);
if(ch=='['){
numSt.push(num);//将[前面的数字入栈
strSt.push(ans.toString());//将[前面的字符串入栈
num=0;//num置零
ans=new StringBuilder();//ans置零
}else if(ch==']'){
StringBuilder tmp=new StringBuilder();
int k=numSt.pop();
for(int j=0;j<k;j++){
tmp.append(ans);
}
ans=new StringBuilder(strSt.pop()+tmp);//更新ans 上一个[前的字符串 加上当前[]内的字符串
}else if(ch>='0'&&ch<='9'){
num=num*10+(ch-'0');//累加数字
}else{
ans.append(ch);
}
}
return ans.toString();
}
}
//O(n)
//O(n)
592. 分数加减运算
https://leetcode.cn/problems/fraction-addition-and-subtraction/
思路:简单模拟题,先单独求出第一个分数,然后在循环中不断地求出第2个分数,将两个分数计算的结果作为新的第一个分数,再去循环找第二个分数进行计算,将若干个分数的累加看出若干组两个分数的加法运算
class Solution {
public String fractionAddition(String expression) {
int m1=0,m2=0;//分子
int n1=0,n2=0;//分母
int sign1=1;//正负情况
int sign2=1;
if(expression.charAt(0)=='-'){
sign1=-1;
}
int i=sign1==-1?1:0;
int n=expression.length();
int start1=i;
while(expression.charAt(i)!='/'){
i++;
}
//计算出第一个数的分子
m1=Integer.parseInt(expression.substring(start1,i));
i++;
start1=i;
//计算出第一个数的分母
while(i<n&&expression.charAt(i)!='+'&&expression.charAt(i)!='-'){
i++;
}
n1=Integer.parseInt(expression.substring(start1,i));
while(i<n){
sign2=expression.charAt(i)=='+'?1:-1;//第2个数的正负情况
int start2=i+1;//分子开始位置
while(expression.charAt(i)!='/'){
i++;
}
//计算第2个数的分子
m2=Integer.parseInt(expression.substring(start2,i));
start2=i+1;//分母开始位置
while(i<n&&expression.charAt(i)!='+'&&expression.charAt(i)!='-'){
i++;
}
//计算第2个数的分母
n2=Integer.parseInt(expression.substring(start2,i));
int num=n1*n2/gcd(n1,n2);//最小公倍数 作为分母
int ans=(num/n1*m1)*sign1+(num/n2*m2)*sign2;//分子求和
if(ans<0){//分子求和小于0 取绝对值 标记符号位位-1
ans=-ans;
sign1=-1;
}else{
sign1=1;
}
int gcdNum=gcd(num,ans);//计算后的分子分母的最小公约数 对计算结果约分
m1=ans/gcdNum;//计算结果分子约分后作为第1个数的分子
n1=num/gcdNum;//计算结果分母约分后作为第1个数的分母
}
return sign1*m1+"/"+n1;
}
int gcd(int a,int b){
while(b!=0){
int tmp=b;
b=a%b;
a=tmp;
}
return a;
}
}
//O(nlogc) n是分数的个数 logc是计算两个分数的公约数的时间
//O(1)