刷题日记 - 8
记录刷题。
难度:中等
链表 哈希表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
class Solution { public Node copyRandomList(Node head) { HashMap<Node, Node> map = new HashMap<>();
Node dummy = new Node(-1); Node t = dummy; Node p = head;
while (p != null) { Node randomNode = p.random; Node tmp = null; Node tmpRandom = null;
if (map.containsKey(p)) { tmp = map.get(p); } else { tmp = new Node(p.val); map.put(p, tmp); }
if (randomNode != null) { if (map.containsKey(randomNode)) { tmpRandom = map.get(randomNode); } else { tmpRandom = new Node(randomNode.val); map.put(randomNode, tmpRandom); } }
t.next = tmp; t = t.next; t.random = tmpRandom;
p = p.next; }
return dummy.next; } }
|
难点:
- 复制链表中的随机指针 确保新链表中的随机指针指向新链表中的对应节点,而不是原链表中的节点,这就会导致后面的节点可能被提前创建,所以我们引入了哈希表。
难度:中等
链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if(left == right) return head;
ListNode p = head; ListNode dummy = new ListNode(-1, head); ListNode leftNode = dummy; ListNode rightNode = null; int count = 1;
while(count <= right + 1 && p != null) { if(count + 1 == left) leftNode = p; if(count == right + 1) rightNode = p; count++; p = p.next; }
p = leftNode.next; ListNode q = p.next; ListNode t = q.next;
while(true) { q.next = p; p = q; q = t; if(q == rightNode) break; t = q.next; }
leftNode.next.next = rightNode; leftNode.next = p;
return dummy.next; } }
|
需要注意的点:
难度:困难
链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Solution { public ListNode reverseKGroup(ListNode head, int k) { if(k < 2) return head;
ListNode dummy = new ListNode(-1, head); ListNode p = dummy; int count = 0; ListNode pre = null;
while(true) { if(count == 0) { pre = p; } if(count == k + 1) { p = reverseBetween(pre, p); count = 0; continue; } count++; if(p == null) break; p = p.next; }
return dummy.next; }
public ListNode reverseBetween(ListNode pre, ListNode tail) { ListNode p = pre.next; ListNode q = p.next; ListNode t = q.next; ListNode res = pre.next;
while(true) { q.next = p; p = q; q = t; if(q == tail) break; t = q.next; }
pre.next.next = tail; pre.next = p;
return res; } }
|
结合上一个题。
分组处理:每K组做一次反转,注意寻找 pre
节点和tail
节点,即连接反转部分的头和尾。
部分反转与下一次的连接:上一组反转完之后的最后一个节点,将作为下一组反转的pre
节点
注意边界:在这里不能用while(p!=null)
结束,因为最后这个p
还可能作为tail
对最后一组进行反转。
难度:中等
链表 双指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(-1, head); ListNode p = dummy; ListNode q = dummy; int count = 0;
while(p != null) { if(count > n) q = q.next; p = p.next; count++; } q.next = q.next.next; return dummy.next; } }
|
技巧就是利用双指针,指针步长一样,但是出发时机不同,让其中一个指针先跑n+1
步,让先出发那个指针到达终点null
的时候,后出发的指针刚好到要删除的节点的前一个节点。
注意,还是要设置 dummy 节点,因为可能会出现要删除的节点就是第一个节点的情况。
难度:中等
链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Solution { public ListNode deleteDuplicates(ListNode head) { if (head == null || head.next == null) return head; ListNode dummy = new ListNode(-1); ListNode t = dummy; ListNode p = head; while (p != null) { ListNode q = p.next; boolean flag = false; while (q != null && q.val == p.val) { p = q; q = p == null ? null : p.next; flag = true; } if (flag) { p = q; } else { t.next = p; t = t.next; p = q; } } t.next = null; return dummy.next; } }
|
难点:
- 处理重复元素的逻辑:使用节点指针
t
指向哪些非重复元素,我使用了布尔值flag
标记了当前元素是否是重复元素,后续根据flag
值确定t
指针的指向。
- 边界条件处理:有可能出现链表中所有的元素都是重复元素的情况,所以需要一个
dummy
节点
- 确保去重后的链表尾部指向null
难度:简单
字节客户端面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public ListNode deleteDuplicates(ListNode head) { if(head==null||head.next==null)return head; ListNode dummy = new ListNode(-1); ListNode t = dummy; ListNode p = head; while(p!=null){ if(t==dummy||t.val!=p.val){ t.next = p; t = t.next; } p = p.next; } t.next = null; return dummy.next; } }
|
但是面试的时候,给的题目描述是83题的描述,示例给的是82题的示例。
难度:中等
链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class Solution { public ListNode rotateRight(ListNode head, int k) { ListNode dummy = new ListNode(-1, head); if (k == 0 || head == null) return dummy.next; ListNode[] con = new ListNode[501]; con[0] = dummy; ListNode p = head; int count = 1; while (p != null) { con[count++] = p; p = p.next; } int total = count - 1; k %= total; if (k != 0) { reverseBetween(dummy, null); ListNode tmp = con[total - k]; ListNode tmp_1 = reverseBetween(dummy, tmp); reverseBetween(tmp_1, null); } return dummy.next; }
private ListNode reverseBetween(ListNode pre, ListNode tail) { ListNode res = pre.next; ListNode p = pre.next; ListNode q = p.next; ListNode t = q == null ? null : q.next; while (q != tail) { q.next = p; p = q; q = t; t = q == null ? null : q.next; } pre.next.next = tail; pre.next = p; return res; } }
|
思想同189. 轮转数组 - 力扣(LeetCode)
难度:中等
二叉树 DFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { int count = -1; public TreeNode buildTree(int[] preorder, int[] inorder) { return dfs(preorder, inorder, 0, inorder.length - 1); }
private TreeNode dfs(int[] preorder, int[] inorder, int left, int right) { if (left > right) return null; TreeNode cur = new TreeNode(preorder[++count]); int i = left; for (; i <= right; i++) { if (cur.val == inorder[i]) break; } cur.left = dfs(preorder, inorder, left, i - 1); cur.right = dfs(preorder, inorder, i + 1, right); return cur; } }
|
难点:
- 理解递归构建树的过程:从前序遍历中依次取出元素作为根节点,然后在中序遍历中找到该根节点的位置,从而将树分为左右两部分,再递归构建左右子树。
难度:中等
二叉树 DFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Solution { int count; public TreeNode buildTree(int[] inorder, int[] postorder) { count = inorder.length; return dfs(inorder,postorder,0,count-1); }
private TreeNode dfs(int[] inorder,int[] postorder,int left,int right){ if(left>right)return null; int curVal = postorder[--count]; TreeNode curNode = new TreeNode(curVal);
int i = left; for(;i<=right;i++){ if(inorder[i]==curVal)break; }
curNode.right = dfs(inorder,postorder,i+1,right); curNode.left = dfs(inorder,postorder,left,i-1); return curNode; } }
|
道理同上,只是 preorder
换成了postorder
,所以现在从postorder
中拿去子树的根节点,并且左右子树的递归顺序需要改变。
上一个题目:
- 从前序遍历数组中依次选择根节点。
- 在中序遍历数组中找到该根节点的位置,划分左右子树。
- 递归构建左子树,然后递归构建右子树。
这个题目:
- 从后序遍历数组中依次选择根节点。
- 在中序遍历数组中找到该根节点的位置,划分左右子树。
- 递归构建右子树,然后递归构建左子树。