LeetCode:字符串(7道经典题目)

LeetCode:字符串(7道经典题目)




本文带来的是以字符串为主题的一些题目,使用的语言是C++和Python。




344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0, right = s.size() - 1;
        while (left < right) {
            char tmp = s[left];
            s[left] = s[right];
            s[right] = tmp;
            left++; right--;
        }
    }
};

541. 反转字符串 II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
class Solution {
public:
	string reverseStr(string s, int k) {
		for (int i = 0; i < s.size(); i += 2 * k) {
			if (i + k < s.size()) {
				reverseString(s, i, i + k - 1);
				continue;
			}
			else {
				reverseString(s, i, s.size() - 1);
			}
		}
		return s;
	}

	void reverseString(string& s, int left, int right) {
		while (left < right) {
			char tmp = s[left];
			s[left] = s[right];
			s[right] = tmp;
            left++; right--;
		}
	}
};
class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        def reverse(s):
            left, right = 0, len(s) - 1
            while left < right:
                s[left], s[right] = s[right], s[left]
                left += 1
                right -= 1
            return s

        res = list(s)
        for i in range(0, len(s), 2 * k):
            # 字符串末尾如果超过最大长度,则会返回至字符串最后一个值,
            # 这个特性可以避免一些边界条件的处理。
            res[i:i + k] = reverse(res[i:i + k])
        return "".join(res)

剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0;
        int oldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }

        s.resize(s.size() + 2 * count);
        int newSize = s.size();
        for (int i = oldSize - 1, j = newSize - 1; i < j; i--, j--) {
            if (s[i] != ' ') {
                s[j] = s[i];
            }
            else {
                s[j] = '0';
                s[j - 1] = '2';
                s[j - 2] = '%';
                j -= 2;
            }
        }
        return s;
    }
};
class Solution:
    def replaceSpace(self, s: str) -> str:
        count = s.count(' ')
        res = list(s)
        res.extend([' '] * count * 2)  # 1 + 2 = 3

        left, right = len(s) - 1, len(res) - 1
        while left >= 0:  # 从后往前
            if res[left] != ' ':
                res[right] = res[left]
                right -= 1
            else:
                res[right - 2:right + 1] = '%20'
                right -= 3
            left -= 1
        return ''.join(res)

很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

151. 翻转字符串里的单词

给你一个字符串 s ,逐个翻转字符串中的所有 单词 。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。说明:

  • 输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
  • 翻转后单词间应当仅用一个空格分隔。
  • 翻转后的字符串中不应包含额外的空格。

解题思路如下:

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转
class Solution {
public:
	string reverseWords(string s) {
		removeExtraSpaces(s);
		reverseString(s, 0, s.size() - 1);
		reverseEachWord(s);
		return s;
	}

	void reverseEachWord(string& s) {
		int left = 0, right = 0;
		while (left < s.size()) {
			while (right < s.size() && s[right] != ' ') {
				right++;
			}

			reverseString(s, left, right - 1);
			left = right + 1;
			right++;
		}
	}

	// 反转字符串s中左闭又闭的区间[start, end]
	void reverseString(string& s, int start, int end) {
		for (int i = start, j = end; i < j; i++, j--) {
			swap(s[i], s[j]);
		}
	}

	void removeExtraSpaces(string& s) {  // 移除多余空格
		int slowIndex = 0, fastIndex = 0;

		// 去掉字符串前面的空格
		while (fastIndex < s.size() && s[fastIndex] == ' ') {
			fastIndex++;
		}

		// 去掉字符串中间部分的冗余空格
		for (; fastIndex < s.size(); fastIndex++) {
			if (fastIndex - 1 > 0 && s[fastIndex - 1] == s[fastIndex] && s[fastIndex] == ' ') {
				continue;
			}
			else {
				s[slowIndex++] = s[fastIndex];
			}
		}

		if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') {
			s.resize(slowIndex - 1);// 去掉字符串末尾的空格
		}
		else {
			s.resize(slowIndex);// 重新设置字符串大小
		}
	}
};
class Solution:
    def removeExtraSpaces(self, s):  # 去除多余的空格
        n = len(s)
        left, right = 0, n - 1
        while left <= right and s[left] == ' ':
            left += 1
        while left <= right and s[right] == " ":
            right -= 1

        tmp = []
        while left <= right:
            if s[left] != ' ':
                tmp.append(s[left])
            elif tmp[-1] != ' ':  # 跳过中间多余的空格
                tmp.append(s[left])
            left += 1
        return tmp

    def reverse_string(self, nums, left, right):  # 翻转字符串数组
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 1

    def reverse_each_word(self, nums):  # 翻转每个单词
        left, right = 0, 0
        n = len(nums)
        while left < n:
            while right < n and nums[right] != ' ':
                right += 1
            self.reverse_string(nums, left, right - 1)
            left = right + 1
            right += 1

    def reverseWords(self, s: str) -> str:
        s1 = self.removeExtraSpaces(s)
        self.reverse_string(s1, 0, len(s1) - 1)
        self.reverse_each_word(s1)
        return ''.join(s1)
class Solution:
    def reverseWords(self, s: str) -> str:
        from collections import deque
        left, right = 0, len(s) - 1
        while left <= right and s[left] == ' ':
            left += 1
        while left <= right and s[right] == ' ':
            right -= 1

        d = deque()
        word = []
        while left <= right:
            if s[left] == ' ' and word:
                d.appendleft(''.join(word))
                word = []
            elif s[left] != ' ':
                word.append(s[left])
            left += 1
        d.appendleft(''.join(word))
        return ' '.join(d)

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

具体步骤为:

  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串

最后就可以得到左旋n的目的,而不用定义新的字符串,完全在本串上操作。

class Solution {
public:
	string reverseLeftWords(string s, int n) {
		reverseString(s, 0, n-1);
		reverseString(s, n, s.size() - 1);
		reverseString(s, 0, s.size() - 1);
		return s;
	}

	void reverseString(string& s, int start, int end) { // 左闭右闭
		while (start < end) {
			char tmp = s[start];
			s[start] = s[end];
			s[end] = tmp;
			start++; end--;
		}
	}
};
class Solution:
    def reverseLeftWords1(self, s: str, n: int) -> str:
        ss = list(s)
        temp = ss[:n]
        ss[:len(ss) - n] = ss[n:]
        ss[len(ss) - n:] = temp
        return ''.join(ss)

    # 使用切片方法
    def reverseLeftWords2(self, s: str, n: int) -> str:
        return s[n:] + s[:n]

    # 有些面试中不允许使用切片,就用这种方法
    def reverseLeftWords3(self, s: str, n: int) -> str:
        s = list(s)
        s[:n] = list(reversed(s[:n]))
        s[n:] = list(reversed(s[n:]))
        s.reverse()
        return ''.join(s)

    # 如果reversed不让使用,那么自己手写一个
    def reverseLeftWords4(self, s: str, n: int) -> str:
        def reverse_sub(lst, left, right):
            while left < right:
                lst[left], lst[right] = lst[right], lst[left]
                left += 1
                right -= 1

        ss = list(s)
        reverse_sub(ss, 0, n - 1)
        reverse_sub(ss, n, len(ss) - 1)
        reverse_sub(ss, 0, len(ss) - 1)
        return ''.join(ss)

    # 考虑不能用切片的情况下,利用模+下标实现
    def reverseLeftWords5(self, s: str, n: int) -> str:
        new_s = ''
        for i in range(len(s)):
            j = (i + n) % len(s)
            new_s += s[j]
        return new_s

KMP算法(🧡🧡🧡🧡)

在一个串中查找是否出现过另一个串,这是KMP的看家本领。

KMP主要应用在字符串匹配上。KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。

next数组就是一个前缀表(prefix table)。前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。前缀表记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。前缀表要求的就是相同前后缀的长度。

求解next数组的几个概念:

  • 前缀:包含首位字符但不包含末位字符的字串
  • 后缀:包含末位字符但不包含首位字符的字串
  • next数组定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置
  • next[j]:其值为第j位字符前面j-1位字符组成的字串的前后缀重合字符+1

规律:

  • next[j]的值每次最多增加1
  • 模式串的最后一位字符不影响next数组的结果

写法如下面两道题。next数组有两种写法,我更喜欢用不减一的写法。

28. 实现 strStr()

实现 strStr() 函数。给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。说明:

  • 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
  • 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

直接利用前缀表当作next数组

class Solution {
public:
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        vector<int> next(needle.size());
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while (j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size()) {
                return i - needle.size() + 1;
            }
        }
        return -1;
    }

    void getNext(vector<int>& next, const string& s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
};
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        left, right = 0, len(needle)
        while right <= len(haystack):
        	# 这里needle == haystack[left:right]其实是有O(m)复杂度的
            if needle == haystack[left:right]:
                return left
            left += 1
            right += 1
        return -1
class Solution:
    def getNext(self, needle):
        next = [0] * len(needle)
        j = 0
        next[0] = j
        for i in range(1, len(needle)):
            while j > 0 and needle[i] != needle[j]:
                j = next[j - 1]
            if needle[i] == needle[j]:
                j += 1
            next[i] = j
        return next

    def strStr(self, haystack: str, needle: str) -> int:
        if len(needle) == 0: return 0
        next = self.getNext(needle)
        j = 0
        for i in range(len(haystack)):
            while j > 0 and haystack[i] != needle[j]:
                j = next[j - 1]
            if haystack[i] == needle[j]:
                j += 1
            if j == len(needle):
                return i - len(needle) + 1
        return -1

459. 重复的子字符串

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        if (s.size() == 0) {
            return false;
        }
        vector<int> next(s.size());
        getNext(next, s);
        int len = s.size();
        if (next[len - 1] != 0 && (len % (len - next[len - 1])) == 0) {
            return true;
        }
        return false;
    }

    void getNext(vector<int>& next, const string& s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) j++;
            next[i] = j;
        }
    }
};

class Solution:
    def getNext(self, s):
        next = [0] * len(s)
        j = 0
        next[0] = j
        for i in range(1, len(s)):
            while j > 0 and s[i] != s[j]:
                j = next[j - 1]
            if s[i] == s[j]:
                j += 1
            next[i] = j
        return next

    def repeatedSubstringPattern(self, s: str) -> bool:
        if len(s) == 0:
            return False
        next = self.getNext(s)
        if len(s) % (len(s) - next[-1]) == 0 and next[-1] != 0:
            return True
        return False

以上题解大多来自【代码随想录】,在此基础上做了一定总结,并附带一些自己的理解。

后续题目,随缘更新,有错误请指出!

END

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

奋斗的西瓜瓜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值