public static void preOrderUnRecur(Node head) { //1先将头结点压入栈 //2 弹出栈顶元素,再将该节点的左右节点压入栈(先右后左) //3 不断重复2 ,直到stack为空 if (head == null) return; Stack<Node> stack = new Stack<>(); stack.push(head); while (!stack.isEmpty()) { Node cur = stack.pop(); System.out.println(cur.data + " "); if (cur.right != null) { stack.push(cur.right); } if (cur.left != null) { stack.push(cur.left); } } }
public static void midOrderUnrecur(Node head) { //1 申请一个栈 cur=head // 2 将栈左子树依次压入 cur=cur.left // 不断重复2,直到cur==null ,弹出node 并打印,并cur=node.right ,继续重复 if (head == null) return; Stack<Node> stack = new Stack<>(); stack.push(head); while (!stack.isEmpty() || head != null) { if (head!= null) { stack.push(head); head = head.left; }else{ Node node=stack.pop(); System.out.println(node.data); head=node.right; } } }
使用递归方式 不理解
TreeNode prev =null;// 中序遍历这棵树,并保存前驱节点至prev中
//使用递归
public boolean isValidBST(TreeNode root){
if(root != null){
if(!isValidBST(root.left))
return false;
if(prev!=null && root.val<=prev.val){
return false;
}
prev = root;
return isValidBST(root.right);
}
return true;
}1 使用递归方式
2 使用层序遍历方式 层序遍历使用了队列
/* *@description:将二叉树转换为字符串 * # 表示节点为null ,每个节点以 '!' 分割 *@params:[head] *@return:java.lang.String *@author:Mao *@date:2019/4/12 8:46 **/ public static String seriaByPre(Node head) { if (head == null) return "#!"; String res = head.data + "!"; res += seriaByPre(head.left); res += seriaByPre(head.right); return res; } /* *@description:反序列化 将str转换为二叉树 *@params:[str] *@return:com.mao.tree.Node *@author:Mao *@date:2019/4/12 8:53 **/ public static Node reconByPre(String str) { if (str == null) return null; //将str 按照'!'分割成数组 String[] strs = str.split("!"); Queue<String> queue = new LinkedList<>(); for (int i = 0; i < strs.length; i++) { queue.offer(strs[i]); } return reconByPreOrder(queue); } /* *@description:前序递归 *@params:[strs] *@return:com.mao.tree.Node *@author:Mao *@date:2019/4/12 8:59 **/ private static Node reconByPreOrder(Queue<String> queue) { String value = queue.poll(); if ("#".equals(value)) { return null; } Node node = new Node(Integer.valueOf(value)); // 将值与节点的左右子树相连接 node.left = reconByPreOrder(queue); node.right = reconByPreOrder(queue); return node; }
//层序遍历 public static String seriaByLevel(Node node) { if (node == null) return "#!"; String res = node.data + "!"; Queue<Node> queue = new LinkedList<Node>(); queue.offer(node); while (!queue.isEmpty()) { Node head = queue.poll(); if (head.left != null) { res += head.left.data + "!"; queue.offer(head.left); } else { res += "#!"; } if (head.right != null) { res += head.right.data + "!"; queue.offer(head.right); } else { res += "#!"; } } return res; } /* *@description:层序遍历反序列化 *@params:[args] *@return:void *@author:Mao *@date:2019/4/12 9:55 **/ public static Node reconByLevel(String str) { if (str == null) return null; String[] strs = str.split("!"); int index = 0; Node head = generatedNode(strs[index++]); Queue<Node> queue = new LinkedList<>(); queue.offer(head); Node node = null; while (!queue.isEmpty()&& index!=strs.length) { node = queue.poll(); node.left = generatedNode(strs[index++]); node.right = generatedNode(strs[index++]); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } return head; } private static Node generatedNode(String str) { if ("#".equals(str)) { return null; } return new Node(Integer.valueOf(str)); }
注意 :需检查是否为null
1 先进行 t1树的前序遍历
2 在与t1 和t2 的每个节点进行比较
public static boolean contains(Node t1,Node t2){ //需检查t1 是否为null 为null则停止 if(t1==null){ return false; } return check(t1,t2)|| contains(t1.left,t2)|| contains(t1.right,t2); } private static boolean check(Node tt1, Node tt2) { if(tt2==null){ return true; } if(tt1==null || tt2.data!=tt1.data){ return false; } return check(tt1.left,tt2.left) && check(tt1.right,tt2.right); }
- 难度一星
采用递归方式实现
public Node generateTree(int[] sortArr) { if (sortArr == null) return null; return generate(sortArr, 0, sortArr.length - 1); } private Node generate(int[] sortArr, int start, int end) { if (start > end) return null; int mid = (start + end) / 2; Node head = new Node(sortArr[mid]); head.left = generate(sortArr, start, mid - 1); head.right = generate(sortArr, mid + 1, end); return head; }
- 思路一:
- 时间复杂度O(n) 空间复杂度 O(n)
- 1 先找到根结点
- 2 根据根节点就行中序遍历
- 3 遍历结果即为节点的后一个节点
- 找到两个节点最近的祖先
- 思路:
- 采用后序遍历
- 1 如果cur 节点下 左右子树,均无法找到o1 或o2,则返回null
- 2 若cur 节点,左右子树能找到,则返回cur
- 3 如果有一个子树返回为null,另一个子树找到,假设不为空的记为node,则存在两种情况
- 1)则node该节点是O1 或 O2
- 2)node是最近的公共祖先
- 例如遍历节点1 ,发现node节点为3 则 3是o1和o2的父节点
- 时间复杂度: O(n) p172
- 在中序数组找位置i过程可以用hash表来实现,复杂度为O(n)
- 思路:
- 1 先序数组最左边为树的头结点h,中序找到h,假设位置i。则中序
- 数组中,i左边数组就是的头结点左子树的中序数组,长度L,则左子树
- 的先序数组就是先序数组的h+L
- 2 用左子树先序和中序,递归整个过程建立左子树left
- 3 用右子树的的先序和中序数组,递归右子树right
- 4 把head左右孩子设为lef 和 right
public Node preInToTree(int[] preArr,int[] midArr){ if(preArr==null || midArr==null){ return null; } //使用HashMap将midArr数组存储 HashMap<Integer,Integer> map=new HashMap<>(); for (int i=0;i<midArr.length;i++){ map.put(midArr[i],i); } return preTree(preArr,0,preArr.length-1,midArr,0,midArr.length-1,map); } private Node preTree(int[] preArr, int preStart, int preEnd, int[] midArr, int midStart, int midEnd, Map<Integer, Integer> map) { if (preStart> preEnd){ return null; } Node head=new Node(preArr[preStart]); int index=map.get(preArr[preStart]); head.left=preTree(preArr,preStart+1,preStart+midEnd-index,midArr,midStart,index-1,map); head.right=preTree(preArr,preStart+index-midStart+1,midEnd,midArr,index+1,midEnd,map); return head; }
public Node inPosToTree(int[] mid,int[] pos){ if(mid==null || pos==null){ return null; } HashMap<Integer,Integer> map=new HashMap<>(); for (int i=0;i<mid.length;i++){ map.put(mid[i],i); } return posToTree(pos,0,pos.length-1,mid,0,mid.length-1,map); } private Node posToTree(int[] pos, int posStart, int posEnd, int[] mid, int midStart, int midEnd, HashMap<Integer, Integer> map) { if(posStart>posEnd){ return null; } int arr=pos[posEnd]; int index=map.get(arr); Node node=new Node(arr); node.left=posToTree(pos,posStart,posStart+index-midStart-1,mid,midStart,index-1,map); node.right=posToTree(pos,posStart+index-midStart,posEnd-1,mid,index+1,midEnd,map); return node; } public static void main(String[] args) { int[] pos=new int[]{4,5,2,6,7,3,1}; int[] in=new int[]{4,2,5,1,6,3,7}; InPosToTree test=new InPosToTree(); Node head=test.inPosToTree(in,pos); test.pre(head); } public void pre(Node head){ if (head==null) return; System.out.print(head.data); pre(head.left); pre(head.right); }
-
时间复杂度: O(n)
-
思路:
-
1 后序数组的最右边为树的头结点h,中序找到h,假设位置为i。
-
则中序数组中,i 左边数组就是头结点左子树的中序数组,长度为L。
-
则左子树的后序数组就是后序数组的h-L
-
2 用左子树的后序和中序,递归建立左子树left
-
3 用右子树的后序和中序,递归建立右子树right
-
4 把head和左右子树设为left和right
public class GetPosArray { // 根据先序和中序 设置后序数组的最右边的值, // 然后根据先序和中序的右子树设置后序数组, // 左子树同样 public int[] getPostArray(int[] pre,int[] mid){ if(pre==null||mid==null){ return null; } int len=pre.length; int[] pos=new int[len]; HashMap<Integer,Integer> map=new HashMap<>(); for (int i=0;i<mid.length;i++){ map.put(mid[i],i); } setPos(pre,0,pre.length-1,mid,0,mid.length-1,pos,pos.length-1,map); return pos; } //从右往左依次填写数组pos //posEnd为后序数组该填的位置 //返回值为该填的位置 private int setPos(int[] pre, int preStart, int preEnd, int[] mid, int midStart, int midEnd, int[] pos, int posEnd, HashMap<Integer, Integer> map) { if(preStart>preEnd){ return posEnd; } pos[posEnd--]=pre[preStart]; int index=map.get(pre[preStart]); posEnd= setPos(pre,preStart+index-midStart+1,preEnd,mid,index+1,midEnd,pos,posEnd,map); return setPos(pre,preStart+1,preStart+index-midStart,mid,midStart,index-1,pos,posEnd,map); } public static void main(String[] args) { int[] pre=new int[]{1,2,4,5,3,6,7}; int[] in=new int[]{4,2,5,1,6,3,7}; GetPosArray arr=new GetPosArray(); int[] pos=arr.getPostArray(pre,in); for (int i=0;i<pos.length;i++){ System.out.println(pos[i]); } } }
要求:不要重建整个树,直接生成后序数组
- 1 根据当前的先序和中序,设置后序数组的最右边值
- 2 划分出左子树的先序、中序数组;
- 右子树的先序、中序数组。
- 3 根据右子树划分设置好后序数组,再根据左子树划分
判断是否为平衡二叉树 左右子树高度差不大于1
//boolean res 相当于全局变量 /* 当不满足时立刻返回 if(!res[0]) { return level; } */ public static boolean isBanlance(Node head){ boolean[] res=new boolean[1]; res[0]=true; System.out.println("height:"+getHeight(head,1,res)); return res[0]; } private static int getHeight(Node head, int level, boolean[] res) { if(head==null) return level; int lh= getHeight(head.left,level+1,res); if(!res[0]){ return level; } int rh=getHeight(head.right,level+1,res); if(!res[0]) return level; if(Math.abs(lh-rh)>1){ res[0]=false; } return Math.max(lh,rh); } public static void main(String[] args) { String str = "1!2!#!4!#!#!#!"; Node head = reconByLevel(str); System.out.println( isBanlance(head)); }
-
根据数组 判断是否为二叉搜索树
**性质:**依据二叉树后序遍历性质 头结点一定是数组的最后一个元素
根据平衡二叉树的性质 比头结点大的数组一定在二叉树的右边,
比头结点小的在二叉树的左边
思路:
1 声明less 和 more 表示 左右子树的右边界 和 左边界
2 从start 到 end 开始遍历 找到less 和more
3 如果 less==-1 或者 more==end 说明 只存在左子树或右子树
4 若 less==more-1 说明当前左右子树符号条件 分别判断左子树和右子树内部是否满足
5 终止条件 当 起始位置和终止位置 相等时 则证明符号条件
public class IsPostArray { public boolean isPostArray(int[] arrs){ if(arrs==null ||arrs.length==0) return false; return isPost( arrs, 0 , arrs.length-1); } private boolean isPost(int[] arrs, int start, int end) { // 整个递归的终止条件 if(start==end){ return true; } int less=-1; //找到左子树右边界临界点 int more=end; //找到右子树最左边 //找到左子树的右边界,右子树的左边界 for(int i=start;i<end;i++){ if(arrs[end]>arrs[i]){ less=i; }else { //找到第一个比根节点大的索引 more=more==end?i:more; } } //只存在左子树或只存在右子树 if(less==-1 || more==end){ return isPost(arrs,start,end-1); } //若左边界和右边界-1 相等 则继续遍历,不等直接返回false if(less!=more-1){ return false; } //进行左子树和右子树的遍历 return isPost(arrs,start,less)&& isPost(arrs,more,end-1); } }
public Node postArrayToBST(int[] arrs){ if(arrs==null) return null; return posToBST(arrs,0,arrs.length-1); } private Node posToBST(int[] arrs, int start, int end) { if(start>end) return null; int less=-1; int more=end; Node node=new Node(arrs[end]); for (int i=0;i<arrs.length;i++){ if(arrs[end]>arrs[i]){ less=i; }else { more=more==end?i:more; } } node.left=posToBST(arrs,start,less); node.right=posToBST(arrs,more,end); return node; }
完全二叉树性质 每层节点都得填满 ,运行最后一层右节点为null
思路:
-
1 按照层序遍历
-
2 若当前节点有右子树,无左子树 则 返回false
-
3 若当前节点并不是左右子树都有(有左子树无右子树, 均为左右子树),则后续节点必须为叶子节点
public boolean isCBT(Node head){ if(head==null) return false; Queue<Node> queue=new LinkedList<>(); boolean leaf=true; Node left=null; Node right=null; queue.offer(head); while(!queue.isEmpty()){ head=queue.poll(); left=head.left; right=head.right; if(leaf &&(left!=null || right!=null) || (left==null&&right!=null)){ return false; } if(left!=null){ queue.offer(left); } if(right!=null){ queue.offer(right); }else{ leaf=true; } } return true; }
leetcode 98
思路:进行中序遍历 则搜索二叉树节点值为升序
注意: 搜索二叉树的性质 BST
非递归的中序遍历
public boolean isValidBST(TreeNode root) { Stack<TreeNode> stack=new Stack(); double inOrder=-Double.MAX_VALUE; while(!stack.isEmpty() || root!=null){ while(root!=null){ stack.push(root); root=root.left; } root=stack.pop(); if(root.val<=inOrder){ return false; } inOrder=root.val; root=root.right; } return true; }
public boolean isValidBST(TreeNode root) { Stack<TreeNode> stack=new Stack(); double inOrder=-Double.MAX_VALUE; while(!stack.isEmpty() || root!=null){ if(root!=null){ stack.push(root); root=root.left; }else{ root=stack.pop(); if(root.val<=inOrder){ return false; } inOrder=root.val; root=root.right; } } return true; }
思路 : 数组的中间数为二叉树的头结点 ,左侧 为左子树 右侧为右子树
public class GenerateBSTByOrderArr { public static Node generateBST(int[] arr){ if(arr==null){ return null; } return getBst(arr,0,arr.length-1); } /* *@description: 使用递归生成平衡搜索二叉树 *@params:[arr, i, i1] *@return:com.mao.tree.Node *@author:Mao *@date:2019/4/23 8:31 **/ private static Node getBst(int[] arr, int start, int end) { if(start> end){ return null; } int mid=(start+end)/2; Node node=new Node(arr[mid]); node.left=getBst(arr,start,mid-1); node.right=getBst(arr,mid+1,end); return node; }
不懂
使用后序遍历
//使用后序遍历 /* 1 / \ 2 3 / \ / \ 4 5 6 7 / 8 */ public Node lowestAncestors(Node head,Node o1,Node o2){ if(head==null || head==o1 || head==o2){ return head; } Node left=lowestAncestors(head.left,o1,o2); Node right=lowestAncestors(head.right,o1,o2); if(left!=null && right!=null){ return head; } //若其中有一个为null 不为null记为node //说明node要么是o1 要么是o2 //或者 是o1 和o2的最近祖先 return left!=null?left:right; }
若两个字符串str1和str2,如果str1和str2出现字符种类一样且每种字符出现次数一样,
则str1和str2互为变形词。
如 str1=123 str2=132 返回true
str1=123 str2=1223 返回false
思路:
- 字符串转化为char[] ,使用 toCharArray();
- 假设字符串编码在 0到255范围内,用来统计每个字符出现的次数,记为map
- 通过str1确定出现次数
- str2来减少map内字符出现次数,若出现小于0,则不匹配
要求:1 忽略小数点
2 若左侧出现‘-’,当连续为奇数时,则数字为负,相反则为偶数
例:str="ab-----1111223a------1111c34"
思路要点:
- 1 采用3个变量 res 结果 num 当前数字 isN 是否为正负,cur表示当前值
- 2 连续数字 num=num*10+( isN ? cur : -cur)
- 3 如何判断正负 ,判断 “-” 之前的是否为 “-”
如 str=“AB00DF” k=2 str="ABDF"
注意:
1 记录首次出现位置 start=start==-1 ? i : start
若为-1 ,表示重新开始记录0出现的首次位置
若不为-1,则一直保持首次出现位置
2 ASCII NULL值 是 0
思路要点:
- 记录0出现的次数,以及首次出现位置
- 当当前字符非0时,检查0出现的次数是否满足
- 字符串最后一个未检查,需遍历完后检查最后是否满足
- 将两个a拼接起来成A,则A的子串就包含旋转词
- 复杂度若为O(n), 则需要kmp算法
- 使用 jdk 自带的 str.indexOf() , 返回某个指定的字符串值在字符串中首次出现的位置,不是则返回-1
给定str form to三个字符串,把str中所有from的自串全部替换成to字符串,
对连续出现的 from部分要求只替换成一个to字符串,返回最终的结果。
思路:
- 1 将str包含to中的替换为ascii中的0值
- 2 将非0的字符串开始合并
具体过程:
1 遍历,判断str的位置和chaf位置是否相等
1.1 若相等,继续遍历from后继位置,判断from的长度与匹配长度一致
1.1.1 若长度相等则替换为0
1.2 若字符不配,则更新match,重新匹配
2 替换后的str进行转换,拼接
2.1 非0直接拼接
2.2 若为0,则且前一个位置为非0
5.1 补充问题:
给定字符串统计字符串,以及一个整数index,返回cstr的原始字符串上的第index个字符
如 a_1_b_100 第0个位啊 第50位b。
tip:
1 获得连续出现字母数量 num = num * 10 + strs[i] - '0'
2 切换连续字母状态 stage=!stage
3 最后一个需单独计算
使用hashSet 或者数组 boolean[] iscontain=new boolean[255];
char[] 数组中的空格字符,替换为%20
1 统计非空格字符和字符长度
2 从后往前遍历,将空格字符替换为 “%20”
时间复杂度: 0(N) 空间复杂度o(1)
举例:dog love pig 反转 pig love dog
思路:先将字符整体反转,然后将每个单词反转
提示:
start = i == 0 || chars[i - 1] == ' ' ? i : start; end = i == chars.length - 1 || chars[i + 1] == ' ' ? i : end;
给定一个字符串类型的数组chars 和一个整数size,请把大小为size的半区整体移到右边区,右半区整体移到左边
思路:先反转部分,在整体反转
题目:给定一个字符串str,判断是不是有整体有效的括号字符串
思路:1 从左到右依次遍历字符串,判断每一个字符是不是‘(’ 或‘)’,不是直接返回false
2 依次遍历每一个,检查'(' 和 ')' 的数量,如果‘)’更多则直接返回
3 遍历后检查检查'(' 和 ')' 的数量
思路2: 使用动态规划
ASCII 大写字母 65-90
小写字母 97-122
题目: 指出新的字符, 小写字母 一个 大写字母 两个 包括 AA Ab型
思路1:从前遍历,遍历时,划分
思路2 :从k位置向前遍历,并记录大写字母个数num,直到遇到小写字母停止
-
若num为奇数,则为 k-1,k -
若num为偶数,且k为大写字母,则 (K,K+1) -
且K为小写字母,则为 k
Hash函数概念介绍:(4个性质)
位运算
如果给定整数a和b,则可以如下设置:
a=a^b; 记录了a和b整数位信息的所有不同信息
b=a^b;
a=a^b;
题目:给定两个32位整数a和b,返回a和b中较大的
要求:不要做任何比较判断
思路:直到起点 (tl,tr) 和终点 (dl,dr) tl++ tr++ dl-- dr--
矩阵颠倒
保证对角线打印 分为上下两个坐标,分别移动 p335
- 需要排序的最短子数组长度
- 从右向左 找出最小的位置minIndex
- 从左向右 记录最大值,以及maxIndex
- 范围为(minIndex,maxIndex)
- 思路:从右上角开始找 目标k
- 若当前值小于K 则行row+1
- 若当前值大于K,则列col-1
arr=[1,-2,3,5,-2,6,-1] ,则 [3,5,-2,6]累加最大值为12
累加称为负数清零,重新累加
- 思路一:
- 使用除法,重点在零的个数
- 当非零,则除当前数
- 当一个零,则,非零位置,均为零,零位置为其他乘积
- 当大于一个零,则均为零
思路二:
不使用除法,分为左右两个组累乘 lr[] 和rl[]
若不能申请额外空间,则复用res[]
分为左区域无重复区域 右侧重复区域
当当前值不等于左侧区域最后一个数时,将当前值放大左侧区域
还有很多变种问题
如: 给定一个数组arr,其中包含0,1,2 实现arr排序
有一个数组,其中有红、蓝黄 ,红色左边蓝色中间黄色右边
put get isContain setAll 要求时间复杂度都为O(1)
需要考虑泛型的使用方法,泛型类和泛型方法 (泛型数组?)
思路,设计时间戳
题目: 给定一个长度为N且没有重复元素的数组arr和一个整数n,实现函数等概论随机打印arr中的M个数 时间复杂度o(m) 空间复杂度 O(1) 思路一:使用hash表来记录是否打印 思路二:与最后一个元素位置交换
给定一个32位整数,判断是否是回文数
如 121 22 -121 -22
注意:32位整数最小值为 -2147483648 绝对值会产生溢出
"/" 获得高位 "%"获得低位
- 题目: 实现一个特殊的栈,在栈的基础功能上,返回栈中最小元素
- 思路:使用两个栈,一个用来存储数据,一个用来存储当前最小值
- 需要考虑栈入栈和出栈规则
- 1 入栈 stack_min 中与栈顶比较,若比当前小或者相等则入栈,否则不变
- 2 出栈时,stackMin元素总是小于等stackData栈顶元素 相等则都出,min小于data则只出data
- add
- pollAll
- pollDog
- pollCat
- isEmpty
- 思路: 使用时间戳 添加PetEnter类 最后在CatDogQueue内实现
题目: 一个栈中元素的类型为整型,现将该栈从顶到底按从大到小顺序排序,只允许申请一个额外栈。可申请额外变量,但不能申请额外数据结构,如何排序。
思路:排序栈为stack 申请辅助栈为help stack上执行pop,弹出元素为pop
1 若cur小于 或等于 help的栈顶元素,直接压入help
2 若大于 则将help元素逐一弹出,逐一压入stack,直到cur <= help 的栈顶元素
while (!s1.isEmpty()) { int tmp = s1.pop(); // 此处不能有等于 会无限循环 while (!s2.isEmpty() && tmp < s2.peek()) { s1.push(s2.pop()); } s2.push(tmp); }
题目:给定两个有序链表的头指针head1和head2 ,打印公共部分
思路 head1 <head2 head1 往下移动 同理
注意:加强如何构建链表,如何打印链表,以及打印完链表指针后移的要素
原因:没有考虑边界问题 删除时可以利用之前的K
- 边界问题 删除倒数K节点是否超出数组范围,以及头结点和其他节点的不同的删除方式
- 思路: 每移动一次k就减一,根据K是否大于0来判断
- K>0 证明 超出范围
- k=0 恰好头结点,删除头结点
- k<0 则正常情况,删除该节点需获取前一个节点
public static Node removeLastKthNode(Node head, int k,int length) { if (head == null) { return head; } Node cur = head; int k2=k; while (cur != null) { cur = cur.next; k--; } Node cur2 = head; if (k < 0) { int tmp=k2-length; //直接使用K while(++k!=o) while (tmp++ != 0) { cur2 = cur2.next; } Node next = cur.next; cur2.next = next.next; next.next = null; return head; } else if (k == 0) { head = head.next; return head; } else { return head; }
-
进阶问题 a/b处的节点(后序完成)
-
原因:如何删除前一个节点 让h2开始就从头节点移动两次(比h1多移动一次) 此时h1就是前一个节点
-
思路:申请两个节点h1,h2 h1每次只移动一次,h2移动两次
-
当h2的下一次为null 则返回当前h1
public static Node removeMiddleNode(Node node) { if (node == null) { return node; } if (node.next.next == null) { return node.next; } Node pre = node; Node cur = node.next.next; while (cur.next != null && cur.next.next != null) { pre = pre.next; cur = cur.next.next; } pre.next = pre.next.next; return node; }
注意:还可以使用递归方式
https://blog.csdn.net/wxm349810930/article/details/46724691
思路:将头结点从链表拆除,然后对剩余节点逆序,最后将表头节点连接到新链表尾部
4.1 反转单向链表
-
思路:声明两个节点next和pre
-
next 保存头结点后面的节点
-
pre 保存前一个节点
-
head.next=pre //此事head指向pre 已经和原先节点断链
-
pre=head //更新pre节点
-
head=next //将保存后序节点在重新给head
-
最后pre就是头结点head此时为空
-
public static Node reverse(Node head) { Node next = null; Node pre = null; while (head != null) { next = head.next;// 保存头结点后面的节点 head.next = pre; // 此时head指向pre 已经和原先节点断链 pre = head; head = next;// 将保存后序节点在重新给head } // 返回pre而不是head? return pre; }
4.2 反转双向链表
public static DoubleNode reverseDouble(DoubleNode node) { DoubleNode next = null; DoubleNode pre = null; while (node != null) { next = node.next; node.next = pre; node.pre = next; pre = node; node = next; } return pre;
难点:如何获得要反转部分的前一个及最后一个,以及如何正确连接节点
思路:
1 先获得要反转节点from的前一个 preFrom 和反转to的后一个postTo
2 先将反转的第from位置节点和postTo连接 (注意此时断链,需要声明一个节点保存后序信息)
3 反转部分节点
4 若preFrom为null 则是反转了头结点,若反转不为空,返回新节点,
(此时head节点已不是头结点 而是指向了postTo位置节点
public static Node partReverse(Node head, int from, int to) { // 找到要反转节点的前一个节点,且不是头结点 int i = 0; Node h1 = head; Node preFrom = null; Node postTo = null; // 获得链表from前一个preFrom.to的后一个postTo while (h1 != null) { i++; preFrom = i == from - 1 ? h1 : preFrom; postTo = i == to + 1 ? h1 : postTo; h1 = h1.next; } //拿到要反转的第一个节点 h1 = preFrom == null ? head : preFrom.next; //连接翻转后的尾节点 Node h2 = h1.next; h1.next = postTo; // 反转节点 Node next = null; int j = to - from; while (h2 != postTo) { next = h2.next; h2.next = h1; h1 = h2; h2 = next; } // h1.next = pre; if (preFrom != null) { preFrom.next = h1; // 返回旧节点 return head; } // 返回新节点 return h1; }
注意:掌握循环链表,如何找到最后一个节点 注意循环链表的判断条件
-
单向环形链表
-
每m个就删除当前节点,并重新计数
-
直到剩下最后一个
public static Node jsoepuseKill(Node head, int m) { if (head == null || head.next == head || m < 1) return head; // 找到last节点 Node last = head; while (last.next != head) { last = last.next; } int count = 0; while (head != last) { if (++count == m) { last.next = head.next; count = 0; } else { last = last.next; } head = head.next; } return head; }
-
注意:思路二中移动时 Node cur = head.next;
在移动后cur就是右链的第一个节点(这块出错)
-
思路1 :使用栈(没想到),入栈顺序和出栈顺序一致
-
思路2:同样使用栈,只讲链表的一半压入栈,比较左链和右链是否相等
需要进行奇数偶数判断。
注意: 1 需考虑当两个栈都为空,且还存在进位时
2 如何更新节点
3 相加时精简写法
val1=s1.isEmpty()?0:s1.pop(); val2=2.isEmpty()?0:s2.pop(); val3=val1+val2+cal; cal=val3/10; //进位 pre=cur; cur=new Node(cal%10); cur.next=pre;
pre = cur; cur = new Node(val3); cur.next = pre;
- 思路1:加完在生成链表 ,有可能相加时会存在溢出
- 思路2:利用栈,需考虑进位
public static Node addList1(Node head1, Node head2) { if (head1 == null || head2 == null) { return head1 == null ? head1 : head2; } Stack<Integer> s1 = new Stack<Integer>(); Stack<Integer> s2 = new Stack<Integer>(); while (head1 != null || head2 != null) { if (head1 != null) { s1.push(head1.data); head1 = head1.next; } if (head2 != null) { s2.push(head2.data); head2 = head2.next; } } Node pre = null; Node cur = null; int val1 = 0; int val2 = 0; int cal = 0; while (!s1.isEmpty() || !s2.isEmpty()) { if(!s1.isEmpty()){ val1 = s1.pop(); } else { val1 = 0; } if(!s2.isEmpty()){ val2=s2.pop(); } else { val2 = 0; } int val3 = val1 + val2 + cal; if (val3 >= 10) { cal = 1; val3 = val3 % 10; } else { cal = 0; } pre = cur; cur = new Node(val3); cur.next = pre; } // 当两个栈都为空,且还存在进位时 if (cal == 1) { Node n = new Node(cal); n.next = cur; return n; } return cur; }
- 难点:思路1中推荐使用hashset
- 思路1 使用hashMap 时间复杂度O(n) 空间复杂度O(n)
- 思路2 使用选择排序 时间复杂度O(n2) 空间复杂度O(1)
public static Node removeRepeat(Node head) { if(head==null) return null; HashMap<Integer, Integer> map=new HashMap<Integer, Integer>(); map.put(head.data, 1); Node n1 = head; Node pre = head; Node cur = head.next; while (cur != null) { if (map.containsKey(cur.data)) { pre.next = cur.next; cur = cur.next; } else { map.put(cur.data, 1); pre = cur; cur = cur.next; } } return n1; }
难点 :思路二连接链表存在更简单做法
stack.peek().next=head; head=stack.pop();
- 思路1 直接删除 时间复杂度O(N) 空间复杂度O(1)
- 思路2 借助容器 将值不等的节点收集起来然后重新连接(没想到)
//直接删除 public static Node removeSpecialValue(Node head, int num) { if(head==null) return head; Node h = head; Node pre = head; Node cur = head.next; while (cur != null) { if (cur.data == num) { pre.next = cur.next; } else { pre = pre.next; } cur = cur.next; } return h; }
//使用栈 public static Node removeSpecialValueByStack(Node head, int num) { Stack<Node> s = new Stack<Node>(); while (head != null) { if (head.data != num) { s.push(head); } head = head.next; } Node pre = null; Node cur = null; while (!s.isEmpty()) { cur = s.pop(); cur.next = pre; pre = cur; } return cur; }
要求:额外空间复杂度O(1)
选择排序要点:从未排序的部分中找到最小值,然后放在排好序的尾部
- 获取最小节点的前一个节点
// 获得最小节点的前一个节点 private static Node getSmallPre(Node head) { Node smallPre = null; Node small = head; Node pre = head; Node cur = head.next; while (cur != null) { if(cur.data<pre.data){ smallPre=pre; small = cur; } pre = cur; cur = cur.next; } return smallPre; }
-
使用链表进行选择排序
public static Node selectionSort(Node head) { if (head == null) { return null; } Node tail = null; //排序部分的尾部 Node cur=head; //未排序的尾部 Node smallPre = null; // 最小节点的前一个节点 Node small=null; //最小节点 while (cur != null) { small = cur; // 找到最小节点 smallPre = getSmallPre(cur); if (smallPre != null) { small = smallPre.next; smallPre.next = small.next; } // cur==small 则cur节点就是最小节点,然后继续遍历 // cur!=small 则cur节点不是,需继续从cur遍历找到整个未排序部分的最小节点 cur = small == cur ? cur.next : cur; if (tail == null) { tail = small; } else { tail.next = small; } tail = small; } return head; }
只给定一个节点node ,不给的头结点 要求删除该节点
当该节点为null,不能使用复制方式,无法删除
public static void removeNodeWired(Node node) { if (node == null) { return; } if (node.next == null) { throw new RuntimeException("无法删除"); } //把node.next的节点复制给node 删除node.next node.data = node.next.data; node.next = node.next.next; }
- 特殊情况 当num 遍历完后 ,没有找到插入的地方,会有两种情况
1 该节点比头结点小
2该节点比头结点大
- 在环形链表中遍历的 cur !=head
- pre=head cur=head.next;
//经典实现 public static Node insertByCircle2(Node head, Node num) { if (head == null) { num.next = num; return num; } Node pre = head; Node cur = head.next; while (cur != head) { if (pre.data <= num.data && num.data <= cur.data) { break; } pre = cur; cur = cur.next; } pre.next = num; num.next = cur; return head.data > num.data ? num : head; }
//自己实现 public static Node insertByCircle(Node head, Node num) { if (head == null) return null; Node cur = head; Node tail = null; // 获取taile节点 while (cur.next != head) { cur = cur.next; } tail = cur; cur = tail.next; while (cur != tail) { if (cur.data >= num.data && cur.next.data <= num.data) { //插入节点 Node next=cur.next; cur.next=num; num.next=next.next; } cur = cur.next; } if (num.data < head.data ) { tail.next = num; num.next = head; head = num; } if (num.data > tail.data) { tail.next=num; num.next=head; tail = num; } return head; }
public static Node merge(Node head1, Node head2) { if (head1 == null || head2 == null) { return head1 == null ? head2 : head1; } // 头结点为两个中最小的一个 Node head = head1.data < head2.data ? head1 : head2; Node cur1 = head == head1 ? head1 : head2; Node cur2 = head == head1 ? head2 : head1; Node pre=null; //书上 Node next=null; while (cur1 != null && cur2 != null) { if(cur1.data<cur2.data){ pre=cur1; cur1 = cur1.next; } else { Node next = cur2.next; pre.next = cur2; cur2.next = cur1; cur2 = next; pre = cur2; } } pre.next = cur1 == null ? cur2 : cur1; return head; }
思路:1首先要划分出左右半区 需要声明一个mid变量
2 组合左右两半区
public static Node relocate(Node head){ if(head==null) return null; //将链表划分左右两半区 Node mid=head; Node right=head.next; while(right.next!=null && right.next.next!=null){ mid=mid.next; right=right.next.next; } System.out.println("node:"+mid.next.data); Node cur=head; right=mid.next; // 标准写法 // mid.next=null; //while(cur.next!=null) while(cur!=mid){ Node next_left=cur.next; Node next_right=right.next; cur.next=right; right.next=next_left; right=next_right; cur=next_left; } cur.next=right; return head; }
leetcode 62
只能右爬和下爬,输入各自行数和列数,输出需要多种可能性
public class UniquePath{ public int uniquePath(int m,int n){ int[][] res=new int[m-1][n-1]; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(i==0 ||j==0){ res[i][j]=1; }else{ res[i][j]=res[i-1][j]+res[i][j-1]; } } } return res[m-1][n-1]; } }
- 使用递归方式 保存已有信息(记忆化搜索)
public class UniquePath{ public int uniquePaths(int m, int n) { int[][] memo=new int[m][n]; return dfs(m-1,n-1,memo); } public int dfs(int m,int n,int[][] memo){ if(memo[m][n]!=0){ return memo[m][n]; } if( m==0 || n==0){ return 1; } memo[m][n]=dfs(m-1,n,memo)+dfs(m,n-1,memo); return memo[m][n]; } }
leetcode 70
题目描述: 有n阶台阶 可以爬一阶,也可以爬2阶,计算有多少不同方法
与斐波那契数方法一致
Leetcode 509
- 使用递归
public int fib(int n){ int[] memo=new int[n+1]; return cal(n,memo); } private int cal(int n ,int[] memo){ if(n==0) return 0; if(n==1){ return 1; } if(memo[n]!=0){ return memo[n]; } return cal(n-1,memo)+cal(n-2,memo); } public static void main(String[] args) { Fibonacci sol=new Fibonacci(); System.out.println(sol.fib(4)); }
leetcode 121 Best Time to Buy and Sell Stock
//暴力破解 时间复杂度为O(n2) public int maxProfit(int[] prices) { int max=0; for(int i=0;i<prices.length;i++){ for (int j=i+1;j<prices.length;j++){ int tmp=prices[j]-prices[i]; if (tmp>max){ max=tmp; } } } return max; } //时间复杂度为O(n) public int maxProfit2(int[] prices){ int minPrice=Integer.MAX_VALUE; //最低价格 int maxPro=0;//最大利润 for (int i=0;i<prices.length;i++){ if(prices[i]<minPrice){ minPrice=prices[i]; }else if(prices[i]-minPrice>maxPro){ maxPro=prices[i]-minPrice; } } return maxPro; } //考虑使用动态规划?? public int maxProfit3(int[] prices) { if(prices.length == 0 || prices.length == 1) return 0; int maxProfit = 0; int min = prices[0]; for(int i = 0; i < prices.length; i++) { min = Math.min(min, prices[i]); maxProfit = Math.max(maxProfit, prices[i] - min); } return maxProfit; }