排序
归并思想
786. 第 K 个最小的素数分数
class Solution { /* 思路1:多路归并 结果集合可以表示为: arr[0]/arr[1] arr[0]/arr[2],arr[1]/arr[2] arr[0]/arr[3],arr[1]/arr[3],arr[2]/arr[3] ... 问题转化为多个有序数组寻找第k小的数? s1:将每个数组的最小值放入小根堆中 s2:进行k-1次更新操作,此时堆顶就是第k小值 时间复杂度: s1: n*logn s2: k*logn 总: max(n,k)*logn 为什么使用堆? 假设有t个有序数组,暴力解法每次找t个最小值中的最小值,然后将最小值所在数组的指针移动1位,这里通过堆维护t个有序数组中最小值的最小值,降低了寻找最小值的时间复杂度。 思路2:双指针+二分 由于所有结果集的范围在[0,1]的范围内,因此可以猜1个数值x,然后采用双指针策略确定 有序数组中有多少个小于x的结果 时间复杂度:二分次数取决于精度,精度为 C = 10^8,总的时间复杂度 n*logC */ public int[] kthSmallestPrimeFraction(int[] arr, int k) { Queue<int[]> q = new PriorityQueue<>((o1,o2)->{ // 小根堆,存储的是下标 int v1 = arr[o1[0]] * arr[o2[1]]; int v2 = arr[o2[0]] * arr[o1[1]]; return Integer.compare(v1,v2); }); int n = arr.length; for(int j = 1;j < n;++j) q.offer(new int[]{0,j}); while(k-- > 1){ int[] top = q.poll(); int i = top[0],j = top[1]; if(i+1 < j) q.offer(new int[]{i+1,j}); } int[] res = q.peek(); return new int[]{arr[res[0]],arr[res[1]]}; }}
二分
4. 寻找两个正序数组的中位数
class Solution { /* 两个思路: 1)递归求解两个正序数组中从小到大第k个数,每次递归都能舍弃k/2的数,时间复杂度O(log(m+n)) (推荐,容易想) 2)利用中位数的性质二分查找第k个数,时间复杂度O(log(min(m,n))) 注意: 两个方法都需要特别关注一些corner case的处理 */ public double findMedianSortedArrays(int[] nums1, int[] nums2) { int len = nums1.length + nums2.length; if(len % 2 == 0){ int v1 = find(nums1,0,nums2,0,len/2); int v2 = find(nums1,0,nums2,0,len/2+1); return (double)(v1+v2) / 2.0; }else{ return (double)find(nums1,0,nums2,0,len/2+1); } } /* 两个数组分别从s1和s2开始,查找两个数组的第k个数 难点: 下标处理以及数组为空以及越界情况的考虑 */ int find(int[] nums1,int s1,int[] nums2,int s2,int k){ if(nums1.length-s1 > nums2.length-s2) // 确保num1的长度更短 return find(nums2,s2,nums1,s1,k); if(s1 == nums1.length) return nums2[s2+k-1]; // num1长度为0 if(k == 1 && s1 != nums1.length) return Math.min(nums1[s1],nums2[s2]); int ns1 = Math.min(s1 + k/2,nums1.length); // 可能越界 int ns2 = s2 + (k-k/2); if(nums1[ns1-1] > nums2[ns2-1]){ return find(nums1,s1,nums2,ns2,k-(ns2-s2)); }else return find(nums1,ns1,nums2,s2,k-(ns1-s1)); }}
模拟运算
8. 字符串转换整数 (atoi)
class Solution { public int myAtoi(String s) { // 去除空格 int len = s.length(); int i = 0; while(i < len && s.charAt(i) == ' ') ++i; if(i == len) return 0; // 确定符号位 int sign = 1; if(s.charAt(i) == '-'){ sign = -1; ++i; }else if(s.charAt(i) == '+'){ ++i; } // 处理符号位后数字部分(注意溢出处理) int res = 0; while(i < len && Character.isDigit(s.charAt(i))){ int v = s.charAt(i) - '0'; /*溢出处理*/ if(sign > 0 && res > (Integer.MAX_VALUE-v)/10){ res = Integer.MAX_VALUE; break; } if(sign < 0 && res < (Integer.MIN_VALUE + v)/10){ res = Integer.MIN_VALUE; break; } res = res * 10 + sign * v; ++i; } return res; }}
前缀和与差分
干草堆
import java.util.*;import java.io.*;class Main{ /* 基本思路:差分+求中位数 求中位数的的方式: 1)排序,这里值域范围比较小(25000),也可以采用桶排序 2)也可以采用快排思想找到第k大的数 */ public static int solve(int[] arr,int l,int r,int k) { if(l >= r) return arr[l]; int mid = (r - l)/2 + l; int pivot = arr[mid]; int ll = l-1,rr = r+1; do{ do ++ll; while(arr[ll] < pivot); do --rr; while(arr[rr] > pivot); if(ll < rr){ int temp = arr[ll]; arr[ll] = arr[rr]; arr[rr] = temp; } }while(ll < rr); return rr >= k ? solve(arr,l,rr,k) : solve(arr,rr+1,r,k); } public static void main(String[] args) throws NumberFormatException, IOException{ BufferedReader buf =new BufferedReader(new InputStreamReader(System.in)); String[] arr = buf.readLine().split(" "); int N = Integer.parseInt(arr[0]),K = Integer.parseInt(arr[1]); int[] diff = new int[N+2]; while(K-- > 0){ arr = buf.readLine().split(" "); int l = Integer.parseInt(arr[0]),r = Integer.parseInt(arr[1]); diff[l] += 1; diff[r+1] -= 1; } for(int i = 1;i <= N;++i) diff[i] = diff[i-1] + diff[i]; System.out.println(solve(diff,1,N,N/2+1)); }}
双指针枚举
3. 无重复字符的最长子串
class Solution { /* 基本思路: 双指针优化枚举 i:表示以i位置字符结尾的子串集合 j:代表不包含重复字符的最左边界 i,j在枚举过程中都单调不减 */ public int lengthOfLongestSubstring(String s) { int[] map = new int[256]; int res = 0; for(int i = 0,j = 0;i < s.length();++i){ char c = s.charAt(i); map[c]++; while(map[c] > 1){ // 重复的字符必定是划入的字符 map[s.charAt(j)]--; ++j; } res = Math.max(res,i-j+1); } return res; }}
固定大小的滑窗
438. 找到字符串中所有字母异位词
class Solution { /* 基本思路:滑动窗口(双指针)+map维护状态 map[i] = p中字符i出现次数-s中窗口内字符i出现次数 */ public List<Integer> findAnagrams(String s, String p) { int len1 = s.length(),len2 = p.length(); int[] map = new int[26]; // for(int i = 0;i < len2;++i) map[p.charAt(i)-'a']++; int sum = 0; for(int i = 0;i < 26;++i) sum = map[i] > 0 ? sum + 1 : sum; int cnt = 0; List<Integer> res = new ArrayList<>(); int l = 0; for(int r = 0;r < len1;++r){ int k = s.charAt(r)-'a'; map[k]--; if(map[k] == 0) ++cnt; if(r-l+1 > len2){ int idx = s.charAt(l) - 'a'; if(map[idx] == 0) --cnt; map[idx]++; ++l; } if(cnt == sum) res.add(l); } return res; }}
30. 串联所有单词的子串
class Solution { /* 分组+双指针(滑动窗口) 由于每个单词的长度是固定的,设为w, 因此可以从 0, w , 2w, 3w, ... 1, w+1,2w+1,... .... 分组采用滑动窗口判断,通过hash表+cnt标识当前窗口是否由words组成 题目中cnt表示与子集数量一致的单词种类个数,去除注释部分也正确,原因待探究 该题解题思路类似于LC78 最小覆盖子串,但题意上略有差别 */ public List<Integer> findSubstring(String s, String[] words) { List<Integer> res = new ArrayList<>(); if(words.length == 0) return res; int n = s.length(),m = words.length,w = words[0].length(); Map<String,Integer> smap = new HashMap<>(),wmap = new HashMap<>(); for(String t:words) wmap.put(t,wmap.getOrDefault(t,0)+1); int cnt = 0; for(int i = 0;i < w;++i){ smap.clear(); cnt = 0; int l = i; for(int r = i+w;r <= n;r += w){ String tmp = s.substring(r-w,r); if(wmap.containsKey(tmp)){ int v1 = smap.getOrDefault(tmp,0)+1,v2 = wmap.get(tmp); smap.put(tmp,v1); if(v1 == v2) ++cnt; // if(v1 == v2+1) --cnt; } if((r-l)/w > m){ String sl = s.substring(l,l+w); if(wmap.containsKey(sl)){ int v1 = smap.get(sl)-1,v2 = wmap.get(sl); smap.put(sl,v1); // if(v1 == v2) ++cnt; if(v1 == v2-1) --cnt; } l += w; } if((r-l)/w == m && cnt == wmap.size()) res.add(l); } } return res; }}
匹配二元组
5966. 还原原数组
class Solution { /* 排序+双指针检查 思路:nums中最小的数字必定对应着原始数组中最小的数字,根据这个性质枚举k值 双指针的作用:实现lower和higher的两两匹配,固定low匹配high(必须先排序) */ public int n; public boolean[] vis; int[] res; public int[] recoverArray(int[] nums) { n = nums.length; vis = new boolean[n]; res = new int[n/2]; Arrays.sort(nums); int right = -1; for(int i = 1;i < n;++i){ int tmp = nums[i]-nums[0]; if(tmp == 0 || (tmp & 1) == 1) continue; if(check(nums,tmp,i)) break; } return res; } /*l:lower起始位置 r:high起始位置 */ boolean check(int[] nums,int val,int r){ Arrays.fill(vis,false); int l = 0,idx = 0; while(r < n){ // 已经配对 if(vis[l]){ ++l; continue; } // 匹配新的low if(nums[r]-nums[l] == val){ vis[l] = true; vis[r] = true; res[idx++] = nums[l] + val / 2; // 匹配过程中存储值 ++l; } ++r; } // 检查所有数字是否都已经配对 for(int i = 0;i < n;++i) if(!vis[i]) return false; return true; }}