刷题日记 - 9 难度:中等
链表
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 class Solution { public ListNode partition (ListNode head, int x) { ListNode dummy1 = new ListNode (-111 ); ListNode dummy2 = new ListNode (-111 ); ListNode t1 = dummy1,t2 = dummy2; ListNode p = head; while (p!=null ){ int val = p.val; if (val<x){ t1.next = p; t1 = t1.next; }else if (val>=x){ t2.next = p; t2 = t2.next; } p = p.next; } ListNode dummy = new ListNode (-1 ); ListNode t = dummy; if (t1.val!=-111 ){ t.next = dummy1.next; t = t1; } if (t2.val!=-111 ){ t.next = dummy2.next; t = t2; } t.next = null ; return dummy.next; } }
难度:中等
二叉树 队列 BFS
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 class Solution { public List<List<Integer>> levelOrder (TreeNode root) { List<List<Integer>> res = new ArrayList <>(); if (root == null ) return res; Queue<TreeNode> queue = new LinkedList <>(); queue.offer(root); queue.offer(null ); while (true ) { List<Integer> cur = new ArrayList <>(); while (queue.peek() != null ) { TreeNode curNode = queue.poll(); cur.add(curNode.val); if (curNode.left != null ) queue.offer(curNode.left); if (curNode.right != null ) queue.offer(curNode.right); } res.add(cur); queue.poll(); if (queue.isEmpty()) break ; queue.offer(null ); } return res; } }
难点:
层次分割的处理 :通过在队列中加入 null
作为层与层之间的分隔符。
利用队列实现层序遍历(二叉树的BFS)
二叉树的遍历 - 栈 不使用递归实现,而是使用栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public List<Integer> preorderTraversal (TreeNode root) { List<Integer> res = new ArrayList <>(); if (root == null ) return res; Stack<TreeNode> stack = new Stack <>(); stack.push(root); while (!stack.isEmpty()) { TreeNode p = stack.pop(); res.add(p.val); if (p.right != null ) stack.push(p.right); if (p.left != null ) stack.push(p.left); } return res; } }
前序遍历的顺序是:根节点 -> 左子树 -> 右子树 。
为了实现这一顺序,我们需要注意压栈的顺序。首先将右子节点压入栈,再将左子节点压入栈。这样在下一个循环中,左子节点会先从栈中弹出并被处理,确保遍历顺序符合前序遍历的要求。
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 List<Integer> inorderTraversal (TreeNode root) { List<Integer> res = new ArrayList <>(); if (root == null ) return res; Stack<TreeNode> stack = new Stack <>(); stack.push(root); while (!stack.isEmpty()) { TreeNode p = null ; while (true ) { p = stack.peek(); if (p.left != null ) { stack.push(p.left); } else break ; } TreeNode pop; while (!stack.isEmpty()) { pop = stack.pop(); res.add(pop.val); if (pop.right != null ) { stack.push(pop.right); break ; } } } return res; } }
在处理左子树时,需要不断地将左孩子节点压入栈中,直到没有左孩子为止。只要没有左孩子,或者左孩子处理完毕,就可以弹出当前节点
当我们弹出栈顶节点时,如果该节点有右子树,我们需要将右子树的根节点压入栈,然后继续按照中序的顺序处理右子树。
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 List<Integer> postorderTraversal (TreeNode root) { List<Integer> res = new ArrayList <>(); if (root == null ) return res; Stack<TreeNode> stack = new Stack <>(); TreeNode lastPop = null ; stack.push(root); while (!stack.isEmpty()) { TreeNode p = null ; while (true ) { p = stack.peek(); if (p.left != null ) { stack.push(p.left); } else break ; } TreeNode cur = null ; while (!stack.isEmpty()) { cur = stack.peek(); if (cur.right != null && lastPop != cur.right) { stack.push(cur.right); break ; } else { res.add(cur.val); stack.pop(); lastPop = cur; } } } return res; } }
是否弹出当前节点 –> 不仅要判断左孩子是否被处理了,还要判断右孩子是否被处理。
使用栈模拟二叉树前序、中序和后序遍历的区别 栈操作流程
前序遍历 :
根节点先入栈 :首先将根节点入栈。
先处理根,再处理子节点 :从栈中弹出根节点,处理后依次将右孩子和左孩子入栈(注意先右后左,这样出栈时先处理左子树)。
重复上述步骤 :继续弹栈并处理,直到栈为空。
中序遍历 : ⭐
左孩子依次入栈 :先将当前节点的所有左孩子压入栈,直到没有左孩子为止。⭐
弹栈并处理根节点 :弹出栈顶节点,处理后,检查是否有右孩子。⭐
处理右子树 :如果有右孩子,将右孩子作为新的当前节点,重复左孩子入栈的过程。⭐
后序遍历 :
左孩子先入栈 :与中序遍历类似,先将左孩子入栈。⭐
右孩子处理顺序 :在弹出根节点前,必须先处理右孩子。需要特别注意判断右子树是否已经被处理过。⭐
根节点最后处理 :只有在左子树和右子树都处理完毕后,才处理根节点。⭐
栈操作中的关键差异
前序遍历 :
栈的顺序 :由于前序遍历是先处理根节点,因此在入栈时,需要先将右孩子压入栈,再将左孩子压入栈,这样出栈时能够先处理左子树。
中序遍历 :
递归性强 :中序遍历在访问节点时,需要先将所有左孩子压栈,出栈后再检查右子树。这种操作使得中序遍历在栈的使用上较为直观和递归。
后序遍历 :
节点的双重检查 :后序遍历最复杂的部分在于对右子树的处理,需要在出栈之前确认右孩子是否已经访问过。如果右孩子还未访问,则需要将右孩子入栈继续处理。
注意事项
前序遍历 :
需要注意入栈顺序,先右后左,以确保左子树先于右子树处理。
中序遍历 :
处理左子树时,需要注意什么时候该弹栈处理根节点,以及处理右子树的时机。
后序遍历 :
必须时刻跟踪最后访问的节点,确保右子树在根节点之前被访问。需要利用一个额外的指针或变量记录上一次处理的节点(即“lastPop”),来判断右子树是否已经访问。
所以其实只有中序遍历和后续遍历才符合用栈模拟递归 。
难度:困难
类似PDD一面手撕算法题。
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 class Solution { int res; public int atMostNGivenDigitSet (String[] digits, int n) { char [] chars = new char [digits.length]; for (int i = 0 ; i < digits.length; i++) { chars[i] = digits[i].charAt(0 ); } return fun(chars, n); } public int fun (char [] digits, int n) { String nString = String.valueOf(n); int len = nString.length(); res = 0 ; Arrays.sort(digits); int digitsNum = digits.length; for (int i = 1 ; i <= len - 1 ; i++) { res += (int ) Math.pow(digitsNum, i); } dfs(nString.toCharArray(), digits, 0 , false ); return res; } private void dfs (char [] n, char [] digits, int pos, boolean flag) { int len = n.length; int digitsNum = digits.length; if (pos == len) { if (flag) res += 1 ; return ; } char cur = n[pos]; int targetCurIdx = -1 ; for (int i = digitsNum - 1 ; i >= 0 ; i--) { if (digits[i] == cur) { dfs(n, digits, pos + 1 , true ); } if (digits[i] < cur) { targetCurIdx = i; break ; } } int useableNum = targetCurIdx + 1 ; res += useableNum * Math.pow(digitsNum, len - pos - 1 ); } }
PDD面试题 题意与上题差不多,但是只要求返回小于N的最大值。
我的第一想法就是这个:
先将目标值n
转换为char[]
便于一位一位地处理
将digits
排序,便于从大到小遍历(不用二分查找,因为最多也就9个数字)
进入dfs
函数,从n
的高位依次往下遍历,当前位位置为pos
,值为cur = char[pos]-'0'
,在digits
数组中从大到小寻找targetCurIdx
,值小于等于cur
的元素的下标。
如果targetCur
等于cur
,那么进入下一层``dfs,寻找下一位的
targetCur`
如果targetCur
小于cur
,那么直接可以确定后面的所有值了,全用digits
中最大的值填充即可
如果targetCur=-1
,cur
比digits
中的所有值都小,表明当前位是不能作为低于目标位的,要回到上一层dfs去,尝试用前面的位作为“低”位。
如果dfs都到了最后一层,表明当前构造出的值等于目标值,也需要回到上一层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 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 public class Main { public static void main (String[] args) { int [] digits = {2 , 5 , 4 }; int n = 2544 ; System.out.println(new Solution ().fun(digits, n)); } } class Solution { public int fun (int [] digits, int n) { String nString = String.valueOf(n); int len = nString.length(); int digitsNum = digits.length; Arrays.sort(digits); int res = dfs(nString.toCharArray(), digits, 0 , 0 ); if (res == -1 ) { res = 0 ; for (int i = 1 ; i <= len - 1 ; i++) { res = res * 10 + digits[digitsNum - 1 ]; } } return res; } private int dfs (char [] n, int [] digits, int pos, int res) { int len = n.length; int digitsNum = digits.length; if (pos == len) { return -1 ; } int cur = n[pos] - '0' ; int targetCurIdx = -1 ; for (int i = digitsNum - 1 ; i >= 0 ; i--) { if (digits[i] == cur) { int tmp = dfs(n, digits, pos + 1 , res * 10 + digits[i]); if (tmp != -1 ) { return tmp; } } else if (digits[i] < cur) { targetCurIdx = i; break ; } } if (targetCurIdx == -1 ) { return -1 ; } res = res * 10 + digits[targetCurIdx]; int leftPosCount = len - 1 - pos; for (int i = 0 ; i < leftPosCount; i++) { res = res * 10 + digits[digitsNum - 1 ]; } return res; } }
可惜我想的比较复杂,当时又有点紧张,现场没有手撕出来。/(ㄒoㄒ)/~~
也可以不用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 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 public class Main { public static void main (String[] args) { int [] digits = {5 , 9 }; int n = 5555 ; System.out.println(new Solution ().fun(digits, n)); } } class Solution { public int fun (int [] digits, int n) { int digitsNum = digits.length; char [] digitChars = new char [digitsNum]; char max = '0' ; char min = '9' ; for (int i = 0 ; i < digitsNum; i++) { digitChars[i] = (char ) (digits[i] + '0' ); max = digitChars[i] > max ? digitChars[i] : max; min = digitChars[i] < min ? digitChars[i] : min; } Arrays.sort(digitChars); char [] nChars = String.valueOf(n).toCharArray(); int len = nChars.length; char [] res = new char [len]; Arrays.fill(res, '0' ); int p = 0 ; int state = 0 ; while (p < nChars.length) { int targetIdx = -1 ; char cur = nChars[p]; for (int i = digitsNum - 1 ; i >= 0 ; i--) { if (digitChars[i] == cur) { res[p] = digitChars[i]; targetIdx = -2 ; break ; } else if (digitChars[i] < cur) { targetIdx = i; res[p] = digitChars[targetIdx]; break ; } } if (targetIdx == -2 ) { p++; } else { if (targetIdx == -1 ) state = 1 ; if (targetIdx != -1 ) state = 2 ; break ; } ; } if (state == 0 ) { p--; while (p >= 0 && res[p] <= min) { res[p] = max; p--; } if (p == -1 ) { res[0 ] = '0' ; } else { for (int i = digitsNum - 1 ; i >= 0 ; i--) { if (digitChars[i] < res[p]) { res[p] = digitChars[i]; } } } } if (state == 1 ) { for (int i = len - 1 ; i >= 0 ; i--) { if (i < p) break ; res[i] = max; } p--; while (p >= 0 && res[p] <= min) { res[p] = max; p--; } if (p == -1 ) { res[0 ] = '0' ; } else { for (int i = digitsNum - 1 ; i >= 0 ; i--) { if (digitChars[i] < res[p]) { res[p] = digitChars[i]; } } } } if (state == 2 ) { for (int i = len - 1 ; i >= 0 ; i--) { if (i <= p) break ; res[i] = max; } } int resInt = 0 ; for (int i = 0 ; i < len; i++) { char cur = res[i]; resInt = resInt * 10 + (cur - '0' ); } return resInt; } }
其实还是挺难的,过程比较繁杂。