目录
3无重复字符的最长子串
思路:
- 新建数组int[128],用来记录各个字符处在字符串中的最新位置。注意位置从1开始,一方面区别默认的0,另一方面,下面调整left的值时,可以直接移动到刚好去掉重复字符的位置。
- 设置左右边界,右为遍历中的i
- 每遇到新的字符,看是否需要调整left位置。取当前left位置和在record中记录的当前遍历到的字符的位置的最大值。如果该字符的位置大于left,说明新字符与窗口中的某个值重复了。所以left更新为该字符前一次出现的位置。
- 记录/更新当前字符的位置,记得是right+1
- 调整窗口最大值
// 新建数组存放各个字符的最新位置
int[] record = new int[128];
// 设置左
int left = 0, res = 0, n = s.length();
// 遍历
for (int right = 0; right < n; right++) {
// 每遇到新的字符,看是否需要调整left位置
left = Math.max(left, record[s.charAt(right)]);
// 记录当前字符的位置
record[s.charAt(right)] = right - 1;
// 判断新窗口的长度
res = Math.max(res, right - left + 1);
}
return res;
5最长回文子串
思路:
- 少于两个节点的情况
- 设置maxLen和start记录最大值及最大值位置
- 遍历,优化条件n - i > maxLen / 2
- 设置左右指针
- 右指针去重
- i调整到右指针
- 向两边扩展
- 检查max是否需要更新
- 返回结果s.substring(start, start + maxLen)
if (s.length() < 2) return s;
int n = s.length();
int maxLen = 0;
int start = 0;
for (int i = 0; i < n && n-i > maxLen/2; i++){
int left = i, right = i;
while (right < n - 1 && s.charAt(right) == s.charAt(right + 1)) right ++;
i = right;
while (right < n - 1 && left > 0 && s.charAt(right+1) == s.charAt(left-1)){
right++;
left--;
}
if (maxLen < right - left + 1){
maxLen = right - left + 1;
start = left;
}
}
return s.substring(start, start + maxLen);
8字符串转换整数(atoi), 9回文数,7整数反转
思路:
- 变量设置:结果变量、遍历坐标、符号变量、长度、界限
- 去开头的空格,去完后检查是否已到尽头(全为空的情况)
- 判断第一个非空字符是否为正负号,并记录在sign变量中。这里用else if结束
- 循环,只要没有超过范围而且是数字
- 添加数字前检查是否会溢出
- 数字拼接res = res * 10 + (s.charAt(i++) - '0');
- return res * sign
int i = 0, n = s.length(), sign = 1, res = 0, limit = Integer.MAX_VALUE / 10;
while(i < n && s.charAt(i) == ' ') i++;
if (i >= n) return 0;
if (s.charAt(i) == '+') i++;
else if (s.charAt(i) == '-') {
i++;
sign = -1;
}
while (i < n && s.charAt(i) >= '0' && s.charAt(i) <= '9'){
if (res > limit || (res == limit && s.charAt(i) > '7')){
return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
res = res * 10 + (s.charAt(i++) - '0');
}
return res * sign;
相关:回文数字/反转数字
int reversedNum = 0; // 只需反转到一半,或者一半多一个字 while (x > reversedNum) { // 下面用于反转数字 // while (x != 0) { // if (Math.abs(res) > limit) return 0; reversedNum = reversedNum * 10 + x % 10; x /= 10; } // 判断刚好一半,还是一半多一个字 return x == reversedNum || x == reversedNum / 10;
28实现strStr(), 459重复的子字符串(KMP)
Java的 indexOf()。
思路:
- 根据haystack和needle的长度判断可能性:如果needle为空,返回0,如果haystack为空或者haystack的长度小于needle的,返回-1;
- 遍历haystack,从每个字符开始遍历needle,只要出现不同就break。如果顺利完成needle的遍历,那就返回当前haystack的index
- 如果遍历完haystack,说明没有找到,返回-1
int m = hs.length();
int n = nd.length();
if (n == 0) return 0;
if (m == 0 || m < n) return -1;
// 注意到达m-n+1时,说明刚好能够容纳一个needle,所以用小于
for (int i = 0; i < m - n + 1; i++){
int j = 0;
for (j = 0; j < n; j++){
if (hs.charAt(i+j) != nd.charAt(j)) break;
}
if (j == n) return i;
}
return -1;
更好的方法是KMP,可参考下题的方法。
459重复的子字符串
// "abcabc"的dp数组为[-1 0 0 0 1 2 3],dp数组长度要比原字符串长度多一个。
// 那么我们看最后一个位置数字为3,就表示重复的字符串的字符数有3个。
// 如果是"abcabcabc",那么dp数组为[-1 0 0 0 1 2 3 4 5 6]
int j = 0, k = -1, n = s.length();
int[] dp = new int[n+1];
dp[0] = -1;
while (j < n) {
// 过程:k=-1时,会给数组添加0,下一次k不为-1了,就要比较j和k的字是否相同。
// 如果相同,k和j就可以一同前进,数组开始添加为1,即有重复字符串有一个了。
// 如果不同,k变回-1,j不动。
// 如果出现了重复字符串,k会与j相隔这个重复字符串的长度。
// 如果发现当前重复的字符串不对,通过k = dp[k],就能让k回到前面找是否有匹配的。如果没有就从新在-1开始
if (k == -1 || s.charAt(j) == s.charAt(k)) {
dp[++j] = ++k;
} else {
k = dp[k];
}
}
// dp[n] != 0 表示最后一个字符也配对了。否则第二个条件中0 % 任何数(除了0)都为0,就肯定返回true了
// (dp[n] % (n - dp[n])) == 0 中 (n - dp[n])得到重复子字符串的长度,即上面的abc
return dp[n] != 0 && (dp[n] % (n - dp[n])) == 0;
上面可以用来求next数组,dp大小只需n,最后返回dp即可。
返回的next数组可用于上面的strStr。代码与上面几乎一样,就多加了 k < m和 (k == m) ? j - k : -1;
int n = haystack.length(), m = needle.length(), j = 0, k = 0;
int[] next;
next = getNext(needle);
while (j < n && k < m) {
if (k == -1 || haystack.charAt(j) == needle.charAt(k)) {
++j;
++k;
} else {
k = next[k];
}
}
return (k == m) ? j - k : -1;
43字符串相乘
思路:
- 0的情况
- 用一个数组记录每对乘积的结果(相乘结果最多m+n位),遍历更新数组,从后往前记录
- 定义一个carry,遍历将数组的值转化为结果值中的单个数。
- 去掉多余的0,同时确定string开始合并的位置。
- 利用stringbuilder合并
// 设置变量:长度、数组开始更新的位置
int m = num1.length(), n = num2.length(), k = m + n - 2;
// 0的情况
if ((m == 1 && num1.equals("0")) || (n == 1 && num2.equals("0"))) return "0";
// 存储两个单数字乘积结果的数组
// 遍历更新数组
int[] record = new int[m+n];
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
// 注意这里要 +=,因为有i+j两次等于相同的数。
// 另外,最后一个位置此时并不会用上。例如99 * 99中,数组里的数分别是[81, 162, 81, 0]
record[k-i-j] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
}
}
// 将数组的值转化为结果值
// 加总,将各乘积整理成单个数。此处用上最后一个位置,[1 0 8 9]
int carry = 0;
for (int i = 0; i < m + n; i++){
record[i] += carry;
carry = record[i] / 10;
record[i] %= 10;
}
// 去掉多余的0,同时确定string开始合并的位置
int i = m + n - 1;
if (record[i] == 0) i--;
// 利用stringbuilder合并
StringBuilder res = new StringBuilder();
while (i >= 0) res.append(record[i--]);
// 返回结果
return res.toString();
71简化路径
思路:
-
split
-
遍历,如果不是空和".",那么就判断添加还是删除了
- 如果不等于"..",添加
- 否则判断listPath.size()是否大于0,是就去掉最后一个文件
-
如果遍历后是空的话返回"/",如果有多个"/"只保留一个。
String[] paths = path.split("/");
List<String> res = new ArrayList<>();
for (String str : paths) {
if (!str.isEmpty() && !str.equals(".")){
if (!str.equals("..")) res.add(str);
else {
// 如果是"..",且size() > 0,要去掉最后一个文件
if (res.size() > 0) res.remove(res.size() - 1);
}
}
}
if (res.size() == 0) return "/";
StringBuilder builder = new StringBuilder();
for (String re : res) {
builder.append("/").append(re);
}
return builder.toString();
93复原IP地址
思路:
-
新建ArrayList存储结果
-
创建函数递归寻找四个数字的组合(函数中包含:待检验的string、已确定数量的n,例如1表示已经得出255.xx、目前拼接到的)
-
遍历1到4,由于每位最多3个数字。如果s剩余的长度不及遍历的数字k,就结束循环。用一位,两位,三位来尝试,判断是否合法val > 255 || k != String.valueOf(val).length()。合法的话,说明配置了一个,递归调用helper时n+1,out + 配置好的值val,另外如果n==3,则要加"."
-
如果n达到了4,还要检查s是否为空,否则会出现"2.5.5.2"
-
public static List<String> restoreIpAddresses(String s) {
List<String> res = new ArrayList<String>();
helper(s, 0, "", res);
return res;
}
/**
* @param n 已确定的位数,例如 255. 就确定了一位
* @param out 当前累积的结果,如 255. 或 255.1
*/
public static void helper(String s, int n, String out, List<String> res) {
if (n == 4) {
// 保存结果,s.isEmpty()是最后一段刚好3个数的情况
// 要检查s是否已经为空,否则会出现"2.5.5.2"
if (s.isEmpty()) res.add(out);
return;
}
for (int k = 1; k < 4; ++k) {
// 剩余的数可能组不成3个数,如23,此时就没必要尝试k = 3了,否则s.substring(0, k)会outOfIndex
if (s.length() < k) break;
// 用一位,两位,三位来尝试,分别判断其合不合法,如果合法,则调用递归继续分剩下的字符串,最终和求出所有合法组合
int val = Integer.parseInt(s.substring(0, k));
// 比如当k=3时,说明应该是个三位数,但如果字符是"010",那么转为整型val=10,再转回字符串就是"10",此时的长度和k值不同了
if (val > 255 || k != String.valueOf(val).length()) continue;
helper(s.substring(k), n + 1, out + val + (n == 3 ? "" : "."), res);
}
}
60第k个排列
给定 n 和 k,返回第 k 个排列。
思路:
- 新建一个ArrayList,存储[1, ..., n]
- 新建一个数组存储[1, 1!, 2!, 3!, ..., n-1!]阶乘
- k—,把排名转换为下标
StringBuilder sb = new StringBuilder();
for (int i = n; i >= 1; i--) {
// 除以 n - 1 的阶乘,即16/3! = 2,在第三组全排序中
int idx = k / f[i - 1];
// nums.get(idx)取得3
sb.append(nums.get(idx));
// 去掉已选的数字
nums.remove(idx);
// 取余数,更新k在新组中的排名,即第三组中的第16%3! = 4个(0开始)
k %= f[i - 1];
}
return sb.toString();
151翻转字符串里的单词(344反转字符串、557反转字符串中的单词 III)
思路:
- split
- 判断是否为空
- 遍历,从后往前,只要不为空,就添加,并加上空格
- 返回结果,记得trim
151翻转字符串里的单词
输入: "the sky is blue",
输出: "blue is sky the".
344反转字符串
输入: "A man, a plan, a canal: Panama"
输出: "amanaP :lanac a ,nalp a ,nam A"
557反转字符串中的单词
输入: "Let's take LeetCode contest"
输出: "s'teL ekat edoCteeL tsetnoc"
14最长公共前缀
思路:
- 取第一个string作为prefix
- 遍历,从第二个开始
- 循环,只要str.indexOf(prefix) != 0,prefix去掉最后一个字符
- 返回prefix
20有效的括号
思路:
- 新建stack
- 遍历
- 遇到做括号入栈
- 否则,先判断stack是否为空,为空返回false,否则顶层和各个有括号匹配,不匹配返回false
- 返回stack.isEmpty()
567字符串的排列(了解)
输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
思路:
- 把s1都放进int[128]中,放一个,record[i]++;
- 遍历s2找匹配,遍历到的都-1
- 当cnt减到0时,开始判断窗口大小是否刚好等于s1大小,否则缩小left,如果缩小时减去了s1中的值,则cnt+1,继续上一步的遍历。
// 创建数组计数s1的字符
int[] record = new int[128];
for (char i: s1.toCharArray()){
record[i]++;
}
// 遍历,从s2中寻找配对的
for (int right = 0; right < n2; right++){
if (record[s2.charAt(right)]-- > 0) cnt--;
// 如果匹配数等于s1的长度,遍历
while(cnt == 0){
// 如果窗口大小刚好与s1的一样,则找到了
if (right - left + 1 == n1) return true;
// 收缩左边界,如果去掉了s1中的字符,那么s1的匹配数+1
if (++record[s2.charAt(left++)] > 0) cnt++;
}
}
return false;