题目-求1+2+3+...+n java
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路node
因为须要用到的就是一个递归,当n>0时,将后面的数字进行一个递归的相加。当输入0时,即n==0,输出0,直接不进行递归。git
这个题的关键要求在于不能使用xxxxxx一系列语句。正则表达式
那么看起来惟一能用到的只有位运算、加减。算法
用到逻辑与的短路特征:即如($b=false)&&($a=true),当&&前面的语句不成立(false)时,&&后面的语句不会执行;以此来实现递归终止。数组
(ps)逻辑或的短路特征:如($b=true) || ($a=false),当||前面的语句成立(true)时,||后面的语句不会执行。安全
即当n>0时,执行sum+=Sum_Solution(n-1)进行递归的累加;当n==0时,执行&&前面语句的判断(n>0),为false,而后不对sum作什么修改处理,直接就是返回第一句的sum=n=0。网络
解法多线程
public class Solution { public int Sum_Solution(int n) { int sum = n; boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0); return sum; } }
题目-不用加减乘除作加法 app
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路
那么这道题的要求也是使用位运算,咱们来回顾一下经常使用的位运算。
1.移位运算:
左移<<:向左移动,右边低位补0.左移1位至关于乘以2
e.g. 3<<2:00000011 ---> 00001100(12)
无符号右移>>>:向右移动,右边舍弃,左边补0
有符号右移>>:向右移动,右边舍弃,左边补的值取决于原来的最高位。原来是1就补1,原来是0就补0.右移1位至关于除以2.
e.g. 3>>1:00000011 ---> 00000001(1)
2.逻辑运算:
与 & :两个位都为1,才为1
或 | :只有一位为1,就为1
取反 ~ :1变成0,0变成1
按位异或 ^ :相异为真,相同为假
3.应用场景:
1)判断奇偶:(1&i)==1,此时i的最低位为1,偶数;(1&j)==1,此时j的最低位为1,奇数。
解法
public class Solution { public int Add(int num1,int num2) { int sum, carry; do{ //各位相加,不计进位。二进制每位相加至关于各位作异或操做 sum = num1^num2; //记下进位。两个数各位作与运算,再左移一位。 carry = (num1 & num2)<<1; num1 = sum; num2 = carry; }while(num2!=0);//直到进位值为0 return num1; } }
题目-把字符串转换成整数
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
思路
1.这个题须要注意到的是数据的边界,可能会溢出。int范围为[-2147483648,2147483647],能够用Integer.MAX_VALUE和Integer.MIN_VALUE来表示。
2.边界条件:数据溢出;空字符串;正负号状况;非法字符
3.库函数:int atoi(const char *str );//将字符串转换为整型
atol();//将字符串转换为长整型值。
char *itoa( int value, char *string,int radix);//整数转换为字符串。value:欲转换的数据;string:目标字符串的地址;radix:转换后的进制数,能够是10进制、16进制等。
解法
public class Solution { public int StrToInt(String str) { if(str==null || str.length()==0) return 0; //判断是否为负数 boolean isMinus = str.charAt(0)=='-'; //设置为long类型,避免溢出 long ret = 0; for(int i=0; i<str.length(); i++){ char c = str.charAt(i); //符号断定 if(i==0 && (c=='+' || c=='-')) continue; //若是不是数字,非法输入 if(c<'0' || c>'9') return 0; //将字符拼接成数字 ret = ret*10+(c-'0'); } ret=isMinus ? -ret : ret; //溢出就返回0,用long类型的res来比较, //若是定义为int res,那再比较就没有意义了,int范围为[-2147483648,2147483647] if(ret>Integer.MAX_VALUE|| ret<Integer.MIN_VALUE) return 0; return (int)ret; } }
题目-数组中重复的数字
在一个长度为n的数组里的全部数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每一个数字重复几回。请找出数组中任意一个重复的数字。 例如,若是输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路
要求时间复杂度为O(N),空间复杂度为O(1)。所以不能使用排序的方法,也不能使用额外的标记数组。
那么对于这种数组元素在固定范围的问题。考虑用将值为i的元素调整到第i个位置来进行求解。
这样的话,若是当将第i个元素调整到第i位时,咱们发现某个位置i已经有这个元素i,那么就说明重复。
而且只是要求返回任意重复的一个,赋值duplication[0]
解法
public class Solution { // Parameters: // numbers: an array of integers // length: the length of array numbers // duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation; // Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++ // 这里要特别注意~返回任意重复的一个,赋值duplication[0] // Return value: true if the input is valid, and there are some duplications in the array number // otherwise false public boolean duplicate(int numbers[],int length,int [] duplication) { if(numbers==null || length==0) return false; //将值为1的元素调整到第i个位置上求解 for(int i=0; i<length; i++){ while(numbers[i]!=i){ //好比在第二个位置,已经有一个2的值了,那么就记录重复 if(numbers[i]==numbers[numbers[i]]){ duplication[0]=numbers[i]; return true; } swap(numbers, i, numbers[i]); } } return false; } private void swap(int numbers[], int i, int j){ int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
题目-构建乘积数组
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
思路
要求不能使用除法,最简单的思路固然就是连乘,可是时间复杂度为O(N^2),效率低。
那咱们将每一个B[i] 的计算式列出来,考虑B[i] 之间的联系。
列出上表,能够发现B[i] 其实不一样之处就是在于第i位是为1,那么能够将B[i]的计算以哪一位为1为分割线。
B[i]的左半部分与B[i-1]有关,即B[i]的左半部分=B[i-1]的左半部分 * A[i-1]
B[i]的右半部分与B[i+1]有关,即B[i]的右半部分=B[i+1]的右半部分 * A[i+1]
而后分别计算左右半部分,再乘起来。
解法
import java.util.ArrayList; public class Solution { public int[] multiply(int[] A) { int n = A.length; int[] B = new int[n]; B[0] = 1; //B[i]的左半部分和B[i-1]有关 for(int i=1; i<n; i++){ B[i] = B[i-1]*A[i-1]; } //temp表示右半部分的乘积 int temp=1; //B[i]的右半部分和B[i+1]有关 for(int i=n-2; i>=0; i--){ temp *= A[i+1]; B[i] *= temp; } return B; } }
题目-正则表达式匹配
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符能够出现任意次(包含0次)。 在本题中,匹配是指字符串的全部字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,可是与"aa.a"和"ab*a"均不匹配
思路
比较值得注意的就是*的处理。由于它可能表示前面的字符出现任意次(包含0次)
出现的状况可能有:
1.同时匹配到末尾,就匹配成功;
2.模式串到了末尾,可是字符串尚未到末尾。那说明字符串后面的那一部分没办法被匹配,匹配失败;
3.模式串中的下一个字符是*:
1)若是当前字符不匹配,将模式串后移两位,继续匹配。
如模式串为a*b,字符串为bbb.
后移两位是由于,若是当前不匹配,那么只能是说明*表示的是当前字符出现0次,至关于a*被忽略。那么模式串就应该跳过这两个字符,从*后面的b开始匹配字符串。
2)若是当前字符匹配 或者 模式串的当前字符为 ‘.’(因为模式中的字符'.'表示任意一个字符),那么根据*的匹配方式,那么可能出现:
如模式串为a*b,字符串为aaa.
若*表示前面字符出现0次,那么模式串后移两位,字符串不动;
若*表示前面字符出现1次,那么模式串后移两位,字符串后移一位。至关于a*当作一个总体,与字符串的第一个a,来匹配。匹配完后,就都日后移,匹配下一位。
若*表示前面字符出现屡次,那么模式串不动,字符串后移一位。至关于*也就是a,与字符串的第二个a去匹配。由于*不能表示直接来匹配,只能依据它前面依附的字符来匹配。
将这三种可能的匹配方式,来进行或处理。由于*确定是出如今这三种状况里。
4.模式串中下一个字符不是*:
1)当前字符匹配 或者 模式串的当前字符为 ‘.’ ,那么后移匹配下一字符;
2)当前字符不匹配,则匹配失败。
同时还须要在java里,还要时刻注意检查数组是否越界。(好比须要去找,字符串和模式串是否匹配到末尾。由于在匹配过程当中会有须要字串后移的操做)
解法
public class Solution { public boolean match(char[] str, char[] pattern) { //空串则返回false if(str==null || pattern==null){ return false; } //递归地匹配 return matchCore(str, 0, pattern, 0); } //设置两个指针,分别指向字符串和模式串的第一个字符 private boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex){ int m=str.length, n=pattern.length; //若是同时匹配到末尾,那么则匹配成功 if(strIndex==m && patternIndex==n){ return true; } //若是模式串先扫描到末尾,则匹配失败 if(strIndex!=m && patternIndex==n){ return false; } //若是下一个字符是"*" if(patternIndex+1<n && pattern[patternIndex+1]=='*'){ //字符串和模式串当前字符匹配 if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex] == '.')){ return matchCore(str, strIndex, pattern, patternIndex+2)|| //‘*’前的字符出现0次,那么pattern就要多走*和*前面的字符 matchCore(str, strIndex+1, pattern, patternIndex+1)|| //‘*’前的字符只出现1次,那么pattern和str都进入下一步的匹配 matchCore(str, strIndex+1, pattern, patternIndex); //‘*’前的字符出现屡次 } else{ //若是当前字符串不匹配,则认为‘*’前的字符出现0次 //由于只有模式串当前字符不出现时,才可能字符串当前字符和模式串下一字符匹配 return matchCore(str, strIndex, pattern, patternIndex+2); } } //若是下一个字符不是'*' //当前字符匹配 if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex]=='.')){ return matchCore(str, strIndex+1, pattern, patternIndex+1); } else{//当前字符不匹配 return false; } } }
题目-表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 可是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路
这里是使用正则匹配。给出一些经常使用的正则表示:
[xyz]:字符集,匹配括号中包含的任一字符。
[^xyz]:反向字符集,匹配括号中未包含的任一字符。
?:零次或一次匹配前面的字符或子表达式。
+:一次或屡次匹配前面的字符或子表达式。
*:零次或屡次匹配前面的字符或子表达式。
^:匹配输入字符串开始的位置。
$:匹配输入字符串结尾的位置。
\:将下一字符标记为特殊字符、文本、反向引用或八进制转义符。
\d:数字字符匹配,[0-9]。(可是java中\是转移字符前导符,所以在字符串中书写\必须写成\\)
\D:非数字匹配,[^0-9]。
\w:匹配任何字类字符,[A-Za-z0-9]。
经常使用的函数:
public boolean matches();//尝试将整个区域与模式匹配;
public boolean find();//从当前位置开始匹配;
public boolean lookingAt();//尝试将从区域开头开始的输入序列 与 该模式匹配。从第一个字符开始匹配,可是不要求整句都匹配。
解法
public class Solution { public boolean isNumeric(char[] str) { if(str==null || str.length==0) return false; return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); } }
题目-字符流中第一个不重复的字符
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
思路
时间复杂度O(1),空间复杂度O(n)
1.构建一个大小为128的数组,统计每一个字符ch出现的次数;
2.构建一个队列,记录全部第一次出现的ch字符。若是当前字符出现2次,那么弹出;
3.此时队列中全都是只出现一次的字符。若是是空,那么就输出“#”;若是不是空,那么弹出队首。
解法
import java.util.Queue; import java.util.LinkedList; public class Solution { private int[] cnts = new int[256]; private Queue<Character> queue = new LinkedList<>(); //Insert one char from stringstream public void Insert(char ch) { //记录各元素的出现次数 cnts[ch]++; //在队列中记录全部出现过的元素 queue.add(ch); //若是这个元素的次数大于2,那么弹出 while(!queue.isEmpty() && cnts[queue.peek()]>1){ //返回第一个元素,并在队列中删除 queue.poll(); } } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { //若当前字符流没有存在出现一次的字符,返回# return queue.isEmpty() ? '#' : queue.peek(); } }
题目-链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,不然,输出null。
思路
设置快慢指针,从表头同时出发。快指针每次走两步,慢指针每次走一步。
1.若是有环,快慢指针必定会相遇在环中某点。(因为有环,fast先进入环,那么low进入环,能够将二者当作是一个追赶的过程)
2.两个指针分别从链表头和相遇点继续出发,每次走一步,最终必定会相遇在环入口。
设置AC=a,CB=b,BC=c。
相遇时,快指针的路程=a+(b+c)*k+b;慢指针的路程=a+b.
而快指针的速度=2*慢指针的速度。所以2*(a+b)=a+(b+c)*k+b,化简获得a=(k-1)(b+c)+c,即 链表头到环入口的距离=相遇点到环入口的距离+(k-1)圈环长度。
得证。
解法
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { if(pHead==null || pHead.next==null){ return null; } ListNode slow=pHead, fast=pHead; //由于存在环,两个指针一定相遇在环中的某个节点 do{ fast=fast.next.next; slow=slow.next; }while(slow!=fast); //让fast从头开始移动,速度变为一次移动一个节点 fast=pHead; while(slow!=fast){ slow=slow.next; fast=fast.next; } return slow; } }
题目-删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路
使用递归
1)若是当前结点是重复结点,那么删除这个重复结点,从第一个与当前结点不一样的结点开始递归。
2)若是当前结点不重复,那么直接从下一结点开始递归。
解法
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class Solution { public ListNode deleteDuplication(ListNode pHead) { //若是链表为空 if(pHead==null || pHead.next==null) return pHead; ListNode next = pHead.next; //若是出现重复结点,删除重复结点且不保留 if(next.val == pHead.val){ while(next!=null && next.val==pHead.val){ next = next.next; } return deleteDuplication(next); } //若是没有重复结点,那么就判断下一个结点 else{ pHead.next = deleteDuplication(pHead.next); return pHead; } } }
题目-二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点而且返回。注意,树中的结点不只包含左右子结点,同时包含指向父结点的指针。
思路
根据中序遍历的规则:左根右。获得遍历的规律
1.若是有右子树,那么下一个结点就是右子树最左结点;
2.若是没有右子树,那么下一结点是:a)父结点,当前结点为父结点的左孩子时;b)向上找到第一个左子树指向的树,好比J,其下一节点是父结点的父结点的父结点,直到当前结点是这个父结点的左子树部分。由于左根右,当前结点是右结点的时候,说明已经遍历到这棵树的最后一个结点,应该开始遍历另外半棵树。
解法
/* public class TreeLinkNode { int val; TreeLinkNode left = null; TreeLinkNode right = null; TreeLinkNode next = null; //next为指向父结点的指针 TreeLinkNode(int val) { this.val = val; } } */ public class Solution { public TreeLinkNode GetNext(TreeLinkNode pNode) { //中序遍历:左根右 //若是一个结点的右子树不为空,那么该结点的下一个结点是右子树的最左结点 if(pNode.right != null){ TreeLinkNode node = pNode.right; while(node.left != null){ node = node.left; } return node; } //不然,向上找第一个左连接指向的树 包含 该结点的祖先结点 else{ while(pNode.next != null){ TreeLinkNode parent = pNode.next; if(parent.left == pNode){ return parent; } pNode = pNode.next; } } return null; } }
题目-对称的二叉树
请实现一个函数,用来判断一颗二叉树是否是对称的。注意,若是一个二叉树同此二叉树的镜像是一样的,定义其为对称的。
思路
使用递归,只是须要不断判断结点的left和right是否对称;
即左右结点的值要相等&&对称子树left.left与right.right、left.right与right.left也要对称。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { boolean isSymmetrical(TreeNode pRoot) { if(pRoot == null){ return true; } return isSymmetrical(pRoot.left, pRoot.right); } boolean isSymmetrical(TreeNode t1, TreeNode t2){ if(t1 == null && t2 == null) return true; if(t1 == null || t2 == null) return false; if(t1.val != t2.val) return false; return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); } }
题目-按之字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其余行以此类推。
思路
1.将每层数据存入ArrayList,当偶数层时经过Collections.reverse(list);翻转list。可是其实咱们给出来的这种方法使用reverse,处理海量数据时,效率过低。
2.能够利用LinkedList实现的是双向链表的特色,作成双向遍历。
使用queue.iterator();//从前日后遍历
queue.descendingIterator();//从后往前遍历
而且须要在queue中添加null作层分隔符
解法
import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; import java.util.Collections; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); Queue<TreeNode> queue = new LinkedList<>(); queue.add(pRoot); //用变量记录奇数层仍是偶数层,是否反向 boolean reverse = false; while(!queue.isEmpty()){ ArrayList<Integer> list = new ArrayList<>(); int cnt = queue.size(); //若是队列不为空,就循环取值 while(cnt-- > 0){ TreeNode node = queue.poll(); if(node == null) continue; list.add(node.val); queue.add(node.left); queue.add(node.right); } //若是是从右到左,就反转list if(reverse == true) Collections.reverse(list); //反向 reverse = !reverse; if(list.size() != 0){ ret.add(list); } } return ret; } }
题目-把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路
这个题与上一题“按照之字形打印二叉树”相似。
只是去掉了须要翻转的部分。
构建一个队列来存储二叉树,而后将一个结点从队列中取出并存入list的同时,将结点的左右结点添加到队列。不断作输出。
解法
import java.util.ArrayList; import java.util.Queue; import java.util.LinkedList; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); //构造队列来存储二叉树 Queue<TreeNode> queue = new LinkedList<>(); queue.add(pRoot); while(!queue.isEmpty()){ ArrayList<Integer> list = new ArrayList<>(); int cnt = queue.size(); while(cnt-- > 0){ TreeNode node = queue.poll(); if(node == null) continue; //将当前结点存储到list中,待输出 list.add(node.val); //加入当前结点的左右子树 queue.add(node.left); queue.add(node.right); } if(list.size() != 0) ret.add(list); } return ret; } }
题目-序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中创建起来的二叉树能够持久保存。序列化能够基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时经过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。 二叉树的反序列化是指:根据某种遍历顺序获得的序列化字符串结果str,重构二叉树。
思路
1.序列化:就是使用一些遍历规则来将二叉树遍历为字符串,好比前序遍历“根左右”。就按照这个顺序读结点的值、递归左结点、递归右结点。
2.反序列化:按照遍历顺序,重构二叉树。
根据字符串序列,因为序列化时,不一样结点之间有分隔符,经过分隔符,获取每一个结点。
将该结点的字符截取出来,而后截断原字符。根据结点的值构建二叉树、递归重构左结点、递归重构右结点。
3.public String substring(int beginIndex);//返回一个新字符串,它是此字符串的一个子字符串。该子字符串始于指定索引处的字符,一直到此字符串末尾。
public String substring(int beginIndex, int endIndex);//返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 beginIndex 处开始, endIndex:到指定的 endIndex-1处结束。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */
public class Solution { String Serialize(TreeNode root) { //序列化时经过 某种符号表示空节点(#) if(root == null) return "#"; return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); } private String deserializeStr;
TreeNode Deserialize(String str) { deserializeStr = str; return Deserialize(); } TreeNode Deserialize(){ if(deserializeStr.length() == 0) return null; //获取一个结点 int index = deserializeStr.indexOf(" "); //该子字符串从指定的 beginIndex 处开始, endIndex:到指定的 endIndex-1处结束。 String node = index==-1 ? deserializeStr : deserializeStr.substring(0, index); //将字符串截掉已经存入node的那一部分。substring该子字符串始于指定索引处的字符,一直到此字符串末尾。 deserializeStr = index==-1 ? deserializeStr : deserializeStr.substring(index+1); //"#"为空结点 if(node.equals("#")){ return null; } int val = Integer.valueOf(node); TreeNode t = new TreeNode(val); t.left = Deserialize(); t.right = Deserialize(); return t; } }
题目-二叉搜索树的第k个结点
给定一棵二叉搜索树,请找出其中的第k小的结点。例如,(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
思路
二叉搜索树(二叉查找树、二叉排序树):根节点的值大于其左子树中任意一个节点的值,小于其右节点中任意一节点的值,这一规则适用于二叉查找树中的每个节点。
根据二叉搜索树的特色,咱们能够发现其中序遍历(左根右)是有序的。即按照中序遍历的顺序,找到的第k个结点就是结果。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { //二叉搜索树的特性:根结点的值大于其左子树中任意一个结点的值,小于其右子树中任意一结点的值。 //所以中序遍历(左根右)是有序的 private TreeNode ret; private int cnt = 0; TreeNode KthNode(TreeNode pRoot, int k) { inOrder(pRoot, k); return ret; } private void inOrder(TreeNode pRoot, int k){ if(pRoot == null || cnt >= k){ return; }
//左 inOrder(pRoot.left, k); //根,标记第k个结点 cnt++; if(cnt == k){ ret = pRoot; }
//右 inOrder(pRoot.right, k); } }
题目-数据流中的中位数
如何获得一个数据流中的中位数?若是从数据流中读出奇数个数值,那么中位数就是全部数值排序以后位于中间的数值。若是从数据流中读出偶数个数值,那么中位数就是全部数值排序以后中间两个数的平均值。咱们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
传入的数据为:[5,2,3,4,1,6,7,0,8],那么按照要求,输出是"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "。
思路
1.也就是不断根据获取的数据流,得到当前数据的中位数。注意中位数是须要排序的。
2.那么咱们能够经过优先队列PriorityQueue来设置大顶堆和小顶堆。
其中大顶堆用来存较小的数,从大到小排序;小顶堆用来村较大的数,从小到大排序。
这样的话中位数也就是大顶堆的根结点(较小数中的最大) 和 小顶堆的根结点(较大数中的最小)的平均数。也就是中位数依据的 全部数值排序后中间的两个数
3.构造两个堆的时候,须要保证小顶堆的元素都大于等于大顶堆的元素&&须要保证两个堆的数据处于平衡状态(元素个数相差不超过1)。
每一个堆加一个的顺序。
当数目为偶数时,将这个值插入大顶堆中,再从大顶堆中poll出根结点(最大值),插入到小顶堆;
当数目为奇数时,将这个值插入小顶堆中,再从小顶堆中poll出根结点(最小值),插入到大顶堆;
所以取中位数时,若当前个数为偶数,那么就是取大顶堆和小顶堆的根结点的平均值;若当前个数为奇数时,就是取小顶堆的根结点。
4.队列遵循先进先出原则,可是有时须要在队列中基于优先级处理对象。PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素能够默认天然排序(从小到大)或者经过提供的Comparator(比较器)在队列实例化的时排序。优先队列不容许空值,且队列的大小不受限制,在建立时能够指定初始大小(以下面定义maxHeap时给出的11)。当咱们向优先队列增长元素的时候,队列大小会自动增长。
PriorityQueue是非线程安全的,因此Java提供了PriorityBlockingQueue(实现BlockingQueue接口)用于Java多线程环境。
解法
import java.util.Comparator; import java.util.PriorityQueue; public class Solution { //当前数据流读入的元素个数 public int count = 0; //用两个堆来保存数据 //小顶堆,存储右半边元素,右半边元素都大于左半边 public PriorityQueue<Integer> minHeap = new PriorityQueue<>(); //大顶堆,存储左半边元素。 public PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, new Comparator<Integer>(){ public int compare(Integer o1, Integer o2){ return o2 - o1; } }); public void Insert(Integer num) { count++; //插入过程,须要保证两个堆的数据处于平衡状态(元素个数相差不超过1) if(count % 2 ==0){//偶数 //由于右半边元素都要大于左半边,可是新插入的元素不必定比左半边元素大 //所以须要先将元素插入左半边,而后利用左半边为大顶堆的特色(堆顶元素即为最大值),插入右半边) //即偶数状况 都插入到右半边 if(!maxHeap.isEmpty() && num < maxHeap.peek()){ maxHeap.add(num); num = maxHeap.poll(); } minHeap.add(num); } else{//奇数 //将新加入的元素加入小顶堆 if(!minHeap.isEmpty() && num > minHeap.peek()){ minHeap.add(num); num = minHeap.poll(); } maxHeap.add(num); } } public Double GetMedian() { if(maxHeap.size() == minHeap.size()) return (minHeap.peek() + maxHeap.peek()) / 2.0; else if(maxHeap.size() > minHeap.size()) return maxHeap.peek()*1.0; else return minHeap.peek()*1.0; /*if(count % 2 == 0){//总量是偶数 return (minHeap.poll() + maxHeap.poll()) / 2.0; } else{//总量是奇数 return (double)minHeap.poll(); }*/ } }
题目-滑动窗口的最大值
给定一个数组和滑动窗口的大小,找出全部滑动窗口里数值的最大值。例如,若是输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有如下6个。
{[2,3,4],2,6,2,5,1},
{2,[3,4,2],6,2,5,1},
{2,3,[4,2,6],2,5,1},
{2,3,4,[2,6,2],5,1},
{2,3,4,2,[6,2,5],1},
{2,3,4,2,6,[2,5,1]}
思路
1.因为题目要求输出滑动窗口中数值最大值。一样使用java的PriorityQueue,使用大顶堆。经过滑动窗口里的值来维护这个大顶堆。
滑动窗口滑动时,为了维护滑动窗口值组成的大顶堆,将当前元素加入到大顶堆,堆顶元素就是最大值。将滑动窗口最左边的值去掉。
最后把该元素添加到Arraylist中待输出。
2.Queue的用法:
offer()&add():add()在失败时会抛出异常。
poll()&remove():返回第一个元素,并在队列中删除。remove()在失败时会抛出异常。
peek()&element():返回第一个元素.element()在队列为空时,会抛出异常。
解法
import java.util.*; public class Solution { public ArrayList<Integer> maxInWindows(int [] num, int size) { ArrayList<Integer> ret = new ArrayList<>(); if(size > num.length || size < 1){ return ret; } //构建大顶堆. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, new Comparator<Integer>(){ public int compare(Integer o1, Integer o2){ return o2 - o1; } }); //最初添加,滑动窗口滑动时,将当前元素加入大顶堆中,堆顶元素便是最大值。把头三个元素加入到maxHeap中,而且输出最大元素到Arraylist for(int i = 0; i < size ; i++){ maxHeap.add(num[i]); } //同时还要判断当前元素是否存在于当前窗口中,不存在的话弹出,最后将该元素添加到Arraylist ret.add(maxHeap.peek()); //窗口开始滑动。维护一个大小为size的大顶堆 for(int i=0, j=i+size; j < num.length; i++, j++){ //按照窗口,添加元素。maxHeap中始终为窗口内的元素 maxHeap.remove(num[i]); maxHeap.add(num[j]); ret.add(maxHeap.peek()); } return ret; } }
题目-矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串全部字符的路径。路径能够从矩阵中的任意一个格子开始,每一步能够在矩阵中向左,向右,向上,向下移动一个格子。若是一条路径通过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 矩阵中包含一条字符串"bcced"的路径,可是矩阵中不包含"abcb"路径,由于字符串的第一个字符b占据了矩阵中的第一行第二个格子以后,路径不能再次进入该格子。
思路
1.这是一个典型的回溯法应用场景。
2.首先须要建立一个辅助数组 来 标记 每一个位置是不是字符串中的字符;再建立一个变量,用来存储当前路径的长度。当其长度与字符串同样时,说明已经完成。
3.先遍历矩阵,找到和字符串第一个字符相同的字符,做为路径的开始;
4.函数中首先要进行合法性判断&当前路径的字符和字符串上对应位置的字符是否相等。符合条件才能进行下面的递归遍历;
5.矩阵路径此时+1,而且标记辅助数组中的该位置为已访问;
6.因为“每一步能够在矩阵中向左,向右,向上,向下移动一个格子”。接下来对该位置的上下左右四个位置进行递归遍历,在递归中,对于各位置找寻下一个位置。若是找到了,就可以一条顺序下去找到完整的路径,返回true;
7.若是没有找到,须要将路径-1,且标记该位置从新为未访问(清除以前的标记状态)。由于要从新寻找。
解法
public class Solution { private final static int[][] next = {{0,-1},{0,1},{-1,0},{1,0}}; private int rows; private int cols; public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { if(matrix==null || rows<=0 || cols<=0 || str==null){ return false; } if(str.length==0){ return true; } //用来标记路径中已经访问过的结点 boolean[] marked=new boolean[matrix.length]; for(int i=0; i<rows; i++){ for(int j=0; j<cols; j++){ //先遍历矩阵,找到和字符串第一个字符相同的字符,做为路径的开始 if(backtracking(matrix, str, marked, 0, i, j, rows, cols)) return true; } } return false; } //尝试寻找路径 public boolean backtracking(char[] matrix, char[] str, boolean[] marked, int k, int row, int col, int rows, int cols){ //合法性判断&当前路径的字符和字符串上对应位置的字符是否相等 if(row<0 || row>=rows || col<0 || col>=cols || str[k]!=matrix[row*cols+col] || marked[row*cols+col]){ return false; } //直到路径字符串上全部字符都在矩阵中找到合适的位置 if(k==str.length-1){ return true; } //要搜索这个结点,首先须要将其标记为已经使用,防止重复使用 marked[row*cols+col]=true; //对当前位置的上下左右4个相邻的格子进行遍历,匹配到下一个字符 if(backtracking(matrix, str, marked, k+1, row+1, col, rows, cols) || backtracking(matrix, str, marked, k+1, row, col+1, rows, cols) || backtracking(matrix, str, marked, k+1, row-1, col, rows, cols) || backtracking(matrix, str, marked, k+1, row, col-1, rows, cols)){ return true; } //若是4个相邻的格子都没有匹配字符串中下一个的字符,代表当前路径字符串中字符在矩阵中的定位不正确,咱们须要回到前一个,而后从新定位。 //这一次搜索结束后,须要将该结点的已经使用状态清除 marked[row*cols+col]=false; return false; } }
题目-机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,可是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人可以进入方格(35,37),由于3+5+3+7 = 18。可是,它不能进入方格(35,38),由于3+5+3+8 = 19。请问该机器人可以达到多少个格子?
思路
这道题与上一题思路基本类似,使用回溯法。
一点点小区别在于:
1.本题规定直接从“坐标0,0的各自开始移动”,上一题须要咱们去遍历数组,获得一个合适的路径开始点;
2.本题中有一些格子不可以走(行坐标和列坐标的数位之和大于k的格子)。每一步移动时,还须要添加一步来判断这个各自是否合法(再合法性判断时添加便可)。也就是至关于上一题中须要判断结点是否匹配的效果。
解法
public class Solution { public int movingCount(int threshold, int rows, int cols) { if(rows <= 0 || cols <= 0){ return 0; } //标记已经访问过的位置 boolean[] marked = new boolean[rows*cols]; for(int i=0; i<marked.length; i++){ marked[i] = false; } //从(0,0)坐标开始移动 int count = movingCountCore(threshold, rows, cols, 0, 0, marked); return count; } public int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] marked){ int count = 0; if(row>=0 && row<rows && col>=0 && col<cols && getDigitSum(row)+getDigitSum(col)<=threshold && !marked[row*cols+col]){ marked[row*cols+col] = true; count = 1+movingCountCore(threshold, rows, cols, row-1, col, marked)+ movingCountCore(threshold, rows, cols, row+1, col, marked)+ movingCountCore(threshold, rows, cols, row, col-1, marked)+ movingCountCore(threshold, rows, cols, row, col+1, marked); } return count; } public int getDigitSum(int num){ int sum = 0; while(num > 0){ sum += (num%10); num /= 10; } return sum; } }
题目-剪绳子
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1而且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,咱们把它剪成长度分别为二、3、3的三段,此时获得的最大乘积是18。
思路
1.本题可使用贪婪算法,可是并非全部问题均可以经过 贪婪算法 获得最优解,依赖于数学证实。贪婪算法的效率也更高。
2.咱们使用动态规划(每一步都是从新将子问题的最优值相加计算出的)来解决这个问题。虽然不少子问题会重复求解,效率不高。
要求的是乘积最大值,那么定义一个函数f(n)表示长度为n的绳子剪成若干段后 各段 乘积 的最大值。
当n=1时,最大乘积只能为0=1*0;
当n=2时,最大乘积只能为1=1*1;
当n=3时,最大乘积只能为2=1*2;
当n=4时,能够分为以下几种状况:1*1*1*1,1*2*1,1*3,2*2,最大乘积为4。
剪第一刀时,选择下第一刀的地方有1~n-1这些地方,有n-1种可能。剪出来的第一段的绳子可能长度为1,2,3,...,n-1。假设在第i处下刀,绳子将分为[0,i]和[i,n],长度分别为i与n-i。那么找出第一刀最合适的位置,就是找i在哪里下刀,可使得[0,i]与[i,n]的乘积最大。
即f(n)=max( f(i)*f(n-i) ),0<i<n。
从下往上推,先计算f(1)接着计算f(2)...直到获得f(n)。大的问题经过按照小问题的最优组合获得。
3.好比要求长度为10的绳子,就计算1-9这9种长度的绳子,每种长度的最大乘积是多少。要求长度为9的绳子,就计算1-8这8种长度的绳子,每种长度的最大乘积是多少。以此类推。
解法
public class Solution { public int cutRope(int target) {
//dp用来存放最大乘积值 int[] dp = new int[target+1]; dp[1] = 1; //记长度为n的最大乘积为f(n),易知f(n)=max{ f(i)*f(n-i) },其中0<i<n。 for(int i=2; i<=target; i++){ for(int j=1; j<i; j++){ dp[i] = Math.max(dp[i], Math.max(j*(i-j), dp[j]*(i-j))); } } return dp[target]; } }
public int cutRope(int length) { if (length < 2) { return 0; } if (length == 2) { return 1; } if (length == 3) { return 2; } //存储长度从 0-len 的最大结果 int[] products = new int[length + 1]; // 将最优解存储在数组中 // 数组中第i个元素表示把长度为i的绳子剪成若干段以后的乘积的最大值 products[0] = 0; products[1] = 1; products[2] = 2; products[3] = 3; int max = 0; for (int i = 4; i <= length; i++) { //i表示长度 max = 0; for (int j = 1; j <= i / 2; j++) { //因为长度i存在(1,i-1)和(i-1,1)的重复,因此只须要考虑前一种 int product = products[j] * products[i - j]; if (product > max) { max = product; } products[i] = max; } } max = products[length] return max; } }
题目-图的广度优先遍历
用队列记录下一步的走向。
1.访问下一个未访问的邻接点,这个顶点必须是当前顶点的邻接点,标记它,加入队列
2.若是由于已经没有 未访问顶点,而不能执行上一条规则,那么从队列头取一个顶点,使其成为当前顶点
3.直到队列为空,完成搜索
题解
public void searchTraversing(GraphNode node){ ArrayList<GraphNode> visited=new ArrayList<GraphNode>(); ArrayList<GraphNode> list=new ArrayList<GraphNode>(); Queue<GraphNode> queue = new LinkedList<GraphNode>(); queue.add(node); while(!queue.isEmpty()){ GraphNode curNode=queue.poll(); if(!visited.contain(curNode)){ visited.add(curNode); list.add(curNode.getLabel()); for(int i=0;i<curNode.edgeList.size();i++){ queue.offer(curNode.edgeList.get(i).getNodeRight()); } } } }
(思路部分的图均源于网络,侵删)