【LeetCode 笔记】字符串


3. 无重复字符的最长子串

题目描述
无重复字符的最长子串

思路: 滑动窗口 + 双指针

设立左指针a和右指针b
b 指针指向右侧伸缩 {

  • 对每个A[b]判断是否在之前的数组出现过;
    • 如果出现,指针a指向出现过的位置的下一个位置;
  • 更新右指针和最大长度maxLength

}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

图片参考 无重复字符的最长子串 c++实现三种解法 多重循环,hashmap优化,桶优化

int lengthOfLongestSubstring(char * s){
    int length = strlen(s);
    int start = 0, end, i, max = 0;
    
    //max 为当前最长字符串长度
    //end 作为外部循环变量,遍历字符串的每一个元素
    //start 初始指向当前检测到的重复元素的下一个元素索引
    //i 遍历从 start 到 end 的子串
    
    for(end = 0; end < length; end++){
        for(i = start; i < end; i++){
        //i遍历start到end这一段的字符串
            if(s[end] == s[i]){
            //遇到重复字符,将下一次遍历的起始标志记录为重复字符的下一个元素
                start = i + 1;
                break;
            }
        }
        if(max < end - start + 1){
            max = end - start + 1;	//更新max
        }
    }
    return max;
}

8. 字符串转换整数 (atoi)

大佬解法:

  • 一开始直接根据情况排除非数字、加减开头情况
  • flag记录开头正负号
  • 后续只需检测str[i]是否为数字
    • 是数字就进行转换
    • 不是数字就直接返回0
int isDigit(char s){
    if(s >= '0' && s <= '9')
        return 1;
    return 0;
}

int strToInt(char* str){
    int flag = 0, i = 0;
    long res = 0;
    
    while(str[i] == ' ')
        i++;
    if(!(str[i] == '+' || str[i] == '-' || (str[i] >= '0' && str[i] <= '9')))
        return 0;
    if(str[i] == '-'){
        flag = 1;
        i++;
    } else if(str[i] == '+')	//注意细节 else if
        i++;
    while(isDigit(str[i])){
        res = res * 10 + str[i] - '0';
        if(!flag && res > INT_MAX)
            return INT_MAX;
        else if(flag && -res < INT_MIN)
            return INT_MIN;
        i++;
    }
    return flag ? -res : res;
}

个人渣渣解法:

这道题有很多判断条件,绕来绕去绕昏了,代码每次提交不是漏解就是堆栈溢出。每次提交修修补补,和分支覆盖测试一样,最终到第十五次提交的时候终于成功了!

signed integer overflow: -1759999999999987655 * 10 cannot be represented in type ‘long long int’

int myAtoi(char * str) {
	int start = -1, end = 0, flag = 0, i = 0;
	if(strlen(str) == 0)
		return 0;
	while(str[i] != '\0'){
        if(str[i] == ' ' && start == -1){                             //开头为空格
            if(str[i + 1] == '\0')                     //字符串全为空格
                return 0;
            i++;
            continue;
        }
        if(start == -1){
            if(str[i] == '+' || str[i] == '-'){
                start = i++;
            } else if(str[i] < '0' || str[i] > '9') {
                return 0;
            } else {
            	start = i;
            	if(str[i + 1] == '\0'){                 //后面是结束符
                    end = i;
                    break;
                } else {                                //后面不是结束符
                    if(str[i] >= '0' && str[i] <= '9'){ //当前位为数字
                        i++;
                    } else {
                        end = i - 1;                    //当前位非数字且非结束符
                        break;
                    }
                }
			}
        } else {
            if(str[start] == '+' || str[start] == '-'){ //开头为 +、-
                if(str[start + 1] < '0' || str[start + 1] > '9')    //后面紧跟非数字
                    return 0;
                else{                                   //后面紧跟数字
                    if(str[i] < '0' || str[i] > '9'){   //当前位为非数字
                        end = i - 1;
                        break;
                    } else {                            //当前位为数字
                        if(str[i + 1] == '\0' || str[i + 1] == ' '){                 //后面是结束符
                            end = i;
                            break;
                        } else {                                //后面不是结束符
                            if(str[i] >= '0' && str[i] <= '9'){ //当前位为数字
                                i++;
                            } else {
                                end = i - 1;                    //当前位非数字且非结束符
                                break;
                            }
                        }
                    }
                }
            } else {                                    //开头为数字
                if(str[i + 1] == '\0' || str[i + 1] == ' '){                 //后面是结束符
                	if(str[i] >= '0' && str[i] <= '9'){ //当前位为数字
                        end = i;
                        break;
                    } else {
                        end = i - 1;                    //当前位非数字
                        break;
                    }
                } else {                                //后面不是结束符
                    if(str[i] >= '0' && str[i] <= '9'){ //当前位为数字
                        i++;
                    } else {
                        end = i - 1;                    //当前位非数字且非结束符
                        break;
                    }
                }
            }
        }
    }
	if(str[start] == '-') {
		flag = 1;
		start++;
	} else if(str[start] == '+') {
		flag = 0;
		start++;
	}
	int temp = 0, length = end - start + 1;
	long long res = 0;
	char *src = malloc(sizeof(char) * (length + 1));
	memset(src, '\0', sizeof(char) * (length + 1));
	strncpy(src, str + start, length);
	
	
	for(int i = 0; i < length; i++){
		res = res * 10 + (src[i] - '0');
		
		if(length >= 10){
			if(flag == 1){
				if(-res < INT_MIN){
					return INT_MIN;
				}
			} else if(flag == 0){
				if(res > INT_MAX){
					return INT_MAX;
				}
			}
		}
	}

	return flag == 0 ? res : -res;
}

用递归法将一个整数 n n n 转换成字符串

例如,输入483,应输出字符串"483" n n n 的位数不确定,可以是任意数的整数

#include <stdio.h>

void convert(int n){
	int i;
	if((i = n/10) != 0)			//表示n还剩下几位数需要转换 
		convert(i);
	putchar((n % 10) + '0');	//输出当前n值的最低位 
}

int main() {
	int number;
	printf("input an integer number: ");
	scanf("%d", &number);
	printf("output:\n");
	
	if(number < 0){
		putchar('-');
		number = -number;
	}
	convert(number);
	printf("\n");
	
	return 0;
}

14. 最长公共前缀

在这里插入图片描述
思路

当字符串数组长度为 0 时则公共前缀为空,直接返回
令最长公共前缀 ans 的值为第一个字符串,进行初始化
遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀
如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回
时间复杂度: O ( s ) O(s) O(s),s 为所有字符串的长度之和
画解算法
来源:画解算法

同时可以注意到,最长公共子串的长度一定不超过这组字符串中最小的那个串的长度minLength。因此,当遍历超过minLength的时候就跳过当前字符串的后续遍历。

char * longestCommonPrefix(char ** strs, int strsSize){
    if(strsSize == 0)
        return "";

    char *res = strs[0];
    int i, j;				//i遍历字符串,j遍历字符串的每个字符
    for(i = 1; i < strsSize; i++){
        for(j = 0; j < strlen(res) && j < strlen(strs[i]); j++) {
        					//限制j不超出两个被比较字符串的长度
            if(res[j] != strs[i][j])
                break;		//不同则跳出该比较循环
        }
        res[j] = '\0';  	//在第j处不同,res在第j处收尾
    }
    return res;
}

242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
输入: s = “anagram”, t = “nagaram”
输出: true
输入: s = “rat”, t = “car”
输出: false
说明: 你可以假设字符串只包含小写字母。

思路: 计数法

  • 由于题目只包含小写字母,就可以申请一个count[26]来记录字符串中字母出现的次数
  • 若两个字符串字母次数出现相同,则以s中字母的次数可以与t中字母的次数抵消:
    • count[s[i] - ‘a’]++; count[t[i] - ‘a’]–;
    • 则 count[i] == 0
  • 那么可同时遍历s和t,使得s在count中增加,t在count中减少,最后检测count中每一个元素是否为零(抵消)
bool isAnagram(char * s, char * t){
    int len1 = strlen(s), len2 = strlen(t);
    if(!len1 && !len2)
        return true;
    if(len1 != len2)
        return false;
    int count[26] = {0};

    for(int i = 0; i < len1; i++){
        count[s[i] - 'a']++;
        count[t[i] - 'a']--;
    }
    for(int i = 0; i < 26; i++)
        if(count[i] != 0)
            return false;
    return true;
}

1332. 删除回文子序列

给你一个字符串 s,它仅由字母 ‘a’ 和 ‘b’ 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。
返回删除给定字符串中所有字符(字符串为空)的最小删除次数。
「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。
「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。
输入: s = “ababa”
输出: 1
解释:字符串本身就是回文序列,只需要删除一次。
输入: s = “baabb”
输出: 2
解释:“baabb” -> “b” -> “”.
先删除回文子序列 “baab”,然后再删除 “b”。

思路在代码里:

int removePalindromeSub(char * s){
    int len = strlen(s);
    if(!len)    //空串,0次
        return 0;
    int i = 0, j = len - 1;
    while(i < j){
        if(s[i++] != s[j--])
            return 2;   //非整体回文,必大于1次
    }
    return 1;   //整体回文,1次
}

409. 最长回文串

给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
注意:假设字符串的长度不会超过 1010。
输入: “abccccdd”
输出: 7
解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

思路: 计数法 + 配对法
说到回文字符串就要想到s[i] == s[length - 1 - i]——前后必成对,这样一来就会发现:

  1. 若length为奇数,则首尾指针同步移动时,会单独剩下一位
  2. 若length为偶数,则首尾指针同步移动时,不会单独剩下一位

那么在统计字母出现次数的时候,遇到偶数个可直接sum+=2,但是奇数个呢?

我们知道:奇数 = 偶数 + 1

在本题,我们可以直接将奇数取为比它少1的偶数,这样以来避免了每次判断奇偶的步骤。在最后,由于返回结果必相等于字符串长度,如果返回结果比strlen(s)小,说明在统计的时候略去了奇数的1,返回值要+1。

int longestPalindrome(char * s){
    int length = strlen(s);
    if(!length)
        return 0;

    int count[52] = {0}, sum = 0;

    for(int i = 0; i < length; i++){
        if(s[i] >= 'a' && s[i] <= 'z')
            count[s[i] - 'a']++;
        else if(s[i] >= 'A' && s[i] <= 'Z')
            count[26 + s[i] - 'A']++;
    }
    //开始统计长度
    for(int i = 0; i < 52; i++){
        sum += count[i] / 2 * 2;    //全部记为偶数个,不必寻找奇数个的字母
    }
    if(sum < length)    //若统计和小于字符串长度,则说明有一位奇数个的"1"被上面"/2"略去了
        ++sum;

    return sum;
}

205. 同构字符串

给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
输入: s = “egg”, t = “add”
输出: true
输入: s = “foo”, t = “bar”
输出: false

思路:
记录每一个字符上次出现的位置,如果该字母上次出现的位置相同,直到遍历结束,则为同构字符串。

bool isIsomorphic(char * s, char * t){
    int preIndex1[256] = {0}, preIndex2[256] = {0};
    int length = strlen(s);
    for(int i = 0; i < length; i++){
        if(preIndex1[s[i]] != preIndex2[t[i]])
            return false;
        preIndex1[s[i]] = i + 1;	//记录当前的s[i]出现的最新位置
        preIndex2[t[i]] = i + 1;
    }
    return true;
}

注:使用该方法必须注意初始化的数字和每次更新记录后的数字要不同,例如:preIndex[256]={-1}; …; preIndex[s[i]] = i + 2(可以为其他数字,保证与初始化的数字不同);


696. 计数二进制子串

给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。
重复出现的子串要计算它们出现的次数。
输入: “00110011”
输出: 6

解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。
请注意,一些重复出现的子串要计算它们出现的次数。
另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。

思路:

由于0和1是分别组合在一起的,也就是说满足要求的字符串(不会存在0和1混合的情况如:0101 1001)

  • 从头开始往后遍历,判断当前数字是否与后一个数字相同(用cur记录当前数字出现的次数)
  • 相同,则cur++;不同则重新开始计数,用pre记录当前数字的重复次数cur,置cur = 1,重新开始下一轮计数
  • 若前一个数字出现的次数pre ≥ 当前数字出现的次数 cur,则一定包含满足条件的子串
    • 例如:0001(三个0,一个1),11100(三个1,两个0)
      此时记录该次数 result++
int countBinarySubstrings(char * s){
    // pre 前一个数字连续出现的次数,cur 当前数字连续出现的次数,result 结果子串个数
    int pre = 0, cur = 1, result = 0;
    for (int i = 0; i < strlen(s) - 1; i++) {
        // 判断当前数字是否与后一个数字相同
        if (s[i] == s[i + 1]) { // 相同,则当前数字出现的次数cur加1
            cur++;
        } else { // 不同,则用pre记录当前数字的重复次数cur,置cur = 1,重新开始下一轮计数
            pre = cur;
            cur = 1;
        }
        if (pre >= cur) { // 前一个数字出现的次数 >= 后一个数字出现的次数,则一定包含满足条件的子串
            result++;
        }
    }
    return result;
}

67. 二进制求和

给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
输入: a = “11”, b = “1”
输出: “100”

思路:

char * addBinary(char * a, char * b) {
	int length = 1 + (strlen(a) > strlen(b) ? strlen(a) : strlen(b)); //加一位进位位
    int i = strlen(a) - 1, j = strlen(b) - 1, k = 0;

    char *sum = malloc(sizeof(char) * (length + 1));    //加一位'\0'
    memset(sum, '0', sizeof(char) * (length + 1));
    char carry = '0', temp;

    while((i >= 0 || j >=0) && k < length + 1){
        char x = (i >= 0 ? a[i] : '0');
        char y = (j >= 0 ? b[j] : '0');
        
        temp = x + (y - '0') + (carry - '0');
        
        if(temp > '1'){
        	carry = '1';
		}
        else{
			carry = '0';
		}
        sum[k] = (temp - '0') % ('2' - '0')  + '0';
            
		k++;
        if(i >= 0)
            i--;
        if(j >= 0)
            j--;
    }
    if(carry == '1'){
        sum[length - 1] = '1';
        sum[length] = '\0';
    } else {
    	sum[length - 1] = '\0';
	}
	int newLength = strlen(sum);
	char *res = malloc(sizeof(char) * (newLength + 1));
	memset(res, '0', sizeof(char) * (newLength + 1));

	for(int i = 0; i < newLength; i++){
        res[i] = sum[newLength - 1 - i];
    }
    res[newLength] = '\0';
    free(sum);
    return res;
}

问题发现

我做这道题时,在处理字符串内部顺序出了问题:

我自己写了一个void reverse(char *s)函数,用于逆置字符串(除了末尾的'\0'):

void reverse(char *s){
	char temp;
	for(int i = 0; i < strlen(s); i++){
		temp = s[i];
		s[i] = s[strlen(s) - 1 - i];
		s[strlen(s) - 1 - i] = temp;
	}
}

但是效果并不理想 —— 压根儿就没有逆置,甚至运行出错。后来一番摸索,发现是字符数组字符指针的问题:使用字符数组形式的字符串逆置即可马上得到正确答案,但是使用字符指针指向的字符串却得不到任何结果。

解决方法就是形式对应

在字符指针指向的字符串中,要使用指针语言来解决遍历问题:

*(s + i) //相对于s首地址的偏移量,即 s[i]

正确写法:

//字符数组
void reverse(char s[]){
	char temp;
	for(int i = 0; i < strlen(s); i++){
		temp = s[i];
		s[i] = s[strlen(s) - 1 - i];
		s[strlen(s) - 1 - i] = temp;
	}
}
//指向字符串的指针
void reverse(char *s){
	char temp;
	int len = strlen(s);
	for(int i = 0; i < len / 2; i++){
		temp = *(s + i);
		*(s + i) = *(s + len - 1 - i);
		*(s + len - 1 - i) = temp;
	}
}

于是得到优化的代码:

void reverse(char *s){
	char temp;
	int len = strlen(s);
	for(int i = 0; i < len / 2; i++){
		temp = *(s + i);
		*(s + i) = *(s + len - 1 - i);
		*(s + len - 1 - i) = temp;
	}
}

char * addBinary(char * a, char * b){
    int length = 1 + (strlen(a) > strlen(b) ? strlen(a) : strlen(b)); //加一位进位位
    int i = strlen(a) - 1, j = strlen(b) - 1, k = 0;

    char *sum = malloc(sizeof(char) * (length + 1));    //加一位'\0'
    memset(sum, '0', sizeof(char) * (length + 1));
    char carry = '0', temp;

    while((i >= 0 || j >=0) && k < length + 1){
        char x = (i >= 0 ? a[i] : '0');
        char y = (j >= 0 ? b[j] : '0');
        
        temp = x + (y - '0') + (carry - '0');	//注意字符加减是ascll码的运算
        
        if(temp > '1'){
        	carry = '1';
		}
        else{
			carry = '0';
		}
        sum[k] = (temp - '0') % ('2' - '0')  + '0';
            
		k++;
        if(i >= 0)
            i--;
        if(j >= 0)
            j--;
    }
    if(carry == '1'){
        sum[length - 1] = '1';
        sum[length] = '\0';
    } else {
    	sum[length - 1] = '\0';
	}
	
	reverse(sum);	//此处直接原地逆置,不用新开辟空间
	
    return sum;
}

796. 旋转字符串

给定两个字符串, A 和 B。
A 的旋转操作就是将 A 最左边的字符移动到最右边。 例如, 若 A = ‘abcde’,在移动一次之后结果就是’bcdea’ 。如果在若干次旋转操作之后,A 能变成B,那么返回True。A 和 B 长度不超过 100。


输入: A = ‘abcde’, B = ‘cdeab’
输出: true


输入: A = ‘abcde’, B = ‘abced’
输出: false

思路一: 使用strncpy函数

  • 不断地记录 从第i位到length - 1 与 从 0 到 第 i - 1 位 拼接形成的字符串
  • 循环进行比较
    时间、空间复杂度 O ( n ) O(n) O(n)
bool rotateString(char * A, char * B){
    int len1 = strlen(A), len2 = strlen(B);
    if(!len1 && !len2)
        return true;
    if(len1 != len2)
        return false;
    char *temp = (char *)malloc(sizeof(char) * (len1 + 1));
    for(int i = 0; i < len1; i++){
        strncpy(temp, A + i, len1 - i); //从第i位到length - 1
        strncpy(temp + len1 - i, A, i);	//从 0 到 第 i 位
        temp[len1] = '\0';
        if(!strcmp(temp, B))
            return true;
    }
    return false;
}

char *strncpy(char *dest, const char *src, size_t n)

  • dest – 指向用于存储复制内容的目标数组。
  • src – 要复制的字符串。
  • n – 要从源中复制的字符数。

strncpy不会自动加’\0’,需要自行填补

思路二: 使用逆置交换

  • 线性代数,矩阵的逆(套娃)
  • 参考博文就地逆置线性表
  • 时间、空间复杂度 O ( n ) O(n) O(n)
void reverse(char *s, int from, int to){
    while(from < to){
        char temp = s[from];
        s[from] = s[to];
        s[to] = temp;
        from++;	to--;
    }
}

//逆置长度为n的字符串,逆置m次
void reverseString(char *s, int m, int n){
    reverse(s, 0, m - 1);
    reverse(s, m, n - 1);
    reverse(s, 0, n - 1);
}

bool rotateString(char * A, char * B){
    int len1 = strlen(A), len2 = strlen(B);
    if(!len1 && !len2)
        return true;
    char temp1[len1 + 1], temp2[len1 + 1];
    strcpy(temp1, A);
    for(int i = 1; i <= len1; i++){
        strcpy(temp2, temp1);
        reverseString(temp2, i, len1);
        if(!strcmp(temp2, B))
            return true;
    }
    return false;
}

557. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入: “Let’s take LeetCode contest”
输出: “s’teL ekat edoCteeL tsetnoc”
注意: 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。

思路

误区

第一眼看上去是反转字符串,而反转字符串的形式有所不同——并不是整体反转,而是单个单词进行反转,所有单词相对位置不变。
(我第一次提交的时候没有注意到,因此有了以下代码)

char * reverseWords(char * s){
    int len = strlen(s);
    int i;
    char temp;
    for(i=0; i<len/2; i++){
    	temp = s[i];
    	s[i] = s[len-i-1];
    	s[len-i-1] = temp;
	}
	return s;
}

此代码是反转整个字符串,与线性表的反转类似【见就地逆置线性表元素

输入:“Let’s take LeetCode contest”
输出:“tsetnoc edoCteeL ekat s’teL”
预期:“s’teL ekat edoCteeL tsetnoc”

正确解题思路

  • 记录s的长度len
  • 若s为空或只有一个字符,则直接返回
  • 否则
    • 用start指向每一个单词的第一个字母,end指向该单词的最后一个字符(遇到空格或者结束符就停下)
    • 将start到end之间的字符逆置

个人代码:

//逆置from到to的字符串
void reverse(char * s, int from, int to){
    char temp;
    while(from < to){
    	temp = *(s + from);
    	*(s + from) = *(s + to);
    	*(s + to) = temp;
        from++; to--;
	}
}

char * reverseWords(char * s){
    int i = 0, start = 0, end;
    while(s[i] != '\0'){
        if(s[i + 1] == ' ' || s[i + 1] == '\0'){	//若下一个字符为空格或结束符,那么就找到一个单词,进行逆置
            end = i;
            reverse(s, start, end);
            start = i + 2;	//start越过空格字符,指向下一个单词的开头
        }
        i++;
    }
    return s;
}

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


来源:LeetCode - kdurant
不同之处:

  • 使用r记录翻转后的字符串,用start指向每一个单词的第一个字母,end指向该单词的最后一个字符(遇到空格或者结束符就停下)
    • 将start到end之间的字符复制到r中

以空间换取时间

char * reverseWords(char * s){
    int len = strlen(s) + 1, index = 0;

    if(len == 1 || len == 2)
        return s;

    char * r = (char *)malloc(len);
    char *start = s, *end;

    for(int i = 0; i < len; i++, s++){
        if(*s == ' ' || *s == '\0'){
            for(end = s - 1; end >= start; end--){
                r[index++] = *end;
            }
            r[index++] = (*s == ' ') ? ' ' : (*s = '\0');
            start = s + 1;
        }
    }
    return r;
}

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


151. 翻转字符串里的单词

题目描述
思路:

  • 先反转整个字符串,再单独反转每个单词
  • 反转的同时,将前面的空格字符填充掉(下图的第三行:移位)
151 图解

图片来源 LeetCode 官方题解

void reverse(char *s, int from, int to){
    char temp;

    while(from < to){
        temp = *(s + from);
        *(s + from) = *(s + to);
        *(s + to) = temp;
        from++; to--;
    }
}

char * reverseWords(char * s){
    int length = strlen(s);
    int start = 0, end, idx = 0;	//idx作为插入点
    
    reverse(s, 0, length - 1);		// 反转整个字符串
    
    for(start = 0; start < length; start++){
        if(s[start] != ' '){
        	// 填一个空白字符然后将idx移动到下一个单词的开头位置
        	if(idx != 0)
        		s[idx++] = ' ';
        	
        	// 循环遍历至单词的末尾
        	end = start;
        	while(end < length && s[end] != ' ')
        		s[idx++] = s[end++];
        	
        	// 反转整个单词
        	reverse(s, idx - (end - start), idx - 1);
        	
        	// 更新start,去找下一个单词
        	start = end;
		}
    }
    s[idx] = '\0';	//去掉末尾多余字符

    return s;
}

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


5390. 数青蛙

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 “croak” )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak” 。请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

注意:要想发出蛙鸣 “croak”,青蛙必须 依序 输出‘c’, ’r’, ’o’, ’a’, ’k’这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。
如果字符串 croakOfFrogs 不是由若干有效的 “croak” 字符混合而成,请返回 -1

题目描述
提示:
1 <= croakOfFrogs.length <= 10^5
字符串中的字符只有 ‘c’, ‘r’, ‘o’, ‘a’ 或者 ‘k’

思路:

思想就是维护croak的个数,如果遇到当前字母,则肯定是由前面字母过来,前面字母数 - 1。
如遇到 r,则必是 c > r,所以 c--
k 代表结尾,其实也是青蛙的起始(一次喊叫结束),所以遇到c的时候,先去消耗k,没有k了,需要新青蛙,res += 1

int minNumberOfFrogs(char * croakOfFrogs){
	int len = strlen(croakOfFrogs);
	if(!len || len % 5 != 0)
		return -1;
		
	int a = 0, c = 0, k = 0, o = 0, r = 0;
    int res = 0;
    for(int i = 0; i < len; i++){
        if(croakOfFrogs[i] == 'c'){
            if(k > 0)	//前一个croak没有结束 
				k--;
            else 		//前一个croak已结束 
				res++;
            c++;
        } else if(croakOfFrogs[i] == 'r'){
            c--; r++;
        } else if(croakOfFrogs[i] == 'o'){
            r--; o++;
        } else if(croakOfFrogs[i] == 'a'){
            o--; a++;
        } else if(croakOfFrogs[i] == 'k'){
            a--; k++;
        }
        if(a < 0 || c < 0 || o < 0 || r < 0){
            break;
        }
    }
    if(a != 0 || c != 0 || o != 0 || r != 0){
        return -1;
    }
    return res;
}

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

再强调一次(给自己看),思维不能僵化!!
【总结】起初想用动态规划的方法来做,发现需要判断很多条件。这个时候就要及时想清楚是不是这种方法本身就有局限性,然后马上换个思路。

NC104 比较版本号

题目描述
牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等
现在给你2个版本号version1和version2,请你比较他们的大小
版本号是由修订号组成,修订号与修订号之间由一个"."连接。1个修订号可能有多位数字组成,修订号可能包含前导0,且是合法的。例如,1.02.11,0.1,0.2都是合法的版本号
每个版本号至少包含1个修订号。
修订号从左到右编号,下标从0开始,最左边的修订号下标为0,下一个修订号下标为1,以此类推。

比较规则:
一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的
二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1
三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 比较版本号
     * @param version1 string字符串 
     * @param version2 string字符串 
     * @return int整型
     */
    public int compare (String version1, String version2) {
        String[] s1 = version1.split("\\.");
        String[] s2 = version2.split("\\.");
        int i = 0;
        for(; i < Math.min(s1.length, s2.length); i++){
            if(Integer.parseInt(s1[i]) == Integer.parseInt(s2[i]))
                continue;
            return Integer.parseInt(s1[i]) > Integer.parseInt(s2[i]) ? 1 : -1;
        }
        while(i < s1.length){
            if(Integer.parseInt(s1[i]) > 0)
                return 1;
            i++;
        }
        while(i < s2.length){
            if(Integer.parseInt(s2[i]) > 0)
                return 1;
            i++;
        }
        return 0;
    }
}

1930. 长度为 3 的不同回文子序列

题目描述
在这里插入图片描述在这里插入图片描述

提示:

  • 3 <= s.length <= 105
  • s 仅由小写英文字母组成

解题思路
方法零:暴力枚举 + HashSet => 三重循环,显然超时

方法一:枚举 + HashMap记录 => 双重循环,超时

先将两边相同的字母找到,然后遍历中间的字母进行判断累加,二重循环,超时。

class Solution {
    public int countPalindromicSubsequence(String s) {
        int n = s.length(), count = 0;
        char[] arr = s.toCharArray();

        Map<Integer, Integer> map = new HashMap<>();
        boolean[] flag = new boolean[26];
        
        for(int i = 0; i < n - 2; i++) {
            for(int j = n - 1; j >= i + 2; j--) {
                if(arr[i] == arr[j] && !flag[arr[i] - 'a']) {
                    flag[arr[i] - 'a'] = true;
                    map.put(i, j);
                }
            }
        }

        boolean[][] flag1 = new boolean[26][26];
        for(Map.Entry<Integer, Integer> entry: map.entrySet()) {
            int start = entry.getKey(), end = entry.getValue();
            for(int i = start + 1; i < end; i++) {
                if(!flag1[arr[start] - 'a'][arr[i] - 'a']) {
                    flag1[arr[start] - 'a'][arr[i] - 'a'] = true;
                    count++;
                }
            }
        }
        
        return count;
    }
}

56 / 70 个通过测试用例

方法二:以26个字母为单位进行遍历,扫描中间字母并计数

该方法同样是先将两边相同的字母找到,然后遍历中间的字母进行判断累加。但它以26个字母为单位进行遍历,大大减小了循环次数。并且改变了查找相同字母的方式,即 不采用索引找字母的方式,而是采用字母查找索引的方法

class Solution {
    public int countPalindromicSubsequence(String s) {
        int count = 0;
        for(int i = 0; i < 26; i++) {
            boolean[] flag = new boolean[26];
            char index = (char)(i + 'a');
            int left = s.indexOf(index);        // 记录最左边的index字母
            int right = s.lastIndexOf(index);   // 记录最右边的index字母
            for(int j = left + 1; j < right; j++) { 
                flag[s.charAt(j) - 'a'] = true; // 夹击扫描每一个字母标记为true
            }
            for(int j = 0; j < 26; j++) {       // 标记后遍历26个字母
                if(flag[j]) {                   // 被标记的就是一个新的回文串
                    /**
                     * 此处不会重复计数,因为外层循环控制只遍历一次26个字母,
                     * left和right只查找最外层的相同字母index,不会重复遍历内层为index的字母
                     */
                    count++;    
                }
            }
        }
        return count;
    }
}

NOTE:

  • int atoi(const char *str): 把参数str所指向的字符串转换为一个整数(类型为 int 型),该函数返回转换后的长整数,如果没有执行有效的转换,则返回零,标准库:<stdlib.h>。从字符串当前位置开始到第一个不为有效整数的字符结束:atoi(“123abc”) —> 123
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beta Lemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值