- 字节跳动
- 牛客高频
- LRU(双向链表 + HashMap)">LRU(双向链表 + HashMap)
- 两个只出现一次的数字(XOR位运算)">两个只出现一次的数字(XOR位运算)
- 国际化电商
- 牛客高频
字节跳动
牛客高频
LRU(双向链表 + HashMap)
// 双向链表,用于修改LRU顺序public static class ListNode {ListNode next;ListNode pre;int key;int val;public ListNode(int key, int val) {this.key = key;this.val = val;}}// 快速查找LRU中的节点,key为LRU节点的key,val为LRU节点本身的ListNodeMap<Integer, ListNode> map = new HashMap<>();// 虚拟头ListNode head = new ListNode(-1, -1);// 虚拟尾ListNode tail = new ListNode(-1, -1);int k;public int[] LRU (int[][] operators, int k) {// 设置虚拟头、尾节点,方便头插节点、尾删节点与节点前移head.next = tail;tail.pre = head;this.k = k;int resultSize = (int)Arrays.stream(operators).filter(o -> o.length == 2).count();int[] result = new int[resultSize];int count = 0;for(int i = 0; i < operators.length; i++) {int[] operator = operators[i];if(operator[0] == 1) {set(operator[1], operator[2]);} else if(operator[0] == 2) {int val = get(operator[1]);result[count] = val;count ++;}}return result;}// 节点前移,当get命中了节点、set插入了新节点时调用,将命中/新增的节点放到链表头部public void putToHead(ListNode node){node.next = head.next;node.pre = head;head.next.pre = node;head.next = node;}// LRU的get方法public int get(int key) {if(!map.containsKey(key)) {return -1;}putToHead(map.get(key));return node.val;}// LRU的set方法public void set(int key, int val) {if(get(key) != -1) {map.get(key).val = val;} else {if(map.size() == this.k) {map.remove(tail.pre.key);// 删除超过长度的LRU队列最后一个节点tail.pre.pre.next = tail;tail.pre = tail.pre.pre;}ListNode newNode = new ListNode(key, val);map.put(key, newNode);putToHead(newNode);}}public void putToHead(ListNode node){// 已存在节点移到头部需要的操作if(node.pre != null) {node.pre.next = node.next;}if(node.next != null) {node.next.pre = node.pre;}node.next = head.next;node.pre = head;head.next.pre = node;head.next = node;}
两个只出现一次的数字(XOR位运算)
public int[] singleNumber(int[] nums) {int[] res = new int[2];// 两个结果数字的xor结果int xor = 0;for(int num : nums) {xor ^= num;}// 保存xor第一位为1的bit位idxint idx = 0;while((xor & (1 << idx)) == 0) {idx++;}// 分两次单独寻找出现一次的数字int res1 = 0, res2 = 0;for(int num : nums) {if((num & (1 << idx)) == 0) {res1 ^= num;} else {res2 ^= num;}}res[0] = res1;res[1] = res2;return res;}
国际化电商
接雨水问题(双指针)
// 双指针做法public long maxWater (int[] arr) {int left = 0, right = arr.length - 1;int height = 0;long result = 0;while(left < right) {int minHeight = Math.min(arr[left], arr[right]);// 保存双指针指向最矮的边与已有高度的最大值作为上界height = Math.max(minHeight, height);// 最矮边高度作为下界求结果if(arr[left] >= arr[right]) {result += height - arr[right];right --;} else {result += height - arr[left];left ++;}}return result;}
改版接雨水问题(双指针)
一批隔板,相邻距离为1,隔板不占体积,能蓄多少水?如[3, 2, 5, 4, 6, 2]返回18。
public int trap(int[] arr) {int l = 0, r = arr.length - 1;int lHeight = 0, rHeight = 0;int res = 0;while (l < r) {if (arr[l] < arr[r]) {lHeight = Math.max(lHeight, arr[l]);res += lHeight;l++;} else {rHeight = Math.max(rHeight, arr[r]);res += rHeight;r--;}}return res;}
二叉树路径和问题(递归 + DFS回溯)
// 最终结果List<List<Integer>> result = new ArrayList<>();// DFS保存的路径List<Integer> path = new ArrayList<Integer>();public void traverse(TreeNode root, int sum, int targetSum) {if(root == null){return;}sum += root.val;path.add(root.val);// root为叶子结点,且求和与目标一致,将结果保存if(root.left == null && root.right == null && sum == targetSum) {result.add(new ArrayList(path));}// 递归左子树traverse(root.left, sum, targetSum);// 递归右子树traverse(root.right, sum, targetSum);// 路径回溯path.remove(path.size() - 1);}public List<List<Integer>> pathSum(TreeNode root, int targetSum) {traverse(root, 0, targetSum);return result;}
二叉树最近公共祖先(递归)
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null) {return null;}// val相同的点必为公共祖先if(root.val == p.val || root.val == q.val) {return root;}// 递归得到左子树与右子树分别的公共祖先TreeNode leftSub = lowestCommonAncestor(root.left, p, q);TreeNode rightSub = lowestCommonAncestor(root.right, p, q);// 左右子树不为null,根为公共祖先if(leftSub != null && rightSub != null) {return root;} else if(leftSub == null) { // 左子树不包含公共祖先,返回右子树的根return rightSub;} else { // 右子树不包含公共祖先,返回左子树的根return leftSub;}}
完全平方数(DP)
public int numSquares(int n) {int[] dp = new int[n + 1];for(int i = 1; i <= n; i++) {int min = Integer.MAX_VALUE;for(int num = 1; num * num <= i; num++) {min = Math.min(min, dp[i - num * num]);}dp[i] = min + 1;}return dp[n];}
合并区间(贪心)
public int[][] merge(int[][] intervals) {if (intervals.length == 0) {return new int[0][2];}// 排序后遍历Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);List<int[]> res = new ArrayList<>();int last = 0;for(int i = 0; i < intervals.length; i++) {int l = intervals[i][0], r = intervals[i][1];last = res.size() - 1;// 判断已确定上界和当前下界是否重合,不重合就新增结果if(res.size() == 0 || res.get(last)[1] < l) {res.add(new int[]{l, r});} else {// 重合则合并res.get(last)[1] = Math.max(res.get(last)[1], r);}}return res.toArray(new int[res.size()][2]);}
统计出现次数(双指针)
数组两端到中间降序排列,求数组中出现的不同数字个数,如[4, 5, 6, 9, 7, 6, 5, 1]返回6。
public int differentNum(int arr[]) {int res = 0;int l = 0, r = arr.length - 1;while (l <= r) {// 保存双指针两侧小的值int cache = Math.min(arr[l], arr[r]);if (cache == arr[l]) {while (l < arr.length && cache == arr[l]) {l++;}}if (cache == arr[r]) {while (r >= 0 && cache == arr[r]) {r--;}}res++;}return res;}
