时间过得真快,距离18年的1024已经一年了,一年前,刚开始在csdn上写博客,那时候对1024没有太上心,觉得自己不是一个程序员。经过了1年的学习,心态有了很大的变化,把写代码变得有仪式感,1024,不只是成长,更是挑战。
LeetCode的初级题或者说是简单题,跟智力和脑子没关系。锻炼的还是编程能力。就是说,这些题基本上看到就能有想法,它们锻炼的是把脑子中的想法快速转换为代码的能力。算是入门级别。
反转相关
1. 反转字符串
这个就是个憨批题:
public void reverseString(char[] s) {
char temp;
int length = s.length;
for (int i = 0; i < length / 2; i ++){
temp = s[i];
s[i] = s[length - 1 - i];
s[length - 1 - i] = temp;
}
}
2. 整数反转
由于这是字符串这一章节的问题,所以我很自然的就把它当成了字符串来做,开局换上字符数组直接开始怼,具体思路是:
-
即从前往后拿数据
1 2 3 4 5
21
3 4 5
public int reverse(int x) { // 全转为正数 char[] numChar = Integer.valueOf(x > 0 ? x : -x).toString().toCharArray(); int reValue = 0; int multi = 1; int temp; int billion = 1000000000; for (char c : numChar) { temp = (c - 48) * multi; if (multi == billion) { // 如果最后一位数比2大或者(等于2,但是后面的数比Max_Value大) if (c > 50 || (reValue > Integer.MAX_VALUE % billion && c == 50)) { return 0; } } reValue += temp; multi *= 10; } return x > 0 ? reValue : -reValue; }
-
另外一种思路是从后往前拿数据,巧妙地运用了
%
和/
的作用。算是用了栈把1 2 3 4 5
54
1 2 3
public int reverse(int x) { int rev = 0; while (x != 0) { int pop = x % 10; x /= 10; if (rev > Integer.MAX_VALUE/10) return 0; if (rev < Integer.MIN_VALUE/10) return 0; rev = rev * 10 + pop; } return rev; }
重复相关
3. 字符串中第一个唯一的数组
这个题不能像只出现一个的数字一样,即不能使用异或来解决,因为这个题中可能会出现多个唯一的数组,然后我们需要拿到第一个唯一的数组。
首先想到的就是使map<Character, Integer>
,value值随着key的重复而++
public int firstUniqChar(String str) {
Map<Character, Integer> map = new HashMap<>(15);
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (int i = 0; i < str.length(); i++) {
if (map.get(str.charAt(i)) == 1) {
return i;
}
}
return -1;
}
4. 有效的字母异位词
这个题和之前的两个数组的交集比较像
-
先排序,然后比较,非常方便,但是感觉有点慢
public boolean isAnagram(String s, String t) { if(s.length() != t.length()){ return false; } char[] cs = s.toCharArray(); char[] ct = t.toCharArray(); Arrays.sort(cs); Arrays.sort(ct); for(int i = 0; i < s.length(); i ++){ if(cs[i] != ct[i]) { return false; } } return true; }
-
同时,还可以利用哈希表来观察这两个字符串中字符的出现次数
public boolean isAnagram(String s, String t) { if(s.length() != t.length()){ return false; } int[] counter = new int[26]; for(int i = 0; i < s.length(); i ++) { // -97是因为前面的数组长度只有26 counter[s.charAt(i) - 97] ++; } for(int i = 0; i < t.length(); i ++) { if(-- counter[t.charAt(i) - 97] < 0){ return false; } } return true; }
9. 最长公共前缀
主要是字符串数组比较特别,确定字符串数组中的字符需要两重循环;str[i].charAt(j)
-
方法一是暴力
两重循环,第一个是数组的循环,第二个是字符串中字符的循环。用力扣管方的说法就是水平扫描
public static String longestCommonPrefix(String[] strs) { if (strs.length == 0) return ""; if (strs.length == 1) return strs[0]; int flag = 0; for (int i = 1; i < strs.length; ) { // 防止charAt(i) 越界 if (flag > strs[i].length() - 1 || flag > strs[i - 1].length() - 1) break; if (strs[i].charAt(flag) == strs[i - 1].charAt(flag)) { // 如果数组循环一遍,则回到第一个数组元素。同时把索引加一 if (i == strs.length - 1) { flag ++; i = 1; continue; } } else break; i ++; } return strs[0].substring(0, flag); }
-
方法二:分治递归
public String longestCommonPrefix(String[] strs) { if (strs == null || strs.length == 0) return ""; return longestCommonPrefix(strs, 0 , strs.length - 1); } private String longestCommonPrefix(String[] strs, int l, int r) { if (l == r) { return strs[l]; } else { int mid = (l + r)/2; String lcpLeft = longestCommonPrefix(strs, l , mid); String lcpRight = longestCommonPrefix(strs, mid + 1,r); return commonPrefix(lcpLeft, lcpRight); } } private String commonPrefix(String left,String right) { int min = Math.min(left.length(), right.length()); for (int i = 0; i < min; i++) { if ( left.charAt(i) != right.charAt(i) ) return left.substring(0, i); } return left.substring(0, min); }
-
方法三:二分法
坑爹的字符相关
主要是使用一些字符串相关API,同时还有字符和int类型的比较
5. 验证回文字符串
这个题当理解了回文串的性质之后,无非就是比较前面和后面的值。没有什么难度,主要是Character
API 的熟悉
public static boolean isPalindrome(String s) {
boolean isNumOrLetterI;
boolean isNumOrLetterJ;
for (int i = 0, j = s.length() - 1; i <= j; i ++, j --){
isNumOrLetterI = Character.isLetterOrDigit(s.charAt(i));
isNumOrLetterJ = Character.isLetterOrDigit(s.charAt(j));
if (isNumOrLetterI && isNumOrLetterJ) {
if (Character.toLowerCase(s.charAt(i)) == Character.toLowerCase(s.charAt(j))) {
continue;
}
return false;
} else {
if (!isNumOrLetterI && !isNumOrLetterJ) {
continue;
}
if (!isNumOrLetterI) {
j ++;
}
if (!isNumOrLetterJ) {
i --;
}
}
}
return true;
}
6. 字符串转整数
这个题字字符的处理上和第五题比较像,在int类型越界上和第二题比较像
-
第一步时处理字符,直至找到数字。有一下情况:
- 如果是空格,则跳过
continue
- 如果是
-
or+
则需要记录它的正负 - 如果不是符号,也不是空格,更不是数字,则
return false
- 如果检测到数字,则到下一步
- 如果是空格,则跳过
-
第二步是计算,并防止越界
if (!mark && (-num < Integer.MIN_VALUE/10 || (-num == Integer.MIN_VALUE/10 && str.charAt(index) > '8'))) return Integer.MIN_VALUE; if (mark && (num > Integer.MAX_VALUE/10 || (num == Integer.MAX_VALUE/10 && str.charAt(index) > '7'))) return Integer.MAX_VALUE;
public static int myAtoi(String str) {
boolean mark = true;
int num = 0;
// 字符串索引
int index = 0;
char temp;
// 第一个循环,找到数字
while(index < str.length()) {
temp = str.charAt(index);
// 前面没有数字且是负号
if (temp == '-' || temp == '+') {
// -为最后一个字符或-后面不为数字
if (index == str.length() - 1 || !isNum(str.charAt(index + 1))) return 0;
else mark = temp != '-';
} else if (temp == ' ') {
index ++;
continue;
} else if (!isNum(temp)) return 0;
else break;
index ++;
}
// 找到数字以后
for(; index < str.length() && isNum(str.charAt(index)); index ++) {
if (!mark && (-num < Integer.MIN_VALUE/10 || (-num == Integer.MIN_VALUE/10 && str.charAt(index) > '8'))) return Integer.MIN_VALUE;
if (mark && (num > Integer.MAX_VALUE/10 || (num == Integer.MAX_VALUE/10 && str.charAt(index) > '7'))) return Integer.MAX_VALUE;
num = (str.charAt(index) - '0') + num * 10;
}
return mark ? num : -num;
}
private static boolean isNum(char c) {
return c >= '0' && c <= '9';
}
7. 实现strStr()
这个类似于Java的indexOf
的函数
-
思路一,暴力双指针
这个题也是用了双指针,数组模块经常用到双指针,如第一题和第六题都是这样。
其实全一点的话是4个指针:
- i 指向长字符串的索引
- j指向短字符串的索引
- beginI指向长字符串开始匹配的索引
- 0是短字符串开始的索引
public static int strStr(String haystack, String needle) { if ("".equals(needle)) return 0; int beginI = 0; // j != 0 说明已经开始比较了 for (int i = 0, j = 0; i < haystack.length() && j < needle.length(); i ++) { if (haystack.charAt(i) == needle.charAt(j)) { beginI = j == 0 ? i : beginI; if (j ++ == needle.length() - 1) { return beginI; } } else if (j != 0) { j = 0; i = beginI; } } return -1; }
-
思路二,KMP算法:
KMP算法在面试中常问到,基本是字符串比较中最优的算法了。这个算法是用空间换时间,时间复杂度是o(1)
- KMP不同于上一个暴力算法,KMP 算法的主串下标永不后退,而暴力算法一旦出错,则回退至匹配起始的下一个下标重头开始
- 为什么说是用空间换时间呢?因为这个算法需要构建一个有限状态机,通过每次比对有限状态机来实现线性耗时
- 第一个for循环先构建有限状态机。构建有限状态机是一个难点
- 第二个for循环和有限状态机核查,然后成功后返回index
public int strStr(String haystack, String needle) { int strLen = haystack.length(), subLen = needle.length(); if (subLen == 0) return 0; if (strLen == 0) return -1; // 构建状态机 int[][] FSM = new int[subLen][256]; int X = 0, match = 0; for (int i = 0; i < subLen; i++) { match = (int) needle.charAt(i); for (int j = 0; j < 256; j++) { // 当前状态 + 匹配失败字符 = 孪生词缀状态 + 匹配字符 FSM[i][j] = FSM[X][j]; } FSM[i][match] = i + 1; if (i > 0) { // 下一孪生前缀状态 = X + match X = FSM[X][match]; } } // 匹配子串 int state = 0; for (int i = 0; i < strLen; i++) { state = FSM[state][haystack.charAt(i)]; if (state == subLen) { return i - subLen + 1; } } return -1; }
-
思路三,Sunday算法:
这个还没看,等到面试之前再刷一哈
8.报数
这题意真是SB,是我脑子不够用了吗?
看了评论,明白这题说的是什么意思了,这题也不算难,非常简单,基本看一下就能出来结果。只要把脑子里的想法写出来就行。
- 先维护一个计数器
counter
- 看
str.charAt(i) == str.charAt(i + 1)
,如果为真,counter++
;如果为假,则把i
的值和counter
增加到stringBuffer
中,之后把counter
变为1,。
public String countAndSay(int n) {
String str = "1";
for (int i = 1; i < n; i ++) {
str = nextStr(str);
}
return str;
}
private String nextStr(String str) {
StringBuilder sb = new StringBuilder("");
int counter = 1;
for (int i = 0; i < str.length(); i ++) {
if (i < str.length() - 1 && str.charAt(i) == str.charAt(i + 1)) {
counter ++;
} else {
// 如果是最后一个且前面没有重复
if (i == str.length() - 1 && str.length() > 1 && str.charAt(i) != str.charAt(i - 1)) {
counter = 1;
}
sb.append(counter).append(str.charAt(i));
counter = 1;
}
}
return sb.toString();
}