Bootstrap

[题解]《算法零基础100讲》(第26讲) 字符串算法(六) - 回文串

一. 概念定义

  回文串:回文串就是正向读取与反向读取结果是一样的,例如:abcba,正向读取结果为abcba,反向读取的结果也为abcba,这样的字符串即为回文串。

二. 判断方法

  我们不难发现,回文串有这样一个特性,首尾字符相等,所以我们可以分别从头部和尾部同时向中间进行判断,查看两端的字符是否相等,如不相等,则非回文串。
如图:
在这里插入图片描述
代码如下:

#include <stdio.h>
#include <stdbool.h>

bool isPalindrome(char* s){
	int left = 0, right = strlen(s) - 1;
	while (left < right){
		//如果出现两端对称不相等的字符,则返回false
		if (s[left++] != s[right--]){
			return false;
		}
	}
	return true;
}

int main(){
	char s1[] = "abcba";
	char s2[] = "sdasc";
	if (isPalindrome(s1)){
		printf("s1是回文串\n");
	}
	else{
		printf("s1不是回文串\n");
	}
	if (isPalindrome(s2)){
		printf("s2是回文串\n");
	}
	else{
		printf("s2不是回文串\n");
	}

	return 0;
}

我们可以看到打印结果为:
在这里插入图片描述

三. 课后习题

3.1 回文排列

题目链接:
面试题 01.04. 回文排列
分析:
  题目要求是给定一个字符串,判断这个字符串是否能够通过调换顺序,使得其变成一个回文串,而一个回文串的特点是,它一定存在一个中间分割线,并且回文串关于此线对称,所以回文串的字符一定是成对存在的,但也都特殊情况,当字符串长度为奇数时,最中间的字母就是分割线,所以它可以是单独存在的。题目并未说明不区分大小写,那我们就默认区分大小写。
代码思路:

  1. 首先我们需定义一个数组Hash[123],用于记录每个字符出现的次数。
  2. 判断是否存在奇数数目的字符,并用cnt统计有多少种单独存在的字符
  3. 根据两种情况判断是否为回文串:
    (1)字符串长度为偶数,那所以字符一定是成对存在的,即(n%2==0 && cnt == 0)
    (2)字符串长处为奇数,那么允许存在一个单独存在的字符,即(n % 2 && cnt <= 1)
    代码如下:
bool canPermutePalindrome(char* s){
    int n = strlen(s);
    int Hash[123] = { 0 };
    //记录出现每个字符出现的次数
    for(int i = 0; i < n; i++){
        Hash[s[i]]++;
    }
    int cnt = 0;
    //记录有多少种单独存在的字符
    for(int i = 0; i < 123; i++){
        if(Hash[i] % 2 == 1){
            cnt++;
        }
        
    }
    //判断是否为回文串
    if(n % 2 && cnt <= 1){
        return true;
    }
    else if(n % 2 == 0 && cnt == 0){
        return true;
    }
    return false;
}

3.2 有效的回文

题目链接:
剑指 Offer II 018. 有效的回文
分析:
  题目要求是给定一个字符串,只考虑字母和数字字符,并且不区分大小写,判断是否为回文串。
思路:
  这一题的难度就在于如何将除字母和数字以外的字符除去,我们可以通过双指针的方式,将所有不需要判断的字符覆盖。
借鉴与这种方法:
双指针

代码如下:

//判断数字字符
bool isNumber(int a){
    if(a >= '0' && a <= '9')return true;
    return false;
}
//判断字母,并将大写转化为小写
bool isAlphabet(char* ch){
    if(*ch >= 'A' && *ch <= 'Z')
    {
        *ch += 32;
        return true;
    }
    else if(*ch >= 'a' && *ch <= 'z')return true;
    return false;
}

bool isPalindrome(char * s){
    int n = strlen(s);
    int slow = 0, fast = 0;
    //将除数字字符和字母以外的字符去除
    while(fast < n){
        if(isNumber(s[fast]) || isAlphabet(&s[fast])){
            s[slow++] = s[fast++]; 
        }
        else{
            fast++;
        }
    }
    int left = 0, right = slow - 1;
    //判断是否为回文
    while(left < right){
        if(s[left++] != s[right--])return false;
    }
    return true;
}

3.3 验证回文串

题目链接:
125. 验证回文串
这道题和上面那道是一样的,但是多了一种情况,空字符串也为回文串,所以要对其进行特殊判断。
代码如下:

//判断数字字符
bool isNumber(int a){
    if(a >= '0' && a <= '9')return true;
    return false;
}
//判断字母,并将大写转化为小写
bool isAlphabet(char* ch){
    if(*ch >= 'A' && *ch <= 'Z')
    {
        *ch += 32;
        return true;
    }
    else if(*ch >= 'a' && *ch <= 'z')return true;
    return false;
}

bool isPalindrome(char * s){
    int n = strlen(s);
    //特殊情况
    if(n == 0)return true;
    int slow = 0, fast = 0;
    //将除数字字符和字母以外的字符去除
    while(fast < n){
        if(isNumber(s[fast]) || isAlphabet(&s[fast])){
            s[slow++] = s[fast++]; 
        }
        else{
            fast++;
        }
    }
    int left = 0, right = slow - 1;
    //判断是否为回文
    while(left < right){
        if(s[left++] != s[right--])return false;
    }
    return true;
}

3.4 最长回文串

题目链接:
409. 最长回文串
分析:
  题目要求是找出最长的回文串,也就是使用给定的字符串中的字符,所能够组成的最长字符串。

代码思路:

  1. 首先我们还是用一个数组Hash[],记录每种字符的数量。
  2. 然后根据回文字符的对称性,我们用一个变量len统计所有的偶数数量的字符,对于奇数数量的字符则-1再加入len中。

代码如下:

int longestPalindrome(char * s){
    int n = strlen(s);
    int Hash[123] = { 0 };
    //记录字符数量
    for(int i = 0; i < n; i++){
        Hash[s[i]]++;
    }
    int cnt = 0;
    int len = 0;
    //判断奇偶
    for(int i = 0; i < 123; i++){
        if(Hash[i] % 2){
            cnt += Hash[i];
            len += (Hash[i] - 1);
        }
        else if(Hash[i] % 2 == 0 && Hash[i])len += Hash[i];   
    }
    //判断是否有奇数数量的字符
    if(cnt == 0){
        return len;
    }
    return len + 1;
    
}

3.5 最多删除一个字符得到回文

剑指 Offer II 019. 最多删除一个字符得到回文
分析:
  根据题目意思,最多只能进行一次减去字符的操作,所以当字符串长度为奇数时,它可以最多能存在三个单独字符,并且第三个一定在中间,当为偶数时,则这能存在两个单独的字符。
代码思路:
  通过双指针的方式从首尾同时判断,如果遇到两端不相等的字符,则有两种方式,一是删除左边的,二是删除右边的,通过回溯的方式实现。

代码如下;

bool Recall(char* s, int left, int right, int change){
    if(left >= right){
        return true;
    }
    while(left < right){
        if(s[left] != s[right]){
            if(change == 0)
                return false;
            else{
                return Recall(s, left+1, right,0) 
                || Recall(s, left, right-1, 0);
            }
            
        }
        left++;
        right--;
    }
    return true;
}

bool validPalindrome(char * s){
    return Recall(s, 0, strlen(s) - 1, 1);
}

3.6 验证回文字符串 Ⅱ

题目链接:
680. 验证回文字符串 Ⅱ
这道题和刚才上面的那道题是一样的。

代码如下:

bool Recall(char* s, int left, int right, int change){
    if(left >= right){
        return true;
    }
    while(left < right){
        if(s[left] != s[right]){
            if(change == 0)
                return false;
            else{
                return Recall(s, left+1, right,0) 
                || Recall(s, left, right-1, 0);
            }
            
        }
        left++;
        right--;
    }
    return true;
}

bool validPalindrome(char * s){
    return Recall(s, 0, strlen(s) - 1, 1);
}

3.7 删除回文子序列

题目链接:
1332. 删除回文子序列
分析:
  这道题乍一看好像很难,完全没有头绪,一开始大多数人肯定会想,这么难得题目怎么能叫简单题呢?但仔细一看,真的是简单题。体验就在于第一句话,字符串只由ab组成,删除回文子串
  如果该串不是回文串,那我们只需要两步,第一步删除所有a(或b),那第二次就是删除所有b(或a),如果是子串,那只需1次删除。

代码如下:

int removePalindromeSub(char * s){
    int left = 0;
    int right = strlen(s) - 1;
    if(right + 1 == 0){
        return 0;
    }
    while(left < right){
        if(s[left] != s[right]){
            return 2;
        }
        left++;
        right--;
    }
    return 1;
}
;