希望看起来没那么乱
牛客的发表博客想再次编辑,好像有点小问题,所以只好另起一篇,看看哪里需要更新或者有新的理解。本轮刷题是在leetcode上完成,因此有些名称和牛客oj不太一样。
这里是一刷的目录
03. 数组中重复的数字
参考原来的一刷的博客即可,在一刷的解法4,名字可以叫原地置换。该方法是时间复杂度和空间复杂度最佳,只是会改变原数组。
最优解如下:
Leetcode上是返回该数字,因此稍微有点不同
class Solution { public int findRepeatNumber(int[] nums) { int n=nums.length; for(int i=0;i<nums.length;i++){ //例如5不在索引为5的位置上,就一直和后面的数做交换 //直到出现两个相同,比如2位置上已经是2,而后面又出现了2 //则描述该情况 nums[i]是后面的2 而nums[nums[i]]是前面的2,相同则找到了所求 while(nums[i]!=i){ if(nums[nums[i]] == nums[i]){ return nums[i]; } int temp = nums[i]; nums[i] = nums[temp]; nums[temp] = temp; } } return -1; } }
04.二维数组中的查找
参考原来的一刷的博客即可。时间复杂度O(n),利用阶梯型走法。
提供一个Leetcode上通过的写法
class Solution { public boolean findNumberIn2DArray(int[][] matrix, int target) { if(matrix == null || matrix.length == 0) { return false; } //以右上角查找为例 则从最后一列开始,从第一行开始 int m = matrix.length;//行 int n= matrix[0].length;//列 int row = 0; int col = n - 1; while(row < m && col >= 0) { if(matrix[row][col] > target) { col--; }else if(matrix[row][col] < target) { row++; }else { return true; } } return false; } }
05.替换空格
依旧参考一刷博客。最容易理解的写法是遇到空格在StringBuider或者StringBuffer上直接添加,而本题考察的是替换,因此,考虑如果给定的是一个字符数组,要求不适用额外的空间,在原数组上进行修改并替换空格,这样的假设下,最正统的方法就是从后往前慢慢加,也就是博客中记录的方法2。考虑本题给定的并不是一个字符数组,因此写法上会有些不同。
同样这里记录一个Leetcode上通过的正统写法。
class Solution { public String replaceSpace(String s) { if(s==null || "".equals(s)) return s; //统计空格数量 int spaceCount = 0; for(int i = 0 ; i < s.length() ;i++){ if(s.charAt(i) == ' ') { spaceCount++; } } //由于String是不可变的,为了容纳更多的字符,需要进行扩容 int length1 = s.length(); int length2 = length1+2*spaceCount; char[] result = new char[length2]; //双指针 int p1 = s.length()-1; int p2 = result.length-1; while(p1>=0){ char c = s.charAt(p1); if(c ==' '){ result[p2--] = '0'; result[p2--] = '2'; result[p2--] = '%'; }else{ result[p2--] = c; } p1--; } return new String(result); //return String.valueOf(result); } }
06. 从尾到头打印链表(有补充)
这里要说明的是,有多种方法,递归,头插,栈,以及二刷要补充的反转链表,其他部分可以参考一刷博客,这里仅记录反转链表的写法。最优解可应该学习栈的写法,但是这个写法不需要额外空间,也是面试中的一个考点
class Solution { public int[] reversePrint(ListNode head) { ListNode temp = reverse(head); LinkedList<Integer> res = new LinkedList<>(); while (temp != null) { res.add(temp.val); temp = temp.next; } int[] ans = new int[res.size()]; for(int i=0;i<ans.length;i++){ ans[i] = res.get(i); } return ans; } private ListNode reverse(ListNode node) { if (node == null || node.next == null ) { return node; } ListNode pre = null; ListNode cur = node; while (cur != null) { ListNode temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; } }
07. 重建二叉树(有新的认识)
这题比较难,在一刷的写法中,我们每次都需要重新在中序中找到对应的值,但是这里可以用Map代替这一查找动作。思路其实一致,只是在找中序的节点时,一个重新找,一个用容器存了查找结果。如果要求不适用额外空间,那么显然是一刷的写法最好。
map中存放的是中序的某个值,以及该值的角标索引。
public class Solution { HashMap<Integer, Integer> map = new HashMap<>(); int[] arr; public TreeNode buildTree(int[] preorder, int[] inorder) { arr = preorder; for(int i = 0; i < inorder.length; i++) map.put(inorder[i], i); return recur(0, 0, inorder.length - 1); } public TreeNode recur(int pre_root, int in_left, int in_right) { if(in_left > in_right) return null; TreeNode root = new TreeNode(arr[pre_root]); int i = map.get(arr[pre_root]);//找到在中序中的位置 //然后计算出各种偏移量。 root.left = recur(pre_root + 1, in_left, i - 1); root.right = recur(pre_root + i - in_left + 1, i + 1, in_right); return root; } }
有必要把原始写法也写出来,方便对比
public class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { TreeNode root=helper(preorder,0,preorder.length-1,inorder,0,inorder.length-1); //传过去一个两个数组以及这个数组的首尾索引 return root; } //前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6} public TreeNode helper(int[] pre,int leftP,int rightP,int[] in,int leftI,int rightI) { //startPre和endpre以及startIn和endIn的缩写 分别代表传入数组的首尾位置的索引 if(leftP>rightP||leftI>rightI) //递归结束标志(也可加入其他健壮性语句) return null; TreeNode root=new TreeNode(pre[leftP]); //找到pre的首节点,就是传过来的树的根 for(int i=leftI;i<=rightI;i++) //遍历传过来的中序,找到根节点。 if(in[i]==pre[leftP]){ //其左边的是子树,调用递归。右边的是右子树,调用递归 root.left=helper(pre,leftP+1,leftP+i-leftI,in,leftI,i-1); root.right=helper(pre,i-leftI+leftP+1,rightP,in,i+1,rightI); break;//找到根节点在中序的位置就可以了,可以提前退出。 } return root; } }
08.二叉树的下一个节点(Leetcode的剑指offer部分暂无本题但有补充)
移步这里
上面的写法是在递归的过程中比较,显然我们还有一个思路,就是遍历出中序所有结果,然后去比较。但是整体上,一刷记录的写法是最优的,针对于各种情况讨论。因此建议掌握一刷中给出的方法。另外如果是找上一节点的话,我们用栈遍历的时候,可以记录上一个节点,但是下一个节点不好记录。
09.用两个栈实现队列
一刷博客中记录的方法,以下为Leetcode上ac代码
public class CQueue { Stack<Integer> stack1; Stack<Integer> stack2; public CQueue() { stack1 = new Stack<>(); stack2 = new Stack<>(); } public void appendTail(int value) { stack1.push(value);//永远往栈1压元素 } public int deleteHead() { if(stack1.empty()&&stack2.empty()){ return -1; } if(stack2.isEmpty()){ //栈2为空,就需要倒一手,不为空就直接弹,跳到return进行弹 while(!stack1.isEmpty()){ //栈2已经空了想取元素,就得把元素全部倒给栈2,直到栈1是空 stack2.push(stack1.pop()); //把栈1的弹出,全部放入栈2 } } return stack2.pop(); } }