每日练习-2022.4.15
2022.4.15
剑指 Offer 06. 从尾到头打印链表
难度简单
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
思路:
-
建立一个数组,倒着遍历数组,顺着遍历链表,达到反转的作用。(会浪费空间)
-
使用arraylist,每遍历一个节点,add一次。最后建立一个数组,反着遍历。(对于数据量较小的数据会更加节省空间)。
arraylist转化为int数组
这俩一个时间超时,一个空间超时
算是意料之中。
所以:
-
使用栈,先进后出,达到反转的目的
执行结果:通过
执行用时:1 ms, 在所有 Java 提交中击败了69.83%的用户
内存消耗:41.9 MB, 在所有 Java 提交中击败了41.54%的用户
斯哈——。
public int[] reversePrint(ListNode head) {
//建立一个栈,存放节点
Stack<ListNode> stack = new Stack<ListNode>();
ListNode cur = head;
//依次push
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
//用栈的大小创建数组,避免空间浪费
int size = stack.size();
int[] ans = new int[size];
//依次弹出去,pop返回值存入数组
for (int i = 0; i < size; i++) {
ans[i] = stack.pop().val;
}
return ans;
}
栈和递归通常相关联(递归的本质就是栈)
递归实现:
int[] res;
int i = 0;
int j = 0;
public int[] reversePrint(ListNode head) {
solve(head);
return res;
}
public void solve(ListNode head){
if(head == null){
res = new int[i];
return;
}
i++;
solve(head.next);
res[j] = head.val;
j++;
}
执行结果:通过
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:42.2 MB, 在所有 Java 提交中击败了8.48%的用户
阿巴阿巴阿巴
剑指 Offer 10- I. 斐波那契数列
难度简单342
写一个函数,输入 n
,求斐波那契(Fibonacci)数列的第 n
项(即 F(N)
)。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
非递归与递归两个写法
public int fib(int n) {
if(n==0){
return 0;
}
if(n==1 || n==2){
return 1 ;
}
long[] fbs = new long[n+1];
fbs[0] = 0L;
fbs[1]=1L;
fbs[2]=1L;
for(int i =3;i<fbs.length;i++){
fbs[i]=fbs[i-1]+fbs[i-2];
}
return (int)(fbs[n]%1000000007);
}
//递归算法
public static int fb2(int n){
if(n==2||n==1){
return 1;
}
return fb2(n-1)+fb2(n-2);
}
-
实际运行中,递归写法会栈溢出,不推荐,而且会有很多重复计算。
-
非递归写法记得要判断特殊情况,n<2的时候
-
数组越界问题,long只能满足90左右的n值,所以,可以考虑先对1000000007求模后再去存储
这点很重要
public int fib(int n) { if(n==0){ return 0; } if(n==1 || n==2){ return 1 ; } long[] fbs = new long[n+1]; fbs[0] = 0L; fbs[1]=1L; fbs[2]=1L; for(int i =3;i<fbs.length;i++){ fbs[i]=(fbs[i-1]+fbs[i-2])%1000000007; } return (int)fbs[n]; }
这是考虑到数组越界的问题后的算法。
这样就可以通过所有的样例了
执行结果:
通过
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:38.5 MB, 在所有 Java 提交中击败了5.33%的用户
这样数组的内存开销会很大,所以需要进一步优化:
我们只存储3个数,pre,cur,next,每一次计算新的值,就更迭以下数字。public int fib(int n) { if(n==0){ return 0; } if(n==1 || n==2){ return 1 ; } long pre = 1L; long cur = 1L; long next = 2L; long temp; while(n>3){ temp =(cur+next)%1000000007; pre = cur; cur = next; next=temp; n--; } return (int)next; }
优化之后明显好了很多
执行结果:
通过
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了72.40%的用户
-
再次优化思路:这样交替更新显得就很耿直。。可以往轮转方向去
剑指 Offer 07. 重建二叉树
难度中等
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:根据前序与中序遍历的顺序,重新构造二叉树。并且进行输出。
重构二叉树的方法:
前序遍历性质: 节点按照 [ 根节点 | 左子树 | 右子树 ]
排序。
中序遍历性质: 节点按照 [ 左子树 | 根节点 | 右子树 ]
排序。
遍历数组:找根节点的左孩子,找到根节点的右孩子。
分别把根节点的左孩子与右孩子当作新的根节点。
(这里很明显可以用递归。很明显,对吧对吧)
。先贴一个官解
class Solution {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
太困了,自己的代码差点儿,明天在写。。。
狗命要紧。