文章目录
两个字符串包含的字母是否完全相同
由于本题的字符串只包含 26 个小写字符,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,不再使用 HashMap。
class Solution {
public boolean isAnagram(String s, String t) {
int[] num = new int[26];
for (char c : s.toCharArray()) {
num[c - 'a']++;
}
for (char c : t.toCharArray()) {
num[c - 'a']--;
}
for (int i : num) {
if (i != 0){
return false;
}
}
return true;
}
}
借助哈希表
class Solution {
public boolean isAnagram(String s, String t) {
Map<Character,Integer> map = new HashMap<>();
for (char c : s.toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (char c : t.toCharArray()) {
Integer cnt = map.get(c);
//s中 不包含字母c
if (cnt == null) return false;
map.put(c, cnt - 1);
}
for (Character character : map.keySet()) {
if (map.get(character) != 0) return false;
}
return true;
}
}
将 s 和 t 排序
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
}
Arrays.sort()
方法采用了快排和优化的归并排序算法,平均时间复杂度都是O(nlogn),比较两个字符串的成本是 O(n),这里O(nlogn) 占主导地位
计算一组字符集合可以组成的回文字符串的最大长度
class Solution {
//aabbbc,最长回文字符串为 abbba 或 abcba
//将出现奇数次的b减一,最终无论将剩下的 'b || c'中的谁放最中间,都是在最终基础上加一
public int longestPalindrome(String s) {
if (s == null) return 0;
if (s.length() == 1) return 1;
// A~Z:65-90;a~z:97-122
int[] cnt = new int[58];
for (char c : s.toCharArray()) {
cnt[c - 'A']++;
}
int res = 0;
for (int i : cnt) {
//每次都加偶数
// i 为 偶数, i & 1 = 0;i 为 奇数, i & 1 = 1
//偶数次直接相加;若字母出现奇数次,则 次数-1,此时变偶数次,分布在对称的两边
res += i - (i & 1);
//或者这样写,例如:3 / 2 * 2 = 2
//res += i / 2 * 2;
}
//res小于原有长度,说明有字母出现次数为奇数次,那么这个字符可以放在回文字符串中间,在res基础上再加一
return (res < s.length()) ? res + 1 : res;
}
}
或者只统计出现奇数次的次数,最后将每组奇数次的个数减一(变成偶数次),最终再放一个在回文串中间
class Solution {
public int longestPalindrome(String s) {
int[] cnt = new int[128];
for (char c : s.toCharArray()) {
cnt[c]++;
}
int count = 0;
for (int i : cnt) {
count += (i % 2);// i % 2 可写成 i & 2
}
return count == 0 ? s.length() : (s.length() - count + 1);
}
}
字符串同构
力扣205
题目可以假设两字符串长度相等
记录字符上一次出现的位置,注意两个数组初始化每个元素为0,将第一个出现的字符的位置定为 1 而不是 0
例如:"ab","aa",
若 preIndex1[sc] = i,preIndex2[tc] = i;
当 i = 0 时
对于两个数组的第一个字符'a',preIndex1[a] = 0,preIndex2[a]=0;
当 i = 1时,由于数组本身元素初始化为 0 , 所以 b 在数组中的上一个位置被记录为 0,
但 a 在数组中的上一个出现的位置已被保存为 0,这样结果就变成 true 了
class Solution {
public boolean isIsomorphic(String s, String t) {
int[] preIndex1 = new int[128];
int[] preIndex2 = new int[128];
for (int i = 0; i < s.length(); i++) {
char sc = s.charAt(i);
char tc = t.charAt(i);
if (preIndex1[sc] != preIndex2[tc]) return false;
preIndex1[sc] = i + 1;
preIndex2[tc] = i + 1;
}
return true;
}
}
将第一个出现的字母映射成 1,第二个出现的字母映射成 2
对于 egg
e -> 1
g -> 2
也就是将 egg 的 e 换成 1, g 换成 2, 就变成了 122
对于 add
a -> 1
d -> 2
也就是将 add 的 a 换成 1, d 换成 2, 就变成了 122
egg -> 122, add -> 122
都变成了 122,所以两个字符串同构。
class Solution {
public boolean isIsomorphic(String s, String t) {
return isIsomorphicHelper(s).equals(isIsomorphicHelper(t));
}
private String isIsomorphicHelper(String s){
int[] index = new int[128];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//字符第一次出现
if (index[c] == 0){
index[c] = i + 1;
}
sb.append(index[c]);
}
return sb.toString();
}
}
换一种写法,本质还是记录字符第一次出现的位置
indexOf()
方法记录字符第一次出现时的索引
class Solution {
public boolean isIsomorphic(String s, String t) {
char[] c1 = s.toCharArray();
char[] c2 = t.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (s.indexOf(c1[i]) != t.indexOf(c2[i])){
return false;
}
}
return true;
}
}
回文子字符串个数(Medium)
Input: "aaa"
Output: 6
Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".
中心扩展法
比如对一个字符串 ababa,选择最中间的 a 作为中心点,往两边扩散,第一次扩散发现 left 指向的是 b,right 指向的也是 b,所以是回文串,继续扩散,同理 ababa 也是回文串。
这个是确定了一个中心点后的寻找的路径,然后我们只要寻找到所有的中心点,问题就解决了。
中心点一共有多少个呢?看起来像是和字符串长度相等,但你会发现,如果是这样,比如搜不到 abba
,想象一下单个字符的哪个中心点扩展可以得到这个子串?似乎不可能。所以中心点不能只有单个字符构成,还要包括两个字符,比如上面这个子串 abba
,就可以有中心点 bb
扩展一次得到,所以最终的中心点由 2 * len - 1
个,分别是 len
个单字符和 len - 1
个双字符。
如果上面看不太懂的话,还可以看看下面几个问题:
为什么有 2 * len - 1
个中心点?
aba 有 5 个中心点,分别是 a、b、c、ab、ba
abba 有 7 个中心点,分别是 a、b、b、a、ab、bb、ba
以 abb 为例,共有 5 个中心点 : a , b ,b ,ab, bb
0 <= i < 5
i = 0 时,left = 0,right = 0,中心点为 a, ans = 1
i = 1 时,left = 0,right = 1,中心点为 ab, ans = 1
i = 2 时,left = 1,right = 1,中心点为 b, ans = 2
i = 3 时,left = 1,right = 2,中心点为 bb, ans = 3
i = 4 时,left = 2,right = 2,中心点为 b, ans = 4
class Solution {
public int countSubstrings(String s) {
int ans = 0;
int len = s.length();
for (int i = 0; i < 2 * len - 1; i++) {
int left = i / 2;
int right = left + i % 2;
while (left >= 0 && right <= len - 1 && s.charAt(left) == s.charAt(right)){
left--;
right++;
ans++;
}
}
return ans;
}
}
或者这样写
private int cnt = 0;
public int countSubstrings(String s) {
for (int i = 0; i < s.length() - 1; i++) {
extendSubstrings(s, i, i); // 奇数长度
extendSubstrings(s, i, i + 1); // 偶数长度
}
return cnt;
}
private void extendSubstrings(String s, int start, int end) {
while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {
start--;
end++;
cnt++;
}
}
动态规划解法
dp[i][j] 表示 i 到 j 的字符串能不能构成回文串
状态转移方程 d p [ i ] [ j ] = ( s [ i ] = = s [ j ] ) & & d p [ i + 1 ] [ j − 1 ] dp\left[ i \right] \left[ j \right] =\ \left( s\left[ i \right] ==s\left[ j \right] \right) \ \&\&\ dp\left[ i+1 \right] \left[ j-1 \right] dp[i][j]= (s[i]==s[j]) && dp[i+1][j−1]
这个状态转移方程是什么意思呢?
- 当只有一个字符时,比如
a
自然是一个回文串。 - 当有两个字符时,如果是相等的,比如
aa
,也是一个回文串。 - 当有三个及以上字符时,比如
ababa
这个字符记作串 1,把两边的a
去掉,也就是bab
记作串 2,可以看出只要串2是一个回文串,那么左右各多了一个 a 的串 1 必定也是回文串。所以当s[i]==s[j]
时,自然要看dp[i+1][j-1]
是不是一个回文串。
class Solution {
public int countSubstrings(String s) {
int cnt = 0;
int n = s.length();
boolean[][] dp = new boolean[n][n];
for (int j = 0; j < n; j++) {
for (int i = 0; i <= j; i++) {
if (s.charAt(i) == s.charAt(j) && ((j - i) <= 1 || dp[i + 1][j - 1])){
dp[i][j] = true;
cnt++;
}
}
}
return cnt;
}
}
回文数
力扣9
尝试用数学方法将数字翻转
class Solution {
public boolean isPalindrome(int x) {
int revertednum = 0;
int num = x;
if (x == 0) return true;
if (num < 0 || num % 10 == 0) return false;
while (num != 0){
revertednum = 10 * revertednum + num % 10;
num /= 10;
}
return x == revertednum;
}
}
为了提高效率,可以在翻转一半时提前结束
class Solution {
public boolean isPalindrome(int x) {
if (x == 0) return true;
if (x < 0 || x % 10 == 0) return false;
int right = 0;
while (x > right){
right = right * 10 + x % 10;
x /= 10;
}
return x == right || x == right / 10;
}
}
将数字转为字符串,翻转,再比较是否相同
效率很低
class Solution {
public boolean isPalindrome(int x) {
String s = new StringBuilder(x + "").reverse().toString();
return (x + "").equals(s);
}
}
四种 int 转为 String 的方法
private static void demo1() {
//int ----> String int转换成String
int i = 100;
String s1 = i + ""; //推荐用
String s2 = String.valueOf(i); //推荐用
Integer i2 = new Integer(i);
String s3 = i2.toString();
String s4 = Integer.toString(i);
System.out.println(s1);
//String----> int String 转换int
String s = "200";
Integer i3 = new Integer(s);
int i4 = i3.intValue(); //将Integer转换成了int数
int i5 = Integer.parseInt(s); //将String转换为int,推荐用这种
}
统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
Input: "00110011"
Output: 6
Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
第一个想法是仿照中心扩展法,从一对不相同的 ‘0 1’或’1 0’开始,往外扩展
class Solution {
public int countBinarySubstrings(String s) {
int cnt = 0;
int left = 0,right = 0;
for (int i = 0; i < s.length() - 1; i++) {
if (s.charAt(i) != s.charAt(i + 1)){
cnt++;
left = i - 1;
right = i + 2;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(i) && s.charAt(right) == s.charAt(i + 1)){
cnt++;
left--;
right++;
}
}
}
return cnt;
}
}
这么做时间复杂度太高
我们可以将字符串 s 按照 0 和 1 的连续段分组,例如 s = 00111011,可以得到分类数组 counts={2,3,1,2}。
这里 counts 数组中两个相邻的数一定代表的是两种不同的字符。假设 counts 数组中两个相邻的数字为 u 或者 v,它们对应着 u 个 0 和 v 个 1,或者 u 个 1 和 v 个 0。它们能组成的满足条件的子串数目为 min { u, v },即一对相邻的数字对答案的贡献。
我们只要遍历所有相邻的数对,求它们的贡献总和,即可得到答案。
对于某一个位置 i,其实我们只关心 i - 1 位置的 counts 值是多少,所以可以用一个 last 变量来维护当前位置的前一个位置,这样可以省去一个 counts 数组的空间。
例如:11001100 ----> 2222
连续 1 和连续 0 数量相同的子字符串个数为:2 + 2 + 2 = 6
1011100 ---> 1132
连续 1 和连续 0 数量相同的子字符串个数为:1 + 1 + 2 = 4,分别为 10、01、1100、10
class Solution {
public int countBinarySubstrings(String s) {
int ptr = 0,last = 0,res = 0;
while (ptr < s.length()){
char c = s.charAt(ptr);
int count = 0;
while (ptr < s.length() && s.charAt(ptr) == c){
ptr++;//移动的指针
count++;//连续的 0 或 1 数量
}
res += Math.min(last, count);
last = count;
}
return res;
}
}
另一种写法
class Solution {
public int countBinarySubstrings(String s) {
int preCnt = 0,curCnt = 1,n = 0;
for (int i = 0; i < s.length() - 1; i++) {
if (s.charAt(i) == s.charAt(i + 1)) {
curCnt++;
}else {
preCnt = curCnt;
curCnt = 1;
}
//其实就是上一种写法 min(last,count)的另一种写法
if (curCnt <= preCnt) n++;
}
return n;
}
}