目录
DAY11
45:把数组排成最小的数
思路:排序,数组中任意两个数字组成的字符串为x,y
- 若组成的字符串x+y>y+x则y比较“小”,应该排在前面;反之排在后面
- sort无返回值,动态修改数组
- (x,y) -> (x + y).compareTo(y + x)
class Solution {
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for(int i = 0; i < nums.length; i++)
strs[i] = String.valueOf(nums[i]); //将nums中数字转换成字符
Arrays.sort(strs, (x,y) -> (x + y).compareTo(y + x));
StringBuilder res = new StringBuilder();
for(String s : strs)
res.append(s);
return res.toString();
}
}
61:扑克牌中的顺子(此题思路牛)
思路:排序(大神的思路真的好牛逼)。每次抽五张牌,数组长度为5。大小王0可以代替任何数字,所以满足顺子的条件就是max-min < 5,而中间空缺的数字可以用0补齐。因为需要5个连续的数字才算是顺子,所以用最大的牌 - 5就是中间所需填补数字的个数,一定要小于大小王的个数才能补齐。
- 关键就在于max - min < 5 就能构成顺子这个思路
class Solution {
public boolean isStraight(int[] nums) {
int joker = 0;
Arrays.sort(nums);
for(int i = 0; i < 4; i++){
if(nums[i] == 0) joker++;
else if(nums[i] == nums[i + 1]) return false; //有重复的数肯定不是顺子
}
return nums[4] - 5 < nums[joker];
}
}
DAY12
40:最小的k个数
思路一:快速排序。将arr[0]作为第一轮递归的哨兵,i,j两个指针分别从头和尾开始移动,与哨兵作比较,将右部分比哨兵小的和左部分比哨兵大的交换,当i与j相遇时完成第一轮排序。然后分成前后两部分子数组分别进行快排,将每部分的第一个元素作为哨兵进行比较排序。
思路二:堆排序
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
quickSort(arr, 0, arr.length - 1);
return Arrays.copyOf(arr, k);
}
void quickSort(int[] arr, int left, int right){
if(left >= right) return;
int i = left, j = right;
while(i < j){
while(i < j && arr[j] >= arr[left]) j--;
while(i < j && arr[i] <= arr[left]) i++;
swap(arr, i, j);
}
swap(arr, i, left); //交换arr[i]和arr[left]
//递归左右子数组
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
void swap (int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
41:数据流中的中位数(hard 重点看!!!)
思路:大根堆小根堆。向队列中添加元素时为了保证元素的序列,插入位置后元素要移动。规定当元素为奇数时A的size比B的大。PriorityQueue会自动将输入的元素进行排序并弹出最小的。
- 当元素个数为偶数时:将元素加入A中,再将A中最小的弹到B中(因为num的大小不确定,所以不能直接存入A中)
- 当为奇数时:因为规定A中比B重元素多,所以将元素添加到B中,在弹出最大的到A中
-
PriorityQueue是优先队列(需要看源码),确保每次弹出的是元素中最小的,所以构建小顶堆(存放较大的一部分)时用这个,而PriorityQueue<>((x,y) -> (y-x))为大顶堆的正则写法
class MedianFinder {
Queue<Integer> A, B;
/** initialize your data structure here. */
public MedianFinder() {
A = new PriorityQueue<>(); //小顶堆,确保每次弹出都是最小的,所以保存较大的一部分
B = new PriorityQueue<>((x, y) -> (y - x)); //大顶堆,与上相反
}
public void addNum(int num) {
if(A.size() != B.size()){ //奇数个
A.add(num);
B.add(A.poll());
}else{
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) /2.0;
}
}
35:复杂链表的复制
思路:哈希表。构建哈希表,键值对存放<旧节点,新节点>。然后构建新节点的next和random指向。(这题的意思就是原来的链表只有next指向,现在要构建一个既有next指向又有random指向的新链表,每个节点不仅指向下一个,同时还随机指向一个节点。是这样理解的吧...)
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> hash = new HashMap<>(); //构建hash表
while(cur != null){
hash.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
//构建新链表的next和random指向
while(cur != null){
hash.get(cur).next = hash.get(cur.next);
hash.get(cur).random = hash.get(cur.random);
cur = cur.next;
}
return hash.get(head);
}
}
DAY13
55-Ⅰ:二叉树的深度
思路一:递归。DFS
- 当根节点为空时深度为0
- 分别计算左、右子树的深度,选择深度最大的一条路径返回
思路二:递归。BFS。类似从上到下打印二叉树。把同一层的子节点一起放入queue中,遍历完每一层将计数器加一。
//DFS
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
//BFS
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
List<TreeNode> queue = new LinkedList<>(), temp;
queue.add(root);
int count = 0;
while(!queue.isEmpty()){
temp = new LinkedList<>();
for(TreeNode node : queue){
if(node.left != null) temp.add(node.left);
if(node.right != null) temp.add(node.right);
}
queue = temp;
count++;
}
return count;
}
}
55-Ⅱ:平衡二叉树
思路一:递归。DFS后序遍历,自下而上。
- 当root为空则为没有子节点了,返回高度为0
- 当左/右子树深度为-1则此树的左/右子树不是平衡树,因此剪枝返回-1
- 当root左/右子树的深度差<=1,则返回当前子树的深度,即root的左/右子树的深度最大值+1。(最后要用根节点左最大深度-右最大深度判断是否平衡)
思路二:递归,先序遍历。同上一题,计算出左右子树的最大深度判断是否平衡。
//思路一:后序遍历
class Solution {
public boolean isBalanced(TreeNode root) {
return depth(root) != -1;
}
public int depth(TreeNode root){
if(root == null) return 0;
int left = depth(root.left);
if(left == -1) return -1;
int right = depth(root.right);
if(right == -1) return -1;
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
}
}
//思路二:先序遍历
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int depth(TreeNode root){
if(root == null) return 0;
return Math.max(depth(root.left), depth(root.right)) + 1;
}
}
DAY 14
64:求1+2+...+n
思路:递归。题目要求不能用乘除,if/while等一系列关键字和判断语句。本题重点是当n = 1时要终止递归,所以设置temp,因为在&&中前面的条件是false后面的就不执行了,所以当n=1时不再执行sumNums(n - 1)了。
class Solution {
int sum = 0;
public int sumNums(int n) {
boolean temp = n > 1 && sumNums(n - 1) > 0;
sum += n;
return sum;
}
}
//简便写法
class Solution {
public int sumNums(int n) {
boolean temp = n > 1 && (n += sumNums(n - 1)) > 0;
return n;
}
}
68-Ⅰ:二叉搜索树的最近公共祖先
思路:两个要点:深度和祖先。二叉搜索树特征:任意节点的右节点的值小于本身,左节点大。设root为p,q的公共祖先,有三种情况:
- p,q分别在root的左/右子树中且分布在异侧
- p=root且q在root的左/右子树中
- q=root且p在root的左/右子树中
方法一:迭代
方法二:递归
//方法一:迭代
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null){
if(root.val > p.val && root.val > q.val) root = root.left;
else if(root.val < p.val && root.val < q.val) root = root.right;
else break; //找到了最近的公共节点
}
return root;
}
}
//方法二:递归
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
else if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
}
68-Ⅱ:二叉树的最近公共祖先(多看!好喜欢这道题...)
思路:递归+回溯,先序遍历。递归左右子节点,分别记为left和right
- 若root开始遍历,先遇到p或q则直接返回,自己也能是祖先节点
- 遍历左子树,若left和right都在左子树,则调用上一条,先找到谁谁就是祖先节点
- 遍历右子树同2
- 若left和right同时不为空时,则说明p,q分布在此时root的异侧,则root为最近的公共节点
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root.val == p.val || root.val == q.val) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
//遍历完left没找到p,q则说明都在右子树,返回right即返回在右子树先找到的p或q为祖先
if(left == null) return right;
else if(right == null) return left;
//若left和right均不为空,则说明他们处于此时root的异侧,此时root为最近的公共祖先
else return root;
}
}
DAY15
07:重建二叉树(重点看)
思路:分治算法,递归。建立哈希表存放中序数组的元素值和索引值的键值对
先序遍历:根节点 | 左子树 | 右子树
中序遍历:左子树 | 根节点 | 右子树
先序遍历根节点索引 | 中序遍历左边界 | 中序遍历右边界 | |
左子树 | root + 1 | left | i - 1 |
右子树 | root + 1 + i - left | i +1 | right |
- i 为中序遍历中根节点的索引,右子树的根节点为:根节点 + 1 - 左子树的长度
class Solution {
HashMap<Integer, Integer> dic = new HashMap<>();
int[] preorder; //保留先序遍历数组
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
for(int i = 0; i < preorder.length; i++)
dic.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 node = new TreeNode(preorder[pre_root]); //先序遍历的第0个元素为树的根节点
int i = dic.get(preorder[pre_root]); //找到根节点在中序遍历中对应的索引值
node.left = recur(pre_root + 1, in_left, i - 1);
node.right = recur(pre_root + 1 + i - in_left, i + 1, in_right);
return node;
}
}
16:数值的整数次方(看思路!!!)
思路:分治算法,递归。计算时,先递归计算出
(
为向下取整)
- 当n为偶数时,
- 当n为奇数时,
- 当n=0时候结果都为1,所以n=0是递归的边界
class Solution {
public double myPow(double x, int n) {
long N = n;
return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
}
double quickMul(double x, long N){
if(N == 0) return 1.0;
double y = quickMul(x, N / 2);
return N % 2 == 0 ? y * y : y * y * x;
}
}
33:二叉搜索树的后序遍历
思路:分治算法,递归。二叉搜索树,任意节点的左子节点小于本身,右子节点大于本身。数组最后一个元素为根节点,比它小的是左子树,比它大的是右子树。先找到左右子树的分界点,m记为右子树的第一个节点。左子树区间为[left,m - 1],右子树区间[m,right - 1],根节点索引为right。判断是否为二叉搜索树:
- 左子树:[left,m - 1]内所有节点都小于postorder[right]
- 右子树:[m,right - 1]内所有节点都大于postorder[right],当遇到根节点则跳出循环
- p = right判断此树是否正确。只有正确的树p才能走到right的位置。
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int left, int right){
if(left >= right) return true;
int p = left;
while(postorder[p] < postorder[right]) p++;
int m = p; //m为第一个比根节点大的元素索引
while(postorder[p] > postorder[right]) p++;
return p == right && recur(postorder, left, m - 1) && recur(postorder, m, right - 1);
}
}