01.01. 判定字符是否唯一
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
示例 1:
输入: s = “leetcode”
输出: false
示例 2:
输入: s = “abc”
输出: true
限制:
0 <= len(s) <= 100
如果你不使用额外的数据结构,会很加分。
解题思路
由于题目提示可以不用额外的数据结构解题,那么我们应该抛弃直观上的用set解题的方法。双重循环的暴力求解由于O(n^2)的时间复杂度,也不应该考虑。
位运算方法的思路本质上,跟使用一个bool数组来记录astr的每一位是否已经出现过的思路是一样的。
基于bool数组的方法
由于题目没有明确说明,根据示例我判断字符串中出现的字符应该在[‘a’,‘z’]之间,实践证明确实如此。基于这个前提,使用bool数组的做法是定义一个长度为26的初始值全为0 bool数组,逐个字符遍历astr,如果bool数组中对应的下标(‘a’->0, …, ‘z’->25)的值为1则重复出现,返回false,否则设置对应下标值为1。
基于位运算的方法
我们可以使用一个int类型的变量(下文用mark表示)来代替长度为26的bool数组。假设这个变量占26个bit(在多数语言中,这个值一般不止26),那么我们可以把它看成000…00(26个0),这26个bit对应着26个字符,对于一个字符c,检查对应下标的bit值即可判断是否重复。那么难点在于如何检查?这里我们可以通过位运算来完成。首先计算出字符char离’a’这个字符的距离,即我们要位移的距离,用move_bit表示,那么使用左移运算符1 << move_bit则可以得到对应下标为1,其余下标为0的数,如字符char = ‘c’,则得到的数为000…00100,将这个数跟mark做与运算,由于这个数只有一个位为1,其他位为0,那么与运算的结果中,其他位肯定是0,而对应的下标位是否0则取决于之前这个字符有没有出现过,若出现过则被标记为1,那么与运算的结果就不为0;若之前没有出现过,则对应位的与运算的结果也是0,那么整个结果也为0。对于没有出现过的字符,我们用或运算mark | (1 << move_bit)将对应下标位的值置为1。
由于没有看到比较清楚的位运算题解,所以自己写了一下,不知道解释清楚了没有(´・_・`)
public static boolean isUnique(String astr) {
int aa = 0;
int cc = 1;
for (int i = 0; i < astr.length(); i++) {
char t = astr.charAt(i);
int offset = t - 'a';
int bb = cc << offset;
if ((aa & bb) != 0) {
return false;
}
aa |= bb;
}
return true;
}
01.02. 判定是否互为字符重排
给定两个字符串 s1 和 s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
示例 1:
输入: s1 = “abc”, s2 = “bca”
输出: true
示例 2:
输入: s1 = “abc”, s2 = “bad”
输出: false
说明:
0 <= len(s1) <= 100
0 <= len(s2) <= 100
解法1:哈希表计数
思路:
遍历字符串,利用哈希表存储两个字符串中每个字符出现的次数。判断两个字符串中26个字母出现次数是否相同。
实现细节:
使用unordered_map记录出现次数。
为节省空间,遍历第一个字符串时对map中计数做加法处理,遍历第二个字符串时做减法处理,最后遍历26个字母,判断计数值是否均为0,这样只需要使用一个unordered_map即可。
代码:
class Solution {
public:
bool CheckPermutation(string s1, string s2) {
unordered_map<char, int> mp;
for(char c : s1) {
mp[c]++;
}
for(char c : s2) {
mp[c]--;
}
for(int i = 0; i < 26; i++) {
char c = i + 'a';
if(mp[c] != 0) {
return false;
}
}
return true;
}
};
复杂度分析:
假设两个字符串长度分别为N1和N2,则时间复杂度为O(max(N1, N2, 26));
利用哈希表存储,空间复杂度为O(max(N1, N2))。
解法2:排序
思路:
将两个字符串进行排序,然后比较两字符串是否相等即可。
代码:
class Solution {
public:
bool CheckPermutation(string s1, string s2) {
sort(s1.begin(), s1.end());
sort(s2.begin(), s2.end());
return s1 == s2;
}
};
复杂度分析:
排序复杂度为O(NlgN),比较字符串复杂度为O(N),故总时间复杂度为O(N);
空间复杂度为O(1)。
01.03. URL化
URL化。编写一种方法,将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。(注:用Java实现的话,请使用字符数组实现,以便直接在数组上操作。)
示例1:
输入:"Mr John Smith ", 13
输出:“Mr%20John%20Smith”
示例2:
输入:" “, 5
输出:”%20%20%20%20%20"
第一种解法:直接用String的API
效率太差,而且要求也不是用这种方法,不过第一反应就是用这种方法做的。
用了54ms,47.3MB
class Solution {
public String replaceSpaces(String S, int length) {
return S.substring(0, length).replaceAll(" ", "%20");
}
}
第二种解法:字符数组
用了11ms,47.7MB
class Solution {
public String replaceSpaces(String S, int length) {
char[] ch = new char[length * 3];
int index = 0;
for (int i = 0; i < length; i++) {
char c = S.charAt(i);
if (c == ' ') {
ch[index++] = '%';
ch[index++] = '2';
ch[index++] = '0';
} else {
ch[index] = c;
index++;
}
}
return new String(ch, 0, index);
}
}
第三种解法:使用StringBuilder
用了19ms,47.4MB
class Solution {
public String replaceSpaces(String S, int length) {
//
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
char ch = S.charAt(i);
if (ch == ' ') {
sb.append("%20");
continue;
}
sb.append(ch);
}
return sb.toString();
}
}
01.04. 回文排列
给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。
回文串不一定是字典当中的单词。
示例1:
输入:“tactcoa”
输出:true(排列有"tacocat"、“atcocta”,等等)
方法一
每个字符出现的次数为偶数, 或者有且只有一个字符出现的次数为奇数时, 是回文的排列; 否则不是.
class Solution {
public boolean canPermutePalindrome(String s) {
Set<Character> set = new HashSet<>();
for (char c : s.toCharArray()) {
if (!set.add(c)) {
set.remove(c);
}
}
return set.size() <= 1;
}
}
方法二
不使用jdk现成的数据结构, 自己用数组实现哈希表逻辑.
count记录"出现次数为奇数"的字符的个数
对于当前字符c, 如果之前已出现过奇数次, 则count减1; 否则count加1.
class Solution {
public boolean canPermutePalindrome(String s) {
int[] map = new int[256];
int count = 0;
for (char c : s.toCharArray()) {
if ((map[c]++ & 1) == 1) {
count--;
} else {
count++;
}
}
return count <= 1;
}
}
01.05. 一次编辑
字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
示例 1:
输入:
first = “pale”
second = “ple”
输出: True
示例 2:
输入:
first = “pales”
second = “pal”
输出: False
解题思路
主要思路是,只有一个地方需要修改,那么不妨定位到不同字符处。有以下两种情况
(1)长度相同:leetcode 与 leetkode。
那么我们需要找到 ‘c’ 和 ‘k’,然后比较 ‘ode’ 和 ‘ode’ 是否相同。
(2)长度不同:leetcode 与 leetode。
我们发现 ‘c’ 和 ‘o’ 不相同,然后比较 ‘ode’ 和 ‘ode’ 是否相同。
代码
class Solution {
public boolean oneEditAway(String first, String second) {
if (first == null || second == null) return false;
int len1 = first.length();
int len2 = second.length();
if (Math.abs(len1 - len2) > 1) return false;
if (len2 > len1) return oneEditAway(second, first);
// 保持第一个比第二个长
for (int i = 0; i < len2; i++){
if (first.charAt(i) != second.charAt(i)){
// 如果是长度相同字符串,那就比较下一个,如果长度不一样,那就从该字符开始进行比较。
return first.substring(i + 1).equals(second.substring(len1 == len2 ? i + 1 : i));
}
}
return true;
}
}
复杂度
时间复杂度:O(n)。因为要遍历字符串的每个字符。
空间复杂度:O(1)。
01.06. 字符串压缩
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
示例1:
输入:“aabcccccaaa”
输出:“a2b1c5a3”
示例2:
输入:“abbccd”
输出:“abbccd”
解释:“abbccd"压缩后为"a1b2c2d1”,比原字符串长度更长。
方法一:模拟
思路
字符串压缩的方式就是将连续出现的相同字符按照 字符 + 出现次数 压缩。如果压缩后的字符串长度变短,则返回压缩后的字符串,否则保留原来的字符串,所以我们模拟这个过程构建字符串即可。
算法
我们从左往右遍历字符串,用 ch 记录当前要压缩的字符,cnt 记录 ch 出现的次数,如果当前枚举到的字符s[i] 等于 chch ,我们就更新 cnt 的计数,即 cnt = cnt + 1,否则我们按题目要求将 ch 以及 cnt 更新到答案字符串ans 里,即 ans = ans + ch + cnt,完成对 ch 字符的压缩。随后更新 ch 为 s[i],cnt 为 1,表示将压缩的字符更改为 s[i]。
在遍历结束之后,我们就得到了压缩后的字符串 ans,并将其长度与原串长度进行比较。如果长度没有变短,则返回原串,否则返回压缩后的字符串。
class Solution {
public String compressString(String S) {
if (S.length() == 0) { // 空串处理
return S;
}
StringBuffer ans = new StringBuffer();
int cnt = 1;
char ch = S.charAt(0);
for (int i = 1; i < S.length(); ++i) {
if (ch == S.charAt(i)) {
cnt++;
} else {
ans.append(ch);
ans.append(cnt);
ch = S.charAt(i);
cnt = 1;
}
}
ans.append(ch);
ans.append(cnt);
return ans.length() >= S.length() ? S : ans.toString();
}
}
复杂度分析
时间复杂度:O(n),其中 n 为字符串的长度,即遍历一次字符串的复杂度。
空间复杂度:O(1),只需要常数空间(不包括存储答案 ans 的空间)存储变量。
01.07. 旋转矩阵
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
方法一:使用辅助数组
我们以题目中的示例二
5 1 9 11
2 4 8 10
13 3 6 7
15 14 12 16
作为例子,分析将图像旋转 90 度之后,这些数字出现在什么位置。
对于矩阵中的第一行而言,在旋转后,它出现在倒数第一列的位置:
5 1 9 11 x x x 5
x x x x =旋转后=> x x x 1
x x x x x x x 9
x x x x x x x 11
并且,第一行的第 xx 个元素在旋转后恰好是倒数第一列的第 xx 个元素。
对于矩阵中的第二行而言,在旋转后,它出现在倒数第二列的位置:
x x x x x x 2 x
2 4 8 10 =旋转后=> x x 4 x
x x x x x x 8 x
x x x x x x 10 x
对于矩阵中的第三行和第四行同理。这样我们可以得到规律:
对于矩阵中第 i 行的第 j 个元素,
在旋转后,
它出现在倒数第 i 列的第 j 个位置。
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
int[][] matrix_new = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix[i][j] = matrix_new[i][j];
}
}
}
}
复杂度分析
时间复杂度:O(N^2),其中 N 是matrix 的边长。
空间复杂度:O(N^2),我们需要使用一个和matrix 的大小相同的辅助数组。
方法二:原地旋转
题目中要求我们尝试在不使用额外内存空间的情况下进行矩阵的旋转,也就是说,我们需要「原地旋转」这个矩阵。那么我们如何在方法一的基础上完成原地旋转呢?
我们观察方法一中的关键等式:
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < (n + 1) / 2; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
}
复杂度分析
方法三:用翻转代替旋转
我们还可以另辟蹊径,用翻转操作代替旋转操作。我们还是以题目中的示例二
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 水平翻转
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < n; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
// 主对角线翻转
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}
01.08. 零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
步骤如下:
1、矩阵中某个数为零,则将该数所在行的第一个数置零,所在列的第一个数置零,即matrix[0][j] = matrix[i][0] = 0
2、遍历上面处理过的首行和首列的每个元素([0][0]位置除外),如果为零,则说明该行或者该列应该都为零,将该行或列全部元素置零
3、第一步操作会让首行首列是否有零这个信息损失掉,因为首行首列被用来存储其他信息了,会改变他们的取值,所以再定义两个变量row0,col0记录首行首列是否有零,最后根据row0和col0来处理首行和首列自己
代码
class Solution {
public void setZeroes(int[][] matrix) {
int m=matrix.length;
int n=matrix[0].length;
boolean row0=false,cow0=false;
//因为第一行和第一列被征用了,所以原始状态需要提前记录下来
for(int i=0;i<m;i++){//首列
if(matrix[i][0]==0) cow0=true;
}
for(int j=0;j<n;j++){//首行
if(matrix[0][j]==0) row0=true;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
//第i行,第j列都需要置零,所以把首列第i行和首行第j列标记上
if(matrix[i][j]==0){
matrix[i][0]=0;
matrix[0][j]=0;
}
}
}
for(int i=1;i<m;i++){
if(matrix[i][0]==0) {
matrix[i]=new int[n];
}
}
for(int j=1;j<n;j++){
if(matrix[0][j]==0){
for(int i=0;i<m;i++) {
matrix[i][j]=0;
}
}
}
if(row0) {
matrix[0]=new int[n];
}
if(cow0){
for(int i=0;i<m;i++) matrix[i][0]=0;
}
}
}
01.09. 字符串轮转
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
示例1:
输入:s1 = "waterbottle", s2 = "erbottlewat"
输出:True
示例2:
输入:s1 = "aa", s2 = "aba"
输出:False
提示:
字符串长度在[0, 100000]范围内。
说明:
你能只调用一次检查子串的方法吗?
思路:
长度相等时,若s2是s1旋转而成的,那么把s2和自身拼接一次,s1就会出现在其中
“erbottlewat” + “erbottlewat” = erbottle waterbottle wat
如果s2不是s1旋转而成的,那么那么把s2和自身拼接一次,s1就肯定不会出现在其中
class Solution {
public boolean isFlipedString(String s1, String s2) {
// 长度不相等,肯定不符合要求
if (s1.length() != s2.length()) {
return false;
}
// 长度相等时,若s2是s1旋转而成的,那么把s2和自身拼接一次,s1就会出现在其中
// "erbottlewat" + "erbottlewat" = erbottle waterbottle wat
// 如果s2不是s1旋转而成的,那么那么把s2和自身拼接一次,s1就肯定不会出现在其中
return (s2 + s2).indexOf(s1) != -1;
}
}