异位词分组
异位词在排序之后它所组成的字符串肯定是一样的,我们可以使用一个哈希表啦存储这样的list
public List<List<String>> groupAnagrams(String[] strs) {
//两个字符串包含的字母相同,所以两个字符串分别进行排序之后得到的字符串一定是相同的
HashMap<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars=str.toCharArray();
Arrays.sort(chars);
String key=new String(chars);
List<String> list=map.getOrDefault(key,new ArrayList<String>());
list.add(str);
map.put(key,list);
}
return new ArrayList<List<String>>(map.values());
}
旋转矩阵
仔细观察旋转后的图形,其实也就是先把当前数字按照上下位置翻转再进行一次对角线翻转就行了
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;
}
}
}
跳跃游戏
farthest代表最有潜力的元素也就是自身下标+当前值最大的元素;用end变量指向在上一个最有潜力元素的跳跃范围内的最有潜力的元素,一开始都是从0开始的
public int jump(int[] nums) {
int n=nums.length;
int end=0,farthest=0,jumps=0;
for (int i = 0; i < n-1; i++) {
//在到达最远索引之前不断的更新最有潜力的元素
farthest=Math.max(nums[i]+i,farthest);
//end表示每次能跳跃的最远索引
if(end==i){
//到达最远索引处,此时无论如何我们都要跳一步了
jumps++;
//更新下一次截止距离为最有潜力的元素能跳到的最远索引值
end=farthest;
}
}
return jumps;
}
匹配字符
10题
此题中*并非万能的,而是要和上一个字符串去进行匹配,但是也可以起到将上一个元素删除的作用
public boolean isMatch(String s, String p) {
int n1 = s.length();
int n2 = p.length();
//s的第i-1个字符与p的j-1个字符段是否匹配
boolean[][] dp = new boolean[n1 + 1][n2 + 1];
//base
dp[0][0] = true;//空串匹配空串
for (int i = 1; i <= n2; i++) {
if (p.charAt(i - 1) == '*' && dp[0][i - 2]) {//可以尝试删除两个数看看是否相等
dp[0][i] = true;
}
}
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
}
if (p.charAt(j - 1) == '*') {
//两者不匹配 但是*还有删除前一个字符的特效 ,因为*可以匹配零个
if (p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') {
dp[i][j] = dp[i][j - 2];//相当于丢弃 字母加* 的匹配字段
} else {//匹配成功 可以继承 删掉*的匹配情况,删掉*+字符的匹配情况,s中有连续字符的匹配情况
dp[i][j] = dp[i][j - 1] || dp[i][j - 2] || dp[i - 1][j];
}
}
}
}
return dp[n1][n2];
}
44题
字符串的匹配问题一般用动态规划做,此题的base状态是,当s为空串的时候,如果p为*可以一直匹配,直到出现字母为止
当p出现?的时候其实就相当于匹配了字符,此时dp[i][j]=dp[i-1][j-1];
当p出现*的时候由于它可以匹配任意长度的字符所以dp[i][j]的状态可以从任意左方继承或者上方获取,从上方获取例如下面的a==a,从左方获取是只要有一个地方为true,该格的所有右方都为true
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
//dp[i][j]表示s的(0-i+1)和p的(0-j+1)是否可以匹配
boolean[][] f = new boolean[m + 1][n + 1];
f[0][0] = true;
//base true状态一直继承到不是*为止
for (int i = 1; i <= n; i++) {
if (p.charAt(i - 1) == '*')
f[0][i] = f[0][i - 1];
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
//如果两个字符匹配或者说通过?匹配,两个字符都可以往前移动一位
if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?') {
f[i][j] = f[i - 1][j - 1];
}
//如果当前规律串是*,f[i][j]可以直接继承f[i-1][j]和f[i][j-1]的正确状态,这是*的特效
if (p.charAt(j - 1) == '*') {
f[i][j] = f[i][j - 1] || f[i - 1][j];
}
}
}
return f[m][n];}
竖式乘法
要注意的其实只有一点,从后往前运算,索引i和j贡献的地方是i+j和i+j+1,由于两数相乘结果一定小于100,所以在i+j+1的地方填写取模运算结果在i+j处填写除法运算结果;
在计算sum的时候需要累加当前数组已经储存的结果,只要连续累加i+j+1这个位置就能遍布到所有位置了
public String multiply(String num1, String num2) {
if(num1.equals("0")||num2.equals("0"))return "0";
int[] res = new int[num1.length() + num2.length()];
for (int i = num1.length() - 1; i >= 0; i--) {
int n1 = num1.charAt(i) - '0';
for (int j = num2.length() - 1; j >= 0; j--) {
int n2 = num2.charAt(j) - '0';
int sum = (res[i + j + 1] + n1 * n2);
res[i + j + 1] = sum % 10;
res[i + j] += sum / 10;
}
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < res.length; i++) {
if (i == 0 && res[i] == 0) continue;
result.append(res[i]);
}
return result.toString();
}
缺失的正数
使用原地哈希解决此类问题,对于长度为n的数组,它所要寻找的结果一定存在于[1,n+1]这个范围里,其中n+1这个结果只在全部为正数且连续的情况下返回;
我们遍历数组中范围是[1,N]的数字,如果当前数字不在他应该在的位置,我们就将其交换到他应该在的位置,对于应该在的位置的定义是nums[i]=i+1;在while循环中我们对交换过来的在[1,N]范围的数进行不断的运算,这样就可以保证每个数都正确的被交换了
public int firstMissingPositive(int[] nums) {
for (int i = 0; i < nums.length; i++) {
//满足在指定范围内,并且没有被放在正确的位置才将其交换
while (nums[i]>0&&nums[i]<=nums.length&&nums[nums[i]-1]!=nums[i]){
swap(nums,nums[i]-1,i);
}
}
for (int i = 0; i < nums.length; i++) {
if(nums[i]!=i+1){
return i+1;
}
}
//全都替换正确
return nums.length+1;
}
private void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
数独游戏
是否是有效的数独
36题
注意box的表示方式为box[j/3+(i/3)*3],注意row,col等的定义就行
public boolean isValidSudoku(char[][] board) {
//board有9行,每一个行每个数字是否都存在过(0-9)所以填的10
int [][]row =new int[9][10];
int [][]col =new int[9][10];
int [][]box =new int[9][10];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if(board[i][j]=='.'){
continue;
}
int curNum=board[i][j]-'0';
if(row[i][curNum]==1)return false;
if(col[j][curNum]==1)return false;
if(box[j/3+(i/3)*3][curNum]==1)return false;
row[i][curNum]=1;
col[j][curNum]=1;
box[j/3+(i/3)*3][curNum]=1;
}
}
return true;
}
解数独
37题
利用回溯的方法求解,回溯中我们逐列查看,在遇到数字的时候跳过,剩下的就是1到9穷举了,注意判断3*3框里有没有重复数的判断方式
public void solveSudoku(char[][] board) {
backtrack(board,0,0);
}
boolean backtrack(char[][] board, int i, int j) {
int m = 9, n = 9;
//穷举到最后一列了,换到下一行重新开始
if (j == n) return backtrack(board, i + 1, 0);
if (i == m) return true;//找到一个可行解,触发base case
if (board[i][j] != '.') {
//有数字不需要自己写
return backtrack(board, i, j + 1);
}
//1到9穷举
for (char ch = '1'; ch <= '9'; ch++) {
//遇到不合法的数字就跳过
if (!isValid(board, i, j ,ch)) continue;
board[i][j] = ch;
//找到了一个可行解,结束
if (backtrack(board, i, j + 1)) return true;
board[i][j] = '.';
}
return false;//穷举完还是没有
}
private boolean isValid(char[][] board, int r, int c, char n) {
for (int i = 0; i < 9; i++) {
// 判断行是否存在重复
if (board[r][i] == n) return false;
// 判断列是否存在重复
if (board[i][c] == n) return false;
// 判断 3 x 3 方框是否存在重复
if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)
return false;
}
return true;
}
括号问题
括号问题一般具有递归的性质,这是由于它可以用栈来处理
有效的括号
使用栈来处理类似问题,遇到左括号的时候push进去,遇到右括号的时候比较上一个左括号是否相等
最后为空就证明合理
public boolean isValid(String s) {
Stack<Character> stackLeft = new Stack<>();
int n = s.length();
for (int i = 0; i < n; i++) {
if (s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{') {
stackLeft.push(s.charAt(i));
} else {
if (!stackLeft.isEmpty() && left(s.charAt(i)) == stackLeft.peek()) {
stackLeft.pop();
} else return false;
}
}
return stackLeft.isEmpty();
}
public char left(char c) {
if (c == ')') return '(';
if (c == ']') return '[';
return '{';
}
}
生成括号
22题
此类问题明显是要考深度优先遍历来做的,left和right表示可存放的左右括号数量,这和之前的回溯框架有些许不同,这题其实只要不先放右括号就都是有效的解
public List<String> generateParenthesis(int n) {
if(n==0)return null;
//记录所有组合
ArrayList<String> res = new ArrayList<>();
StringBuilder track=new StringBuilder();
backtrack(n,n,track,res);
return res;
}
//left和right表示左右两个括号的数量
private void backtrack(int left, int right, StringBuilder track, ArrayList<String> res) {
if(right<left)return;//左括号剩下的多,不合法
if(right<0||left<0)return;//小于0不合法
if(left==0&&right==0){
res.add(track.toString());//添加结果
return;
}
//尝试放一个左括号
track.append('(');//选择
backtrack(left-1,right,track,res);//回溯
track.deleteCharAt(track.length()-1);//撤销
//尝试放一个右括号
track.append(')');
backtrack(left,right-1,track,res);
track.deleteCharAt(track.length()-1);
}
有效的括号
32题
由于括号具有的对称性质,所以可以考虑两次遍历解决这个问题,正向遍历中,当left==right的时候记录最大值,当右括号大于左括号数量的时候将left和right置零,反向遍历的时候当左括号大于右括号的时候置零,两者之间取最大值就行了
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {
left = right = 0;
}
}
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}
下一个排列
31题
我们希望一个尽可能在低位的最大数与该低位尽可能靠近的比该数小的数进行交换,注意这里的二分查找细节,由于是一个逆序的数组,当前值比target大的时候,应该压缩左边界,由于要求大于target的最大下标,所以要尽可能的把下标向右移动
这里return right会返回大于target的最大下标,return left会返回小于target的最小下标
public void nextPermutation(int[] nums) {
int n = 0;
if(nums == null || (n = nums.length) == 0) return;
// 从后往前查找第一次出现 nums[i] < nums[i+1] 的位置
int i = n-2;
while (i>=0 && nums[i] >= nums[i+1]) {
i--;
}
// if i == -1 nums 则整体逆序
if(i >= 0){
// 此时 nums[i+1, n-1] 降序, 查找其中 大于nums[i] 的最大下标,也就是大于nums[i]的最小数的下标
int j = binarySearch(nums, i+1, n-1, nums[i]);
swap(nums, i, j); //交换这两个值
}
// 此时 nums[i+1, n-1] 仍然降序,将其改为升序,只需要反转即可。
reverse(nums, i+1, n-1);
}
// nums[left, right] 逆序,查找其中 > target 的 最大下标
private int binarySearch(int[] nums, int left, int right, int target){
while(left <= right){
//雀氏帅
int mid = (left + right) >>> 1;
if(nums[mid] > target){
left = mid + 1; // 尝试再缩小区间
}else{
right = mid - 1;
}
}
return right;
}
private void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
private void reverse(int[] nums, int i, int j){
while(i < j){
swap(nums, i++, j--);
}
}
串联子串
30题
哈希表也是可以比较的,这里主要注意哈希表的equals方法
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) return res;
HashMap<String, Integer> map = new HashMap<>();
int one_word = words[0].length();
int word_num = words.length;
int all_len = one_word * word_num;//总长度
//放入<单词,次数>
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
for (int i = 0; i < s.length() - all_len + 1; i++) {
//截取一个新的子串
String tmp = s.substring(i, i + all_len);
//临时哈希表
HashMap<String, Integer> tmp_map = new HashMap<>();
for (int j = 0; j < all_len; j += one_word) {
String w = tmp.substring(j, j + one_word);
//把新的子串中每个单词放入零食哈希表
tmp_map.put(w, tmp_map.getOrDefault(w, 0) + 1);
}
//如果两个哈希表一样,那么就把i加入结果
if (map.equals(tmp_map)) res.add(i);
}
return res;
}
两数相除
29题
需要注意的是int的范围问题(-2^31,2^31-1),当一个整形从负数变为正数的时候有可能会发生整形溢出问题,一般的处理方式可以改成用long形储存
divide方法的逻辑如下,60/8 = (60-32)/8 + 4 = (60-32-16)/8 + 2 + 4 = 1 + 2 + 4 = 7
public int divide(int dividend, int divisor) {
// 当除数为1,直接返回被除数
if (divisor == 1) {
return dividend;
}
// 当除数为-1且被除数为Integer.MIN_VALUE时,将会溢出,返回Integer.MAX_VALUE
if (divisor == -1 && dividend == Integer.MIN_VALUE) {
return Integer.MAX_VALUE;
}
// 把被除数与除数调整为正数,为防止被除数Integer.MIN_VALUE转换为正数会溢出,使用long类型保存参数(int的范围为-2^31,2^31-1)
if (dividend < 0 && divisor < 0) {
return divide(-(long) dividend, -(long) divisor);
} else if (dividend < 0 || divisor < 0) {
return -divide(Math.abs((long) dividend), Math.abs((long) divisor));
} else {
return divide((long) dividend, (long) divisor);
}
}
public int divide(long dividend, long divisor) {
// 如果被除数小于除数,结果明显为0
if (dividend < divisor) {
return 0;
}
long sum = divisor; // 记录用了count个divisor的和
int count = 1; // 使用了多少个divisor
//如果当前和比被除数小
while (sum+sum<=dividend) {
// 每次翻倍
sum =sum+sum;
count =count+count;
}
// 此时dividend >= sum
// 将count个divisor从dividend消耗掉,剩下的还需要多少个divisor交由递归函数处理
return count + divide(dividend - sum, divisor);
}
n数之和问题
2数之和
需要注意的只有一点,在判断之后再put这样可以避免[3,3] 6这样的情况无解
//聪明的哈希表
public static int[] findSmart(int[] arr,int target){
HashMap<Integer, Integer> hashMap = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
if(hashMap.containsKey(target-arr[i])){
return new int[]{hashMap.get(target-arr[i]),i};
}
hashMap.put(arr[i],i);
}
return new int[0];
}
3数之和
15题
使用双指针解法,先对数组进行排序,对于排序后的数组,我们使用三个指针,总指针,前指针和后指针,在总指针遍历的过程中,前后指针构成双指针进行运算
当我们的总指针大于0的时候,总和肯定大于0了,可以直接结束循环;当数字重复的时候跳过进行下一轮循环;当左指针小于右指针的时候,我们计算每轮三个数相加的结果,等于0合法则加入结果集,注意在前后指针遇到相同元素的时候需要跳过当前元素;小于0向后移动前指针,大于0向前移动后指针
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList();
int len = nums.length;
if(nums == null || len < 3) return ans;
Arrays.sort(nums); // 排序
for (int i = 0; i < len ; i++) {
if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重不加入结果集
int L = i+1;
int R = len-1;
while(L < R){
int sum = nums[i] + nums[L] + nums[R];
//找到合法结果
if(sum == 0){
ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
//有相同元素时跳过只记录一组
while (L<R && nums[L] == nums[L+1]) L++;
while (L<R && nums[R] == nums[R-1]) R--;
L++;
R--;
}
else if (sum < 0) L++;
else if (sum > 0) R--;
}
}
return ans;
}
4数之和
18题
根据三指针的思路,无非是把总指针变成了两个,后续还是使用双指针进行计算
private int i=0;
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int len = nums.length;
for(int i = 0; i < len - 3; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复
for (int j = i + 1; j < len - 2; j++) {
if (j > i + 1 && nums[j] == nums[j-1]) continue; // 跳过重复
int left = j + 1;
int right = len - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 跳过重复
while(left < right && nums[left+1] == nums[left]) left++;
while (left < right && nums[right-1] == nums[right]) right--;
// 逼近中间
left++;
right--;
} else if (sum > target) {
right--;
} else {
left++;
}
}
}
}
return res;
}
最接近的三数之和
16题
依旧是总指针加双指针那一套,但是需要不断的更新结果值,取到最小的绝对值,注意的是不能去Int.max因为经过绝对值计算后会有溢出的风险
public int threeSumClosest(int[] nums, int target) {
int res=999999999;//取特殊值
//排序
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
int left=i+1;
int right=nums.length-1;
while (left<right){
int sum=nums[left]+nums[right]+nums[i];
if(Math.abs(sum-target)<Math.abs(res-target)){
res=sum;
}
if(sum>target){
right--;
continue;
}
if(sum<target){
left++;
continue;
}
//没有比和他相同更小的数了
return res;
}
}
return res;
}
电话号码字母组合
17题
此类题型首先需要建立好题目要求的映射表,此题是一个标准的回溯问题,回溯添加结果集的时候是当index等于str的长度的时候,说明已经遍历完成了
//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
//这里也可以用map,用数组可以更节省点内存
String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
//注意边界条件
if(digits==null || digits.length()==0) {
return new ArrayList<>();
}
iterStr(digits, new StringBuilder(), 0);
return res;
}
//最终输出结果的list
List<String> res = new ArrayList<>();
//递归函数
void iterStr(String str, StringBuilder letter, int index) {
//用index记录每次遍历到字符串的位置
if(index == str.length()) {
res.add(letter.toString());
return;
}
//获取index位置的字符,假设输入的字符是"234"
//第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
char c = str.charAt(index);
int pos = c - '0';
String map_string = letter_map[pos];
//遍历字符串,比如第一次得到的是2,就是遍历"abc"
for(int i=0;i<map_string.length();i++) {
//调用下一层递归
letter.append(map_string.charAt(i));
iterStr(str, letter, index+1);
//如果不删除,在for循环里,下一次运行时,letter数据就被污染了。(ad,ae)变成(ad,ade)
letter.deleteCharAt(letter.length()-1);
}
}
罗马数字
整数转罗马数字
12题
枚举每个可能的排列组合,使用贪心算法每次都找到最大的可以被消耗的数字然后减去
public String intToRoman(int num) {
// 贪婪算法,类似于找零钱
// 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
// 并且按照阿拉伯数字的大小降序排列
int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
int index = 0;
StringBuilder result = new StringBuilder();
while (index < 13) {
if (num >= nums[index]) {
result.append(romans[index]);
num -= nums[index];
} else {
index ++;
}
}
return result.toString();
}
罗马数字转整数
13题
枚举每一个字母代表的值,并且使用pre遍历存储上一个值,当上一个值小于下一个值的时候就减去审通过一个值,否则就是加上上一个值,最后要注意还需要加上最后一个数字,算法才算完成
public int romanToInt(String s) {
if(s.length()==1)return getValue(s.charAt(0));
int sum=0;
int PreNum=getValue(s.charAt(0));
for (int i = 1; i < s.length(); i++) {
if(getValue(s.charAt(i))<=PreNum){
//正常情况就加上
sum+=PreNum;
}else{
//特殊情况减去
sum-=PreNum;
}
PreNum=getValue(s.charAt(i));
}
//加上最后一个数字
sum+=PreNum;
return sum;
}
private int getValue(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
盛水最多的容器
11题
其实就是求面积,宽度和高度决定面积,高度由更短的高度来决定,宽度是两者的索引差,使用双指针来做,一开始将双指针放在数组两边,如果向内移动短板,其高度有可能会增加,但是向内移动长板高度一定会降低或者不变,所以只要不断的移动短板更新面积就行了
public int maxArea(int[] height) {
int j=height.length-1,i=0,res=0;
//水槽的高度由短板来决定,向内移动,长度一定会变窄
while (i<j){
//如果向内移动短板,短板高度min(h[i],h[j])有可能会变大
//如果向内移动长板,如果长板变的比短板大,那么高度还是由短板决定,高度不变,如果长板变的比短板小,高度相比之前变得更低,体积还是在变小
res=height[i]<height[j]?
Math.max(res,(j-i)*height[i++]):
Math.max(res,(j-i)*height[j--]);
}
return res;
}
整数翻转
不使用字符串来判断回文数
9题
我们可以对数字进行翻转操作,如果翻转以后数字依然一样的话,就说明是回文数;由于负数肯定不是回文数所以不需要处理负数的情况;由于回文数的特点,能溢出的数肯定不是回文数,而溢出后结果一定不对,所以正好就返回begin==res
public static boolean isPalindrome(int x){
if(x<0)return false;
int begin=x;//取出x的值以后做对比
int res=0;
while (x!=0){
int temp=x%10;
res=res*10+temp;
x/=10;
}
return begin==res;
}
整数翻转
7题
注意是要处理溢出的情况,因为此题存在负数翻转的情况,在-Int.Min转Int.Max的时候会溢出,在正数1534236469这样的情况也会发生溢出;并且题目规定在溢出的时候应该返回0;同时我们使用一个last变量记录乘法前的情况,当它*10之后得到res,res/10之后会直接省略最后的temp理论上来说如果不溢出,res/10==last
public static int reverse(int x){
int res=0;
int last=0;
while(x!=0){
int temp=x%10;//取末尾数字
last=res;
res=res*10+temp;//把上一次的结果往上挪一位然后加入新的尾数
if(last!=res/10){//如果不相等就是溢出,在超出int值的时候,二进制最前面的位数会变化
return 0;
}
x/=10;//每次去掉一个末尾的数
}
return res;
}
字符串转整数
8题
使用正则表达式去做
^[\\+\\-]?\\d+
^ 表示匹配字符串开头,我们匹配的就是 '+' '-' 号
[] 表示匹配包含的任一字符,比如[0-9]就是匹配数字字符 0 - 9 中的一个
? 表示前面一个字符出现零次或者一次,这里用 ? 是因为 '+' 号可以省略
\\d 表示一个数字 0 - 9 范围
+ 表示前面一个字符出现一次或者多次,\\d+ 合一起就能匹配一连串数字了
find方法是部分匹配,他会搜索出str中与模式串匹配的部分,start和end可以返回str中匹配该模式串的索引
public int myAtoi(String str) {
//清空字符串开头和末尾空格(这是trim方法功能,事实上我们只需清空开头空格)
str = str.trim();
//java正则表达式
Pattern p = Pattern.compile("^[\\+\\-]?\\d+");
Matcher m = p.matcher(str);
int value = 0;
//判断是否能匹配
if (m.find()){
//字符串转整数,溢出
try {
value = Integer.parseInt(str.substring(m.start(), m.end()));
} catch (Exception e){
//由于有的字符串"42"没有正号,所以我们判断'-'
value = str.charAt(0) == '-' ? Integer.MIN_VALUE: Integer.MAX_VALUE;
}
}
return value;
}
Z子形变换
当输入的numRows小于等于1的时候或者字符串长度小于rows的时候其实就是返回原来的字符串;
我们使用一个flag来作为标志位,当他遇到第一层或者最后一层的时候就会转变方向
public static String change(int numRows,String s){
if(numRows<=1)return s;//必须要排除为1的情况,等于1的时候由于只有一个pos在后面个体的时候数组会越界
if(s.length()<=numRows){return s;}
ArrayList<StringBuilder> builders = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
builders.add(new StringBuilder());
}
int pos=0,flag=-1;
for (char c : s.toCharArray()) {
if(pos==0||pos==numRows-1)flag=-flag;
builders.get(pos).append(c);
pos+=flag;
}
StringBuilder res=new StringBuilder();
for (StringBuilder builder : builders) {
res.append(builder);
}
return res.toString();
}
寻找最长回文子串
使用中心扩展方法求解,expand方法输入左右索引返回能成为回文串的最大长度;
主方法中分别对i,i和i,i+1进行中心扩展,这其实是两个回文中心,这样就不会遗漏情况,当他们的长度超过了原先预定的start和end长度的时候,更新start和end 的值;start的值需要画图找到规律,当i为中位数(偶数情况为前面的一个数)的情况,整个字符串长度为len,begin=i-(len-1)/2,end=i+(len)/2
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start+1) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;//right-left+1-2,此时字符串已经不满足了
}
寻找中位数
要求log(m+n)所以要想到使用二分法求解 ,这题二分法比较复杂,遇到还是放弃吧
看一下O(m+n)算法的思路
增加a指针的条件为b指针已经超过了范围或者当前a指针所指的值比b指针小的时候,大条件还是a指针至少得在范围中,使用left指针保存right指针的上一个记录,这样可以解决偶数的情况
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
int len = m + n;
int left = -1, right = -1;
int aStart = 0, bStart = 0;
for (int i = 0; i <= len / 2; i++) {
left = right;
if (aStart < m && (bStart >= n || A[aStart] < B[bStart])) {
right = A[aStart++];
} else {
right = B[bStart++];
}
}
if ((len & 1) == 0)
return (left + right) / 2.0;
else
return right;
}
无重复的最长子串
因为要找不重复的子串,可以想到用滑动窗口求解,定义一个左右指针,正常情况下不断的移动右窗口扩展,并且更新数组里记录的值,当我们遍历的时候发现,当前数组已经记录了该值就说明已经重复,此时要做的事情是压缩左窗口,把重复的值剔除出去,在每次压缩后更新最大不重复子串长度
public static int find(String s){
int max=0;
int[] window=new int[128];
int left=0;
int right=0;
while (right<s.length()){
char c=s.charAt(right);
right++;
window[c]++;
while (window[c]>1){
char d=s.charAt(left);
window[d]--;
left++;
}
max=Math.max(max,right-left);
}
return max;
}
返回链表的和
和操作两个数组的情况类似,需要考虑进位
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0);
ListNode cur = pre;
int carry = 0;
while(l1 != null || l2 != null) {
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
carry = sum / 10;
sum = sum % 10;
cur.next = new ListNode(sum);
cur = cur.next;
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
}
if(carry == 1) {
cur.next = new ListNode(carry);
}
return pre.next;
}