103.锯齿形层序遍历236.最近公共祖先20.有效的括号5.最长回文子串33.搜索旋转排序数组+java对象引用

JAVA对象的引用

  1. 对象的引用通过"="赋值给另一个对象的引用时,实际上是把这个对象在堆中的地址赋值给另一个引用,这时候两者对象的引用指向的是同一个地址。以下面这个为例子:
public class Linklist {
     int val;
     Linklist next;
     Linklist(){}
     Linklist(int _val){this.val=_val;this.next=null;}
     }
     Linklisk p1=new Linklist(8);//实例化一个Linklist对象,并让p1指向这个对象
     Linklist p2=p1;//把p1这个引用所指向对象的地址赋值给p2
     p1.val=6;
     p1=new Linklist(7);

这里对应两种应用调用操作:

  • 通过p1(p2)来改变对应地址的属性p1.val,那么所有指向该对象地址的引用访问该对象属性的值时都会改变。即p1指向对象的内容改了,那么p2指向对象的内容也会相应改变。
  • 当直接改变p1指向对象的地址,相当于让p1换一个对象时,p2的指向不受影响,还是指向val=8的对象。
  1. 形参为对象的引用时的方法调用。例子如下:
public static void  main(String[] args)
{
		Linklist p1=new Linklist(6);
		fun(p1);
    	//p.val=7
}
public void fun(Linklist p)
{
		p.val=7;//改变引用地址的属性。或者改变p.next
		p=new Linklisk(8);
}

当你调用函数时,先会隐式的生成(复制)一个所传入实参p1引用的形参副本p,这个形参p和p1指向的是同一个堆中的对象。同样对应两种操作:

  • 当通过形参副本p修改p所指向的堆中的对象的属性值时,那么主函数中的主元(实参)所指向的地址的内容确实被改变了。
  • 直接修改形参副本p的指向,让p等于(指向)另一个对象时,改变的仅仅只是副本引用的指向,你在主函数中实参引用指向的对象的地址仍然没变,还是原来那个对象。
    在这里插入图片描述
    在这里插入图片描述

因此,修改一个对象引用的对象属性,会影响到所有指向该对象的引用;修改一个对象引用的指向(p1=p2),只会影响到被修改引用自身。所以如果需要通过子函数修改引用的指向,只能够过子函数返回那个对象的地址,在主函数中赋给那个引用(p1=fun(p1))

103.二叉树的锯齿形层序遍历

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]

  1. 锯齿形层序遍历和层次遍历的区别在于,在每一层访问当前节点并且把孩子节点放入下一次访问的队列当中时,先放入的孩子在下一层的访问中最后才被访问,实际上让你实现的就是一个分层(分组)访问的栈。一开始的想法是用两个栈来实现,一个栈用来保存并访问当前层的节点,另一个栈用来保存当前层节点的孩子节点;但是提交了发现莫名其妙的超时了。
  2. 后面换了一种方式用链表,设置三个指针,beg标记当前访问节点,end标记当前层最后访问的一个节点,nend标记下一层最后访问的节点,beg走到end表示当前层走完开始进入下一层。有两个需要注意的点:
  • beg的孩子节点插入链表需要用头插法(先进后出)。
  • 每层遍历中nend节点更新为当前层往链表后插入的第一个孩子节点。

当然也可以用容器LinkedList,直接用封装的size()方法来标记当前层有几个节点,不过add()方法默认尾插法,因此访问每一层时需要调整顺序(或者使用add(int index,E element)头插)。

 			while(!queue.isEmpty()) // LinkedList<TreeNode> queue=new LinkedList<>();
            {
                List<Integer> path=new ArrayList<>();
                int size=queue.size();
                for(int i=size-1;i>=0;i--)
                {
                    TreeNode node=queue.get(i);
                    path.add(node.val);
                    if(right==1)
                    {                       
                        if(node.left!=null)
                        queue.add(node.left);
                        if(node.right!=null)
                        queue.add(node.right);
                    }
                    else
                    {                       
                        if(node.right!=null)
                        queue.add(node.right);
                        if(node.left!=null)
                        queue.add(node.left);
                    }
                    queue.remove(i);
                }
                res.add(path);
                right=0-right;
            }

236.二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5

  1. 一开始的想法是通过二叉树顺序存储的下标找到最近公共祖先。分为两个操作,首先需要遍历整个二叉树找到p和q对应的索引下标,再根据pnum=(pnum-1)/2得到父节点索引下标,直至pnum=qnum就得到最近的公共祖先的索引下标。但是不知道是我写的有问题,还是题目给的一个例子有问题(感觉是用例bug),[-1,0,null,1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null…]这个用例没能通过。(这用例给的是棵什么鬼树???还是说它想表达的是一颗链状树?)。总之要点在于:
  • left=pnum*2+1; right=pnum*2+2; parent=(pnum-1)/2
  1. 另一种思路是用哈希表记录当前节点值和父节点的关系HashMap<Integer p.val,TreeNode p.parent >;再用一个队列存放p的父节点,别忘记要先把p自身放入队列中。当list.contains(hashmap.get(q.val))时则找到最近公共祖先。

  2. 最优的方法是后序遍历。后序遍历的特点是先递归后处理,天然的自底向上二叉树回溯,最先处理的一定是叶子节点,写法如下:

    • 确定终止退出条件
    • 递归调用
    • 处理节点

    一发现一个节点左子树有p,右子树有q节点,那么这个节点就是最近的公共祖先,因为后序遍历是从底向上找,所以找到的一定是最深的。难点的在于当左子树和右子树一边找到p另一边什么都没找到返回null时,这时候函数直接返回找到的p或者q节点,这个节点依旧具有深度最深的特性

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
                if(root==null)
                return null;
                if(root==p||root==q)
                return root;
                TreeNode left=lowestCommonAncestor(root.left,p,q);
                TreeNode right=lowestCommonAncestor(root.right,p,q);
                if(left!=null&&right!=null)
                return root;
                if(left==null&&right==null)
                return null;
                return (right!=null)?right:left;
    
        }
    

20.有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合

输入:s = “(]”
输出:false

  1. 简单题。用栈保存访问状态。左括号入栈,匹配出栈。

5.最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

  1. 双for循环,外层for循环固定回文子串的中心,内层for循环从中心向左右两侧扩展直。需要注意的是要分两种情况,第一种中心只有一个字符(回文子串长度为奇数),第二种情况中心有两个字符(回文子串长度为偶数)。
  2. 动规。dp[i][j]表示下标i到j是否是回文子串,状态转移方程是:if(s[i]=s[j]&&dp[i+1][j-1]==1) dp[i][j]=1;需要注意的一个地方是要求出dp[i][j]需要提前求出dp[i+1][j-1]的值,而后者在前者的左下方,因此遍历顺序是从下到上,从左到右。而且只需要求出dp的上三角矩阵。

33.搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。给你旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1。

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

  1. 一开始想的是先找到旋转点,再二分查找,这样子时间是O(n),时间浪费在找旋转点上。
  2. 比较难想的方法是直接二分,数组劈成两半一定有一部分是有序的,基于这个特点可以这样做:每次都要判断目标在哪一部分,在有序的部分则用二分查找,在无序的部分则再一分为二,继续调用调用本算法。判断是否是有序的关键在于,用第一个元素和mid进行比较,比mid小则前半部分有序,否则后半部分有序(因为第一个元素肯定会比旋转到后面的子数组每个元素都大)


今日总结

刷题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值