前言
今天开始刷面试算法题,虽然之前在蓝桥杯、程序设计天梯赛中拿过两个省一和一个国三,但是基本靠的都是我对 Java 语言的熟悉,至于算法我只会基本的双指针、快慢指针、差分数组等,最擅长的其实还是暴力。但是自认为应付面试还是远远不够的,所以今天开始是该好好修炼一下算法了。
leetcode 编号 | 完成时间 | 复习时间 |
---|---|---|
1. 两数之和 | 2024-06-30 | |
2. 两数相加 | 2024-06-30 | |
3. 无重复字符的最长子串 | 2024-06-30 | |
4. 寻找两个正序数组的中位数 | 2024-07-01 | |
5. 最长回文串 | 2024-07-01 | |
6. | ||
7. | ||
8. | ||
9. 回文数 | 2024-07-02 | |
10. |
1、两数之和(哈希)
1.1、暴力法
- 思路💡:双重遍历,确保数组中每两个数字都进行过一次求和,直到找到 nums[i] + nums[j] = target 的两个目标索引
- 时间复杂度🕖: O(n2)
class Solution {public int[] twoSum(int[] nums, int target) {int[] res = new int[2];for(int i=0;i<nums.length-1;i++){for(int j=i+1;j<nums.length;j++){if(nums[i]+nums[j]==target){res[0]=i;res[1]=j;return res;}}}return nums;}
}
1.2、哈希表
- 思路💡:使用一个哈希表来存储遍历过的元素,每次插入元素前判断是否存在 target - nums[i]
- 时间复杂度🕖:因为哈希表的插入和查询操作的复杂度是 O(1),所以总的时间复杂度是 O(n)
class Solution {public int[] twoSum(int[] nums, int target) {int[] res = new int[2];Map<Integer,Integer> map = new HashMap<>();for(int i = 0; i < nums.length; i++){if(map.containsKey(target-nums[i])){res[0] = map.get(target-nums[i]);res[1] = i;return res;}else{map.put(nums[i],i);}}return nums;}
}
2、两数相加(模拟)
2.1、模拟算法
- 思路💡:模拟普通列式计算的过程,用变量 t 代表当前位的计算和,则 t%10 为当前位的结果,t/10 作为下一轮计算的加数
- 时间复杂度🕖:O(n)
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(-1); // 首节点ListNode cur = dummy; // 当前节点(负责穿针引线,将每位的结果连起来)int t = 0; // 每位的计算和while(l1 !=null || l2 !=null || t!=0){if(l1!=null){t += l1.val;l1 = l1.next;}if(l2!=null){t += l2.val;l2 = l2.next;}cur.next = new ListNode(t % 10);t /= 10;cur = cur.next;}return dummy.next;}
}
3、无重复字符的最长子串(双指针——前后指针)
3.1、暴力法
- 思路💡:双重循环,遍历每一种没有重复字符的字符串组合
- 时间复杂度🕖:O(n2)
class Solution {public int lengthOfLongestSubstring(String s) {if(s.length()==0 || s.length()==1) return s.length();char[] arr = s.toCharArray();int max = Integer.MIN_VALUE;for(int i=0;i<arr.length-1;i++){Map<Character,Integer> map = new HashMap<>();map.put(arr[i],1);for(int j=i+1;j<arr.length;j++){if(map.containsKey(arr[j])) break;else map.put(arr[j],1);}max = Math.max(max,map.size());}return max;}
}
3.2、前后指针
- 思路💡:指针 j 负责在前面探索,当哈希表中存在索引 j 的元素时,指针 i 开始移动,直到区间内没有重复元素
- 时间复杂度🕖:由于 i,j 均最多增加n次,且哈希表的插入和更新操作的复杂度都是 O(1)
,因此,总时间复杂度 O(n)
class Solution {public int lengthOfLongestSubstring(String s) {char[] arr = s.toCharArray();Map<Character,Integer> map = new HashMap<>();int res = 0;for(int i=0,j=0;j<arr.length;j++){map.put(arr[j],map.getOrDefault(arr[j],0)+1);while(map.get(arr[j]) > 1){map.put(arr[i],map.get(arr[i++])-1);}res = Math.max(res,j - i + 1);}return res;}
}
4、寻找两个正序数组的中位数(归并排序)
- 思路💡:使用归并算法将两个数组合并
- 时间复杂度🕖:O( n + m )n 和 m 分别是两个数组的长度
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int m = nums1.length;int n = nums2.length;int[] nums = new int[m+n];// 如果nums1为空,则直接返回nums2的中位数if (m == 0) {if (n % 2 == 0) {return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;} else {return nums2[n / 2];}}// 如果nums2为空,则直接返回nums1的中位数if (n == 0) {if (m % 2 == 0) {return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;} else {return nums1[m / 2];}}int count = 0; // 合并后的数组的索引int i = 0,j = 0; // nums1 和 nums2 的索引while(count != (m+n)){if(i == m){ // 如果数组 nums1 的元素合并完毕while(j != n){nums[count++] = nums2[j++];}break;}if(j == n){while(i != m){nums[count++] = nums1[i++];}break;}if(nums1[i] < nums2[j]){nums[count++] = nums1[i++];}else{nums[count++] = nums2[j++];}}// 到这里已经合并结束了 count=nums.lengthreturn count%2==0?(nums[count/2-1] + nums[count/2])/2.0:nums[count/2];}
}
5、最长回文子串
5.1、双指针 + 暴力
- 思路💡:使用双指针判断字符串是否是回文串,然后使用双重循环枚举出每一种子串
- 时间复杂度🕖:O( n3 )
class Solution {public String longestPalindrome(String s) {String longest = "";for(int i=0;i<s.length();i++){for(int j=i+1;j<s.length()+1;j++){String sub = s.substring(i,j);if(isPalindrome(sub)){longest = sub.length()>longest.length()?sub:longest;}}}return longest;}/*** 双指针判断回文串*/public boolean isPalindrome(String str){int i = 0,j = str.length()-1;while(str.charAt(i) == str.charAt(j)){if(i >= j) return true;i++;j--;}return false;}
}
5.2、 中心扩散法
- 思路💡:暴力枚举所有回文串的中心,然后使用中心扩散计算回文串的长度
- 首先寻找边界,因为回文串的中心可能不是一个字符(比如 "abba",或者 "abbba" ,这种可以统一将右指针移动到边界)
- 此时,左右指针都指向相同的字符,然后左右指针开始同时扩散,找到该中心最长的回文串
- 长度 = 右指针 - 左指针 + 1
- 时间复杂度🕖:O(N2)
class Solution {public String longestPalindrome(String s) {int left=0,right=0;int max = 0; // 最长for(int i=0;i<s.length();i++){int subLeft = i,subRight = i;// 寻找右边界while(subRight<s.length()-1 && s.charAt(i)==s.charAt(subRight+1)){subRight++;}// 左右扩散while(subRight<s.length()-1 && subLeft-1>=0 && s.charAt(subLeft-1)==s.charAt(subRight+1)){subLeft--;subRight++;}int len = subRight - subLeft + 1;if(len > max){left = subLeft;right = subRight;max = len;}}return s.substring(left,right+1);}
}
6、Z 字形变换
感觉这种题对面试没意义,跳过
7、整数反转
纯数学题,跳过
8、字符串转换整数
纯模拟题,这类题做太多了,基本通过了,剩下个别一两个用例懒得写了,意义不大。
这里写一下从中学到的一点,从来没相关在算法里 try-catch 不过还挺好用的:
try{if(Long.parseLong(sb.toString())< Integer.MIN_VALUE){return Integer.MIN_VALUE;}else if(Long.parseLong(sb.toString()) > Integer.MAX_VALUE){return Integer.MAX_VALUE;}}catch(Exception e){return 0;}
9、回文数
- 思路💡:双指针
- 时间复杂度🕖:O(N)
class Solution {public boolean isPalindrome(int x) {String s = String.valueOf(x);int i = 0,j = s.length()-1;while(s.charAt(i) == s.charAt(j)){if(i>=j) return true;i++;j--;}return false;}
}
10、正则表达式
- 思路💡没有思路,纯模拟,而且是最难的模拟
- 时间复杂度🕖:
果断放弃,之后有思路了 再来